FreeSWITCH 与 Asterisk(译)

Anthony Minssale/文 Seven/译 html

VoIP通讯,与传统的电话技术相比,不只仅在于绝对的资费优点,更重要的是很容易地经过开发相应的软件,使其与企业的业务逻辑紧密集成。Asterisk做为开源VoIP软件的表明,以其强大的功能及相对低廉的建设成本,受到了全世界开发者的青睐。而FreeSWITCH做为VoIP领域的新秀,在性能、稳定性及可伸缩性等方面则更胜一筹。本文原文在http://www.freeswitch.org/node/117, 发表于2008年4月,相对突飞猛进的技术来说,彷佛有点过期。但本文做为FreeSWITCH背后的故事,仍颇有翻译的必要。所以,本人不揣鄙陋,但愿与你们共读此文,请不吝批评指正。 –译者注 node

FreeSWITCH 与 Asterisk 二者有何不一样?为何又从新开发一个新的应用程序呢?最近,我听到不少这样的疑问。 为此,我想对全部在该问题上有疑问的电话专家和爱好者们解释一下。我曾有大约三年的时间用在开发 Asterisk 上,并最终成为了 FreeSWITCH 的做者。所以,我对二者都有至关丰富的经验。首先,我想先讲一点历史以及我在 Asterisk 上的经验;而后,再来解释我开发FreeSWITCH的动机以及我是如何以另外一种方式实现的。 linux

我从2003年开始接触 Asterisk,当时它还不到1.0版。那时对我来说,VoIP仍是很新的东西。我下载并安装了它,几分钟后,从插在我电脑后面的电话机里传出了电话拨号音,这令我很是兴奋。接下来,我花了几天的时间研究拨号计划,绞尽脑汁的想可否能在链接到个人Linux PC上的电话上实现一些好玩的东西。因为作过许多Web开发,所以我积累了好多新鲜的点子,好比说根据来电显示号码与客户电话号码的对应关系来猜测他们为何事情打电话等。我也想根据模式匹配来作个人拨号计划,并着手编写个人第一个模块。最初,我作的第一个模块是app_perl,如今叫作res_perl,当时曾用它在Asterisk中嵌入了一个Perl5的解释器。如今我已经把它从个人系统中去掉了。 程序员

后来我开始开发一个Asterisk驱动的系统架构,用于管理咱们的呼入电话队列。我用app_queue和如今叫作AMI(大写字母老是看起来比较酷)的管理接口开发了一个原型。它确实很是强大。你能够从一个T1线路的PSTN号码呼入,并进入一个呼叫队列,坐席表明也呼入该队列,从而能够对客户进行服务。很是酷!我一边想一边看着个人可爱的Web页显示着全部的队列以及他们的登陆状况。而且它还能周期性的自动刷新。使人奇怪的是,有一次我浏览器一角上的小图标在过了好长时间后仍在旋转。那是我第一次据说一个词,一个令我永远没法忘记的词 — 死锁。 算法

