1. 为什么我最终选择了FreeSWITCH而不是Kamilio或Asterisk
接触VOIP相关项目也有几年了,早期做呼叫中心的时候,团队里为了选型吵过好几次。当时市面上主流就是Asterisk、Kamailio和FreeSWITCH三家。Asterisk胜在生态老、文档多,但单机高并发和媒体处理能力说实话有点跟不上;Kamailio强在信令代理和路由,可它不是完整PBX,媒体这块你得另找B2BUA配合;FreeSWITCH恰好卡在中间——信令、媒体、路由、计费接口全都有,而且底层用C写的,线程模型和内存管理比Asterisk那套要现代不少。
我手头那个业务场景是给某虚拟运营商做语音落地网关,同时要对接运营商的SIP中继,还要承载几千路并发通话。单机性能、稳定性、可定制化是刚需,还要能很方便地装到CentOS和Debian两种系统上做横向对比。论核心能力,FreeSWITCH自带的mod_sofia处理SIP协议栈,mod_rtc处理WebRTC,mod_conference做多方会议,mod_fifo做排队,基本覆盖了我们需要的全部功能,不用像Asterisk那样拼一堆第三方模块。
另一个加分项是它的ESL接口(Event Socket Library)。业务侧需要实时感知通话状态、动态外呼、挂断、转坐席,ESL可以直接用socket连上去收发事件,比数据库轮询靠谱一个量级。我后来做外呼营销系统的时候,所有话务调度都靠ESL,这玩意儿在Asterisk里对应的是AMI,机制老,性能也差一些。
BDS这个说法其实就是把FreeSWITCH跑在自家服务器上,自己控制SIP端口、RTP端口、数据库和拨号计划,不依赖任何云厂商的托管方案。很多C2C或小规模项目直接买云厂商的语音服务,但想做到精细控制呼叫流程、自定义IVR、计费策略,绕不开自建这条路。标题里写的CentOS/Debian两个发行版,最终我都实际部署过一遍,后面会把两者之间的坑和差异详细列出来。
如果你现在还在选型阶段,我的建议是:纯粹做SBC或信令路由,选Kamailio;想快速搭PBX并且不介意性能天花板,选Asterisk;要做多路并发、强媒体处理、深度二次开发,FreeSWITCH是目前最省心的路。后面所有实操步骤我都以FreeSWITCH安装配置为主线展开。
2. 环境准备:CentOS和Debian在部署前的关键差异
动手装之前,先把两台机器的环境聊透。很多人栽跟头不是因为FreeSWITCH本身,而是因为操作系统层面的细节没处理好。CentOS 7和Debian 10/11各自有各自的脾气,我两台都踩过坑,逐个说。
2.1 系统版本选择和内核参数的基础认知
CentOS这边我建议直接用CentOS 7.9,虽然它已经进入维护周期尾声,但市面上大量服务器镜像还是基于它,而且FreeSWITCH的预编译包对7.x支持最完善。CentOS 8/Stream的仓库变动大,容易出现依赖缺失,除非你特别熟悉dnf源管理,否则别给自己找麻烦。
Debian我建议Debian 11或12。Debian的优点是从头到尾都是开源软件,apt源干净,版本新,装依赖基本不用愁。但缺点也明显——默认防火墙是nftables而不是iptables,SIP这种老协议跟nftables配合起来要格外小心,后面我会专门讲。
不管哪个系统,装系统时尽量选择Minimal安装,不要带GUI。FreeSWITCH是纯命令行服务,多装一个桌面环境就多一批安全暴露面,内存也被无谓吃掉。我的机器配置一般是这样:CPU 4核起(并发500路以上建议8核),内存8GB起,磁盘用SSD,系统盘和数据盘分开。RTP媒体包都要经过服务器转发,磁盘I/O主要影响录音和日志,但录多路并发的时候,机械盘很容易成为瓶颈。
装完系统第一步,先关掉SELinux。CentOS默认开着的SELinux会让FreeSWITCH的端口绑定和文件读写莫名失败,关掉能省去大量排查时间。命令是:
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config setenforce 0Debian这边没有SELinux,但要注意,Debian 12默认没装sudo的话,你只能用root登录操作。我习惯直接切root,因为FreeSWITCH的很多命令和文件权限用root操作最省心。生产环境当然建议用普通用户加sudo,但个人学习或内部测试,root没毛病。
2.2 编译安装依赖:最容易被忽视的底层库
FreeSWITCH官方提供了两种安装方式:yum/apt装预编译包,或者源码编译。我强烈建议源码编译,原因有三:
- 预编译包落后版本较多,而且部分模块没编译进去(比如很多商业模块和额外编解码器)。
- 编译过程能让你理解FreeSWITCH模块化设计,排查问题心里有底。
- 优化参数可控,比如启用更高级的CPU指令集。
CentOS下装依赖,核心命令:
yum install -y epel-release yum groupinstall -y "Development Tools" yum install -y git wget curl autoconf automake libtool gcc-c++ ncurses-devel libxml2-devel openssl-devel libcurl-devel pkgconfig sqlite-devel libpng-devel libjpeg-devel yasm nasm zlib-devel lua-devel libsndfile-devel这里有个非常容易忽略的包:libsndfile-devel。不装它,后面编译的mod_sndfile模块会异常,导致放音文件无法播放。还有个yasm和nasm,它们是汇编器,编译某些编解码器时需要,不装的话模块编译被跳过,你还不知道为啥。
Debian下对应命令:
apt update apt install -y build-essential git wget curl autoconf automake libtool pkg-config libncurses-dev libxml2-dev libssl-dev libcurl4-openssl-dev libsqlite3-dev libpng-dev libjpeg-dev yasm nasm zlib1g-dev liblua5.2-dev libsndfile1-dev注意Debian里Lua的包名有版本号,liblua5.2-dev和CentOS的lua-devel不是同一个东西。如果你的FreeSWITCH要跑Lua IVR脚本,版本必须对齐,否则编译时报找不到lua.h。
装完依赖,确认gcc版本:
gcc --versionCentOS 7自带的是gcc 4.8.5,比较老,但足够编译FreeSWITCH了。如果你要编译最新主线版本且需要某些新特性,可能要装devtoolset-7以上的工具链,一般用不到,别折腾。
3. 完整编译安装流程:源码拉取、模块配置与编译参数
源码编译是这整篇里最核心的部分,也是最容易出幺蛾子的地方。我把自己实际操作过的完整路径写下来,包括中间遇到过的编译失败和对应解法。
3.1 拉取源码并切换稳定分支
FreeSWITCH的官方仓库在GitHub上,国内访问偶尔慢,建议先配好代理镜像或者用gitee镜像。我操作的时候直接用官方源加--depth=1来减少拉取量:
cd /usr/local/src git clone --depth=1 -b v1.10 https://github.com/signalwire/freeswitch.git这里解释一下为什么选v1.10分支而不是master或v1.8。v1.8是旧稳定版,很多新模块和协议支持缺失;master是开发版,功能激进但可能有bug不会立刻修;v1.10是目前维护最积极且经过长期验证的版本,WebRTC、mod_verto、安全补丁都同步跟进。生产环境用它最稳。
如果你只想要特定模块而不想全量编译,可以在克隆后直接编辑modules.conf。这个文件控制哪些模块会被编译进系统。我通常的做法是禁用不用的模块,比如mod_java、mod_python这类极少用到的嵌入式语言模块,还有mod_tts之类的商业模块。减少无谓模块能让编译更快,运行时内存占用也更低。
编辑modules.conf,以我常用的配置为例,注释掉不需要的行:
cd freeswitch cp modules.conf modules.conf.bak vim modules.conf在文件中搜索mod_java、mod_python、mod_tts_command,行首加#注释掉。另一个值得关注的是编译开关,在configure阶段可用--disable-core-odbc-support等参数去掉不需要的功能。Debian和CentOS的库兼容性差异,在这里就会开始显现——比如某些发行版缺少libpq-dev(PostgreSQL开发库),如果你不需要数据库对接,直接禁用更省事。
3.2 configure、make和make install的完整执行与排错
接下来进入标准的编译三连。先执行bootstrap脚本生成configure文件:
./bootstrap.sh这个脚本会拉取子模块并生成configure。如果网络不太好,可能出现子模块拉取失败,报错类似Failed to recurse into submodule path。此时不要慌,重新执行一次bootstrap脚本基本能解决,Git的缓存机制会让失败的子模块续传。如果反复失败,检查网络代理设置,或者手动编辑.gitmodules把子模块的URL替换为国内镜像。
bootstrap成功后执行configure:
./configure --prefix=/usr/local/freeswitch--prefix指定安装目录,保持默认即可。configure阶段如果报缺失某个库,回头补装对应的-devel包。CentOS 7上最容易缺的是libuuid-devel,如果你正好要用mod_avmd或者其他依赖UUID的模块:
yum install -y libuuid-develDebian下对应是uuid-dev:
apt install -y uuid-devconfigure完成后,看输出末尾有没有configure: error行。没有错误的话,直接make,这一步耗时最长,机器差的话可能要20到30分钟:
make -j4-j4是并行编译参数,代表4个任务同时编译。如果你机器是8核,可以-j8。但注意,FreeSWITCH编译时某些模块有依赖关系,并行任务过多偶尔会报头文件找不到的假错。如果加了-j8报错,试试去掉-j单线程make,通常就能过。
make完成后再执行:
make install make cd-sounds-install make cd-moh-installmake install把二进制和模块装到/usr/local/freeswitch。后面两条命令是安装默认的提示音和背景音乐文件,不装的话,很多IVR示例和保持音乐都会是静音。这一步经常有人跳过,结果Demo跑起来发现没声音,还以为是声卡问题。
安装完成后,验证一下版本:
/usr/local/freeswitch/bin/freeswitch -version能打印出版本号就说明基础安装成功。到这里只能算装了32位色版的空壳,真正能干活还得看模块加载和配置。
3.3 模块加载与核心配置初探
FreeSWITCH的模块目录在/usr/local/freeswitch/mod。启动时读的是/usr/local/freeswitch/conf下的配置。核心配置文件是freeswitch.xml,它通过X-PRE-PROCESS指令把所有模块配置、拨号计划、SIP配置拼接成一个整体。
我把自己的习惯说一下:默认配置里加载了几乎所有模块,但生产环境我只保留核心几个:
- mod_sofia:SIP协议栈,必开
- mod_events:事件系统,必开
- mod_dialplan_xml:XML拨号计划,必开
- mod_dptools:拨号工具,必开
- mod_conference:会议,业务需要时开
- mod_fifo:队列,呼叫中心需要时开
- mod_lua:跑IVR脚本,需要时开
- mod_curl:HTTP请求,对接外部接口时需要开
实际启用和禁用模块,在conf/autoload_configs/modules.conf.xml里改。把不需要的模块行注释掉,别删,方便以后恢复。
模块配置弄好后,还需要调整SIP相关的配置。SIP配置文件在conf/sip_profiles/,里面默认有internal.xml(内网SIP端口5060)和external.xml(外网SIP端口5080)。我通常只保留internal并改端口。
改内网SIP端口的原因很实际——公网扫描器几乎天天都在扫5060端口,只要暴露在公网,一天能收到上万条注册尝试。把SIP端口改到非标准端口(比如15060),能挡掉90%以上的扫描。RTP端口范围默认是16384-32768,按需调小,比如10000-20000,更容易做防火墙放行策略。
4. 最小化运行验证:从启动到第一个SIP注册
编译安装完了,不能说就这?不行,得先跑起来,验证SIP注册、呼叫路由、放音这几个核心链路。
4.1 前台启动与后台守护切换
首次启动建议用前台模式,这样能直接看到日志输出,方便排错:
/usr/local/freeswitch/bin/freeswitch -nonat-nonat参数告诉FreeSWITCH当前环境没有NAT,避免它去做内外网地址映射检测。如果你在云服务器上有公网IP,但没做特殊路由,这个参数尤其重要,否则启动日志里会出现大量公网IP探测的警告。
如果一切正常,你会看到一行提示Successfully Started,然后进入FreeSWITCH控制台。控制台是一个freeswitch@localhost>风格的交互式shell。输入sofia status可以查看SIP协议栈的运行状态:
freeswitch@localhost> sofia status正常情况下能看到internalprofile处于RUNNING状态,并监听在你配置的端口上。如果显示ERROR或NOT RUNNING,多半是端口被占用或SIP配置有fatal错误。用netstat -tunlp | grep freeswitch确认监听端口。
验证没问题后,下次启动可以直接用后台模式:
/usr/local/freeswitch/bin/freeswitch -nc -nonat-nc是no console模式,进程转入后台。用fs_cli连接控制台:
/usr/local/freeswitch/bin/fs_cli如果fs_cli连不上,检查/usr/local/freeswitch/conf/autoload_configs/event_socket.conf.xml里的listen-ip和listen-port。默认是本机8021端口,不要暴露到公网,否则任何人用默认密码esl_password都能控制你的交换机。
4.2 创建分机并测试SIP注册
配置分机的传统路径是改directory/default/下的XML文件。我直接给一个最小分机配置,比如分机号1000:
mkdir -p /usr/local/freeswitch/conf/directory/default vim /usr/local/freeswitch/conf/directory/default/1000.xml文件内容:
<include> <user id="1000"> <params> <param name="password" value="1234"/> <param name="vm-password" value="1234"/> </params> <variables> <variable name="user_context" value="default"/> <variable name="effective_caller_id_number" value="1000"/> <variable name="effective_caller_id_name" value="Extension 1000"/> </variables> </user> </include>保存后,在控制台执行:
freeswitch@localhost> reloadxml然后再用软电话注册。软电话推荐MicroSIP或Zoiper,设置里填服务器地址为你的服务器IP:15060,用户名1000,密码1234。注册成功后,控制台会刷出REGISTER事件,sofia status里也能看到1000处于Registered状态。
注册成功后,随手做一个音效测试。在控制台对分机拨一个测试号码:
freeswitch@localhost> originate user/1000 &playback(!!sound/test.wav)如果分机响铃并能听到测试音,说明SIP信令、RTP媒体、拨号计划、文件放音这条链路全部通了。这是整个FreeSWITCH最基础的验收路径,后面再做IVR、外呼、会议,都是在这条链路上加东西。
5. 拨号计划设计:从一条测试号码到可落地的路由逻辑
拨号计划是FreeSWITCH的灵魂,也是大家最容易懵的地方。XML拨号计划用正则表达式做匹配,规则匹配后执行若干应用,比如桥接分机、放音、转外线。
5.1 XML Dialplan的基本结构与正则匹配
默认拨号计划在conf/dialplan/default.xml。一个拨号计划块的结构大致是:
<extension name="example"> <condition field="destination_number" expression="^1000$"> <action application="answer"/> <action application="playback" data="!!sound/test.wav"/> </condition> </extension>condition标签里的field决定匹配caller的哪个字段,常用的是destination_number(被叫号码)。expression是正则表达式。action是要执行的操作,按顺序执行。
提示:正则里的
^和$必须写。很多人不写结尾锚点,导致号码1000能匹配10001,产生诡异路由。
我设计外呼路由时,通常会做一个出局号码的统一入口。比如所有以9开头的5位号码都走SIP中继出去:
<extension name="outbound"> <condition field="destination_number" expression="^9(\d{4})$"> <action application="set" data="dialed_number=$1"/> <action application="bridge" data="sofia/gateway/my_gateway/$1"/> </condition> </extension>这里用set把正则捕获的$1存到变量dialed_number,随后bridge时直接引用。变量传递是FreeSWITCH里极其常用的手段,呼叫流程中很多动态逻辑都靠它。
5.2 示例:分机互拨、IVR菜单、外呼中继路由
我在生产环境里常用三个典型场景做模板:
第一个,分机互拨。1000到1099之间的号码互相呼叫,直接bridge到对应分机:
<extension name="internal_call"> <condition field="destination_number" expression="^(10[0-9]{2})$"> <action application="bridge" data="user/$1"/> </condition> </extension>第二个,IVR菜单。用户拨入后播放语音菜单,按键转不同分机。我用Lua脚本去实现菜单逻辑,但简单场景用XML也够:
<extension name="ivr_menu"> <condition field="destination_number" expression="^8000$"> <action application="answer"/> <action application="playback" data="ivr/ivr-welcome_to_freeswitch.wav"/> <action application="playback" data="ivr/ivr-please_enter_extension.wav"/> <action application="start_dtmf"/> <action application="read" data="3 5 'tone_stream' '%s' 5000 #"/> <action application="transfer" data="${read_result} XML default"/> </condition> </extension>read应用会收DTMF按键,最长5位,超时5秒,遇到#号结束。收到的数字存在变量read_result里,然后用transfer转到对应分机。这个流程刚跑通时会发现transfer后没有声音,原因是没做answer,遇到这种问题先查answer是否在read之前执行。
第三个,外呼中继路由。对接SIP中继网关时,网关配置在conf/sip_profiles/external.xml或专门的gateway文件里。我一般把网关信息写在conf/sip_profiles/gateways/my_gateway.xml:
<gateway name="my_gateway"> <param name="proxy" value="中继服务器IP"/> <param name="register" value="false"/> <param name="username" value="your_account"/> <param name="password" value="your_password"/> </gateway>如果中继不需要注册,只做透传,register=false即可。然后在拨号计划里bridge sofia/gateway/my_gateway/号码。这样设计的好处是:所有外呼都在一个入口收敛,计费、限速、号码转换可以在一个地方统一处理。
6. 常见故障排查与性能调优经验
跑到这里,FreeSWITCH已经基本能用了。最后一个环节我想集中讲排查和优化,因为这块的经验比安装本身更值钱。
6.1 日志分级、抓包定位SIP信令问题
遇到SIP注册失败或者通话异常,不要瞎猜,先看日志。FreeSWITCH的日志默认在/usr/local/freeswitch/log/freeswitch.log。控制台里可以用console loglevel debug让控制台输出更详细的日志,但生产环境慎用,日志量太大容易拖垮磁盘I/O。
日志级别从低到高是DEBUG、INFO、NOTICE、WARNING、ERR、CRIT。日常排障用INFO,剖析协议细节用DEBUG,生产环境一般保持在NOTICE或WARNING即可。
如果需要看SIP信令交互,更直接的方法是抓包。tcpdump抓SIP和RTP包:
tcpdump -i eth0 -s 0 -w /tmp/sip.pcap udp port 15060抓到后用Wireshark打开,过滤sip和rtp,能清楚看到注册流程的REGISTER -> 401 -> REGISTER -> 200完整过程。常见的注册失败无非这么几类:
- 401后客户端没带上正确的认证信息,多半是密码错或算法不支持。
- 服务器回了
403 Forbidden,多半是IP被ACL拦了。 408 Request Timeout,说明服务器根本没收到客户端的后续请求,检查防火墙是不是只放行了SIP端口但没放行RTP端口。
我用这个方法排查过一个诡异问题:注册成功但一打电话就断。抓包发现客户端的RTP流从50000端口发出,而FreeSWITCH只监听在10000-20000,需要调整RTP端口范围并同步防火墙策略。
6.2 并发与内存调优:单机跑到多少路不卡
FreeSWITCH单机性能很大程度取决于内存带宽和CPU主频。每路通话占用的内存大约在2MB到4MB左右,再加上录音和媒体buffers,很难精确估算,但大致给一个参考值:8GB内存的机器跑500路并发通话一般够用,如果跑编码转换或会议,内存和CPU都会成倍上涨。
想提高单机并发,可以做三件事:
第一件事,调整RTP的媒体buffer。默认rtp_rewrite_timestamps和rtp_autoflush参数都能影响媒体延迟和CPU占用。在SIP profile里,把rtp-autoflush设为true,可以降低媒体转发时的CPU占用。实测对性能有帮助。
第二件事,关掉不需要的模块。前面说过,模块加载越多内存占用越高,而且某些模块还会注册全局定时器,增加无谓CPU唤醒。我生产环境只保留12个模块左右,启动后内存占用比默认配置少大概四分之一。
第三件事,如果机器是多核的,把SIP的线程模型调整一下。FreeSWITCH默认用1个核心处理所有SIP信令,如果并发高,可以把sip_threads参数调大。不过这个参数是编译期写死的,需要在源码里改src/mod/endpoints/mod_sofia/mod_sofia.c中的MAX_SIP_THREADS,重新编译。除非你确实遇到信令瓶颈,否则不建议折腾。
调优最怕瞎猜,先压测再调参。我压测用的工具是sngrep和sipsak,前者看信令,后者发简单SIP请求。真实的压力测试还是得用专业的SIPp工具,它能模拟大量注册和呼叫。如果一个8核16GB的机器,用SIPp打满1000路并发注册和400路并发通话,CPU如果压在70%以下,你的配置就基本合格了。
6.3 开机自启与systemd配置建议
最后提一下开机自启。源码编译安装的FreeSWITCH默认不注册systemd服务,需要自己写。我在CentOS和Debian上都用同一份unit文件:
[Unit] Description=FreeSWITCH After=network.target [Service] Type=forking ExecStart=/usr/local/freeswitch/bin/freeswitch -nc -nonat ExecStop=/usr/local/freeswitch/bin/freeswitch -stop Restart=on-failure LimitNOFILE=999999 [Install] WantedBy=multi-user.target注意LimitNOFILE必须设大。FreeSWITCH每路通话要开一堆文件描述符,默认1024根本不够,我一般直接改成999999。我遇到过部署后跑了两小时,突发高并发把fd吃光,服务直接崩溃的情况,原因就是忘了设这个参数。
保存到/etc/systemd/system/freeswitch.service后:
systemctl daemon-reload systemctl enable freeswitch systemctl start freeswitch用systemd管理的好处是崩溃自动重启、开机自启、日志统一由journald捕获。生产环境我强烈建议用这套方式,而不是简单的nohup后台跑。
7. 写在最后:关于学习和排障的个人体会
从选型到编译到上线,整个流程走下来,最大的体会是:FreeSWITCH的坑不是它本身设计的坑,而是操作系统的坑、网络的坑和没有理解协议流程的坑。只要把SIP的注册、呼叫、媒体协商这三个基本链路弄明白,绝大多数问题都能在十分钟内定位。
我个人的习惯是把每一台服务器的freeswitch.xml、SIP profile、拨号计划都纳入版本管理。改配置之前先git commit,出问题随时回滚。这个习惯救过我很多次,特别是调拨号计划时,一个正则写错,可能让所有外呼都呼不出去。有版本管理兜底,至少可以快速回到上一个稳定状态。
另外,FreeSWITCH的社区邮件列表和Jira问题追踪系统里存了大量历史问题。遇到报错,把报错信息完整贴到社区搜索里,八成能找到答案。关键是报错信息要完整,不要只贴一行。比如MOD_SOFIA: recv 401这种日志,很多人就只截一半,别人想帮你都无从下手。我自己现在排查问题,第一步永远是打开完整日志,看上下文。
如果你刚开始学这套东西,别急着上来就搞高并发压测,先把一个分机注册、打通一条外呼再说。基础链路通了,后面加业务逻辑其实都很快。