Doubango
voip
框架分析之tinysip
协议栈
1.
tinysip
介绍 :
兼容性 : SIP
(RFC 3261)
以及 3GPP
IMS/LTE (TS 24.229) implementation
依赖 tinySAK,
tinyNET, tinySDP, tinyMEDIA, tinyHTTP and tinyIPSec.
2.
SIP
协议 - tinysip
的实现机制
SIP
是一个分层结构的协议,这意味着它的行为是根据一组平等独立的处理阶段来描述,每一阶段之间只是松耦合。协议分层描述是为了表达,从而允许功能的描述可在一个部分跨越几个元素。它不指定任何方式的实现。当我们说某元素包含某层,我们是指它顺从该层定义的规则集。
不是协议规定的每个元素都包含各层。而且,由SIP
规定的元素是逻辑元素,不是物理元素。一个物理实现可以选择作为不同的逻辑元素,甚至可能在一个个事务的基础上。
SIP
的最底层是语法和编码。它的编码使用增强Backus-Nayr
形式语法(BNF
)来规定。
第二层是传输层。它定义了网络上一个客户机如何发送请求和接收响应以及一个服务器如何接收请求和发送响应。所有的SIP
元素包含传输层。
第三层是事务层。事务是SIP
的基本元素。一个事务是由客户机事务发送给服务器事务的请求(使用传输层),以及对应该请求的从服务器事务发送回客户机的所有响应组成。事务层处理应用层重传,匹配响应到请求,以及应用层超时。任何用户代理客户机(UAC
)完成的任务使用一组事务产生。用户代理包含一个事务层,有状态的代理也有。无状态的代理不包含事务层。事务层具有客户机组成部分(称为客户机事务)和服务器组成部分(称为服务器事务),每个代表有限的状态机,它被构造来处理特定的请求。
事务层之上的层称为事务用户(TU
)。每个SIP
实体,除了无状态代理,都是事务用户。当一个TU
希望发送请求,它生成一个客户机事务实例并且向它传递请求和IP
地址,端口,和用来发送请求的传输机制。一个TU
生成客户机事务也能够删除它。当客户机取消一个事务时,它请求服务器停止进一步的处理,将状态恢复到事务初始化之前,并且生成特定的错误响应到该事务。这由CANCEL
请求完成,它构成自己的事务,但涉及要取消的事务。
SIP
通过EMAIL
形式的地址来标明用户地址。每一用户通过一等级化的URL
来标识,它通过诸如用户电话号码或主机名等元素来构造(例如:SIP:vision-com.com
)。因为它与EMAIL
地址的相似性,SIP
URLs
容易与用户的EMAIL
地址关联。
SIP
提供它自己的可靠性机制从而独立于分组层,并且只需不可靠的数据包服务即可。SIP
可典型地用于UDP
或TCP
之上。
Register,Invite, Options …
Nat Tranversal
Dialog Layer
Transaction Layer
Parsing Layer
Transport Layer
sip
协议栈分层结构图
根据sip
消息流向可以分为incoming
message
和outgoing message,
incoming
消息从 下到上,即Transport
Layer → Register,Invite, Options; outgoing message
消息流向与此相反。
3
.
根据以上定义,tinysip
分如下模块:
1).api
外部接口,对sip
协议支持的方法的接口封装,协议栈提供的发起请求及接受请求对应的接口,
包括
registar layer, presence
layer
等上层应用,当前版本支持如下请求:
REGISTER
,SUBSCRIBE(
订阅),MESSAGE(
即时通信),PUBLISH
(状态展示),OPTIONS
(查询服务器能力),INVITE
(发起请求),Cancel
(取消一个请求),
BYE
(结束通话)。
2).
Nat traversal
:Nat
穿越层,tinysip
目前支持stun
,turn
穿透。
3).Dialog
,
会话模块,一路呼叫的唯一标识,
处于sip
事务层之上。
4).parsers
,sip
消息解析,处于
sip
语法层,解析从传输层传递的数据包为协议栈理解的结构。
5).
transactions
, 事务层,事务是一个请求以及与此请求相关的所有响应组成,用
transaction id
唯一标识,由于sip
信令一般由udp
承载,所以不能保证信息的可靠到达,所以事务层必须提供一种机制处理udp
所不能提供的功能,这里一般通过定时器及一个有限状态机来实现。
6).
transports ,
传输层,即 udp
, TCP, TLS, SCTP socket
系统调用系列,此层隐藏了所有传输层的细节,对于incoming
sip message
, 此层为sip
消息的入口,
对于outgoing
sip message,
此层为sip
消息的出口。
4.
doubango sip
协议栈使用流程:
1
).
初始化
doubango
sip
协议栈依赖于tinyNET
模块,所以必须先调用tnet_startup
函数初始化,退出时调用tnet_cleanup
清除资源,初始化sip
协议栈之前必须设置用户的域(realm
参见(1
))
及用户的私有(IMPI
(2
))及共有标别(IMPU
(3
)),
这些为ims
引入的概念。
(1)
realm
解释:reaml
为域名,用来作客户端认证用(authenticate
).
必须是一个有效的 sip
uri
如:sip:vision-com.com.cn
,realm
为sip
协议栈启动之前必须设置的选项,一旦协议栈启动realm
就不可以更改,如果不填写sip
代理服务器地址,则系统会用realm
通过 dns NAPTR + SRV
或者DHCP
(
还没实现)
动态
查找机制确定sip
服务器地址。
(2)
用户私有标识,为用户所属网络赋予的唯一值,用来做验证,为IMS
中的概念,如果用doubango
sip
协议栈作为普通sip
功能,即非IMS
网络中的sip
功能,此处的impi
意义与sip
协议栈中的验证域名相同。
(3
)用户公有标识,ims
网络中
一个impi
可以对应多个impu.
对比理解,比如我们的手机只能属于一个地方,而当我们到不同外地后会有漫游,此处的impi
即是我们手机本地给的唯一标识,而impu
是手机到外地后,当地网络给的一个标识。
2
).
创建以及启动
通过调用 tsip_stack_create
创建协议栈, 调用tsip_stack_start
启动,
完整例子:
tsip_stack_handle_t
*stack = tsk_null;
int
ret;
const
char* realm_uri = "sip:vision-com.com.cn";
const
char* impi_uri = "lideping@vision-com.com.cn";
const
char* impu_uri = "sip:bob@vision-com.com.cn";
//
...
必须先初始化tnet
工具
tnet_startup();
// ...
//
创建协议栈,指定回调函数,参数为域名,公有及私有标识。
stack
= tsip_stack_create(sip_callback, realm_uri, impi_uri, impu_uri,
TSIP_STACK_SET_PASSWORD("yourpassword"),
//
...other macros...
//
此处初始化
其他信息,比如代理服务器地址,编码信息等。
tsip_stack_start(stack)
//
启动协议栈
TSIP_STACK_SET_NULL());
//
用来终止传给app_callback
的参数。
TSK_OBJECT_SAFE_FREE(stack);
tnet_cleanup();
//
释放资源
//
事件回调
int
sip_callback(const tsip_event_t *sipevent)
{
//
事件类型
switch(sipevent->type){
case
tsip_event_register:
{ /*
REGISTER */
break;
}
case
tsip_event_invite:
{ /*
INVITE */
break
}
case
tsip_event_message:
{ /*
MESSAGE */
break
}
case
tsip_event_publish:
{
/* PUBLISH */
break
}
case
tsip_event_subscribe:
{ /*
SUBSCRIBE */
break
}
case
tsip_event_options:
{ /*
OPTIONS */
break
}
case
tsip_event_dialog:
{ /*
Common to all dialogs */
break
}
/*
case …*/
}
3
).
代码分析
首先调用tsip_stack_create
创建协议栈
,tsip_stack_create
内部首先检查参数是否合法,然后创建协议栈结构tsip_stack_t
,设置realm,
IMPI and IMPU
,初始化一些协议栈默认值。创建
SigComp
信令压缩模块(可选)
创建dns
处理模宽,DHCP
context
,接下来创建上面提到的sip
协议栈各层,分别调用tsip_dialog_layer_create
,tsip_transac_layer_create,tsip_transport_layer_create
创建会话层,事务层及传输层。至此,创建协议栈毕。
在真正启动协议栈之前,即
tsip_stack_create
与tsip_stack_start
之间
可调用协议栈提供的api
初始化其他参数,
然后调用tsip_stack_start
启动协议栈,首先启动定时器线程,这里定时器主要在事务层提供状态机功调度功能。然后设置传输层类型,设置是否用ipsec
把sip
信令加密,
然后如果协议栈是处于客户端模式并且代理服务器地址没有设置则用认证的域名查找代理服务器的地址(用S
NAPTR+SRV),
然后设置Runnable
回调,启动run
线程,run
内部不断从消息队列里取消息,这里的消息是从传输层从下到上传送过来,最终串联到消息队列,然后调用协议栈创建时指定的回调sip_callback
,所有incoming
sip
消息以及媒体信息的改变最终都会走到此回调函数,此函数内部根据消息类型的不同调用相应的handler
,
接下来启动nat
穿越模块,设置stun
地址。然后调用tsip_transport_layer_start
启动传输层线程,在sip
端口5060
接收数据,最后,设置stack->started
= tsk_true;
至此协议栈启动完毕,各层在相应端口或状态机上监听,不断轮询到来的事件并处理。
驱动过程:
协议栈启动完毕后,对于每一个incoming
及outgoing
消息的
入口是不一样的,下面分别分析对于呼入请求(incoming)
及外乎请求(outgoing)
的代码流程。
a.
呼入请求
1
)客户端传输层在5060
端口上接收到udp
包,语法层解析成识别的sip
消息后传给事务层。
2
)事务层锁住本地事务链表,根据sip
消息的
事务id
在事务链查找是否存在匹配的事务,没有则创建。
3
)一旦找到事务或创建新事务完毕,释放锁并把消息传递到会话层。
4
)会话层收到sip
消息后查找会话链,找不到则创建会话,同时根据消息类型(invite,
ack
等)设置此消息的状态机,状态机内指定具体事件的回调。
b.
呼出请求
........
外部编程接口
为了在android
上层通过java
访问doubango
核心,imsdroid
对doubango voip
框架做了面向对象封装,根据具体模块功能抽象成具体Java
类供应用层使用,应用层通过jni
访问doubango
核心,同时,在imsdroid
2.0
版本中,根据android
上应用层的架构抽象出一个类库,doubango-ngn-stack,
利用此类库我们可以在android
上自己开发一些客户端应用程序,包括语音,视频,即时通信,多媒体共享,会议等应用。Imsdroid
2.0
即是构建在 doubango-ngn-stack
上的一个具体应用。
Doubango-ngn-stack
原理
doubango-ngn-stack
是对doubango
voip
框架的一个java
层封装,内部通过java
本地调用技术实现(jni),
这与android
上的框架设计是相符的(
如
java
类库提供的摄像头功能即依赖于底层驱动,上层通过jni
访问底层驱动)
,
doubango/bindings/java
|