SIP协议栈OSIP分析

之前整理的linephone中使用的OSIP协议栈文档,版本为2-3.1.0,仅供参考。

 关于osip 2

 osip库的模块构成 2

 关键数据结构及其说明 2

 初始化所做的工作 6

 sip消息收发的整体处理框架 8

 关键数据结构之间的关联 9

 osip的使用 12

 参考资料 17

一 关于osip

   osip为开源的sip协议栈实现。它的核心特性为sip协议数据的解析和事务的管理。数据包的收发、RTP流的处理等,并不在osip中完成。应用程序使用osip时需要单独去实现这些模块。也因为如此,osip并不与sip终端挂钩,它可以用于sip proxy,sip server等许多需要处理sip消息的场合。

二 osip库的模块构成

1 语法分析器

  目录src/parse下为解析器的源代码,完成对sip协议相关字段的构造和解析。比如,将紧凑的存储于内存buffer中的sip数据解析到清晰定义的数据结构体中,每一个字段代表sip协议中有意义的一个头域。

   在这一部分,需要完成uri的解析,sip message的解析处理,sdp的解析处理等。

2 有限状态机

  Osip实现了sip协议定义的处理四种事务的状态机:ict(Invite Client (outgoing) Transaction),ist(Invite Server (incoming) Transaction),nict(Non-Invite Client (outgoing) Transaction) ,nist(Non-Invite Server (incoming) Transaction)。Src/osip2下,ict.c、ict_fsm.c、ist.c、ist_fsm.c、nict.c、nict_fsm.c、nist.c、nist_fsm.c、fsm_misc.c这几个文件实现了状态机的处理。

3 事务和对话管理

  主要是dialog和transaction的使用和管理。

4 操作系统移植层接口

  下述几个文件实现了操作系统层的porting接口。

  Port_condv.c条件变量相关的接口,实现了linux,vxworks以及windows平台的支持

  Port_fifo.c,先进先出类型消息队列(FIFO)的定义。基于信号量和互斥锁实现多任务环境的保护。这一点不同于osip_list.c中定义的list。list模块只是简单实现了一个链表,以及对链表的操作,其中并没有保护。因此如果有多个任务同时对同一个链表进行操作,那么可能会出现一些意想不到的错误。其实,fifo模块中的元素添加和删除也是基于list模块的接口来完成的。

  Port_sema.c 互斥锁和信号量的实现。同样支持多个平台。

  Port_thread.c 任务(在linux下为线程模式实现)接口的实现。

  Osip_time.c 与时间相关的处理。

三 关键数据结构及其说明

1 osip

osip结构体的定义如下图所示:


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

2 transition_t

transition_t结构体定义了事务,如下图:

   state为事务的状态,type是事务对应事件的类型,method为函数指针,定义了当前状态收到type类型事件后应该执行的动作。next和parent为构成链表和队列时的前向和后向指针。

这是一个通用的结构体,也就是说,ict、ist、nict,nist都是用该结构体表示事务的。

3 ___osip_message_config_t

___osip_message_config_t结构体主要用于sip消息的解析,定义如下:

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

4 osip_transaction

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_t ctx_type中定义)的,它的主要属性值有callid,transactionid,分别来标识dialog和transaction,其中还有一个时间戳birth_time标识事务创建时间,可由超时处理函数用来判断和决定超时情况下的事务的进行和销毁。它的state属性也是非常重要的,根据上述的事务类型不同,其值也不同,它是前面提到的状态机的“状态”,在实际状态机的逻辑执行中是一个关键值。(参见参考资料1)。

5 ixt_t

   该结构体用于管理需要重传的2xx响应消息。Dialog为其关联的对话,msg2xx为重传的数据buffer,ack指向ack 消息,start为重传开始的时间,interval为重传间隔,剩下的为目的地址,目的端口,所用socket描述符以及重传计数(从7开始?)。

6 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定义。(参见参考资料2)

7 osip_message_t

