将 Adhearsion 应用于 Asterisk
随着其他企业服务器从 Windows NT 和 UNIX 等闭源平台转向的趋势,VoIP 也正蓬勃发展,大量迁移到开源的 Asterisk PBX 和 Linux。就其自身而言,Asterisk 看起来比以往任何时候都更准备好满足这些需求。Asterisk 的维护公司 Digium 的优秀员工强调产品质量并引领 VoIP 创新。
将技术与 VoIP 集成也使前景更加美好。随着标准计算机处理呼叫,连接这些点显然很有意义。然而,传统的 Asterisk 集成方式实际上是权宜之计,而不是全面的解决方案。
这个新的开源框架解决了从企业必备功能到客厅黑客项目等一系列集成问题。Adhearsion 用 Ruby 编写,开箱即用,其功能远超许多公司花费数千美元购买的软件。虽然它的名字来源于“你可以听到的粘合力 (adhesion you can hear)”,但即使在 VoIP 框架之外尝试将两种或多种技术拼接在一起时,它也可以在没有 Asterisk 的情况下工作。由于 Ruby 和 Adhearsion 都旨在提高生产力,因此 VoIP 系统现在比以往任何时候都更适合成为您业余时间的有趣项目,或成为拉近员工、客户和世界距离的有效工具。
当呼叫进入由 Adhearsion 管理的 Asterisk 服务器时,Asterisk 充当呼叫的隐喻内核,通过管理底层硬件、转换编解码器、消除回声等。对于它在呼叫期间应执行的逻辑,例如播放消息、连接到某人或接收输入,它会建立与正在运行的 Adhearsion 守护进程的 TCP 连接,并一次接收一个命令,执行每个命令并发送回响应。
让我们运行一个可工作的 Adhearsion 盒子来演示。
Adhearsion 像大多数其他 Ruby 项目一样,使用出色的包管理器 RubyGems。如果您阅读过 Linux Journal 2006 年 7 月刊关于 Ruby 的文章,或者接触过这门语言,您可能已经遇到过这个出色的软件。如果还没有,任何流行的 Linux 发行版的包管理器几乎没有区别。Adhearsion 的安装过程包括安装 Ruby 和 RubyGems,然后使用以下命令拉取 Adhearsiongem install命令。
下面的安装示例使用 Ubuntu/Debian 的 apt-get 包管理器。因此,您可能需要对您的发行版的软件包名称进行细微修改。如果您没有 RubyGems 系统包,请访问其网站(参见“资源”部分)并获取 tarball
apt-get install ruby1.8 ruby1.8-dev irb1.8 tar zxf rubygems*.tgz cd rubygems* ruby setup.rb gem install adhearsion --include-dependencies
通过最后一个命令,RubyGems 创建了 ahn(发音为 Anne)命令。
您可以使用 ahn 创建新的 Adhearsion 项目,正常或作为守护进程执行 Adhearsion 进程,远程安装开源框架扩展,阅读帮助文档等等。输入ahn help以获取更多信息。
本指南假设您已配置了现有的 Asterisk 服务器。如果您想了解更多关于如何操作的信息,请参阅“资源”部分。只需在 extensions.conf 配置文件中添加一个简单的指令,即可让 Asterisk 使用 Adhearsion。如果您的电话用户被路由到您的“internal”上下文,您将按如下方式修改“internal”上下文的定义
[internal] exten => _X.,1,AGI(agi://192.168.1.2)
接下来,使用以下命令创建一个新的 Adhearsion 项目
ahn create ~/helloworld
这将生成一个完整的目录结构,您可以在其中安全地操作。要启动 Adhearsion,请将 Adhearsion 目录的路径提供给 ahn
ahn start ~/helloworld
继续阅读以了解您可以修改这个新项目以满足您的兴趣的方法。
传统上,工程师通过两种方式路由 Asterisk 呼叫:修改 extensions.conf 文件或编写 Asterisk 网关接口(可以理解为 CGI)脚本。大多数情况下使用前者,但在集成很重要的情况下,可以将两者一起使用。通常,开发人员在他们的 Asterisk 机器的文件系统上编写 AGI 脚本,这些脚本通过 STDIN 和 STDOUT 传输纯文本。
但是,当代码复杂性和使用复杂性都扩展时,问题就出现了。由于 Asterisk 将此脚本作为子进程执行,因此它实际上会构建 AGI 程序所需的任何开销(例如,Perl 解释器或数据库套接字),执行代码,然后再次将其全部拆除。虽然在较小的情况下可以容忍,但随着并发使用量的增加,进程对内存和运行时间的消耗也更大。此外,在没有框架的情况下,以这种方式编写的脚本也包含次优的重复量。
Adhearsion 利用 Asterisk 的 FastAGI 功能来管理来自另一个可寻址 IP 的机器的呼叫。这样,宝贵的 RAM 和 CPU 周期几乎不会对 Asterisk 盒子产生影响,而且同样重要的是,Adhearsion 已经在每个呼叫进入之前就建立了开销。
使用您之前创建的新示例 Adhearsion 项目,打开您的 extensions.rb 文件。下面,我包含了一个示例文件,您可以从中了解 Adhearsion 如何处理呼叫。现在花一点时间阅读一下
# File extensions.rb internal { case extension when 101...200 employee = User.find_by_extension extension if employee.busy? then voicemail extension else dial employee, :for => 10.rings voicemail unless last_call_successful? end when 888 play weather_report("Dallas Texas") when 999 then +joker_voicemail end } joker_voicemail { play %w"a-connect-charge-of 22 cents-per-minute will-apply" sleep 2.seconds play 'just-kidding-not-upset' check_voicemail }
如果您有冒险精神,请尝试现在在您的 Asterisk 盒子上的任何电话中使用它。
请注意,这都是有效的 Ruby 代码。Ruby 从根本上允许修改语言各个方面的功能。因此,Ruby 代码可以针对特定需求或特定领域的需求进行修改——因此得名 Ruby 场景中常用的名称:领域特定语言或 DSL。然而,Ruby 语言的全部优势仍然存在,使其兼具两全其美的优点。
因为我们上面的示例 Asterisk extensions.conf 在名为 internal 的上下文中调用 Adhearsion,所以 Adhearsion 按照约定直接映射它。Adhearsion 在处理上下文之前创建许多与呼叫相关的变量作为简单的局部变量。用于自动生成的扩展变量的 case 语句显示了 Ruby 对数字范围的出色支持,但是类似 Perl 的正则表达式文字可以很好地替换它。
下一行employee = User.find_by_extension extension可能证明出乎意料地微妙。您可以很容易地推断出存在一些具有扩展名的用户集合,以及一些通过其扩展名查找它们的方法。如果 ActiveRecord 对您来说是新的,您可能没有立即意识到这实际上是抽象了数据库访问。在深入解释其工作原理之前,让我们先完成对该文件的理解。
确定是否应打扰用户的实现方式可能有所不同,但我们只需要知道是或否的答案。通过将呼叫者静默发送到他们的语音邮件,用户可以保持专注。
Ruby 方法
请注意示例中 Ruby 方法通常与其他编程语言的不同之处。当不存在歧义时,调用方法不需要括号。这起初可能看起来很奇怪,但如果您可以说服自己接受它,您可能会发现阅读和编写 Ruby 代码有点愉快。
此外,Ruby 允许在方法名称末尾使用问号和感叹号,以更好地表达含义。这遵循 Ruby 的最少意外原则,以识别通常用于表示用户可以采取行动的某些条件的方法,或者使用感叹号可选地表示该方法对变量具有破坏性。
拨打 888 的用户会获得从互联网资源转换而来的天气报告,转换为一系列随附于标准 asterisk-sounds 包的播放声音文件。如果他们拨打 999,事情会变得很花哨——joker_voicemail 前面的加号正是您可能想到的那样:它执行 joker_voicemail 代码块。
joker_voicemail 部分使用%w"",这是一个用于单词数组的出色 Ruby 字面量。Ruby 会自动按空格分隔此字符串,创建一个包含一系列声音的数组,这些声音的名称直观地对应于它们产生的声音。但您会问,“这里的 22 呢?”
当 Adhearsion 遇到数字时,它不会执行 Playback() 应用程序,而是使用 SayNumbers()。这会将数字分解为单词 twenty 和 two——这两个单词都存在声音。最终结果不包含逗号,索引之间没有引号,也没有指定用于播放文件的应用程序。为什么要这样呢?
您可以在 Adhearsion 网站上免费找到更详细、可用的拨号计划和文档。有关更多信息,请参阅“资源”部分。
如果您了解过 Ruby on Rails,您可能知道其明星级的对象关系映射器 ActiveRecord。37signals 的杰出人士并没有将 ActiveRecord 与 Rails 框架紧密地交织在一起,而是允许任何人通过 gem 使用这个出色的库。当您安装 Adhearsion 时,--include-dependencies部分会为您拉取它。
从数据库建模的角度来看,典型的 VoIP 应用程序具有用户和用户组。因此,在我们的数据库中——无论您使用 MySQL、PostgreSQL、SQLite 还是几乎任何其他流行的关系数据库管理系统——拥有一个名为 users 的表和一个名为 groups 的表是有意义的。因为每个记录代表数据库中的单个用户或组,所以拥有 User 对象和 Group 对象也是有意义的。开始了解对象关系映射器如何在此处应用了吗?
这是一个具体的、有用的例子
class User < ActiveRecord::Base belongs_to :group validates_uniqueness_of :extension def busy? # Implemented how you wish end end class Group < ActiveRecord::Base has_many :users end
这就是魔力发生的地方。通过如此少的代码,ActiveRecord 实际上可以得出许多非常符合逻辑的结论。给定类名 User 和 Group,它通过将它们转换为小写,然后分别查找复数形式 users 和 groups 来找到它们合适的表。如果我们使用类名 Person 而不是 User,ActiveRecord 将假定其关联表为 people。
为了帮助我们自己,我们告诉 ActiveRecord User 属于 Group,而 Groups 有许多用户。鉴于此,users 中记录映射到的外键默认情况下被假定为 group_id。通过这种识别,只需调用 jay.group 即可检索 User 实例所属的 Group 对象。如果 jay 变量属于 Group codemecca,则可以通过 codemecca.users 检索 jay 和任何其他潜在变量。
那么让我们看看上面的拨号计划示例。我们正在调用 User 上名为 find_by_extension 的方法。我们是否必须在这里的任何地方创建该方法?没有。但是为什么不呢?ActiveRecord 实际上创建了该方法,因为它查看了 users 表内部,找到了其列的名称并创建了诸如 find_by_extension 之类的方法。如果将其解释为 MySQL select 语句,它将读取SELECT * FROM users WHERE EXTENSION='somenumber' LIMIT 1;。不错的快捷方式,对吧?
在企业环境中,企业依赖于协作,人们有充分的理由寻找任何改进协作的方法。Adhearsion 提供了一个免费的协作工具,本着将一切粘合在一起的精神,将传统协作更进一步。
现代 IP 桌面电话通常具有微型浏览器,可以从 HTTP 拉取 XML 文档并将其转换为设备上的交互式菜单。虽然这看起来像是一项伟大的技术,但有一个重要的注意事项:每个供应商的 XML 格式都不同,没有人很好地记录该功能,并且可用功能差异很大。一些供应商支持图像、HTML 表单和用于初始化呼叫的特殊 URI,而另一些供应商则为您提供大约三到四个 XML 元素供您选择。
如果供应商无法在一致性方面进行协作,则必须通过翻译来抽象他们的差异(和怪癖)。Micromenus 正是通过一些额外的巧妙提示来做到这一点的。
由于 Micromenus 子框架作为内置助手存在,因此您可以在 config/helpers/micromenus/ 目录中管理菜单。当 HTTP 请求通过端口 1337 进入时,Adhearsion 将 URL 路径中的第一个参数作为所需的文件名。例如,如果您将电话编程为使用 http://192.168.1.228/main 作为其 Micromenus URL,Adhearsion 将尝试加载文件 config/helpers/micromenus/main.rb。修改和创建此处的文件即使在不重启 Adhearsion 的情况下也会生效。
使用此示例文件名,让我们用一些 Micromenu 代码填充它
# config/helpers/micromenus/main.rb call "Check your voicemail" do check_voicemail end call 505, "Call Support" item "Join a conference" do call 'Join Marketing' do join :marketing end call 'Join Support' do join :support end call 'Join Ninjas' do join :ninjas end end item "Weather forecasts" do call "New York, New York" do play weather_report('New York, New York') end call "San Jose, California" do play weather_report('San Jose, California') end call "University of Texas at Dallas" do play weather_report(75080) end end item "System Administration" do item 'Uptime: ' + %x'uptime' item "View Current SIP users" do PBX.sip_users.each do |user| item "#{user[:username]@#{user[:ip]}" end end end
这个简单的示例虽然非常简洁,但却生成了一个完整的、体面的公司内联网。为 item 方法提供一个代码块会自动将其转换为链接,从而抽象化 URL。在子菜单中放置实际的 Ruby 代码会赋予菜单行为。在电话上使用 call 方法实际上会将呼叫置于指定的扩展(没有代码块)或,因为 Adhearsion 也处理呼叫本身,所以在它发现用户通过 Micromenu 生成呼叫时,会在代码块内执行拨号计划行为。这例证了在同一框架中将呼叫路由和基于 Web 的协作紧密结合在一起的好处。
正如任何 IP 电话工程师都可以肯定,采用别人的 VoIP 功能并将其与自己的功能合并并非易事。您也无法在线找到大量的 VoIP 功能。重用此类代码的巨大难度严重阻碍了交易。如果没有将它们置于透视图中的其他六个配置文件,标准的 PBX 拨号计划配置通常毫无意义。
Adhearsion 不仅允许其他人轻松地与其 VoIP 应用程序集成,而且还促进了集成。Adhearsion 网站托管了一个友好的社区,用户可以在其中提交、标记、浏览和评价扩展。所有这些都可以免费下载并复制到您的 helpers/ 目录,Adhearsion 将在其中找到它并自动将其功能吸收到框架中。扩展可以从向拨号计划 DSL 添加新方法到在单独线程中运行的整个 Web 服务器不等。
假设您一时冲动决定编写一个 VoIP 计算器应用程序,该应用程序会说出答案。因为您有很多不同的数学运算可供选择,所以您决定实现一个阶乘方法并将其公开给整个框架。这只需要创建文件 helpers/factorial.rb 并向其中添加以下代码
# helpers/factorial.rb def your_factorial num (1..num).inject { |sum,n| n+sum } end
当您启动 Adhearsion 时,您将能够在您的拨号计划、您的 Micromenus、Adhearsion 的分布式计算服务器以及框架的任何其他角落中使用它。但是,您说这还不够。
像任何动态类型语言一样,对于非常非常大的数字,这只是花费太长时间。如果我们可以用 C 编写这个扩展,那不是很好吗?好吧,我们可以。
出色的第三方库 RubyInline 接受 C/C++ 代码字符串,从文件或其他方式读取,并使用 Ruby 源代码头自动编译、缓存并动态加载到 Ruby 解释器中。该库甚至找到任何方法签名并创建匹配的 Ruby 方法。当本机代码完成其工作时,静态返回类型会自动转换为 Ruby 等效类型(int 转换为 Fixnum,double 转换为 Float)。借助此库,Adhearsion 允许使用 Ruby 以外的语言更有效地扩展框架。
由于 RubyInline 需要 Ruby 开发头文件和配置的编译环境,因此它不作为 Adhearsion 依赖项提供。如果您有 GCC 及其外围开发要求,请执行gem install RubyInline,并将此代码放入文件 helpers/factorial.alien.c 中
int fast_factorial(int num) { int sum = 0, counter = 1; while(counter <= num) { sum += counter++; } return sum; }
与其他扩展一样,Adhearsion 会自动找到此文件并将其功能挂钩到框架中。如果 C 代码需要特殊的编译指令或 include 语句,您可以轻松地将这些添加到帮助器的配置文件中,所有帮助器都可以选择拥有该文件。这些配置文件存在于 config/helpers 目录中,其名称与它们所属的帮助器相同,但带有 YAML .yml 扩展名。
让您的想象力尽情驰骋。如果您对 VoIP 系统提出了一个绝妙的新想法,Adhearsion 扩展架构将成为轻松实现您的概念的绝佳跳板。
资源
Adhearsion 网站: adhearsion.com
PwnYourPhone,Adhearsion 官方视频播客: PwnYourPhone.com
Adhearsion 官方博客: jicksta.com
VoIP-Info Wiki: voip-info.org
Adhearsion Wiki: docs.adhearsion.com
Codemecca 网站: codemecca.com
RubyGems 网站: rubyforge.org/projects/rubygems
Jay Phillips 是一位从 VoIP 和 Ruby 爱好者转变为企业家的专家。作为 Adhearsion 的创建者和项目经理,Jay 通过他新创立的位于达拉斯的公司 Codemecca, LLC 提供 Adhearsion 支持和咨询服务。