osip2协议栈原理分析以及总结 下载本文

内容发布更新时间 : 2024/6/16 3:58:55星期一 下面是文章的全部内容请认真阅读。

3.6 osip_message_t

可以看到这是一个非常大的结构体。该结构体用来保存与sip 消息相关的大部分信息。一般都是一个消息头对应其中的一项。比如,像sip 头数据中的call_id、from、to、via、等等,都能在该结构体中找到。在程序中,接收到的sip消息都是以紧凑的方式放在buffer中的,解析器模块的功能就是将其进行解析分类,放到这个结构体的具体对应项上,这样便于在程序中使用。同时,如果需要发送数据时,解析器会根据该结构体中的信息重新将sip信息以紧凑方式放到buffer中供发送模块使用。简单来说,sip协议中定义的各个头,在接收发送处理中都是一个接一个在内存中存放的,而在osip中对其的使用是按照上面的结构体来的,我们在程序中不再需要移动指针从buffer中来找各个sip头数据。

3.7 osip_event

这个结构体用来表示事务上的事件。Type 指出事件的类型,transactionid 指出事务的id,sip 指向上面介绍的osip_message 结构体,也就是事件对应的sip 消息。

4、osip协议栈工作原理

4.1 系统初始化过程

在OSIP工作之前,必须先初始化,主要有以下几个部分: (1)系统资源申请,包括资源和链表的处理 函数:osip_init()

(2)设置系统CALLBACK函数

Callback 函数有很多,但是主要可以分为以下四类: ? ? ? ?

用于发送sip 消息的网络接口。这通过osip 结构体的cb_send_message 函数指针指向。

当一个sip 事务被terminate 的时候调用的回调函数。这由osip 结构体的kill_callbacks 数组保存

当消息通过网络接口发送失败的时候调用的回调函数。这由osip 结构体的 tp_error_callbacks 数组保存。

sip 事务处理过程中需要通知用户的回调函数。这部分由osip 结构体的

msg_callbacks 数组保存。

上面的回调函数中,有一些是必须设置的,比如第一项,有些则是可选的,比如第四项中的部分回调函数。对于可选的回调函数,简单的处理可以是不做任何处理,或者仅仅打印通知信息。

osip_set_cb_send_message ();

//系统的信令发送函数,在这个函数中,要完成信令包的向外发送功能。 osip_set_kill_transaction_callback ();

5

//设置四个状态机下消息传输失败的处理函数。 osip_set_message_callback ();

//设置各种状态机下各种事件发生后用户的回调

(3)打开接收远端消息接收通道

打开通道就是保证远方的消息能够顺利的被本地接收到,然后交给SIP CORE处理,它包括以下几个步骤

? Step1:监听端口

? 打开SIP端口SOCKET,然后监听此端口,如5060。需要注意的是,SIP支持TCP

和UDP两种方式,SIP首先UDP方式。 ? Step2:解析消息

? 接收到消息后,调用osip_parse函数,来完成对消息包的解析。这个函数完成后,

消息就从普通信令消息分解成了OSIP2自己可以理解的消息事件。osip_parse函数在src\\osipparser2下。

? Step3:将消息送给SIP CORE处理 ? 解析后的消息,系统调用osip_find_transaction_and_add_event函数,把消息发

送给和此消息相关的处理事物(或者说session),如果系统中没有和这个消息相

关的session(也就是说是个新的request),那么此时将新建立一个session(或者说transaction,因为在不同层面有不同理解),用于处理新的事务。实际上,这个session创立的过程也就是一个新的系统状态机的建立过程,根据这个消息的种类,osip_transaction_init负责初始化,建立一个新的和种类对应的状态机(四

种状态机中的一种),然后调用osip_transaction_add_event把消息扔给这个刚建立起来的状态机处理。一个新的状态机也就开始运做。 到此,OSIP就已经正常工作,可以接收网络来的消息。

4.2 一个呼叫过程简要分析

下面通过一对呼叫的连接过程说明OSIP的消息的整个处理过程。假设终端A呼叫终端B,两放都是用的OSIP CORE。

? Stp1:A生成一个Invite消息

