Asterisk — 把企业总机变成一台 Linux 服务器
是什么
Asterisk 是一套用软件实现的 PBX(企业内部电话总机)。日常类比:以前公司前台旁边那台几万美元的 Avaya 黑盒子——分机互打、外线进来转给谁、留言、IVR 语音菜单——它把这堆事全用 C 写成一台 Linux 服务器上能跑的程序。
1999 年 Mark Spencer 在奥本大学读书时为自己创业的 Linux 技术支持公司省钱写出来,2004 年发 1.0,公司后来更名 Digium,2018 年被 Sangoma 收购。GPL 许可,开源软交换的事实标准,至今还在维护。
GitHub 3.1k 星,名字来自 Unix shell 通配符 *——象征”什么都能接”。
为什么重要
- 直接催生整个 VoIP 经济——把企业级电话从 5 万美元的硬件黑盒变成一台 PC + 开源软件,门槛塌了
- 20 多年仍在维护,是开源长期主义的样本:FreePBX / Elastix / 大量 ITSP 服务商的内核都是它
- 抽象被云电话借鉴——Twilio 的 TwiML 本质上就是 dialplan 的 XML 化版本
- 学一遍电信协议栈(SIP / RTP / DTMF / 编解码)最直接的活教材
核心要点
Asterisk 内核就四个概念外加一个粘合层:
- channel(通道):抽象的”一通呼叫的一端”。它可以是 SIP 软电话、IAX 客户端、模拟电话线、PSTN 数字中继。类比:socket——内核不在乎对面是谁、走什么协议
- channel driver:每种协议一个
.so模块(chan_pjsip/chan_iax2/chan_dahdi)。新协议进来只要再写一个 driver - dialplan:写在
extensions.conf里,结构是context → extension → priority → application。“按了 100 该去哪”用一种自创 DSL 描述 - application:dialplan 里能调的动作——
Dial(拨号)/Playback(放语音)/Voicemail(留言)/Queue(呼叫队列)/Background(边放语音边收按键) - PBX core:上面四件事的事件循环 + 桥接器(bridge),把两端 channel 接起来
支持的协议:SIP、IAX2、H.323、MGCP、SCCP、DAHDI(数字/模拟硬件)。其中 IAX2 是 Asterisk 自创——单 UDP 端口 4569 同时载信令和媒体,比 SIP 更友好穿越 NAT。
实践案例
案例 1:dialplan 长什么样
[from-internal]exten => 100,1,Dial(PJSIP/alice,20)exten => 100,2,Voicemail(100@default)exten => 100,3,Hangup()逐行翻译:
[from-internal]是一个 context(一组规则的命名空间)100是 extension(号码),,1,2,3是优先级(按顺序往下走)- 第 1 步:拨 alice 这个 SIP 用户,振铃 20 秒
- 第 2 步:没人接 → 进信箱
- 第 3 步:挂断
这就是为什么有人说 dialplan “是脚本不是配置”——它有顺序、能跳转、能调函数。
案例 2:典型架构
一台中小企业部署看起来是:
SIP 软电话 ─┐SIP 硬话机 ─┼─→ Asterisk ──→ SIP trunk 服务商 ──→ PSTN(外线)模拟电话 ─┘ (PBX core)内部分机互打不出 Asterisk 的盒子;打外线时它把 channel 一端接到 SIP trunk(运营商提供的批发线路),另一端接你的话机,bridge 起来后媒体直接两头流。
案例 3:dialplan 写不动了怎么办
dialplan 这套 DSL 写复杂逻辑(数据库查、API 调外部)会很难看。逃生口叫 AGI(Asterisk Gateway Interface)——一个文本协议,让 dialplan 调用外部 Python / PHP 脚本,stdin/stdout 通信。再复杂的业务逻辑都跳出 DSL 用熟悉的语言写。
伪代码:
#!/usr/bin/env python3import syscaller = sys.argv[1] # 主叫号码# 查数据库决定路由target = lookup_db(caller)# 通过 stdout 把变量写回 dialplanprint(f"SET VARIABLE TARGET {target}")dialplan 端:
exten => _X.,1,AGI(route.py,${CALLERID(num)})exten => _X.,2,Dial(PJSIP/${TARGET})类比 nginx 的 try_files 失败后跳到 PHP-FPM——常见路径在 DSL 里跑得快,复杂分支跳出去用通用语言。
案例 4:bridge 是怎么把两个 channel 接起来的
A 拨给 B,Asterisk 内部其实建了两个 channel(PJSIP/A-00001 和 PJSIP/B-00002),然后调 Dial 把它们 bridge 在一起。bridge 模块负责:
- 把 A 收到的 RTP 包转给 B(甚至能直接命令两端互相直发,自己退出媒体路径,叫 direct media / re-INVITE)
- 处理转接、会议(多个 channel 进 ConfBridge)、监听(spy)
- 任一边挂断就拆桥
理解了 bridge,就理解了为什么 Asterisk 能把”模拟电话 + SIP 软电话 + 会议”放一起开会——它只看 channel 抽象。
踩过的坑
chan_sip已死:老教程里满屏的chan_sip在 2018 后已经废弃,新部署一律用chan_pjsip(基于 PJSIP 协议栈)。配置语法有差异,照搬老博客会碰壁- NAT 是头号杀手:SIP 信令里写的是内网 IP,过 NAT 后对面拨不回来——表现就是”能呼出但听不到声音”。要嘛配
external_media_address,要嘛干脆走 IAX2 - 音频回音和 jitter:实时音频对延迟敏感,时钟源没配好(
/etc/asterisk/chan_dahdi.conf里的 timing source)会出现金属音、回声 - 单进程 + C:扩展性靠多机集群(DUNDi 分布式拨号方案)。单机几千并发 channel 是上限,再上去要堆机器
- dialplan 调试只能看日志:没有断点、没有 IDE,靠
core set verbose 5+agi debug看输出。习惯前会很痛
适用 vs 不适用场景
适用:
- 中小企业内部分机 + IVR + 留言信箱
- 呼叫中心后端(配上 Queue + 报表插件)
- SIP trunk 网关(把老 PRI/E1 数字中继接到 SIP 世界)
- 学 VoIP 协议栈的实验台
不适用:
- 运营商级千万级并发 → 用 OpenSIPS / Kamailio(这俩是纯 SIP 代理,不做媒体)
- 多线程高吞吐媒体处理 → FreeSWITCH(2006 年由前 Asterisk 开发者另起炉灶,主打多线程)
- WebRTC 浏览器直连为主 → janus-gateway / mediasoup
- 想要现代云原生 → Asterisk 的进程模型还停在 2004,容器化勉强能做,不算优雅
历史小故事(可跳过)
- 1999:Mark Spencer 在奥本大学读书,自己开 Linux Support Services 公司,没钱买 Avaya,写了 Asterisk
- 2004:1.0 正式发布,公司更名 Digium,开始卖配套硬件卡
- 2006:前开发者 Anthony Minessale 觉得单线程不够用,分叉出 FreeSWITCH
- 2018:Sangoma 收购 Digium,Asterisk 仍开源
- 现在:每年发新版(最新 22.x),核心架构没大变——证明这套抽象选对了
学到什么
- channel + dialplan + application 这三件抽象 20 年没变形,是软交换领域的”x86 指令集”
- DSL 配脚本钩子(dialplan 配 AGI)是工程上经典模式——常见路径在 DSL 里短小,复杂逻辑跳到通用语言
- 协议适配靠模块(channel driver
.so):内核不动,新协议挂上来即可,是开源项目长寿的常见配方 - 专用硬件 → 通用 PC + 开源软件 这条曲线 1999 年发生在电话上,2010 年发生在网络(SDN),2015 年发生在存储(Ceph)——同一种”软件吃硬件”的剧本
延伸阅读
- 官方文档:Asterisk Documentation(docs.asterisk.org,最权威,按版本分)
- 经典书:Asterisk: The Definitive Guide(O’Reilly 出的免费版,作者 Russell Bryant 等核心开发者)
- 入门发行版:FreePBX(图形界面套壳的 Asterisk,新人不想啃 dialplan 直接用它)
- 对比阅读:FreeSWITCH(同源不同路)
- aiortc —— Python WebRTC 协议栈,和 Asterisk 同属”软件实现协议栈”流派