消息结构体的定义如下图:

   可以看到这是一个非常大的结构体。该结构体用来保存与sip消息相关的大部分信息。一般都是一个消息头对应其中的一项。比如,像sip头数据中的call_id、from、to、via、等等,都能在该结构体中找到。

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

8 osip_event

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

四 初始化所做的工作

   使用osip库之前,需要先进行初始化。这通过调用接口osip_init完成。在该接口中主要完成如下两项工作:
1 创建多任务相关的互斥信号量。如果是第一次初始化,则创建全局状态机,否则只是简单增加引用计数。这步通过调用__osip_global_init接口完成。在__osip_global_init中,首先加载状态机,并为相关变量指针分配内存。如下图:

   每当有新的事务到来时,就在其对应的事务链上进行检查,匹配状态和类型,然后执行method指向的执行体,并将事务的状态按照状态机跳转到新的状态。

   接着调用parse_init初始化解析模块,完成后的内存状态如下:

   Pconfig为__osip_message_config_t类型的静态数组,对于sip消息中不同的头域,通过pconfig数组即可找到处理函数。

   最后为事务队列的处理定义多任务环境下使用的互斥信号量。

2 创建osip结构体,并初始化事务队列。完成后的内存状态为

   在这里,回调函数还没有被设置,所以整个数组都是空的。另外,osip指针指向刚创建的结构体,这个指针会作为参数返回给调用osip_init的函数。

   综合来看,osip初始化后,内存中就存在四个状态机、pconfig以及osip三部分了。

   如果只是仅仅使用osip来进行sip信令的解析和生成工作,那么初始化到这里就算完成了。如果要使用状态机进行事务的管理,则需要设置一些回调函数,在sip事务状态发生变化时,这些回调函数会被调用,用来通知用户。Callback函数有很多,但是主要可以分为以下四类:

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

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

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

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

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

五 sip消息收发的整体处理框架

这部分参考资料1中的描述。

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*/

   该接口创建了一个新的事务,并自动根据事务类型、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消息的处理,并推动状态机,上述的九个函数需要不停执行,可以将它放入单独线程中。

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来根据“事件”查找事务(有兴趣可以深入看一下,事务的查找是通过SIP消息Via中的branch来匹配的),否则新建事务,然后推动状态机执行。

六 关键数据结构之间的关联

这部分用于整体把握软件架构。

1数据结构之间的关联

   tr为事务结构体,ev为事件结构体,osip_msg为sip消息结构体。

   从上图可以看出,事务有事件队列,用于存放该事务的事件,同时,事件上载有osip_message消息结构体。另外,事务结构体中包含了ict、ist、nict以及nist几部分,提供或保存状态机处理的辅助信息。

2初始时

   开始时,内存中主要存有初始化时完成的三个结构体。此时的osip上面还没有任何的事务,不过完成了部分必要的回调函数的设置。

3运行一段时间

   Osip运行一段时间(有sip请求和相应处理)后内存中数据结构之间的关系可能如下图:

   此时osip上的事务队列上可能就已经有事务了,而且某些事物的事件队列上也可能还有事件等待处理。

4有消息到来

   当有消息到来,osip首先调用解析器,完成对消息包的解析。之后,消息就从buffer中的紧凑模式转变到osip_message结构中了,同时,普通信令消息也被分解成了osip自己可以理解的消息事件并将其归类,确定其应该属于上述四类中的哪一类。接着,查看对应的事务队列,确定是否有现成的事务可以匹配。如果有,就根据当前的sip消息提取出一个事件放到该事务的事件队列上;如果没有,则创建一个新的事务,放到事务队列中,同样提取出事件放到事件队列上。如下图所示:

上图为有事务匹配的图示。图中红色块则表示发生的变化

   上图展示了新建事务的图示。

5sip message处理中用到的主要数据结构之间的关系

   这些数据结构体在解析sip消息的过程中发挥着重要作用。从上图可以看出,其中的大部分为list(淡绿色)或者char *(深蓝色)类型的。

七 osip的使用

一下5个小部分来自代码文档(参考资料3)

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);

    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, "cannot allocate\n"); return -1; }

