C# · 12月 20, 2021

doubango sip 协议栈使用流程

doubango sip 协议栈使用流程:

1).初始化

doubango sip协议栈依赖于tinyNET模块,所以必须先调用tnet_startup函数初始化,退出时调用tnet_cleanup清除资源,初始化sip

协议栈之前必须设置用户的域(realm参见(1))及用户的私有(IMPI(2))及共有标别(IMPU(3)),这些为ims引入的概念。

(1)realm 解释:reaml为域名,用来作客户端认证用(authenticate).必须是一个有效的 sipuri 如:sip:vision-com.com.cn,realm 为sip协议栈启动之前必须设置的选项,一旦协议栈启动realm就不可以更改,如果不填写sip代理服务器地址,则系统会用realm通过 dns NAPTR + SRV 或者DHCP(还没实现)动态查找机制确定sip 服务器地址。

(2)用户私有标识,为用户所属网络赋予的唯一值,用来做验证,为IMS中的概念,如果用doubangosip协议栈作为普通sip功能,即非IMS网络中的sip功能,此处的impi意义与sip协议栈中的验证域名相同,私有ID用在身份认证,授权等,主要是安全方面的作用。

(3)用户公有标识,ims网络中一个impi可以对应多个impu.,公有ID用在业务配置、计费等,主要是业务方面的作用。

更进一步解释:

IMPU,它更靠近业务层,用于标识业务签约关系,计费等,还表示用户身份以及用于路由,但它不能表示用户实际的位置信息,当然这个是对于非传统固话来说,传统固定方式下用户号码与位置存在绑定关系,因为用户线接入是固定的。

然后是IMPI,它是网络层的东东,用于表示用户和网络的签约关系,一般也可以唯一的表示一个终端。使用IMPI,网络可以通过鉴权来识别用户是否可以使用网络。

最后是用户的联系地址,这个是注册的时候要带的,它是用户真实的寻址地址,IMPU和联系地址分离才能支持移动性,对于传统GSM网络,漫游号起到了类似联系地址的作用,你也可以认为GSM中的位置更新也是一种注册流程。

注册后S-CSCF知道IMPU以及联系地址,当有人呼叫此IMPU,根据联系地址即可找到实际的用户,因此注册维护了一个用户的寻址通路。

2).创建以及启动

通过调用 tsip_stack_create创建协议栈, 调用tsip_stack_start启动,

完整例子:

tsip_stack_handle_t*stack = tsk_null;

intret;

constchar* realm_uri = “sip:vision-com.com.cn”;

constchar* impi_uri = “lideping@vision-com.com.cn”;

constchar* 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();

//释放资源

//事件回调

intsip_callback(const tsip_event_t *sipevent)

{

//事件类型

switch(sipevent->type){

casetsip_event_register:

{ /*REGISTER */

break;

}

casetsip_event_invite:

{ /*INVITE */

break

}

casetsip_event_message:

{ /*MESSAGE */

break

}

casetsip_event_publish:

{/* PUBLISH */

break

}

casetsip_event_subscribe:

{ /*SUBSCRIBE */

break

}

casetsip_event_options:

{ /*OPTIONS */

break

}

casetsip_event_dialog:

{ /*Common to all dialogs */

break

}

/*case …*/

}

会话事件,协议栈事件放入事件队列,协议栈启动时创建一个线程不断从队列里取事件,然后通过调用事件回调通知上层用户。

5.代码分析

首先调用tsip_stack_create创建协议栈,tsip_stack_create内部首先检查参数是否合法,然后创建协议栈结构tsip_stack_t,设置realm,IMPI and IMPU,初始化一些协议栈默认值。创建SigComp 信令压缩模块(可选)创建dns处理模宽,DHCPcontext ,接下来创建上面提到的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信令加密,然后如果协议栈是处于客户端模式并且代理服务器地址没有设置则用认证的域名查找代理服务器的地址(用SNAPTR+SRV),然后设置Runnable回调,启动run 线程,run内部不断从消息队列里取消息,这里的消息是从传输层从下到上传送过来,最终串联到消息队列,然后调用协议栈创建时指定的回调sip_callback,所有incomingsip消息以及媒体信息的改变最终都会走到此回调函数,此函数内部根据消息类型的不同调用相应的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.呼出请求

1)构造外乎请求,包括消息头和消息体.