这个过程是大家都熟悉的过程,也就是利用本地信息,组建一个SIP包的过程,需

要注意的是:rtp流的本地接收发送端口是在这里就建立的,然后放到SDP中。 ? Step2:A把生成的消息交给核心处理。

首先系统调用osip_transaction_init ,产生一个新的状态机。在发送invite请求的时候,这个状态机是ICT(带invite的client端状态机)。状态机产生后,系统调用函数把消息扔给状态机处理,osip_transaction_add_event负责把消息插到队列中。

? Step3:A的ICT状态机调用ict_snd_invite函数发送invite包给对方,状态机自身

从初始状态跳转到calling状态,等待对方回应,并设置响应的A超时和B超时,当A超时到达的时候,向对方重发一次数据包,并且把A时间的时间增加一倍(最长为4S,如果超过4S,将不再增加)。当B时间到达后,系统认为对方没有响应,则释放资源,结束本次操作。

? Step4:B的信令监听端口接收到此invite请求包,调用osip_parse函数,来完成对

消息包的解析,然后系统调用osip_find_transaction_and_add_event函数来试图把此消息包插入已经存在的session的传输队列(比如系统还有另外一个呼叫),

6

当系统发现当前信令包并不属于已经存在的session的时候,系统认为之是一个新的呼叫过程,因此,系统调用osip_transaction_init函数建立一个信的状态机来处理这个session,这个状态机也就是IST(带invie的server状态机),然后把信令消息插到新状态机的事件列表中,供其处理。

? Step5:B的IST处理A的invite请求,自身的状态机从IST_PRE_PROCEEDING跳转到

IST_PROCEEDING状态,并发送100消息给对方,调用用户设置的回掉函数来完成用户收到请求时候的处理。注意,B应该发两次1XX消息给对方,一个是100,一个是180。下面的状态也一样。

? Step6:A收到1xx的回应,自身从ICT_CALLING状态跳转到ICT_PROCEEDING状态,并

调用用户的回掉函数来完成用户的要求。注意,A会收到B过来的两次1XX消息,参考step5的说明。

? Step7:B的用户认可,可以接收A的此次呼叫,B发送调用ist_snd_2xx函数,发送

200 OK消息给A,B的状态机IST从IST_PROCEEDING IST_TERMINATED。完成一次IST的服务。

? Step8:A接收到200 0K,状态机跳转到ICT_TERMINATED状态,完成一个ICT服务。 ? Step9:信令结束后,RTP流的建立

在Invite信令中,A告诉了B本地的RTP接收地址和端口,B在200 OK中告知了A本地的RTP的接收地址和端口,因此,信令结束后,A和B就可实现流的互通(通常是音频),需要注意的是,RTP并不是OSIP的一部分,从模块上说,它是逻辑上的另外一个部分,RTP流是依靠SIP信令里的信息建立起来的,但和SIP或者SIP信令本身而言并没有任何必然联系。

另外,就RTP要传送的音频数据本身而言(例如音频数据),和SIP更没有必然联系。通常,数据是这样产生的,A采集音频数据,然后交给编码部分编码(例如711,723),压缩完后的数据,由RTP打包,然后发送给B。B接收这些数据,拆RTP包,拿出原始数据,交给解码,解出数据后给播放进行播放。从这里也可以看出,OSIP2和这部分也是没有任何联系的。

关于RTP流的建立的过程请看朱葵阳整理rtp文挡。 4.3 sip消息发送接收详细分析 4.3.1 sip 消息的发送

发送 SIP 消息,需要用到如下三个主要的数据结构:osip_messag_t,保存待发送消息; osip_dialog_t,保存dialog 信息;osip_transaction_t,保存事务信息。

首先,调用osip_malloc 新分配一个dialog 类型的结构体,使用osip_to_init,osip_to_parse,osip_to_free 这类parser 函数按RFC 设置call-id,from,to,local_cseq 等必要字段(原则是:后面生成实际SIP 消息结构体要用到的字段就需要设置)。接着,使用osip_message_init 初始化一个sip msg,并根据dialog 来填充该结构体(不同的消息填充的数据不同,实际应该填充的信息可参考RFC 中的描述)。如果要给SIP 消息添加Body,例如SDP 段,则需要使用osip_message_set_body,osip_message_set_content_type 函数,设置的值是纯文本。另外,如果是SDP,Osip 有提供简单的解析和生成便捷函数, 例如sdp_message_to_str,sdp_message_a_attribute_add,但只是简单的字符操作,要填充合法的字段需要自己参考SDP 的RFC 文档。最后,就是事务的创建和触发,这通过调用

