osip2协议栈原理分析以及总结

更新时间:2024-06-25 01:11:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

OSIP2协议栈学习总结

1、Osip2协议栈介绍

Osip2是一个开放源代码的sip协议栈,是开源代码中不多使用C语言写的协议栈之一,它具有短小简洁的特点。它的核心特性为sip协议数据的解析和事务的管理。数据包的收发、RTP 流的处理等,并不在Osip2中完成。应用程序使用Osip 时需要单独去实现这些模块。Osip2的缺点是没有很好的上层api封装,使得上层应用在调用协议栈时很破碎;只做到了transaction层次的协议过程解析,缺少call、session、dialog等过程的解析,这也增加了使用的难度。

2、Osip2协议栈体系结构

OSIP主要由解析模块、工具模块和状态机模块构成,其核心是状态机模块.OSIP结构如图所示.

应用程序 SIP解析 SRL解析 SDP解析 状态机模块 有限状态

2.1 语法解析器

libosip库源码src/osipparser2为解析器源码,OSIP解析模块主要用于对SIP请求与响应进行封装与解析处理,分为SIP解析、URL解析与SDP解析完成对sip协议相关字段的构造和解析。比如,将紧凑的存储于内存buffer中的sip 数据解析到清晰定义的数据结构体中,每一个字段代表sip协议中有意义的一个头域。。SIP解析主要负责SIP标题头的解析与封装。SDP解析除了对数据包中SDP会话各类型进行解析外还包含对各类型的初始化和释放操作以及对整个SDP包的一些基本操作。URL解析主要负责对SIP URI中包含的host,port,username, password等信息进行解析与设置。

2.2 有限状态机

SIP状态机模块负责完成对某个事务状态的维持及处理。并且在特定的状态下触发相应的事件或者回调函数。OSIP协议栈的状态机主要分为4类:INVITE客户端事务ICT,非INVITE客户端事务NICT,INVITE服务器端事务IST,非INVITE服务器端事务NIST。

2.3 工具模块

OSIP工具模块分为对话管理工具和SDP协商工具。对话管理工具使用户能够根据RFC3261对dialog进行操作,建立相应dialog结构体。并通过对dialog结构体的添加、删除、查询实现对话管理。SDP协商工具负责向SIP终端用户提供协商codec等功能。

2.4 协议栈框架

框架并不是指代码的某一部分,而是指它的构成形式。主要有三部分:底层套接字接收/发送,模块间通信管道,上层调用api接口。

Osip2并不实现底层套接字的接收/发送,由eXosip实现,现在只支持UDP的链路连接。

1

DIALOG工具 SDP协商工具 模块间的通信管道包括:transaction的消息管道、jevent的消息管道。Transaction的消息管道是驱动其状态机的部件,通过不断的接收来自底层套接字的远端信令,或者来自上层调用的指令,根据上述的状态机制来驱动这个transaction的运转。Jevent的消息管道是eXosip实现的,用于汇报底层事件,使得调用程序能处理感兴趣的事件。

上层调用的api接口大致有两类:sip协议的调用接口和sdp协议的调用接口。EXosip封装了大部分的sip协议调用接口,一般来说都不需要直接调用osip2的接口函数。接口函数很多,在这里就不详述了,函数定义请参照源代码部分的注释。

2.5 osip2与sip协议层次的关系

LIBOSIP2开发库层次关系

2

3、OSIP关键数据结构及其说明

3.1 osip结构体定义

从该结构体的定义可以看出,osip主要有两部分构成,其一是事务链表,各个状态的事务的事件都挂载在该链表上;其二是回调函数,回调函数中前三个是内部使用,也就是osip 库在处理消息的过程中,如果匹配某个状态,就执行对应的回调函数。用户可以使用这些回调函数显示一些状态,处理一些错误等。最后一个是消息发送回调函数,也就是osip需要发送sip数据包时,就是通过该回调函数完成的。

3.2 transition_t:transition_t结构体定义了事务

state 为事务的状态,type 是事务对应事件的类型,method 为函数指针,定义了当前状态收到type 类型事件后应该执行的动作。next 和parent 为构成链表和队列时的前向和后向指针。这是一个通用的结构体,也就是说,ict、ist、nict,nist 都是用该结构体表示事务的。

3.3 osip_message_config_t:该结构体主要用于sip消息的解析