2)创建会话层,事务层,事务层调用传输层接口发出请求。

6. 外部编程接口

为了在android上层通过java访问doubango核心,imsdroid对doubango voip框架做了面向对象封装,根据具体模块功能抽象成具体java类供应用层使用,应用层通过jni访问doubango核心,同时,在imsdroid2.0版本中,根据android上应用层的架构抽象出一个类库,doubango-ngn-stack,利用此类库我们可以在android上自己开发一些客户端应用程序,包括语音,视频,即时通信,多媒体共享,会议等应用。Imsdroid2.0即是构建在 doubango-ngn-stack上的一个具体应用。

Doubango-ngn-stack原理

doubango-ngn-stack是对doubangovoip框架的一个java层封装,内部通过java本地调用技术实现(jni),这与android上的框架设计是相符的(如java类库提供的摄像头功能即依赖于底层驱动,上层通过jni访问底层驱动),

doubango/bindings/java

用SWIG工具把c/C++函数封装成JAVA中的类,目前SWIG已经可以支持Python,Java,C#,Ruby,PHP,等语言。

7. 代码实例分析

      注册过程

1). 注册流程(java–>C++–>C)

register(NgnSipService.java)

|

register(NgnRegistrationSession.java)

|

register_(sipsession.cxx)

|

tsip_action_REGISTER(stip_api_register.c)

tsip_action_REGISTER

分三步:

_tsip_action_create创建注册请求,tsip_dialog_layer_new 创建注册session,为后续进入状态机作准备, tsip_dialog_fsm_act 进入状态机模式。

1._tsip_action_create

创建注册请求,这里只是创建一个抽象的请求,请求对应sip的方法,sip协议定义了

register,invite,publish,subscribe,bye,message等方法。

然后调用_tsip_action_set初始化上层(java)传过来的参数。

请求创建成功后创建会话层tsip_dialog_layer_new

sip协议中每个请求方法对应一个会话,即session,此函数根据会话类型创建相应会话的session.对于注册会话,会调用tsip_dialog_register_create创建会话层,然后把创建后的会话保存到协议栈的会话层链表。

(1)tsip_dialog_register_create(tsip_dialog_register.c)创建注册会话

内部new一个注册对象tsip_dialog_register_def_t,构造函数中执行如下动作。

tsip_dialog_register_ctor内部分三步:

a.tsip_dialog_init 初始化基本的会话信息,这里分客户端及服务器端。同时会创建此会话的状态机,并初始化状态机的状态。

b.tsk_fsm_set_callback_terminated 设置注册会话结束状态机回调。

c.tsip_dialog_register_init 初始化具体注册会话信息

tsip_dialog_register_client_init初始化注册请求的客户端状态机回调。

这里实际上是当一个请求过程中,当请求从一个状态到另一个状态时应该调用的回调函数。

tsip_dialog_register_server_init功能相同。

tsip_dialog_register_event_callback设置从传输层过来的注册事件的回调,通知上层。

3. tsip_dialog_fsm_act,是所有sip会话开始进入状态机的入口。

参数为 会话,会话类型,sip消息,请求。

此函数内部会调用状态机通用函数tsk_fsm_act。执行一个具体的请求,同时可能改变相应会话的状态机的状态。

inttsk_fsm_act(tsk_fsm_t* self,tsk_fsm_action_id action,const void*cond_data1,const void* cond_data2,…)

内部是一个循环,不断检测状态,根据状态调用相应的回调。