i=osip_uri_parse(uri, buffer);

if (i!=0) { fprintf(stderr, "cannot parse uri\n"); }

osip_uri_free(uri);

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

char *dest;

i = osip_uri_to_str(uri, &dest);

if (i!=0) { fprintf(stderr, "cannot get printable URI\n"); return -1; }

fprintf(stdout, "URI: %s\n", dest);

osip_free(dest);

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

2如何解析sip message

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

osip_message_t *sip;

int i;

i=osip_message_init(&sip);

if (i!=0) { fprintf(stderr, "cannot allocate\n"); return -1; }

i=osip_message_parse(sip, buffer, length_of_buffer);

if (i!=0) { fprintf(stderr, "cannot parse sip message\n"); }

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, "cannot get printable message\n"); return -1; }

fprintf(stdout, "message:\n%s\n", dest);

osip_free(dest);

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

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

osip_event_t *evt;

int length = size_of_buffer;

evt = osip_parse(buffer, i);

   需要注意的是,osip的解析器不会对message进行完全的检查,应用层需要对此作出处理。比如,下面的字符串显示一个request-uri中包含一个奇怪的端口:

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

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

3如何管理事务

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

   事件可以分为如下三类:

    SIP messages

    Timers

    transport errors

  1. 管理一个新的事务

   假设你要实现一个用户端代理,并且开始一个注册事务。首先,你必须使用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.

使用相似的代码,可以添加其他事件到状态机中。

  1. 消化事件

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

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 ();
}
  1. 宣布事件给应用层

   这里以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, "testosip: transaction is over\n");

  i = osip_remove_transaction (_osip, tr);

  if (i!=0) fprintf(stderr, "testosip: cannot remove transaction\n");

}

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,也就是被呼叫方。

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

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

void cb_rcv1xx(osip_transaction_t *tr,osip_message_t *sip)
{

  osip_dialog_t *dialog;

  if (MSG_IS_RESPONSEFOR(sip, "INVITE")&&!MSG_TEST_CODE(sip, 100))
  {
    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

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

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

osip_dialog_t *dialog;

osip_dialog_init_as_uas(&dialog, original_invite, response_that_you_build);

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

5如何使用SDP协商

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

  1. 是否需要SDP negotiator

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

  1. 如何初始化SDP negotiator

   下面的代码展示了如何初始化SDP negotiator:    

struct osip_rfc3264 *cnf;

int i;

i = osip_rfc3264_init(&cnf);

if (i!=0)
{
  fprintf(stderr, "Cannot Initialize Negotiator feature.\n");

  return -1;
}
  1. 如何添加媒体的支持

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

sdp_media_t *med;

sdp_attribute_t *attr;

i = sdp_media_init(&med);

med->m_proto = osip_strdup("RTP/AVP");

med->m_media = osip_strdup("audio");

osip_list_add(med->m_payloads, osip_strdup("18"), -1);

i = sdp_attribute_init (&attr);

attr->a_att_field = osip_strdup("rtpmap");

attr->a_att_value = osip_strdup("G729/8000");

osip_list_add (med->a_attributes, attr, -1);

osip_rfc3264_add_audio_media(cnf, med, -1);
  1. 如何执行协商

   执行协商是复杂的调用序列。这是因为为了给开发者提供足够的灵活性来适应各种环境。这使得negotiator使用起来变得复杂。源码中的src/test/torture_rfc3262.c为这部分的测试程序,从中你可以找到为一个answer建立一个offer的完整的协商过程。

   当针对所有媒体行的协商完成后,仍然必须手动修改SDP message中缺失的一些元素。特别的,必须针对当前的环境配置来填写ip地址和端口号(这部分和sip穿越NAT有关)。

八 参考资料

1 Osip协议栈使用入门(网络资料)

2 Osip工作原理和工作过程 (网络资料)

3 http://www.gnu.org/software/osip/doc/html/ 

4 osip源代码框架详解(网络资料)

  • 6
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙赤子

你的小小鼓励助我翻山越岭

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值