【www.shanpow.com--简历下载】
篇一:[exosip]eXosip例子
/******************************************编译方法: gcc xxx.c -o xxx -leXosip2****************************************//*******************UAS*****************************************************本文可以任意转载,但必须保留出处作者:rainfish网址:http://blog.csdn.net/bat603/测试环境:eXosip3.0.1/redhat AS 4***************************************************************************/#include <eXosip2/eXosip.h>#include <osip2/osip_mt.h>#include <stdio.h>#include <stdlib.h>#include <netinet/in.h>#include <sys/socket.h>#include <sys/types.h>
intmain (int argc, char *argv[]){eXosip_event_t *je = NULL;osip_message_t *ack = NULL;osip_message_t *invite = NULL;osip_message_t *answer = NULL;sdp_message_t *remote_sdp = NULL;
int call_id, dialog_id;int i,j;int id;char *sour_call = "sip:[email protected]";char *dest_call = "sip:[email protected]:5060";
char command;char tmp[4096];char localip[128];
int pos = 0;
//初始化sipi = eXosip_init ();if (i != 0) { printf ("Can"t initialize eXosip!\n"); return -1; }else { printf ("eXosip_init successfully!\n"); }
i = eXosip_listen_addr (IPPROTO_UDP, NULL, 5060, AF_INET, 0);if (i != 0) { eXosip_quit (); fprintf (stderr, "eXosip_listen_addr error!\nCouldn"t initialize transport layer!\n"); }
for(;;) { //侦听是否有消息到来 je = eXosip_event_wait (0,50);
//协议栈带有此语句,具体作用未知 eXosip_lock (); eXosip_default_action (je); eXosip_automatic_refresh (); eXosip_unlock ();
if (je == NULL)//没有接收到消息 continue; // printf ("the cid is %s, did is %s\n", je->did, je->cid); switch (je->type) { case EXOSIP_MESSAGE_NEW://新的消息到来 printf (" EXOSIP_MESSAGE_NEW!\n"); if (MSG_IS_MESSAGE (je->request))//如果接受到的消息类型是MESSAGE { { osip_body_t *body; osip_message_get_body (je->request, 0, &body); printf ("I get the msg is: %s\n", body->body); //printf ("the cid is %s, did is %s\n", je->did, je->cid); } //按照规则,需要回复200 OK信息 eXosip_message_build_answer (je->tid, 200,&answer); eXosip_message_send_answer (je->tid, 200,answer); } break; case EXOSIP_CALL_INVITE: //得到接收到消息的具体信息 printf ("Received a INVITE msg from %s:%s, UserName is %s, password is %s\n",je->request->req_uri->host, je->request->req_uri->port, je->request->req_uri->username, je->request->req_uri->password); //得到消息体,认为该消息就是SDP格式. remote_sdp = eXosip_get_remote_sdp (je->did); call_id = je->cid; dialog_id = je->did; eXosip_lock (); eXosip_call_send_answer (je->tid, 180, NULL); i = eXosip_call_build_answer (je->tid, 200, &answer); if (i != 0) { printf ("This request msg is invalid!Cann"t response!\n"); eXosip_call_send_answer (je->tid, 400, NULL); } else { snprintf (tmp, 4096, "v=0\r\n" "o=anonymous 0 0 IN IP4 0.0.0.0\r\n" "t=1 10\r\n" "a=username:rainfish\r\n" "a=password:123\r\n"); //设置回复的SDP消息体,下一步计划分析消息体 //没有分析消息体,直接回复原来的消息,这一块做的不好。 osip_message_set_body (answer, tmp, strlen(tmp)); osip_message_set_content_type (answer, "application/sdp"); eXosip_call_send_answer (je->tid, 200, answer); printf ("send 200 over!\n"); } eXosip_unlock (); //显示出在sdp消息体中的 attribute 的内容,里面计划存放我们的信息 printf ("the INFO is :\n"); while (!osip_list_eol (remote_sdp->a_attributes, pos)) { sdp_attribute_t *at; at = (sdp_attribute_t *) osip_list_get (remote_sdp->a_attributes, pos); printf ("%s : %s\n", at->a_att_field, at->a_att_value);//这里解释了为什么在SDP消息体中属性a里面存放必须是两列 pos ++; } break; case EXOSIP_CALL_ACK: printf ("ACK recieved!\n"); // printf ("the cid is %s, did is %s\n", je->did, je->cid); break; case EXOSIP_CALL_CLOSED: printf ("the remote hold the session!\n"); // eXosip_call_build_ack(dialog_id, &ack); //eXosip_call_send_ack(dialog_id, ack); i = eXosip_call_build_answer (je->tid, 200, &answer); if (i != 0) { printf ("This request msg is invalid!Cann"t response!\n"); eXosip_call_send_answer (je->tid, 400, NULL); } else { eXosip_call_send_answer (je->tid, 200, answer); printf ("bye send 200 over!\n"); } break;
case EXOSIP_CALL_MESSAGE_NEW://至于该类型和EXOSIP_MESSAGE_NEW的区别,源代码这么解释的 /* /* request related events within calls (except INVITE) */ EXOSIP_CALL_MESSAGE_NEW, /**< announce new incoming request. */ /* response received for request outside calls */ EXOSIP_MESSAGE_NEW, /**< announce new incoming request. */ 我也不是很明白,理解是: EXOSIP_CALL_MESSAGE_NEW是一个呼叫中的新的消息到来,比如ring trying都算,所以在接受到后必须判断 该消息类型,EXOSIP_MESSAGE_NEW而是表示不是呼叫内的消息到来。 该解释有不妥地方,仅供参考。 */ printf(" EXOSIP_CALL_MESSAGE_NEW\n"); if (MSG_IS_INFO(je->request)//如果传输的是INFO方法 { eXosip_lock (); i = eXosip_call_build_answer (je->tid, 200, &answer); if (i == 0) { eXosip_call_send_answer (je->tid, 200, answer); } eXosip_unlock (); { osip_body_t *body; osip_message_get_body (je->request, 0, &body); printf ("the body is %s\n", body->body); } } break; default: printf ("Could not parse the msg!\n"); } }}/*******************UAC*****************************************************本文可以任意转载,但必须保留出处作者:rainfish网址:http://blog.csdn.net/bat603/测试环境:eXosip3.0.1/redhat AS 4***************************************************************************/
#include <eXosip2/eXosip.h>#include <stdio.h>#include <stdlib.h>#include <netinet/in.h>#include <sys/socket.h>#include <sys/types.h>
int main (int argc, char *argv[]){eXosip_event_t *je;osip_message_t *reg = NULL;osip_message_t *invite = NULL;osip_message_t *ack = NULL;osip_message_t *info = NULL;osip_message_t *message = NULL;
int call_id, dialog_id;int i,flag;int flag1 = 1;int id;char *identity = "sip:[email protected]";char *registerer = "sip:192.168.0.133:5060";char *source_call = "sip:[email protected]";char *dest_call = "sip:[email protected]:5060";char command;char tmp[4096];char localip[128];
printf("r 向服务器注册\n\n");printf("c 取消注册\n\n");printf("i 发起呼叫请求\n\n");printf("h 挂断\n\n");printf("q 退出程序\n\n");printf("s 执行方法INFO\n\n");printf("m 执行方法MESSAGE\n\n");//初始化i = eXosip_init ();if (i != 0) { printf ("Couldn"t initialize eXosip!\n"); return -1; }else { printf ("eXosip_init successfully!\n"); }
i = eXosip_listen_addr (IPPROTO_UDP, NULL, 5060, AF_INET, 0);if (i != 0) { eXosip_quit (); fprintf (stderr, "Couldn"t initialize transport layer!\n"); return -1; }flag = 1;while (flag) { printf ("please input the comand:\n"); scanf ("%c", &command); getchar (); switch (command) { case "r": printf ("This modal isn"t commpleted!\n"); break; case "i":/* INVITE */ i = eXosip_call_build_initial_invite (&invite, dest_call, source_call, NULL, "This si a call for a conversation"); if (i != 0) { printf ("Intial INVITE failed!\n"); break; } //符合SDP格式,其中属性a是自定义格式,也就是说可以存放自己的信息,但是只能是两列,比如帐户信息 //但是经测试,格式:v o t必不可少,原因未知,估计是协议栈在传输时需要检查的 snprintf (tmp, 4096, "v=0\r\n" "o=anonymous 0 0 IN IP4 0.0.0.0\r\n" "t=1 10\r\n" "a=username:rainfish\r\n" "a=password:123\r\n"); osip_message_set_body (invite, tmp, strlen(tmp)); osip_message_set_content_type (invite, "application/sdp"); eXosip_lock (); i = eXosip_call_send_initial_invite (invite); eXosip_unlock (); flag1 = 1; while (flag1) { je = eXosip_event_wait (0, 200); if (je == NULL) { printf ("No response or the time is over!\n"); break; } switch (je->type) { case EXOSIP_CALL_INVITE: printf ("a new invite reveived!\n"); break; case EXOSIP_CALL_PROCEEDING: printf ("proceeding!\n"); break; case EXOSIP_CALL_RINGING: printf ("ringing!\n"); // call_id = je->cid; // dialog_id = je->did; printf ("call_id is %d, dialog_id is %d \n", je->cid, je->did); break; case EXOSIP_CALL_ANSWERED: printf ("ok! connected!\n"); call_id = je->cid; dialog_id = je->did; printf ("call_id is %d, dialog_id is %d \n", je->cid, je->did);
eXosip_call_build_ack (je->did, &ack); eXosip_call_send_ack (je->did, ack); flag1 = 0; break; case EXOSIP_CALL_CLOSED: printf ("the other sid closed!\n"); break; case EXOSIP_CALL_ACK: printf ("ACK received!\n"); break; default: printf ("other response!\n"); break; } eXosip_event_free (je); } break; case "h": printf ("Holded !\n"); eXosip_lock (); eXosip_call_terminate (call_id, dialog_id); eXosip_unlock (); break; case "c": printf ("This modal isn"t commpleted!\n"); break; case "s": //传输INFO方法 eXosip_call_build_info (dialog_id, &info); snprintf (tmp , 4096, "hello,rainfish"); osip_message_set_body (info, tmp, strlen(tmp)); //格式可以任意设定,text/plain代表文本信息 osip_message_set_content_type (info, "text/plain"); eXosip_call_send_request (dialog_id, info); break; case "m": //传输MESSAGE方法,也就是即时消息,和INFO方法相比,我认为主要区别,是MESSAGE不用建立连接,直接传输信息,而INFO必须 //在建立INVITE的基础上传输。 printf ("the mothed :MESSAGE\n"); eXosip_message_build_request (&message, "MESSAGE", dest_call, source_call, NULL); snprintf (tmp, 4096, "hellor rainfish"); osip_message_set_body (message, tmp, strlen(tmp)); //假设格式是xml osip_message_set_content_type (message, "text/xml"); eXosip_message_send_request (message); break; case "q": eXosip_quit (); printf ("Exit the setup!\n"); flag = 0; break; } }return (0);}
篇二:[exosip]exosip 分析
先来看看主函数main
参数分析,将分析出的参数放到cfg(struct _main_config_t)中
初始化eXosip函数库(eXosip_init)
eXosip_init
设置eXosip版本
设置全局变量eXosip里的一些参数
初始化osip函数库(osip_init)
osip_init
增加一个全局变量ref_count的计数(increase_ref_count),这个值用于防止某些函数(__osip_global_init)多次调用而造成一些变量多次初始化而出错. 实际上increase_ref_count这个函数有个错误的,因为它的返回值永远是0,所以如果第二次调用osip_init就会导致struct osip里的一些变量被清零(就是下一步),导致内存泄漏.不过,这也只能怪自己,谁让你多次调用osip_init呢.
初始化struct osip的部分成员,主要是将各种状态(ICT IST NICT NIST)的事务链表清零
将全局变量eXosip的地址挂到struct osip::application_context上(osip_set_application_context),这样只要我们得到osip结构体就可以访问eXosip
设置回调函数(eXosip_set_callbacks).
eXosip_set_callbacks
注册发信息的回调函数(cb_snd_message)
cb_snd_message
得到host和port
得到第一个via
根据via中的协议调用cb_XXX_snd_message(XXX指udp或tcp)
cb_udp_snd_message
在对传入的信息做一些判断和取得目标网络的相关信息,并把message转化成字符串,最后调用_eXosip_sendto把信息发送出去
注册一些从链表中删除事务的函数(cb_ict_kill_transaction,cb_ist_kill_transaction等)
注册回显函数(cb_rcvresp_retransmission),这个函数并没有实际用处,只是在debug阶段显示一些信息而已,当然,你可以自己添加一些实际的功能在里面.
注册发生错误时处理函数(cb_transport_error).这个函数的作用时在发生错误时把对应的element从eXosip的事件链表里删除掉,这样收到其他消息时就不会再去响应
注册一些发送时显示函数(cb_sndinvite,cb_sndack等).这些函数也只是显示信息而已
注册一些发送时显示函数(cb_rcv1xx,cb_rcv2xx等).这些函数在收到回应时调用,更改一些事件状态,向主线程的fifo添加事件,向主线程报告事件的发生(report_event)
初始化控制用的pipe(eXosip_t::j_socketctl),这个pipe用于通知子线程事件的发生
初始化事件用的pipe(eXosip_t::j_socketctl_event),用于通知主线程事件的发生
初始化fifo(osip_fifo_init).所有用于通知主线程的事件将放入这个fifo等待被响应
加载名片(jfriend_load)
加载认证信息(jidentity_load)
对参数中的协议进行映射.实际上这个又是一个错误,先映射后判断,显然错误信息永远都是要输出的
初始化监听功能(eXosip_listen_addr)
eXosip_listen_addr
一些有效性判断,得到本地ip(eXosip_guess_localip)
得到addrinfo(eXosip_get_addrinfo),并对得到的信息进行socket,bind,getsocketname,listen功能的有效性判断,直到找到满足这些条件的一个组合
对服务器进行能否连接的测试.服务器必须在50mS内有响应,否则认为不连通
创建一个线程(osip_thread_create).这个线程调用_eXosip_thread->_eXosip_execute函数.暂时先不管这个函数
如果NAT地址有定义,则设置eXosip中的防火墙地址(eXosip_masquerade_contact)
如果to已经有定义,则开始第一个call.详细流程见发起呼叫的流程
然后就是画GUI界面
先来看看发起呼叫是怎么一个流程
_josua_start_call
将各个参数(from,to,subject等)中的空格去掉(osip_clrspace),并检查from和to的有效性(_check_url)
利用传进来的参数初始化message的结构体(eXosip_call_build_initial_invite)
eXosip_call_build_initial_invite
初始化to(osip_to_init)并填充这个结构体(osip_to_parse->osip_from_parse).主要是填充 displayname/url/param(比如"kingwoo"<sip:[email protected];tag=12345>,那么 diplayname就是kingwoo,url就是sip:[email protected],param就是tag=12345)。
寻找param中的“transport”(osip_uri_uparam_get_byname).如果找到这个参数,则用这个参数和to、from 等参数一起构造一个message结构体(generating_request_out_of_dialog);否则,根据全局性的参数 (eXosip.net_interfaces[X].net_socket)决定是采用tcp还是udp,去构造这个结构体
generating_request_out_of_dialog
分析transport参数是udp还是tcp,默认是udp
初始化一个message结构体(osip_message_init),并填充方法(osip_message_set_method, method:INVITE、REGISTER等)、协议版本(osip_message_set_version)、状态机 (osip_message_set_status_code)、原因(osip_message_set_reason_phrase)
分析method是不是注册请求REGISTER。
如果是,则将route填写到Request_uri中(osip_uri_parse),from填写到to中(osip_message_set_to);
如果不是,则又有如下过程
填充to到message结构体中(osip_message_set_to)
如果有route,
根据route产生一个临时结构o_proxy(osip_route_parse)
寻找"lr"参数是否存在(osip_uri_uparam_get_byname)
如果参数存在
将to->url拷贝给Request_uri(osip_uri_clone)
将上面产生的临时结构o_proxy添加到routes链表中(osip_list_add, 实际上第一个链表数据)
如果参数不存在
将route的url赋给Request_uri
将to信息赋给route(osip_message_set_route)
如果没有定义route
将to->url拷贝给Request_uri(osip_uri_clone)
得到本地IP(eXosip_guess_ip_for_via)
填充from到message结构体中(osip_message_set_from)
填充tag到message结构体中(osip_from_set_tag)
产生一个新的CSeq(根据一个随机字符串、本地ip、方法(method)等)
设置最大跳数为70(osip_message_set_max_forwards.最大跳数指最多允许经过多少个服务器)
填充via和新生成的随机branch到message结构体中(osip_message_set_via). 这一步分服务器和客户端两种略有不同
根据各个不同的request增加各自所特需的header(主要是INVITE和SUBSCRIBE中需要增加Contact), 设置允许的响应(osip_message_set_allow)
设置“User-Agent”参数(osip_message_set_user_agent)
释放临时to这个结构体(osip_to_free)
设置“Subject”参数(osip_message_set_subject)
设置“Expires”参数,Expires=“120”(osip_message_set_expires)
设置“Supported”参数,Supported=“100rel”(osip_message_set_supported)
添加正文SDP部分(osip_message_set_body)
设置正文类型(Content-Type)为“application/sdp”(osip_message_set_content_type)
如果有多个header,则添加这些header信息(osip_message_set_multiple_header)(一般情况不用考虑)
加锁(eXosip_lock) 发送生成的message(eXosip_call_send_initial_invite) eXosip_call_send_initial_invite 初始化一个call结构(eXosip_call_init) 事务(transaction)初始化(osip_transaction_init) osip_transaction_init 得到当前时间(事务的生存时间起始值) 得到事务id(static int transactionid)
得到第一个via值 将via值赋给osip_transaction_t这个结构体(__osip_transaction_set_topvia) 将所有其他header也赋给这个结构体(__osip_transaction_set_XXXX) 初始化此事务专有的fifo(osip_fifo_init),osip_transaction::transactionff, 所有的此事务应该响应的事件都会被添加到这个链表里 初始化状态(osip_transaction::state, ICT_PRE_CALLING/IST_PRE_PROCEEDING/NICT_PRE_TRYING/NIST_PRE_TRYING)
初始化各种变量后根据状态(ICT,IST,NICT,NIST)设置状态并调用不同的初始化函数(主要是处理时间、路由、端口等内容,不必深究) 得到当前消息的一些信息,存于osip_event结构中(osip_new_outgoing_sipmessage) 添加用户自定义结构(osip_transaction_set_your_instance) 往上面初始化的事务专有fifo(osip_transaction::transactionff)里添加事件 (osip_transaction_add_event),即上面生成的osip_event结构.
在eXosip中增加call这个元素(ADD_ELEMENT),也就是说一个call开始了,以后可以在eXosip_t::j_calls链表中找到这个call 根据所有对话的完成情况设置一些变量(eXosip_update) 唤醒处理进程(__eXosip_wakeup) __eXosip_wakeup 往pipe(eXosip_t::j_socketctl)里发送“w”的信息(jpipe_write) 这样在_eXosip_execute->eXosip_read_message里就可以收到这个消息.但这个消息读出后是直接被丢弃的,也就是说只是起到一个让程序从睡眠状态到工作状态转变的一个过程。实际的数据是上面osip_transaction_add_event添加的事件,这个事件在_eXosip_execute->osip_XXX_execute中被读出并处理设置reference(eXosip_call_set_reference)解锁(eXosip_unlock)
篇三:[exosip]exosip,osip 学习篇(一)
最近简单粗略地看了下Osip,eXosip,ortp等要应用Osip到我们的程序中去,首先要看官方文档,文档中对Osip协议栈提供的各个功能部件如何使用都有比较详细的描述,但未进行整体性的分析,某些中文的指导文档也都停留在对其简单的翻译,不能为不熟悉该协议栈使用的用户快速参考使用,本文档不按照Osip的代码进行按功能分块说明,而是根据实际使用时的代码使用顺序来对主要逻辑流程进行分析,并适当对流程中使用到的功能部件进行说明,具体更详细的功能说明或疑问可直接查看官方文档对应部分的解释或直接查看功能函数源代码即可解决。
第一步:了解exosip ,osip中一些基本知识 先认识几个结构体:osip_t,osip_message_t,osip_dialog_t,osip_transaction_t; osip_t是一个全局变量,所有要使用Osip协议栈的事务处理能力的程序都要第一步就初始化它(相对应于只使用osipparser库进行SIP消息字段解析的应用来说,如果只使用parser库到自己的程序中,想必对SIP协议栈已经很熟悉了,不需再往下看了^_^),它内部主要是定义了Osip 协议栈的四个主要事务链表、消息实际发送函数及状态机各状态事件下的回调函数等;
在程序中osip_t被定义为: struct osip {
void *application_context; /**< User defined Pointer */
/* list of transactions for ict, ist, nict, nist */ osip_list_t osip_ict_transactions; /**< list of ict transactions */ osip_list_t osip_ist_transactions; /**< list of ist transactions */ osip_list_t osip_nict_transactions; /**< list of nict transactions */ osip_list_t osip_nist_transactions; /**< list of nist transactions */
osip_list_t ixt_retransmissions; /**< list of ixt elements */
osip_message_cb_t msg_callbacks[OSIP_MESSAGE_CALLBACK_COUNT]; /**@internal */ osip_kill_transaction_cb_t kill_callbacks[OSIP_KILL_CALLBACK_COUNT]; /**@internal */ osip_transport_error_cb_t tp_error_callbacks[OSIP_TRANSPORT_ERROR_CALLBACK_COUNT]; /**@internal */
int (*cb_send_message) (osip_transaction_t *, osip_message_t *, char *, int, int); /**@internal */
#if defined(HAVE_DICT_DICT_H) dict *osip_ict_hastable; /**< htable of ict transactions */ dict *osip_ist_hastable; /**< htable of ist transactions */ dict *osip_nict_hastable; /**< htable of nict transactions */ dict *osip_nist_hastable; /**< htable of nist transactions */#endif };
typedef struct osip osip_t;
osip_message_t是SIP消息的C语言结构体存储空间,收到SIP消息解析后存在该结构中方便程序使用接收到的消息中的指定的字段,发送消息前为方便设置要发送的字段值,将要发送的内容存在该结构中等发送时转为字符串;在该文档中提供了大量的API来对消息进行处理。该结构体如下定义:
typedef struct sdp_message sdp_message_t;
/** * SDP message definition. * @struct sdp_message */ struct sdp_message { char *v_version; /**< version header */ char *o_username; /**< Username */ char *o_sess_id; /**< Identifier for session */ char *o_sess_version; /**< Version of session */ char *o_nettype; /**< Network type */ char *o_addrtype; /**< Address type */ char *o_addr; /**< Address */ char *s_name; /**< Subject header */ char *i_info; /**< Information header */ char *u_uri; /**< Uri header */ osip_list_t e_emails; /**< list of mail address */ osip_list_t p_phones; /**< list of phone numbers * */ sdp_connection_t *c_connection; /**< Connection information */ osip_list_t b_bandwidths; /**< list of bandwidth info (sdp_bandwidth_t) */ osip_list_t t_descrs; /**< list of time description (sdp_time_descr_t) */ char *z_adjustments; /**< Time adjustment header */ sdp_key_t *k_key; /**< Key information header */ osip_list_t a_attributes; /**< list of global attributes (sdp_attribute_t) */ osip_list_t m_medias; /**< list of supported media (sdp_media_t) */ }; 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定义; 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中定义)的,它的主要属性值有callid,transactionid,分别来标识dialog和 transaction,其中还有一个时间戳birth_time标识事务创建时间,可由超时处理函数用来判断和决定超时情况下的事务的进行和销毁,而它的state属性是非常重要的,根据上述的事务类型不同,其值也不同,它是前面提到的状态机的“状态”,在实际状态机的逻辑执行中是一个关键值;
Osip初始化 提到osip的初始化,可能大家都看过官方文档里第一页的代码,首先就是osip_init(&osip)初始化了全局的osip_t结构体,然后对它的回调函数进行设置,很多人估计就是一看到这密密麻麻的一页多的call_back设置被吓到了,但结合前面分析的三个结构体的含义,这里的含义就很清晰了: osip_t中有一个cb_send_message 函数指针,它是Osip最终与外界网络交互的接口,它的参数有( osip_transaction_t * trn, /*本消息所属的事务*/ osip_message_t * sipmsg, /*待发送的消息结构体*/ char *dest_socket_str, /*目标地址*/ int32_t dest_port, /*目标端口*/ int32_t send_sock) /*用来发送消息的socket*/ 其中trn传入主要是为了方便获取事务的上下文数据,它有一个void指针your_instance,可以用来传入更多数据方便发送消息时参考,例如将该事务所属的 dialog指针传入; 而sipmsg则是我们要发送的SIP消息的C结构体,使用osip_message_to_str将其按RFC文档格式转换为一个字符串(osip中的parser模块的主要功能),再通过任意你自己的网络数据发送函数使用send_sock发送给dest_socket_str和dest_port 指定的目标,当然,要记得使用osip_free释放刚才发送出去的字符串占用的内存,Osip中很多osipparser提供的消息解析处理函数都是动态内存分配的,使用完毕后需要及时释放; 使用osip_set_cb_send_message成功设置回调函数,我们的SIP消息就有了出口了,下面继续分析(当然,了解到了上面的流程,也可以手工指定了)。 下面的回调函数分为三类,分别是普通事务消息(osip_message_callback_type_t中定义)的处理回调函数、事务销毁事件(osip_kill_callback_type_t中定义)的清理回调函数以及事务执行过程中的错误事件(osip_transport_error_callback_type_t中定义)处理回调函数: 先说简单的,事务销毁事件,事务正常结束(成功完成状态机流程)或由超时处理函数强制终结等情况下均调用了这些回调函数,一般就是释放事务结构体,为ICT,NICT,IST,NIST各设置或共用一个回调函数均可,只要正确释放不再使用的内存即可; 错误处理函数则是在整个状态机执行过程中发生的任何错误的出口,一般用来安插 log函数方便调试,也可以直接设为空函数; 而最关键的就是正常消息的处理回调函数了,其量是非常大的,但仔细分下类,也和上面的回调函数一样,也是分为四类,我们可有根据实际程序的需要来进行设置,例如,SIP电话机就不需要处理OSIP_NIST_REGISTER_RECEIVED这个SIP注册服务器才需要处理的Register消息事件了,精简一下,如果只是要做一个只需要实现主叫功能且不考虑错误情况的UAC的Demo软电话程序,则只需要设置如下几个事件的回调函数: OSIP_ICT_INVITE_SENT 发出Invite开始呼叫 OSIP_ICT_STATUS_1XX_RECEIVED 收到 180 OSIP_ICT_STATUS_2XX_RECEIVED 收到200 OSIP_ICT_ACK_SENT 发出 ack确定呼叫 OSIP_NICT_BYE_SENT 发出bye结束呼叫 OSIP_NICT_STATUS_2XX_RECEIVED 收到200确认结束呼叫 OSIP_NIST_BYE_RECEIVED 收到 bye结束呼叫 OSIP_NIST_STATUS_2XX_SENT 发出 200确定结束呼叫而要增加接受呼叫的被叫UAS功能,则只需要增加如下事件: OSIP_IST_INVITE_RECEIVED 收到invite开始呼叫 OSIP_IST_STATUS_1XX_SENT 发出180 OSIP_IST_STATUS_2XX_SENT 发出200 OSIP_IST_ACK_RECEIVED 收到ack确认呼叫具体的函数定义,则直接参考osip_message_cb_t,osip_kill_transaction_cb_t, osip_transport_error_cb_t即可,回调函数的设置同上可以手工设置,也可以使用Osip提供的对应的 osip_set_xxx_callback函数;
发出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初始化一个sipmsg,根据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文档,同样没捷径可走。 现在我们有了两个必要条件了,还有最后一个也是最关键的部件,就是事务的创建和触发,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,由前文可知,本类函数有四个,FIFO也有四个,对应 ICT,NICT,IST,NIST,注意这个这里使用osip_transaction_set_out_socket把发送sip消息的socket 接口配给该事务,方便自动调用前面设置的发送消息回调函数使用它自动发送消息; 前文提到了transaction里的state作为状态机的 “状态”,要执行状态机,就需要有“事件”来触发,事件结构体osip_event_t需要使用 osip_new_outgoing_sipmessage来对sipmsg进行探测生成,设置正确的事件值,省却了我们手工设置的工作,它调用 evt_set_type_outgoing_sipmessage来设置“事件”type_t,并将sipmsg挂到事件结构体的sip属性值上,有了根据消息分析出的事件后,使用osip_fifo_add(trn->transactionff, ev)将事件插入到事务的事件FIFO中,即transactionff属性; 有了上面的发送消息的必要条件了,消息是如何实际出发的呢?上面提到了,SIP消息的发送和响应是一个事务,不能隔离开来,即消息的发送需要事务状态机来控制,我们上面设置了状态机的状态和事件,要触发它,就是要执行状态机了: osip_ict_execute osip_nict_execute osip_ist_execute osip_nist_execute 分别用来遍历前面提到的四个事务FIFO,取出事务,再依次取出事务内的事件FIFO上的事件,使用osip_transaction_execute 依次执行(有兴趣的可以更深一步去查看,可以看到它最终就是调用了我们前面设置的消息回调函数,至于具体调用哪个,这就是OSIP协议栈内部帮我们做的大量的工作了^_^); 如果某个事务不能正常终结怎么办呢?例如发出了Invite没有收到任何响应,按RFC定义,不同的事务有不同的超时时间,osip_timers_ict[nict|ist|nist]_execute这些函数就是来根据取出的事务的时间戳与当前时间取差后与规定的超时时间比对,如果超时,就自动设置了超时“事件”并将事务“状态”设为终结,使用前面设定的消息超时事件回调函数处理即可(如果设置了); 如果网络质量不稳定,经常丢失消息,需要使用osip_retransmissions_execute函数来自动重发消息而不是等待超时; 为了即时响应SIP消息的处理推动状态机,上述的九个函数需要不停执行,可以将它放入单独线程中。
收到SIP消息 有了前面的发送SIP消息的理解,接收消息的处理就方便理解了,收到SIP消息,使用osip_parse进行解析,得到一个osip_message_t 的sipmsg,使用evt_set_type_incoming_sipmessage得到事务的“事件”,并同上将sipmsg挂到事件结构体的 sip字段,随后立即使用osip_find_transaction_and_add_event来根据“事件”查找事务(有兴趣可以深入看一下,事务的查找是通过SIP消息Via中的branch来匹配的),否则新建事务,然后推动状态机执行。
状态机内部逻辑 弄清了上面的状态机的大概逻辑,设置正确完备的回调函数,就可以正确使用Osip来进行工作了,如果要进一步深入 Osip,比如要扩展Osip的状态机处理自定义的消息字段和实现新的事务逻辑来生成新业务时,就需要对状态机的内部逻辑有一定的了解; 前面一再强调,Osip内部的几个重要的数据结构osip_message_t,osip_dialog_t,osip_transaction_t,其中面向用户的主要是前后两个,而中间的dialog则很多时候是在状态机内部使用的,例如:收到消息,解析到sipmsg中,查找 transaction并进行驱动,随后找到它关联的dialog(或者新生成)解析填充要发送的消息结构体sipmsg,再次根据dialog和 sipmsg查找或生成transaction。 如果要扩展Osip,要做工作主要有: 扩展osip_message_t,增加要解析的字段或消息头,并参考原Osip函数生成对应的SIP字符串生成和解析函数; 扩展osip_dialog_t,增加新的属性,对应 osip_message_t的新增内容; 扩展状态机的事件和状态类型,设置对应的回调函数,并关联新增事件和状态类型到osip_message_t的解析函数或osip_dialog_t的初始化函数中,而osip_transaction_t大多数时候不需要扩展,只要在对应的事务类型(大多数时候是NICT、NIST)处理逻辑中,增加对新增事件和状态类型的判断和调用回调函数的逻辑即可

