初始化
使用osip时,您的第一个任务是初始化解析器和状态机。这必须在使用libosip2之前完成。
#include <sys/time.h>
#include <osip2/osip.h>
int i;
osip_t *osip;
i=osip_init(&osip);
if (i!=0)
return -1;
如果您想使用状态机(事务管理),第二步是设置一些回调,这些回调将通知您SIP事务状态的任何更改。
以下大多数回调都是可选的。比如:
// callback called when a SIP message must be sent.
osip_set_cb_send_message(osip, &cb_udp_snd_message);
// callback called when a SIP transaction is TERMINATED.
osip_set_kill_transaction_callback(osip ,OSIP_ICT_KILL_TRANSACTION,
&cb_ict_kill_transaction);
osip_set_kill_transaction_callback(osip ,OSIP_NIST_KILL_TRANSACTION,
&cb_ist_kill_transaction);
osip_set_kill_transaction_callback(osip ,OSIP_NICT_KILL_TRANSACTION,
&cb_nict_kill_transaction);
osip_set_kill_transaction_callback(osip ,OSIP_NIST_KILL_TRANSACTION,
&cb_nist_kill_transaction);
// callback called when the callback to send message have failed.
osip_set_transport_error_callback(osip ,OSIP_ICT_TRANSPORT_ERROR,
&cb_transport_error);
osip_set_transport_error_callback(osip ,OSIP_IST_TRANSPORT_ERROR,
&cb_transport_error);
osip_set_transport_error_callback(osip ,OSIP_NICT_TRANSPORT_ERROR,
&cb_transport_error);
osip_set_transport_error_callback(osip ,OSIP_NIST_TRANSPORT_ERROR,
&cb_transport_error);
// callback called when a received answer has been accepted by the transaction.
osip_set_message_callback(osip ,OSIP_ICT_STATUS_1XX_RECEIVED, &cb_rcv1xx);
osip_set_message_callback(osip ,OSIP_ICT_STATUS_2XX_RECEIVED, &cb_rcv2xx);
osip_set_message_callback(osip ,OSIP_ICT_STATUS_3XX_RECEIVED, &cb_rcv3456xx);
osip_set_message_callback(osip ,OSIP_ICT_STATUS_4XX_RECEIVED, &cb_rcv3456xx);
osip_set_message_callback(osip ,OSIP_ICT_STATUS_5XX_RECEIVED, &cb_rcv3456xx);
osip_set_message_callback(osip ,OSIP_ICT_STATUS_6XX_RECEIVED, &cb_rcv3456xx);
// callback called when a received answer has been accepted by the transaction.
osip_set_message_callback(osip ,OSIP_NICT_STATUS_1XX_RECEIVED, &cb_rcv1xx);
osip_set_message_callback(osip ,OSIP_NICT_STATUS_2XX_RECEIVED, &cb_rcv2xx);
osip_set_message_callback(osip ,OSIP_NICT_STATUS_3XX_RECEIVED, &cb_rcv3456xx);
osip_set_message_callback(osip ,OSIP_NICT_STATUS_4XX_RECEIVED, &cb_rcv3456xx);
osip_set_message_callback(osip ,OSIP_NICT_STATUS_5XX_RECEIVED, &cb_rcv3456xx);
osip_set_message_callback(osip ,OSIP_NICT_STATUS_6XX_RECEIVED, &cb_rcv3456xx);
// callback called when a received request has been accepted by the transaction.
osip_set_message_callback(osip ,OSIP_IST_INVITE_RECEIVED, &cb_rcvreq);
osip_set_message_callback(osip ,OSIP_IST_ACK_RECEIVED, &cb_rcvreq);
osip_set_message_callback(osip ,OSIP_NIST_REGISTER_RECEIVED, &cb_rcvreq);
osip_set_message_callback(osip ,OSIP_NIST_BYE_RECEIVED, &cb_rcvreq);
osip_set_message_callback(osip ,OSIP_NIST_CANCEL_RECEIVED, &cb_rcvreq);
osip_set_message_callback(osip ,OSIP_NIST_INFO_RECEIVED, &cb_rcvreq);
osip_set_message_callback(osip ,OSIP_NIST_OPTIONS_RECEIVED, &cb_rcvreq);
osip_set_message_callback(osip ,OSIP_NIST_SUBSCRIBE_RECEIVED, &cb_rcvreq);
osip_set_message_callback(osip ,OSIP_NIST_NOTIFY_RECEIVED, &cb_rcvreq);
osip_set_message_callback(osip ,OSIP_NIST_UNKNOWN_REQUEST_RECEIVED, &cb_rcvreq);
// other callbacks exists... They are optionnal.
如何解析URL
为了演示如何使用libosip2解析器,最简单的方法是开始使用URI。(统一资源标识符)
对于每一个解析器(头、SIP消息或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);
对于URL解析器相关API位于osip_uri.h
,下面是一个例子:
#include <osip2/osip.h>
using namespace std;
int main(void)
{
char buffer[236] = "sip:some@192.168.31.131:50027";
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"); }
printf("uri->string = %s", uri->host);
osip_uri_free(uri);
return 0;
}
下面是将URI转换为可打印字符串所需的序列。注意,在这个序列中,dest是动态分配的,必须在调用序列结束时释放,以避免内存泄漏。
#include <memory>
#include <osipparser2/osip_port.h>
#include <osipparser2/osip_message.h>
int main(int argc, char **argv) {
char buffer[236] = "sip:some@192.168.31.131:50027";
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"); }
// -------------------
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);
osip_uri_free(uri);
return 0;
}
如何解析SIP消息
对于SIP消息解析器相关API位于osip_message.h`:
下面是解析包含sip请求或响应的给定缓冲区所需的序列。由于SIP消息的正文部分可能包含二进制数据,因此必须将缓冲区的长度指定给API。
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);
下面是将消息转换为可打印字符串所需的序列。注意,在这个序列中,dest是动态分配的,必须在调用序列结束时释放,以避免内存泄漏。
转换SIP消息时,分配的缓冲区的最终长度将在第三个参数中返回。然后您就知道了接收到的数据的长度。
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);
在使用libosip2及其事务管理功能时,通常只需要创建合适的事件。因此,您可能会使用此API(仅用于您收到的SIP消息!):
osip_event_t *evt;
int length = size_of_buffer;
evt = osip_parse(buffer, i);
重要的是要理解,libosip2解析器不会完全检查消息是否兼容且格式正确。应用层仍然对此负责。下面的字符串显示了一个包含奇怪端口号的请求URI!
INVITE sip:jack@atosc.org:abcd SIP/2.0
libosip2解析器不会检测到这是一个错误,这将取决于您在evry action上验证事情是否如您所期望的那样!
如何管理事务
osip实现了SIP RFC定义的不同事务的4中状态机:
- ICT :Invite Client Transaction (Section 17.1.1)
- IST:Non Invite Client Transaction (Section 17.1.2)
- IST : Invite Server Transaction
- IST : Invite Server Transaction
下面是一个RFC中一个ICT例子:
如上图所示,ICT有四种状态: CALLING(调用), PROCEEDING(继续), COMPLETED(完成)、TERMINATED (终止)。要“执行”状态机,您将构建事件,将它们提供给正确的事务上下文,如果当前状态允许该事件,则事务的状态将被更新。
事件分为三类:
- SIP消息
- 计时器
- 传输错误
开启一个新事务
假设您想要实现一个用户代理,并且想要启动一个注册事务。使用解析器库,首先必须构建符合SIP的消息。(oSIP作为一个低层库,提供了一个构建SIP消息的接口,但是否正确填写所有必需字段取决于您。)一旦构建了SIP消息,就可以开始新的事务了。以下是代码:
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);
// 如果您有一个要与该事务关联的特殊上下文,可以使用一个特殊方法将您的上下文与该事务上下文关联。
osip_transaction_set_your_instance(transaction, any_pointer);
//此时,oSIP中存在事务上下文,但仍然必须将SIP消息提供给有限状态机。
sipevent = osip_new_outgoing_sipmessage (msg);
sipevent->transactionid = transaction->transactionid;
osip_transaction_add_event (transaction, sipevent);
// 此时,事件将由oSIP处理。(内存资源也将由oSIP处理)。请注意,没有采取任何行动。
在fsm中添加新事件的代码类似。
消费事件
上一步显示了如何创建事务,以及添加新事件的一种可能方法。(请注意,一些事件(超时事件)将由oSIP添加,而不是由应用程序添加)。
在这一步中,我们将描述oSIP将如何使用事件。
- 注意它不允许在同一时间同时消费一个事件!fsm必须在事务中按顺序使用事件。这意味着在调用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 ();
}
通知应用层有事件发生
比如:当一个事件被认为对fsm有用时,这意味着必须在事务上下文中完成从一个状态到另一个状态的转换。如果事件是SND_REQUEST(这是一个outgoing REGISTER 的情况),之前注册这个动作的回调将被调用。
当事务达到终止状态(当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");
}
如何管理Dialogs
对话管理是oSIP提供的一个功能强大的工具。这个特性是SIP终端需要的,它有能力接听呼叫。(例如,对INVITE回答200 OK)。
一个Dialog是在oSIP中建立呼叫的上下文。 一个invite请求可能导致多个call 建立(当有代理时可能会发生)
有两种创建Dialog的方法,在一种情况下,您是CALLER (发起者),而在另一种情况下,您将是(应答者CALLEE)。
创建一个CALLER Dialog
在这种情况下,每次收到代码介于101和299之间的响应时,都必须创建一个Dialog。在oSIP中,实际创建Dialog的最佳位置当然是在此类SIP消息的回调中。当然,每次收到响应时,都必须检查与此invite相关联的现有Dialog,该Dialog可能是由来自同一用户代理的早期SIP应答创建的。回调中的代码如下所示:
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
}
}
创建一个CALLEE Dialog
当接收到INVITE请求的第一个事务时在响应的回调函数中创建一个CALLEE Dialog。
首先,将构建一个SIP应答,比如180或200,您将能够通过调用以下代码来创建一个对话框:
osip_dialog_t*dialog;
osip_dialog_init_as_uas(&dialog、原始邀请、响应构建);
要使事务正常运行,就必须创建一个合法的响应:不要忘记创建一个新tag ,并将其放在“To”标题中。对话管理在很大程度上依赖于这个标签。
如何使用sdp negotiator(已经废弃)
有两个相关的文件: osip_negotiation.h
与 osip_rfc3264.h
提供相同的功能,但是 osip_negotiation.h
已经过时,不应该在使用
只有SIP endpoint才需要这个功能,一般是不需要的
怎么用
先初始化一个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;
}
然后必须添加一组已知的编解码器。为了简化实现,可以添加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);