hname 为sip消息头的某一个域的名称,比如cseq、from、to 或者via 等。setheader 是处理该部分的函数。最后一项是一个标识,当被设置为1时,表明当前头域数据如果无效的话,可以忽略,不影响整个消息头的处理。

3

3.4 osip_transaction

这也是一个事务的定义,与之前的transaction_t 的区别在于:之前定义的事务主要用在状态机处理中,主要包括了事务的状态,事件的类型以及要调用的处理函数;而这里定义的

osip_transaction,则包含了事务相关的所有信息,存放在osip 的事务队列上。该结构体包含了与事务处理相关的信息,包括事务ID,事务队列(其上是事务的事件),事务关联的sip 信息(via、from、to、callid 以及cseq 五元素),关联的sip message(orig_request、last_response、ack),以及该事务在状态机中的上下文信息(ict_context、ist_context、nict_context、nist_context)。osip_transaction_t 是RFC 中的事务的定义,它表示的是一个会话的某个Dialog 之间的某一次消息发送及其

完整的响应,例如invite-100-180-200-ack 这是一个完整的事

务,bye-200这也是一个完整的事务,体现在SIP消息中,就是Via 中的branch 的值相同表示属于一个事务的消息(当然,事务是在Dialog 中的,所以From、To 的tag,Call-id 值也是相同的)。事务对于UAC,UAS 的终端类型不同及消息的不同,分为四类,前面说的invite 的事务,主叫uac中会关联一个ict事务,被叫uas 会关联一个ist事务,而除了invite之外,都归类定义主叫nict,被叫nist。在osip 中,它是靠有限状态机来实现的上述四种事务(osip_fsm_type_tctx_type 中定义)的,它的主要属性值有callid,transactionid,分别来标识dialog和transaction,其中还有一个时间戳birth _time 标识事务创建时间,可由超时处理函数用来判断和决定超时情况下的事务的进行和销毁。它的state 属性也是非常重要的,根据上述的事务类型不同,其值也不同,它是前面提到的状态机的“状态”,在实际状态机的逻辑执行中是一个关键值。

3.5 osip_dialog

osip_dialog 中包含的主要都是与对话相关的sip头中的信息,这些信息可以区分不同的对话,比如call_id,local_tag,remote_tag 等。另外,还指出了对话的类型,是caller 还是callee。osip_dialog_t 是SIP RFC 中的dialog或叫call leg的定义,它标识了uac和uas的一对关系,并一直保持到会话(session) 结束。一个完整的dialog主要包括from,to,callid,fromtag,totag,state,其中fromtag,totag,callid在一个dialog 成功建立后才完整,体现在SIP 消息中,就是From、To 的tag,Call-id 字段的值相同时,这些消息是属于它们对应的一个Dialog 的。例如将要发起invite 时,只有fromtag,callid 填充有值,在收到to远端的响应时,收到

totag 填充到dialog 中,建立成功一个dialog,后继的逻辑均是使用这个dialog 进行处理(如transaction 事务处理)。state表示本dialog的状态,与transaction的state有很大的关联,共同由enum 结构state_t 定义。

4

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

xxxx_to_str(osip_xxx_t *el, char **dest);

如果 buffer 中包含有sip uri,下面的示例代码展示了如何去解析uri: osip_uri_t *uri; int i;

i=osip_uri_init(&uri);

