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