最近簡(jiǎn)單粗略地看了下Osip,eXosip,ortp等要應(yīng)用Osip到我們的程序中去,首先要看官方文檔,文檔中對(duì)Osip協(xié)議棧提供的各個(gè)功能部件如何使用都有比較詳細(xì)的描述,但未進(jìn)行整體性的分析,某些中文的指導(dǎo)文檔也都停留在對(duì)其簡(jiǎn)單的翻譯,不能為不熟悉該協(xié)議棧使用的用戶(hù)快速參考使用,本文檔不按照Osip的代碼進(jìn)行按功能分塊說(shuō)明,而是根據(jù)實(shí)際使用時(shí)的代碼使用順序來(lái)對(duì)主要邏輯流程進(jìn)行分析,并適當(dāng)對(duì)流程中使用到的功能部件進(jìn)行說(shuō)明,具體更詳細(xì)的功能說(shuō)明或疑問(wèn)可直接查看官方文檔對(duì)應(yīng)部分的解釋或直接查看功能函數(shù)源代碼即可解決。
第一步:了解exosip ,osip中一些基本知識(shí) 先認(rèn)識(shí)幾個(gè)結(jié)構(gòu)體:osip_t,osip_message_t,osip_dialog_t,osip_transaction_t; osip_t是一個(gè)全局變量,所有要使用Osip協(xié)議棧的事務(wù)處理能力的程序都要第一步就初始化它(相對(duì)應(yīng)于只使用osipparser庫(kù)進(jìn)行SIP消息字段解析的應(yīng)用來(lái)說(shuō),如果只使用parser庫(kù)到自己的程序中,想必對(duì)SIP協(xié)議棧已經(jīng)很熟悉了,不需再往下看了^_^),它內(nèi)部主要是定義了Osip 協(xié)議棧的四個(gè)主要事務(wù)鏈表、消息實(shí)際發(fā)送函數(shù)及狀態(tài)機(jī)各狀態(tài)事件下的回調(diào)函數(shù)等;
在程序中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語(yǔ)言結(jié)構(gòu)體存儲(chǔ)空間,收到SIP消息解析后存在該結(jié)構(gòu)中方便程序使用接收到的消息中的指定的字段,發(fā)送消息前為方便設(shè)置要發(fā)送的字段值,將要發(fā)送的內(nèi)容存在該結(jié)構(gòu)中等發(fā)送時(shí)轉(zhuǎn)為字符串;在該文檔中提供了大量的API來(lái)對(duì)消息進(jìn)行處理。 該結(jié)構(gòu)體如下定義:
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的定義,它標(biāo)識(shí)了uac和uas的一對(duì)關(guān)系,并一直保持到會(huì)話(huà)(session)結(jié)束,一個(gè)完整的dialog主要包括from,to, callid,fromtag,totag,state等(可查看源碼),其中fromtag,totag,callid在一個(gè)dialog成功建立后才完整,體現(xiàn)在SIP消息中,就是From、To的tag,Call-id字段的值相同時(shí),這些消息是屬于它們對(duì)應(yīng)的一個(gè)Dialog的,例如將要發(fā)起 invite時(shí),只有fromtag,callid填充有值,在收到to遠(yuǎn)端的響應(yīng)時(shí),收到totag填充到dialog中,建立成功一個(gè)dialog,后繼的邏輯均是使用這個(gè)dialog進(jìn)行處理(如transaction事務(wù)處理),state表示本dialog的狀態(tài),與transaction的 state有很大的關(guān)聯(lián),共用由Enum結(jié)構(gòu)state_t定義; osip_transaction_t則是RFC中的事務(wù)的定義,它表示的是一個(gè)會(huì)話(huà)的某個(gè)Dialog之間的某一次消息發(fā)送及其完整的響應(yīng),例如 invite-100-180-200-ack這是一個(gè)完整的事務(wù),bye-200這也是一個(gè)完整的事務(wù),體現(xiàn)在SIP消息中,就是Via中的 branch的值相同表示屬于一個(gè)事務(wù)的消息(當(dāng)然,事務(wù)是在Dialog中的,所以From、To的tag,Call-id值也是相同的),事務(wù)對(duì)于 UAC,UAS的終端類(lèi)型不同及消息的不同,分為四類(lèi),前面說(shuō)的invite的事務(wù),主叫uac中會(huì)關(guān)聯(lián)一個(gè)ict事務(wù),被叫uas會(huì)關(guān)聯(lián)一個(gè)ist事務(wù),而除了invite之外,都?xì)w類(lèi)定義主叫nict,被叫nist,在Osip中,它是靠有限狀態(tài)機(jī)來(lái)實(shí)現(xiàn)的上述四種事務(wù)(osip_fsm_type_t中定義)的,它的主要屬性值有callid,transactionid,分別來(lái)標(biāo)識(shí)dialog和 transaction,其中還有一個(gè)時(shí)間戳birth_time標(biāo)識(shí)事務(wù)創(chuàng)建時(shí)間,可由超時(shí)處理函數(shù)用來(lái)判斷和決定超時(shí)情況下的事務(wù)的進(jìn)行和銷(xiāo)毀,而它的state屬性是非常重要的,根據(jù)上述的事務(wù)類(lèi)型不同,其值也不同,它是前面提到的狀態(tài)機(jī)的“狀態(tài)”,在實(shí)際狀態(tài)機(jī)的邏輯執(zhí)行中是一個(gè)關(guān)鍵值;
- Osip初始化
提到osip的初始化,可能大家都看過(guò)官方文檔里第一頁(yè)的代碼,首先就是osip_init(&osip)初始化了全局的osip_t結(jié)構(gòu)體,然后對(duì)它的回調(diào)函數(shù)進(jìn)行設(shè)置,很多人估計(jì)就是一看到這密密麻麻的一頁(yè)多的call_back設(shè)置被嚇到了,但結(jié)合前面分析的三個(gè)結(jié)構(gòu)體的含義,這里的含義就很清晰了: osip_t中有一個(gè)cb_send_message 函數(shù)指針,它是Osip最終與外界網(wǎng)絡(luò)交互的接口,它的參數(shù)有( osip_transaction_t * trn, /*本消息所屬的事務(wù)*/ osip_message_t * sipmsg, /*待發(fā)送的消息結(jié)構(gòu)體*/ char *dest_socket_str, /*目標(biāo)地址*/ int32_t dest_port, /*目標(biāo)端口*/ int32_t send_sock) /*用來(lái)發(fā)送消息的socket*/ 其中trn傳入主要是為了方便獲取事務(wù)的上下文數(shù)據(jù),它有一個(gè)void指針your_instance,可以用來(lái)傳入更多數(shù)據(jù)方便發(fā)送消息時(shí)參考,例如將該事務(wù)所屬的 dialog指針傳入; 而sipmsg則是我們要發(fā)送的SIP消息的C結(jié)構(gòu)體,使用osip_message_to_str將其按RFC文檔格式轉(zhuǎn)換為一個(gè)字符串(osip中的parser模塊的主要功能),再通過(guò)任意你自己的網(wǎng)絡(luò)數(shù)據(jù)發(fā)送函數(shù)使用send_sock發(fā)送給dest_socket_str和dest_port 指定的目標(biāo),當(dāng)然,要記得使用osip_free釋放剛才發(fā)送出去的字符串占用的內(nèi)存,Osip中很多osipparser提供的消息解析處理函數(shù)都是動(dòng)態(tài)內(nèi)存分配的,使用完畢后需要及時(shí)釋放; 使用osip_set_cb_send_message成功設(shè)置回調(diào)函數(shù),我們的SIP消息就有了出口了,下面繼續(xù)分析(當(dāng)然,了解到了上面的流程,也可以手工指定了)。
下面的回調(diào)函數(shù)分為三類(lèi),分別是普通事務(wù)消息(osip_message_callback_type_t中定義)的處理回調(diào)函數(shù)、事務(wù)銷(xiāo)毀事件(osip_kill_callback_type_t中定義)的清理回調(diào)函數(shù)以及事務(wù)執(zhí)行過(guò)程中的錯(cuò)誤事件(osip_transport_error_callback_type_t中定義)處理回調(diào)函數(shù): 先說(shuō)簡(jiǎn)單的,事務(wù)銷(xiāo)毀事件,事務(wù)正常結(jié)束(成功完成狀態(tài)機(jī)流程)或由超時(shí)處理函數(shù)強(qiáng)制終結(jié)等情況下均調(diào)用了這些回調(diào)函數(shù),一般就是釋放事務(wù)結(jié)構(gòu)體,為ICT,NICT,IST,NIST各設(shè)置或共用一個(gè)回調(diào)函數(shù)均可,只要正確釋放不再使用的內(nèi)存即可; 錯(cuò)誤處理函數(shù)則是在整個(gè)狀態(tài)機(jī)執(zhí)行過(guò)程中發(fā)生的任何錯(cuò)誤的出口,一般用來(lái)安插 log函數(shù)方便調(diào)試,也可以直接設(shè)為空函數(shù); 而最關(guān)鍵的就是正常消息的處理回調(diào)函數(shù)了,其量是非常大的,但仔細(xì)分下類(lèi),也和上面的回調(diào)函數(shù)一樣,也是分為四類(lèi),我們可有根據(jù)實(shí)際程序的需要來(lái)進(jìn)行設(shè)置,例如,SIP電話(huà)機(jī)就不需要處理OSIP_NIST_REGISTER_RECEIVED這個(gè)SIP注冊(cè)服務(wù)器才需要處理的Register消息事件了,精簡(jiǎn)一下,如果只是要做一個(gè)只需要實(shí)現(xiàn)主叫功能且不考慮錯(cuò)誤情況的UAC的Demo軟電話(huà)程序,則只需要設(shè)置如下幾個(gè)事件的回調(diào)函數(shù): OSIP_ICT_INVITE_SENT 發(fā)出Invite開(kāi)始呼叫 OSIP_ICT_STATUS_1XX_RECEIVED 收到 180 OSIP_ICT_STATUS_2XX_RECEIVED 收到200 OSIP_ICT_ACK_SENT 發(fā)出 ack確定呼叫 OSIP_NICT_BYE_SENT 發(fā)出bye結(jié)束呼叫 OSIP_NICT_STATUS_2XX_RECEIVED 收到200確認(rèn)結(jié)束呼叫 OSIP_NIST_BYE_RECEIVED 收到 bye結(jié)束呼叫 OSIP_NIST_STATUS_2XX_SENT 發(fā)出 200確定結(jié)束呼叫 而要增加接受呼叫的被叫UAS功能,則只需要增加如下事件: OSIP_IST_INVITE_RECEIVED 收到invite開(kāi)始呼叫 OSIP_IST_STATUS_1XX_SENT 發(fā)出180 OSIP_IST_STATUS_2XX_SENT 發(fā)出200 OSIP_IST_ACK_RECEIVED 收到ack確認(rèn)呼叫 具體的函數(shù)定義,則直接參考o(jì)sip_message_cb_t,osip_kill_transaction_cb_t, osip_transport_error_cb_t即可,回調(diào)函數(shù)的設(shè)置同上可以手工設(shè)置,也可以使用Osip提供的對(duì)應(yīng)的 osip_set_xxx_callback函數(shù);
- 發(fā)出SIP消息
要發(fā)送 SIP消息,從上面的分析可知有幾個(gè)必要的條件,osip_messag_t結(jié)構(gòu)的待發(fā)送消息,osip_dialog_t結(jié)構(gòu)體的dialog以及 osip_transaction_t的事務(wù); 首先osip_malloc新分配一個(gè)dialog,使用osip_to_init,osip_to_parse,osip_to_free這類(lèi) parser函數(shù)功能函數(shù)按RFC設(shè)置call-id,from,to,local_cseq等必要字段(原則是:后面生成實(shí)際SIP消息結(jié)構(gòu)體要用到的字段就需要設(shè)置),使用osip_message_init初始化一個(gè)sipmsg,根據(jù)dialog來(lái)填充該結(jié)構(gòu)體(不同的消息填充的數(shù)據(jù)是不同的,沒(méi)有捷徑可走,只能看RFC根據(jù)需要填充字段),如果要給SIP消息添加Body例如SDP段,需要使用osip_message_set_body, osip_message_set_content_type函數(shù),設(shè)置的值是純文本,如果是SDP,Osip有提供簡(jiǎn)單的解析和生成便捷函數(shù)例如 sdp_message_to_str,sdp_message_a_attribute_add,但只是簡(jiǎn)單的字符操作,要填充合法的字段需要自己參考 SDP的RFC文檔,同樣沒(méi)捷徑可走。 現(xiàn)在我們有了兩個(gè)必要條件了,還有最后一個(gè)也是最關(guān)鍵的部件,就是事務(wù)的創(chuàng)建和觸發(fā), int osip_transaction_init( osip_transaction_t ** transaction, /*返回的事務(wù)結(jié)構(gòu)體指針*/ osip_fsm_type_t ctx_type, /*事務(wù)類(lèi)型ICT/NICT/IST/NIST*/ osip_t * osip, /*前文說(shuō)的全局變量*/ osip_message_t * request) /*前面生成的sipmsg*/ 創(chuàng)建了一個(gè)新的事務(wù),并自動(dòng)根據(jù)事務(wù)類(lèi)型、dialog和sipmsg進(jìn)行了初始化,最重要的是它使用了__osip_add_ict等函數(shù),將本事務(wù)插入到全局的osip_t結(jié)構(gòu)體的全局FIFO鏈表中去了,不同的事務(wù)類(lèi)型對(duì)應(yīng)不同的FIFO,由前文可知,本類(lèi)函數(shù)有四個(gè),F(xiàn)IFO也有四個(gè),對(duì)應(yīng) ICT,NICT,IST,NIST,注意這個(gè)這里使用osip_transaction_set_out_socket把發(fā)送sip消息的socket 接口配給該事務(wù),方便自動(dòng)調(diào)用前面設(shè)置的發(fā)送消息回調(diào)函數(shù)使用它自動(dòng)發(fā)送消息; 前文提到了transaction里的state作為狀態(tài)機(jī)的 “狀態(tài)”,要執(zhí)行狀態(tài)機(jī),就需要有“事件”來(lái)觸發(fā),事件結(jié)構(gòu)體osip_event_t需要使用 osip_new_outgoing_sipmessage來(lái)對(duì)sipmsg進(jìn)行探測(cè)生成,設(shè)置正確的事件值,省卻了我們手工設(shè)置的工作,它調(diào)用 evt_set_type_outgoing_sipmessage來(lái)設(shè)置“事件”type_t,并將sipmsg掛到事件結(jié)構(gòu)體的sip屬性值上,有了根據(jù)消息分析出的事件后,使用osip_fifo_add(trn->transactionff, ev)將事件插入到事務(wù)的事件FIFO中,即transactionff屬性; 有了上面的發(fā)送消息的必要條件了,消息是如何實(shí)際出發(fā)的呢?上面提到了,SIP消息的發(fā)送和響應(yīng)是一個(gè)事務(wù),不能隔離開(kāi)來(lái),即消息的發(fā)送需要事務(wù)狀態(tài)機(jī)來(lái)控制,我們上面設(shè)置了狀態(tài)機(jī)的狀態(tài)和事件,要觸發(fā)它,就是要執(zhí)行狀態(tài)機(jī)了: osip_ict_execute osip_nict_execute osip_ist_execute osip_nist_execute 分別用來(lái)遍歷前面提到的四個(gè)事務(wù)FIFO,取出事務(wù),再依次取出事務(wù)內(nèi)的事件FIFO上的事件,使用osip_transaction_execute 依次執(zhí)行(有興趣的可以更深一步去查看,可以看到它最終就是調(diào)用了我們前面設(shè)置的消息回調(diào)函數(shù),至于具體調(diào)用哪個(gè),這就是OSIP協(xié)議棧內(nèi)部幫我們做的大量的工作了^_^); 如果某個(gè)事務(wù)不能正常終結(jié)怎么辦呢?例如發(fā)出了Invite沒(méi)有收到任何響應(yīng),按RFC定義,不同的事務(wù)有不同的超時(shí)時(shí)間,osip_timers_ict[nict|ist|nist]_execute這些函數(shù)就是來(lái)根據(jù)取出的事務(wù)的時(shí)間戳與當(dāng)前時(shí)間取差后與規(guī)定的超時(shí)時(shí)間比對(duì),如果超時(shí),就自動(dòng)設(shè)置了超時(shí)“事件”并將事務(wù)“狀態(tài)”設(shè)為終結(jié),使用前面設(shè)定的消息超時(shí)事件回調(diào)函數(shù)處理即可(如果設(shè)置了); 如果網(wǎng)絡(luò)質(zhì)量不穩(wěn)定,經(jīng)常丟失消息,需要使用osip_retransmissions_execute函數(shù)來(lái)自動(dòng)重發(fā)消息而不是等待超時(shí); 為了即時(shí)響應(yīng)SIP消息的處理推動(dòng)狀態(tài)機(jī),上述的九個(gè)函數(shù)需要不停執(zhí)行,可以將它放入單獨(dú)線(xiàn)程中。
- 收到SIP消息
有了前面的發(fā)送SIP消息的理解,接收消息的處理就方便理解了,收到SIP消息,使用osip_parse進(jìn)行解析,得到一個(gè)osip_message_t 的sipmsg,使用evt_set_type_incoming_sipmessage得到事務(wù)的“事件”,并同上將sipmsg掛到事件結(jié)構(gòu)體的 sip字段,隨后立即使用osip_find_transaction_and_add_event來(lái)根據(jù)“事件”查找事務(wù)(有興趣可以深入看一下,事務(wù)的查找是通過(guò)SIP消息Via中的branch來(lái)匹配的),否則新建事務(wù),然后推動(dòng)狀態(tài)機(jī)執(zhí)行。
- 狀態(tài)機(jī)內(nèi)部邏輯
弄清了上面的狀態(tài)機(jī)的大概邏輯,設(shè)置正確完備的回調(diào)函數(shù),就可以正確使用Osip來(lái)進(jìn)行工作了,如果要進(jìn)一步深入 Osip,比如要擴(kuò)展Osip的狀態(tài)機(jī)處理自定義的消息字段和實(shí)現(xiàn)新的事務(wù)邏輯來(lái)生成新業(yè)務(wù)時(shí),就需要對(duì)狀態(tài)機(jī)的內(nèi)部邏輯有一定的了解; 前面一再?gòu)?qiáng)調(diào),Osip內(nèi)部的幾個(gè)重要的數(shù)據(jù)結(jié)構(gòu)osip_message_t,osip_dialog_t,osip_transaction_t,其中面向用戶(hù)的主要是前后兩個(gè),而中間的dialog則很多時(shí)候是在狀態(tài)機(jī)內(nèi)部使用的,例如:收到消息,解析到sipmsg中,查找 transaction并進(jìn)行驅(qū)動(dòng),隨后找到它關(guān)聯(lián)的dialog(或者新生成)解析填充要發(fā)送的消息結(jié)構(gòu)體sipmsg,再次根據(jù)dialog和 sipmsg查找或生成transaction。 如果要擴(kuò)展Osip,要做工作主要有: 擴(kuò)展osip_message_t,增加要解析的字段或消息頭,并參考原Osip函數(shù)生成對(duì)應(yīng)的SIP字符串生成和解析函數(shù); 擴(kuò)展osip_dialog_t,增加新的屬性,對(duì)應(yīng) osip_message_t的新增內(nèi)容; 擴(kuò)展?fàn)顟B(tài)機(jī)的事件和狀態(tài)類(lèi)型,設(shè)置對(duì)應(yīng)的回調(diào)函數(shù),并關(guān)聯(lián)新增事件和狀態(tài)類(lèi)型到osip_message_t的解析函數(shù)或osip_dialog_t的初始化函數(shù)中,而osip_transaction_t大多數(shù)時(shí)候不需要擴(kuò)展,只要在對(duì)應(yīng)的事務(wù)類(lèi)型(大多數(shù)時(shí)候是NICT、NIST)處理邏輯中,增加對(duì)新增事件和狀態(tài)類(lèi)型的判斷和調(diào)用回調(diào)函數(shù)的邏輯即可
|