每个状态机都有一个初始状态作为运转入口,对于注册请求,入口为,

TSK_FSM_ADD_ALWAYS(_fsm_state_Started,_fsm_action_oREGISTER,_fsm_state_InProgress,tsip_dialog_register_Started_2_InProgress_X_oRegister,”tsip_dialog_register_Started_2_InProgress_X_oRegister”),

tsk_fsm_act一开始会执行tsip_dialog_register_Started_2_InProgress_X_oRegister回调,

此回调是由状态_fsm_state_Started到_fsm_state_InProgress时执行的操作,

内部先更改自己的当前状态为_fsm_state_InProgress,这样给tsk_fsm_act 提供了调用下一个状态的入口。

函数内部调用tsip_dialog_register_send_REGISTER 创建事务层,初始化事务层状态机,最后调用传输层socket接口把请求发送出去。

由于sip根据请求的类型把事务层分为几种类型,包括客户端请求(invite)事务,客户端非请求事务(如bye,register),服务器端请求事务,服务器端非请求事务。

所以对于注册请求,会创建非请求客户端事务层。

tsip_dialog_register_send_REGISTER—> tsip_dialog_request_new

—>tsip_dialog_request_send—->tsip_transac_layer_new—>tsip_transac_start

—>tsip_transac_nict_start,进入事务层状态机模式。

这里,在创建事务层时会设置事务层事件回调,tsip_transac_nict_event_callback。

比如发出register请求,服务器端给200ok响应。此时传输层会把此响应作为事件给事务层,事务层收到此事件,解析后进入事务层状态机。

所以对于 一次 sip请求,有两个状态机在运转,一个为会话层状态机,一个为事务层状态机。

至此,一个register请求已经发送出去,会话层,事务层状态机都在运转,

此时,如果服务器返回注册成功,则会给客户端传输层发送200OK响应。

tsip_transport_layer_dgram_cb,tsip_transport_layer_handle_incoming_msg,

tsip_transac_layer_handle_incoming_msg,tsip_transac_layer_find_client

客户端传输层收到响应后会根据响应的事务id查找是否为已经创建的事务。

,找到后会根据此事务创建时指定的回调,调用相应事务层的回调函数,这里为tsip_transac_nict_event_callback,此函数内部根据消息类型调用tsip_transac_fsm_act执行事务层状态机,比如 200ok ,会调用事务层创建时指定的回调。这里为tsip_transac_nict_Trying_2_Completed_X_200_to_699。

内部调用会话层回调通知上层注册成功。tsip_dialog_register_event_callback

此函数根据状态调用相应会话层状态机,tsip_dialog_register_InProgress_2_Connected_X_2xx,修改状态机当前状态,为下一个状态作准备,最后调用TSIP_DIALOG_REGISTER_SIGNAL发射 注册成功事件给上层用户。

这里事件机制:TSIP_DIALOG_REGISTER_SIGNAL 通过tsip_register_event_signal创建一个注册事件,然后把此事件放入协议栈启动时的事件队列中,

tsip_stack_start内部启动run线程处理协议栈事件。

/* ===Runnable === */

TSK_RUNNABLE(stack)->run= run;

if((ret= tsk_runnable_start(TSK_RUNNABLE(stack),tsip_event_def_t))){

stack_error_desc= “Failed to start timer manager”;

TSK_DEBUG_ERROR(“%s”,stack_error_desc);

gotobail;

}

在run线程中不断扫描事件队列,pop出一个事件,然后送给sip协议栈创建时指定的事件回调,从而把协议栈事件(事务层事件,会话层事件,协议栈事件)返回给上层用户。

至此,一次正常(没考虑异常,401认证过程等)的注册流程分析完毕。

2.)外乎流程:

screenAV.java

