知行合一
全部博文(31)
分类: 系统运维
2009-08-19 22:19:17
通话前转是VoIP服务提供商提供的重要的特性。它的实现有两种方式,一种是派生(forking),一种是重定向。当使用派生时,新的通话分支将会被创建,在首选的目的地失败时(busy或timeout),它会向新的目的地发送新的INVITE消息。而使用重定向,代理只是会向发起者发送应答信息,并将新的重定向的通话地址告诉发起者。
本章结束你将能够:
l
描述诸如派生和重定向的概念
l
实现通话前转
l
实现忙线时的通话前转
l
使用AVP资源存储通话前转数据
l
使用失败路由块(failure
route)前转没有应答和忙线的通话
在本材料中,我们只适用派生,因为它比重定向更加安全,而且还能够允许我们对通话进行计费。重定向的方法对于VoIP服务提供者来说几乎是无用的,因为代理不在通话的路径当中,在这种情况下根本无法计费。
让我们来检验一下我们的进度。VoIP服务提供商的解决方案中有许多部件。为了避免失去对整体的把握,我们在每一章中都会展示下面的这幅图。在这一章中,我们将同媒体服务器合作(采用Asterisk Voice Mail)。还有许多其他的媒体服务器可供选择,如SEMS(SIP Express Media Server ),Yate,FreeSwitch等等。我们之所以选择Asterisk是因为它非常流行。“前转”对于在某些情况下将通话送到媒体服务器是非常重要的特性。本章我们会介绍语音邮件(voicemail)的使用。媒体服务器还可以被很多应用使用,如:IVRs,播放提示信息,播放语音文本,和声音识别等。请记住,SIP代理从不会去处理媒体,所以在需要对媒体进行的处理的情况下,你需要媒体服务器。
本章学习完后,VoIP服务提供商将能够使用连续派生将无应答和忙线的通话发送到语音邮箱服务器上。
本章中,我们将实现三种通话前转。此前转对于语音邮箱的操作很重要。
l
盲转(Blind call
forwarding)——所有打给某一电话号码的通话的INVITE请求都会被立即重新指向存储在user_preferences表中的电话号码。SIP路由器将派生出一路指向新地址的通话。配置了前转的话机无论是否注册上,都不会振铃。
l
忙线前转(Forward
on busy)——在这种情况下,我们将使用failure_route特性来拦截“486 Busy”消息,并创建一个新的通话将INVITE请求发送到最终的目的地。
l
无应答前转(Forward
on no answer)——如果话机对于INVITE请求的回复是“408 Request Timeout”,那么OpenSER也会使用failure_route特性来拦截该消息并创建新的通话将INVITE消息发送到最终的目的地。
所有的呼叫前转的目的地址都被存储在叫做user_preferences的表中。我们在这一章将介绍如AVPs(Attribute-Value Pairs)和伪变量(pseudo-variables)的一些新的概念。AVPs是OpenSER的核心使用。AVPOPS(Attribute-Value Pairs Operations)模块提供了几个操作AVPs(如,同SIP请求和数据库进行交互,使用字符串和正则表达式进行操作。)的函数。
伪变量是系统变量,你可以在你的脚本中将其作为参数或内部函数(inside functions)使用。在脚本执行之前,这些变量会被相应的值替换掉。有些模块可以接受伪变量,如:
l
ACC
l
AVPOPS
l
TEXTOPS
l
UAC
l
XLOG
伪变量的名字总是以“$”开头。如果你想要在你的脚本中使用$字符,那么你必须像这样进行换码——$$。有一个伪变量的预定义集合。OpenSER1.1中使用的OpenSER伪变量如下:
$ar |
Auth realm |
$au |
Auth username |
$br |
Request's first branch |
$bR |
All Request's branches |
$ci |
Call-ID header |
$cl |
Content length |
$cs |
Cseq |
$ct |
Contact Header |
$cT |
Content Type |
$dd |
Domain of destination URI |
$di |
Diversion header URI |
$dp |
Port of destination URI |
$dP |
Transport protocol of destination URI |
$ds |
Destination set |
$du |
Destination URI |
$fd |
From URI domain |
$fn |
From display name |
$ft |
From Tag |
$fu |
From URI |
$fU |
From URI username |
$mb |
SIP message buffer |
$mf |
Flags |
$mF |
Flags in hexadecimal |
$mi |
SIP message ID |
$ml |
SIP message length |
$od |
Domain in SIP request's original URI |
$op |
|
$oP |
Transport protocol of SIP request's original URI |
$ou |
Request's original URI |
$oU |
Username in SIP request's original URI |
$pp |
Process id |
$rd |
Domain in SIP request's URI |
$rb |
Body of request/reply |
$rc |
Returned code |
$rm |
SIP request's method |
$rp |
SIP request's port of R-URI |
$rP |
Transport protocol of SIP request URI |
$rr |
SIP reply reason |
$rs |
SIP reply status |
$rt |
Refer-to URI |
$ru |
SIP request's URI |
$rU |
Username in SIP request's URI |
$Ri |
Received IP address |
$Rp |
Received Port |
$si |
IP source address |
$sp |
Source port |
$td |
To URI domain |
$tn |
To display name |
$tt |
To tag |
$tu |
To URI |
$tU |
To URI username |
$Tf |
String formatted time |
$Ts |
Unix time stamp |
$ua |
User agent header |
$re |
Remote-Party-ID header URI |
属性-值对的操作相当于是允许了对用户的首选项(user preferences)进行访问和操作。AVP可以看作是与标识(字符串或整数)相关联的一个值。在OpenSER的处理过程中,AVP与事务捆绑在一起。当事务开始时,AVP被分配,当其结束时,则被释放。
AVPs的出现创造了一些服务实现和用户或域名的用户首选项处理的新的可能性。它们可以在配置脚本中被直接使用并从MySQL数据库中加载数据。
属性-值对的引用与变量的引用非常相似。
$avp(id[N])
Where ID is:
l
si : name —— AVP标识名称。“s”和“i”分别表示字符串和整数。
l
name —— 别名AVP的名称。可以是字符串,也可以是整数。
例子:
$avp (i: 700)
$avp (s: blacklist)
对于了解Asterisk的人来说,AVPOPS模块之于OpenSER就相当于AstDB函数之与Asterisk。然而,实现方式非常不同,AVPs更加强大,允许一些更加高级的特性,如数据库的查询和直接将数据插入SIP包等。有许多与AVPs相联系的函数如下:
l
avp_db_load:将AVPs从数据库加载至内存
l
avp_db_store:将AVPs存进数据库
l
avp_db_delete:从数据库中删除AVPs
l
avp_db_query:进行数据库查询并将结果存进AVP中
l
avp_delete:从内存中删除AVPs
l
avp_pushto:将AVP的值插入sip消息
l
avp_check:使用一个操作符和一个值来检查AVP的值
l
avp_copy:拷贝AVP到另一个
l
avp_printf:格式化一个字符串到AVP
l
avp_subst:查找并替换一个值到AVP
l
avp_op:允许在AVPs上进行算术操作
l
is_avp_set:检查这个AVP名字是否被设置
l
avp_print:打印内存中的所有AVPs(为了debug)
由于文档中的这些函数,你可以对句法进行检查。现在,我们必须要了解如何使用avp_db_load和avp_pushto,它们将用在我们的脚本中。关于AVPs在上有一篇非常棒的教程。
AVPs不是很简单。但是如果你把它想象成一对简单的属性和值,那么它们其实也不是那么的复杂。然而,数据库中的AVPs的加载是非常令人费解的。默认的表是usr_preference。有时候我们想要的值并不是和一个特定的用户相关,而是同一个域相关。不管怎样,所有从数据库中被加载的AVPs都来自usr_preference表。
举例:对于呼叫前转,我们有一个与用户相关的前转呼叫。确实是一个usr_preference(用户优先)。让我们来查看usr_preference表的结构。
Id是一个自增域。
l
uuid是一个唯一的用户标识
l
username是用户名
l
domain代表域
l
attribute(AVP名称)
l
type(0–AVP str|Val Str, 1–AVP str|Val Int, 2–AVP int|Val Str,3-AVP int|Val int)
l
value(AVP值)
l
last modified(最后修改的日期)
AVPs可以与一个用户相关联也可以与一个域相关联。所以,你能够使用这两个参数中的任何一个来加载AVPs。你可以将AVP与uuid(唯一的用户ID)相关联,与一个用户名相关联(单域设置),或者与用户名和域建立多域设置关系。
函数 avp_db_load的第一个参数是源(source),第二个是avp_name。所以,下面的函数将以字符串的形式加载与被请求用户名中的URI相符合的用户的AVP名称为callfwd的AVP值。
(avp_db_load("$ruri/username","s:callfwd")
之后,我们将把这个AVP插进SIP包中,以将原来的$ruri进行改造。
avp_pushto("$ruri","s:callfwd");
换句话说,如果这个用户设置了对应的呼叫前转号码,那么这通通话将不会呼给原来的用户,而是呼给存储在AVP s : callfwd中的用户。呼叫前转的特别之处就在于将呼叫前转号码插入了usr_preference表中。
在模块加载中,我们指定数据库地址,访问参数和AVP表。
loadmodule
"/usr/lib/openser/modules/avpops.so"
modparam("avpops",
"avp_url", "mysql://openser:openserrw@localhost/openser")
modparam("avpops", "avp_table", "usr_preferences")