那是第一次,但决不是最后一次。那一天,我几乎学到了全部关于GNU调试器的东西,而那只是许多问题的开始。队列程序的死锁,管理器的死锁。控制台的死锁开始还比较少,后来却成了一个永无休止的过程。如今,我很是熟悉“段错误(Segmentation Fault)”这个词,它真是一个计算机开发者的玩笑。通过一年的辛勤排错,我发现我已出乎意料的很是精通C语言而且有绝地战士般的调试技巧。我有了一个分布于七台服务器、运行于DS3 TDM信道的服务平台。与此同时,我也为这一项目贡献了大量的代码,其中有好可能是我具备明确版权的完整文件(http://www.cluecon.com/anthm.html)。 数据库

到了2005年,我已经俨然成了很是有名的Asterisk开发者。他们甚至在CREDITS文件以及《Asterisk,电话将来之路》这本书中感谢我。在Asterisk代码树中我不只有大量的程序,并且还有一些他们不须要或者不想要的代码,我把它们收集到了个人网站上。(至今仍在 http://www.freeswitch.org/node/50) 编程

Asterisk 使用模块化的设计方式。一个中央核心调入称为模块的共享目标文件以扩展功能。模块用于实现特定的协议(如SIP)、程序(如个性化的IVR)和其它外部接口(如管理接口)等。 Asterisk的核心是多线程的,但它很是保守。仅仅用于初始化的信道以及执行一个程序的信道才有线程。任何呼叫的B端都与A端都处于同一线程。当某些事件发生时(如一次转移呼叫必须首先转移到一个称做伪信道的线程模式),该操做把一个信道全部内部数据从一个动态内存对象中分离出来,放入另外一个信道中。它的实如今代码注释中被注明是“肮脏的”[1]。反向操做也是如此,当销毁一个信道时,须要先克隆一个新信道,才能挂断原信道。同时也须要修改CDR的结构以免将它视为一个新的呼叫。所以,对于一个呼叫,在呼叫转移时常常会看到3或4个信道同时存在。 windows

这种操做成了从另外一个线程中取出一个信道事实上的方法,同时它也正是开发者许许多多头痛的源头。这种不肯定的线程模式是我决定着手重写这一应用程序的缘由之一。 浏览器

Asterisk使用线性链表管理活动的信道。链表经过一种结构体将一系列动态内存串在一块儿,这种结构体自己就是链表中的一个成员,并有一个指针指向它本身,以使它能连接无限的对象并能随时访问它们。这确实是一项很是有用的编程技术,可是,在多线程应用中它很是难于管理。在线程中必须使用一个信号量(互斥体,一种相似交通灯的东西)来确保在同一时刻只有一个线程能够对链表进行写操做,不然当一个线程遍历链表时,另外一个线程可能会将元素移出。甚至还有比这更严重的问题 ─ 当一个线程正在销毁或监听一个信道的同时,如有另一个线程访问该链表时,会出现“段错误”。“段错误”在程序里是一种很是严重的错误,它会形成进程当即终止,这就意味着在绝大多数状况下会中断全部通话。咱们全部人都看到过“防止初始死锁”[2]这样一个不太为人所知的信息,它试图锁定一个信道,在10次不成功以后,就会继续往下执行。 服务器

管理接口(或AMI)有一个概念,它将用于链接客户端的套接字(socket)传给程序,从而使你的模块能够直接访问它。或者说,更重要的是你能够写入任何你想写入的东西,只要你所写入的东西符合Manager Events所规定的格式(协议)。但遗憾的是,这种格式没有很好的结构,于是很难解析。

Asterisk的核心与某些模块有密切的联系。因为核心使用了一些模块中的二进制代码,当它所依赖的某个模块出现问题,Asterisk就根本没法启动。若是你想打一个电话,至少在 Asterisk 1.2中,除使用app_dial和res_features外你别无选择,这是由于创建一个呼叫的代码和逻辑其实是在app_dial中,而不是在核内心。同时,桥接语音的顶层函数实际上包含在res_features中。

Asterisk的API没有保护,大多数的函数和数据结构都是公有的,极易致使误用或被绕过。其核心很是混乱,它假设每一个信道都必须有一个文件描述符,尽管实际上某些状况下并不须要。许多看起来是如出一辙的操做,却使用不一样的算法和杰然不一样的方式来实现,这种重复在代码中随处可见。

这仅仅是我在Asterisk中遇到的最多的问题一个简要的归纳。做为一个程序员,我贡献了大量的时间,并贡献了个人服务器来做为CVS代码仓库和Bug跟踪管理服务器。我曾负责组织每周电话会议来计划下一步的发展,并试图解决我在上面提到过的问题。问题是,当你对着长长的问题列表,思考着须要花多少时间和精力来删除或重写多少代码时,解决这些问题的动力就渐渐的没有了。值得一提的是,没有几我的赞成个人提议并愿意同我一道作一个2.0的分支来重写这些代码。因此在2005年夏天我决定本身来。

在开始写FreeSWITCH时,我主要专一于一个核心系统,它包含全部的通用函数,即受到保护又能提供给高层的应用。像Asterisk同样,我从Apache Web服务器上获得不少启发,并选择了一种模块化的设计。第一天,我作的最基本的工做就是让每个信道有本身的线程,而无论它要作什么。该线程会经过一个状态机与核心交互。这种设计能保证每个信道都有一样的、可预测的路径和状态钩子,同时能够经过覆盖向系统增长重要的功能。这一点也相似其它面向对象的语言中的类继承。

作到这点其实不容易,容我慢慢讲。在开发FreeSWITCH的过程当中我也遇到了段错误和死锁(在前面遇到的多,后来就少了)。可是,我从核心开始作起,并从中走了出来。因为全部信道都有它们本身的线程,有时候你须要与它们进行交互。我经过使用一个读、写锁,使得能够从一个散列表(哈希)中查找信道而没必要遍历一个线性链表,而且能绝对保证当一个外部线程引用到它时,一个信道没法被访问也不能消失。这就保证了它的稳定,也不须要像Asterisk中“Channel Masquerades”之类的东西了。

FreeSWITCH核心提供的的大多数函数和对象都是有保护的,这经过强制它们按照设计的方式运行来实现。任何可扩展的或者由一个模块来提供方法或函数都有一个特定的接口,从而避免了核心对模块的依赖性。

整个系统采用清晰分层的结构,最核心的函数在最底层,其它函数分布在各层并随着层数和功能的增长而逐渐减小。

例如,咱们能够写一个大的函数,打开一个任意格式的声音文件向一个信道中播放声音。而其上层的API只需用一个简单的函数向一个信道中播放文件,这样就能够将其做为一个精减的应用接口函数扩展到拨号计划模块。所以,你能够从你的拨号计划中,也能够在你个性化的C程序中执行一样的playback函数,甚至你也能够本身写一个模块,手工打开文件,并使用模块的文件格式类服务而无需关注它的代码。

FreeSWITCH由几个模块接口组成,列表以下:

拨号计划(Dialplan): 实现呼叫状态,获取呼叫数据并进行路由。

终点(Endpoint): 为不一样协议实现的接口,如SIP,TDM等。

自动语音识别/文本语音转换(ASR/TTS): 语音识别及合成。

目录服务(Directory): LDAP类型的数据库查询。

事件(Events): 模块能够触发核心事件,也能够注册本身的个性事件。这些事件能够在之后由事件消费者解析。

事件句柄(Event handlers): 远程访问事件和CDR。

格式(Formats): 文件模式如wav。

日志(Loggers): 控制台或文件日志。

语言(Languages): 嵌入式语言,如Python和JavaScript。

语音(Say): 从声音文件中组织话语的特定的语言模块。

计时器(Timers): 可靠的计时器,用于间隔计时。

应用(Applications): 能够在一次呼叫中执行的程序,如语音信箱(Voicemail)。

FSAPI(FreeSWITCH 应用程序接口) 命令行程序,XML RPC函数,CGI类型的函数,带输入输出原型的拨号计划函数变量。

XML 到核心XML的钩子可用于实时地查询和建立基于XML的CDR。

全部的FreeSWITCH模块都协同工做并仅仅经过核心API或内部事件相互通讯。咱们很是当心地实现它以保证它能正常工做,并避免其它外部模块引发不指望的问题。

FreeSWITCH的事件系统用于记录尽量多的信息。在设计时,我假设大多数的用户会经过一个个性化的模块远程接入FreeSWITCH来收集数据。因此,在FreeSWITCH中发生的每个重要事情都会触发一个事件。事件的格式很是相似于一个电子邮件,它具备一个事件头和一个事件主体。事件可被序列化为一个标准的Text格式或XML格式。任何数量的模块都可以链接到事件系统上接收在线状态,呼叫状态及失败等事件。事件树内部的mod_event_socket可提供一个TCP链接,事件能够经过它被消费或记入日志。另外,还能够经过此接口发送呼叫控制命令及双向的音频流。该套接字能够经过一个正在进行的呼叫进行向外链接(Outbound)或从一个远程机器进行向内(Inbound)链接。

FreeSWITCH中另外一个重要的概念是中心化的XML注册表。当FreeSWITCH装载时,它打开一个最高层的XML文件,并将其送入一个预处理器。预处理器能够解析特殊的指令来包含其它小的XML文件以及设置全局变量等。在此处设置的全局变量能够在后续的配置文件中引用。

如,你能够这样用预处理指令设置全局变量:

<X-PRE-PROCESS cmd="set" data="moh_uri=local_stream://moh"/>

如今,在文件中的下一行开始你就可使用 $$(moh_uri},它将在后续的输出中被替换为 local_stream://moh。处理完成后XML注册表将装入内存,以供其它模块及核心访问。它有如下几个重要部分:

配置文件: 配置数据用于控制程序的行为。

拨号计划: 一个拨号计划的XML表示能够用于 mod_dialplan_xml,用以路由呼叫和执行程序。

分词: 可标记的IVR分词是一些能够“说”多种语言的宏。

目录: 域及用户的集合,用于注册及帐户管理。

经过使用XML钩子模块,你能够绑定你的模块来实时地查询XML注册表,收集必要的信息,以及返回到呼叫者的静态文件中。这样你能够像一个WEB浏览器和一个CGI程序同样,经过同一个模型来控制动态的SIP注册,动态语音邮件及动态配置集群。

经过使用嵌入式语言,如Javascript, Java, Python和Perl等,可使用一个简单的高级接口来控制底层的应用。

FreeSWITCH工程的第一步是创建一个稳定的核心,在其上能够创建可扩展性的应用。我很高兴的告诉你们在2008年5月26日将完成FreeSWITCH 1.0 PHOENIX版。有两位敢吃螃蟹的人已经把还没到1.0版的FreeSWITCH 用于他们的生产系统。根据他们的使用状况来看,咱们在一样的配置下能提供Asterisk 10倍的性能。

我但愿这些解释能足够归纳FreeSWICH和Asterisk的不一样之处以及我为什么决定开始FreeSWITCH项目。我将永远是一个Asterisk开发者,由于我已深深的投入进去。而且,我也但愿他们在之后的Asterisk开发方面有新的突破。我甚至还收集了不少过去曾经觉得已经丢失的代码,放到我我的的网站上供你们使用, 也算是做为我对引导我进入电话领域的这一工程的感激和美好祝愿吧。

Asterisk是一个开源的PBX,而FreeSWITCH则是一个开源的软交换机。与其它伟大的软件如 Call Weaver、Bayonne、sipX、OpenSER以及更多其它开源电话程序相比,二者还有很大发展空间。我每一年都指望能参加在芝加哥召开的 ClueCon大会,并向其它开发者展现和交流这些项目(http://www.cluecon.com)。

咱们全部人均可以相互激发和鼓励以推动电话系统的发展。你能够问的最重要的问题是:“它是完成该功能的最合适的工具吗?”

[1] / XXX This is a seriously wacked out operation. We’re essentially putting the guts of the clone channel into the original channel. Start by killing off the original channel’s backend. I’m not sure we’re going to keep this function, because while the features are nice, the cost is very high in terms of pure nastiness. XXX /

[2] Avoiding initial deadlock

 

PS:摘抄网上的:

1:Asterisk是针对百人左右的小型系统,相同的硬件配置下单系统并发也就几百路(不一样版本性能有必定差别,大概在 200-400之间),而根据国外爱好者测试freeswitch 可达到2000-3000路sip通道(媒体流并发),

2:Asterisk用动态链表来管理每一个打开的通道,这样在多线程中很是难于管理(须要频繁的锁定和解锁)。而freeswitch每一个呼叫通道都会用一个线程来管理呼叫状态,大大减小了死锁发生的概率,freeswitch核心代码高度抽象,尽可能将复杂代码集中化。

3:Asterisk用DUNDi协议设计分布式系统,Fs使用外部数据库实现分布系统,作得更好,甚至能够一台服务器经过数据库注册到另外一台服务器上。

4:freeswitch 支持夸平台,linux, unix, windows 等,asterisk基本只支持 linux, bsd系列。

5. freeswitch配置采用xml,asterisk采用linux下面通用配置文件格式语法,而 采用xml格式配置文件是freeswich使用者抱怨最多的部分,对于不懂xml格式的开发者在刚开始使用时是个折磨。

相关文章
相关标签/搜索