osip_transaction_init 完成。osip_transaction_init 的原型声明如下:

int osip_transaction_init(

osip_transaction_t ** transaction, /*返回的事务结构体指针*/ osip_fsm_type_t ctx_type, /*事务类型ICT/NICT/IST/NIST*/ osip_t * osip, /*前文说的全局变量*/

osip_message_t * request) /*前面生成的sipmsg*/

7

该接口创建了一个新的事务,并自动根据事务类型、dialog 和sipmsg 进行初始化,最重要的是它使用了__osip_add_ict 等函数,将本事务插入到全局的osip_t 结构体的全局FIFO 链表中去了,不同的事务类型对应不同的FIFO。在前面关于osip 结构体的描述可知,有四个FIFO,分别对应ICT,NICT,IST,NIST。事务创建好后,就可以按照状态机的设置,进行状态转换的处理。这步需要事件来触发。应用可以调用osip_new_outgoing_sipmessage 对sip message 进行处理,产生事件,保存到结构体osip_event_t 中。这一步省却了手动去设置。另外, 还调用evt_set_type_outgoing_sipmessage 设置事件的type_t,并将sip message 挂到事件结构体的SIP属性值上。有了根据消息分析出的事件后,使用osip_fifo_add(trn->transactionff, ev)将事件插入到事务的事件FIFO 中。

现在条件都具备了,那么消息是如何发出的呢?实际上,SIP 消息的发送和响应是一个 事务,不能单独隔离开来,所以消息的发送需要事务状态机来控制。我们上面设置了状态机的状态和事件,要触发它,就是要执行状态机了:

osip_ict_execute osip_nict_execute osip_ist_execute osip_nist_execute

上面四个函数分别用来遍历前面提到的osip 全局结构体上的四个事务FIFO。首先取出 事务,再依次取出事务内的事件FIFO 上的事件,使用osip_transaction_execute 依次执行。

最终会调用到osip 结构体的cb_send_message 回调函数。在osip 初始化时,我们为这个函数指针指定了具体的处理函数,此时就会调用该处理函数发送数据。一般我们在实现这个回调函数时,也是按照网络socket 编程,用send 系统调用实现的。如果某个事务不能正常终结怎么办呢?例如发出了Invite 没有收到任何响应,按RFC 定义,不同的事务有不同的超时时间,osip_timers_ict[nict|ist|nist]_execute 这些函数就是来根据取出的事务的时间戳与当前时间取差后与规定的超时时间比对,如果超时,就自动设置超时“事件”,并将事务“状态”设为终结,使用初始化时设定的消息超时事件回调函数处理即可( 如果设置了);如果网络质量不稳定,经常丢失消息,需要使用osip_retransmissions_execute 函数来自动重发消息而不是等待超时。为了即时响应SIP 消息的处理,并推动状态机,上述的九个函数需要不停执行,可以将它放入单独线程中。

4.3.2 sip 消息的接收 有了前面的发送SIP 消息的理解,接收消息的处理就方便理解了,收到SIP 消息,使用osip_parse 进行解析,得到一个osip_message_t 的sip msg ,使用

evt_set_type_incoming_sipmessage 得到事务的“事件”,同上,将sip msg 挂到事件结构体的sip字段,随后立即使用osip_find_transaction_and_add_event 来根据“事件”查找事务,否则新建事务,然后推动状态机执行。

5、osip的使用

5.1 如何解析URI

对于 sip 消息每一部分的解析(头,sip messages,uri),通常都使用如下类似的函数接口:

// allocation/release of memory.

xxxx_init(osip_xxx_t **el); xxxx_free(osip_xxx_t *el);

xxxx_parse(osip_xxx_t *el, char *source);

8