publicstatic boolean makeCall(String remoteUri,NgnMediaType mediaType){

makeCall(StringremoteUri,NgnMediaType mediaType)

|

createOutgoingSession

|

makeCall(remoteUri);(NgnAVSession.java)

|

callAudioVideo

|

callAudioVideo(sipsession.cxx)

|

__droid_call(sipsession.cxx)

|

__droid_call_thread(sipsession.cxx)

|

tsip_action_INVITE(tsip_api_invite)

至此,由上层调用到底层协议栈。

同样分三步:

_tsip_action_create

tsip_dialog_layer_new

tsip_dialog_fsm_act

从创建会话层开始分析:

tsip_dialog_layer_new

tsip_dialog_invite_create

tsip_dialog_invite_ctor(tsip_dialog_invite.c)

构造函数内过程:

(1)tsip_dialog_init创建相关头域,创建invitesession状态机并初始化状态。

(2)tsk_fsm_set_callback_terminated设置 invite会话状态机结束的回调函数tsip_dialog_invite_OnTerminated。

(3)tsip_dialog_invite_init初始化invite请求本身,具体如下:

a.tsip_dialog_invite_client_init初始化服务器端invite会话信息,实际上为设置客户端invite会话的状态机。

Started-> (send INVITE) -> Outgoing-> Connected ……

状态转换过程中调用相应回调。

b.tsip_dialog_invite_server_init 设置服务器端invite会话状态机。

//Started -> (Bad Extendion) -> Terminated

//Started -> (Bad content) -> Terminated

//Started -> (Session Interval Too Small) -> Started

……….

tsip_dialog_invite_hold_init设置 hold 状态机,3GPPTS 24.610:

CommunicationHold。

d.tsip_dialog_invite_stimers_init,设置sessiontimer 状态机,rfc RFC

4028:SessionTimers

e.tsip_dialog_invite_qos_init,设置 qos状态机,RFC 3312

f.初始化其他状态机。

TSIP_DIALOG(self)->callback= TSIP_DIALOG_EVENT_CALLBACK_F(tsip_dialog_invite_event_callback);

设置invite会话层事件回调,比如服务器响应,则会调用此回调,内部根据响应类型调用状态机,根据状态执行相应回调。

tsip_dialog_fsm_act,所有准备阶段完成后,第三步进入状态机模型,轮转吧。

第一个调用的状态机回调为c0000_Started_2_Outgoing_X_oINVITE,

Started-> (oINVITE) -> Outgoing。

此函数内部:

首先调用tmedia_session_mgr_create初始化自己的媒体信息,后面放到invite请求的

sdp里面。

接下来更新此次请求的状态机阶段,这样下一个状态机回调以此为起点。

然后设置invite请求的一些特殊头域。tmedia_session_mgr_set_qos,/*100rel */

self->supported._100rel等。

最后调用send_INVITE,TSIP_DIALOG_SIGNAL产生invite事件通知上层用户。

send_INVITE又调用send_INVITEorUPDATE

内部又分如下过程。

a.tsip_dialog_request_new,创建一个通用的sip 请求。

b.tmedia_session_mgr_get_lo创建invite消息的消息体,即sdp信息。

初始化其他头域

tsip_dialog_request_send发送请求。 创建事务层。

tsip_transac_layer_new,tsip_transac_ict_create

tsip_transac_ict_ctor初始化 客户端invite事务层:

tsip_transac_init

tsk_fsm_set_callback_terminated

tsip_transac_ict_init

最终开启客户端请求事务状态机。

tsip_transac_start

当服务器端返回 200ok后,会调用事务层,事务层又转给会话层,最终转到

c0000_Outgoing_2_Connected_X_i2xxINVITE回调函数,

内部对200ok响应做出里:

tsip_dialog_update更新会话状态,

tsip_dialog_invite_process_ro处理对端 sdp,建立rtp流。

send_ACK 给 ACK响应。

TSIP_DIALOG_INVITE_SIGNAL最后给上层发射 事件,通知用户接通。