if (i!=0) { fprintf(stderr, \i=osip_uri_parse(uri, buffer);

if (i!=0) { fprintf(stderr, \osip_uri_free(uri);

反过来,需要将osip_uri 结构体中的信息转换到buffer 中,可参考下面的代码: char *dest;

i = osip_uri_to_str(uri, &dest);

if (i!=0) { fprintf(stderr, \fprintf(stdout, \

osip_free(dest);

需要注意的是,dest 所指向的内存是在接口中动态分配的,所以使用完后续用调用 osip_free 进行释放,以免造成内存泄露。 5.2 如何解析sip message

如果 buffer 中包含有sip 请求或者响应消息,下面的示例代码展示了如何将其解析到

osip_message 结构体中:

osip_message_t *sip; int i;

i=osip_message_init(&sip);

if (i!=0) { fprintf(stderr, \i=osip_message_parse(sip, buffer, length_of_buffer);

if (i!=0) { fprintf(stderr, \

osip_message_free(sip);

因为 sip message 中可能包含二进制数据,所以buffer 的长度在调用时必须给出。相反的

过程如下:

char *dest=NULL;

int length=0;

i = osip_message_to_str(sip, &dest, &length);

if (i!=0) { fprintf(stderr, \fprintf(stdout, \osip_free(dest);

类似于上面,dest 指向的内存在使用完后续用释放。

当使用osip 库的事务管理特性时,通常需要创建一个合适的事件。对于收到的sip message,可以使用下面的接口完成该项工作: osip_event_t *evt;

int length = size_of_buffer; evt = osip_parse(buffer, i);

需要注意的是,osip 的解析器不会对message 进行完全的检查,应用层需要对此作出

9

处理。比如,下面的字符串显示一个request-uri 中包含一个奇怪的端口:

INVITE sip:jack@atosc.org:abcd SIP/2.0

但是,osip 的解析器并不会检测到这个错误。它将被提交给应用层去确认。 5.3 如何管理事务

要去“执行”状态机,你需要建立事件(events),并将它提交给正确的事务上下文,如果事件在当前状态中被允许的话,事务的状态将会被更新。

事件可以分为如下三类:

SIP messages Timers

transport errors a. 管理一个新的事务

假设你要实现一个用户端代理,并且开始一个注册事务。首先,你必须使用osip 库构建一个sip 消息(osip 作为一个底层的库,提供构建sip message 的接口,但是需要手动去填充相关必要的域)。一旦构建好sip message,就可以使用下面的代码开始一个新的事务:

osip_t *osip = your_global_osip_context;

osip_transaction_t *transaction;

osip_message_t *sip_register_message; osip_event_t *sipevent;

application_build_register(&sip_register_message); osip_transaction_init(&transaction,

NICT, //a REGISTER is a Non-Invite-Client-Transaction osip,

sip_register_message);

// If you have a special context that you want to associate to that // transaction, you can use a special method that associate your context // to the transaction context.

osip_transaction_set_your_instance(transaction, any_pointer);

// at this point, the transaction context exists in oSIP but you still have // to give the SIP message to the finite state machine. sipevent = osip_new_outgoing_sipmessage (msg);

sipevent->transactionid = transaction->transactionid;

osip_transaction_add_event (transaction, sipevent);

// at this point, the event will be handled by oSIP. (The memory resource will // also be handled by oSIP). Note that no action is taken there. 使用相似的代码,可以添加其他事件到状态机中。 b. 消化事件

之前的步骤展示了如何创建一个事务,并且提供了一种添加新的事件的可行的方式(注 意,一些事件,比如超时事件,是由osip 库来添加的,而不是由应用程序完成)。下面的代码展示了osip 如何消费这个事件。实际上,这非常简单,但是你必须意识到,在任何时间消费一个事件并不总是允许的。状态机必须顺序的消费一个事务上的事件。这也就意味着,当调用 osip_transaction_execute()时,在同一个事务上下文中再次调用该方法将是被禁止的,

知道之前的调用返回。在一个多线程的应用中,如果一个线程捕获一个事务,代码如下:

10

while (1)

{

se = (osip_event_t *) osip_fifo_get (transaction->transactionff); if (se==NULL)

osip_thread_exit ();

if ( osip_transaction_execute (transaction,se)<1) // deletion asked osip_thread_exit (); }

c. 宣布事件给应用层

这里以outgong REGISTER transaction 为例。如果一个事件看起来对状态机是有用的,那么,这就意味着在该事务的上下文中,需要完成从一个状态到另一个状态的转变。如果事件是SND_REQUEST,那么之前注册的用于宣布该行为的回调函数将被调用。当在这步没有 什么动作必须执行时,这个回调函数对应用程序而言就没有什么用。当消费最先收到的最终 响应时,一个更加感兴趣的宣告将被做出。如果关联与2xx message 的回调被调用,表明事务已成功处理。在该回调函数中,可能需要通知用户,注册已经成功完成,可以去做相关的其他动作了。

如果最终响应不是2xx,或者network 回调被调用了,则可能需要采取一些动作。比如, 如果收到的是302,可能需要往新的位置重新尝试注册。所有的这些都由用户自己来决定。 当事务抵达terminate 状态时,*kill*回调会被调用,在这里,需要将事务从事务列表中 移除:

static void cb_ict_kill_transaction(int type, osip_transaction_t *tr)

{

int i;

fprintf(stdout, \i = osip_remove_transaction (_osip, tr);

if (i!=0) fprintf(stderr, \}

5.4 如何管理对话

对话管理是osip 提供的一个强大功能。这一特性会被sip 终端使用到,这些终端具有响应call 的能力。一个对话是osip 中已经建立的一个call 的上下文。It's not useless to say that ONE invite

request can lead to several call establishment. This can happen if your call has been forked by a

proxy and several user agent was contacted and replied at the same time. It is true that this case

won't probably happen several times a month...

有两种情况创建一个对话,一种是作为caller,呼叫方,另一种是作为callee,也就是被呼叫方。

a. 作为呼叫方创建一个会话

在这种情况下,每当接收到一个code 在101 到299 的应答后,就必须创建一个对话。在osip 中,创建一个对话最好的地方自然就是宣告该sip 消息的回调函数了。当然了,每当接收到一个响应时,需要检查是否已经存在一个对话,这个对话由这个客户代理之前的应答创建,并且也关联于当前的INVITE 请求。回调函数中的执行代码应该类似下面的示例:

void cb_rcv1xx(osip_transaction_t *tr,osip_message_t *sip)

11

{

osip_dialog_t *dialog;

if (MSG_IS_RESPONSEFOR(sip, \{

dialog = my_application_search_existing_dialog(sip); if (dialog==NULL) //NO EXISTING DIALOG {

i = osip_dialog_init_as_uac(&dialog, sip); my_application_add_existing_dialog(dialog); } } else

{

// no dialog establishment for other REQUEST } }

b. 作为被呼叫方创建一个会话

作为一个被呼叫方,当接收到第一个INVITE 请求的传输时就需要创建对话。做这项工 作正确的地方自然也是在回调函数中,此时的回调函数的位置应该是此前注册的用于通告新的INVITE 请求的回调。首先创建一个180 或者200 的应答,然后使用下面类似的代码创建一个对话:

osip_dialog_t *dialog;

osip_dialog_init_as_uas(&dialog, original_invite, response_that_you_build); 要让这一切工作起来,必须保证应答是有效的,比如,不能忘了生成一个新的tag,并将它放到应答的头部to 域。对话的管理对此有非常大的依赖。

5.5 如何使用SDP 协商

SDP 的offer/answer 模型几乎是所有sip 互操作性问题的来源。rfc 文档中定义的SDP 常常没有按期往的那样实现。举个例子,几乎所有的sip 应用都忘了添加强制的's'域到SDP 包中。另外一个错误是认为SDP 包不需要一个'p'和'e'域。即使它们都是可选的,但是,它们都是强制的。由于这些原因,协商看起来是一项艰巨任务。

a. 是否需要SDP negotiator

自然,仅仅sip 终端才需要SDP negotiator(sdp协商)。高级的应用可能觉得它没什么用,但是,当能够通过协商修改SDP 的应答,我想就没有不使用它的理由了。它将简化协商的代码。

b. 如何初始化SDP negotiator

下面的代码展示了如何初始化SDP negotiator: struct osip_rfc3264 *cnf; int i;

i = osip_rfc3264_init(&cnf); if (i!=0) {

fprintf(stderr, \return -1; }

12

c. 如何添加媒体的支持

这步必须添加一组已知的编解码器。为了简化实现,可以添加sdp_media_t 元素。下面的代码展示了如何添加对G729 编码器的支持:

sdp_media_t *med;

sdp_attribute_t *attr;

i = sdp_media_init(&med);

med->m_proto = osip_strdup(\

med->m_media = osip_strdup(\

osip_list_add(med->m_payloads, osip_strdup(\i = sdp_attribute_init (&attr);

attr->a_att_field = osip_strdup(\attr->a_att_value = osip_strdup(\osip_list_add (med->a_attributes, attr, -1); osip_rfc3264_add_audio_media(cnf, med, -1);

d. 如何执行协商

执行协商是复杂的调用序列。这是因为为了给开发者提供足够的灵活性来适应各种环境。

这使得negotiator 使用起来变得复杂。源码中的src/test/torture_rfc3262.c 为这部分的测试程序,从中你可以找到为一个answer 建立一个offer 的完整的协商过程。当针对所有媒体行的协商完成后,仍然必须手动修改SDP message 中缺失的一些元素。特别的,必须针对当前的环境配置来填写ip 地址和端口号。

13

本文来源:https://www.bwwdw.com/article/ysx3.html

Top