oSIP協議棧(及eXoSIP,Ortp等)使用入門(轉)

 

  一直沒空仔細研究下oSIP,最近看到其版本已經到了3.x版本,看到網上的許多幫助說明手冊都過於陳舊,且很多文檔內容有點誤人子弟的嫌疑~~
  Linux下oSIP的編譯使用應該是很簡單的,其Install說明文檔裏也介紹的比較清楚,本文主要就oSIP在Windows平臺下VC6.0開發環境下的使用作出描述。
  雖然oSIP的開發人員也說明了,oSIP只使用了標準C開發庫,但許多人在Windows下使用oSIP時,第一步就被卡住了,得不到oSIP的LIB庫和DLL庫,也就沒有辦法將oSIP使用到自己的程序中去,所以第一步,我們將學習如何得到oSIP的靜態和動態鏈接庫,以便我們自己的程序能夠使用它們來成功編譯和執行我們的程序。


第一階段:
------------------------------------------------------
  先創建新工程,網上許多文檔都介紹創建一個Win32動態鏈接庫工程,我們這裏也一樣,創建一個空白的工程保存。
  同樣,將oSIP2版本3.0.1 src目錄下的Osipparser2目錄下的所有文件都拷到我們剛創建的工程的根目錄下,在VC6上操作:
       Project-Add To Project-Files
  將所有的源程序和頭文件都加入到工程內,保存工程。
  這時,我們可以嘗試編譯一下工程,你會得到許多錯誤提示信息,其內容無非是找不到osipparser2/xxxxx.h頭文件之類。
  處理:在Linux下,我們一般是將頭文件,lib庫都拷到/usr/inclue;/usr/lib之類的目錄下,c源程序裏直接寫#include <xxx.h>時,能直接去找到它們,在VC裏,同樣的,最簡單的方法就是將oSIP2源碼包中的Include目錄下的osipparser2目錄直接拷到我們的Windows下默認包含目錄即可,這個目錄在VC6的Tool-Options-Directories裏設置,(當然,如果你知道這一步,也可以不用拷貝文件,直接在這裏把oSIP源碼包所在目錄加進來就可以了),默認如果裝在C盤,目錄則爲C:\Program Files\Microsoft Visual Studio\VC98\Include。
  這時,我們再次編譯我們的工程,順利編譯,生成osipparser2.dll,這時,網上很多文檔裏可能直接就說,這一步也會生成libs目錄,裏面裏osipparser2.lib文件,但我們這裏沒有生成:)
  最簡單的方法,不用深究,直接再創建一個工程,同上述創建動態鏈接庫方法,創建一個Win32靜態鏈接庫工程,直接編譯,即可得到osipparser2.lib。
------------------------------------------------------
  上面,我們得到了Osip的解析器開發庫,下面再編譯完整的Osip協議棧開發庫,同樣照上述方法,分別創建動態鏈接庫工程和靜態鏈接庫工程,只是要拷的文件換成src下的osip目錄下文件和include下的osip目錄,得到osip2.dll和osip2.lib。
  在編譯osip2.dll這一步可能會再次得到錯誤,內容含義是找不到鏈接庫,所以,我們要把前面編譯得到的osipparser2.lib也拷到osip工程目錄下,並在VC6中操作:
  Project-Setting-Link中的Object/Library Modules:
       kernel32.lib user32.lib ... xxx.lib之類的內容最後增加: osipparser2.lib
  保存工程後再次編譯,即可成功編譯osip2.dll。
------------------------------------------------------
  至此,我們得到了完整的oSIP開發庫,使用時,只需在我們的程序裏包含oSIP的頭文件,工程的鏈接參數裏增加osipparser2.lib和osip2.lib即可。
------------------------------------------------------
  下面我們驗證一下我們得到的開發庫,並大概瞭解一下OSIP的語法規範。
  在VC裏創建win32控制檯程序工程,將libosip源碼包的SRC目錄下的Test目錄內的C源程序隨便拷一個到工程時,直接編譯(工程設置裏照前文方法在link選項裏增加osip2.lib,osipparser2.lib引用我們之前成功編譯得到的靜態庫文件)就可以運行(帶參數運行,參數一般爲一個文本文件,同樣從Test目錄的res目錄裏拷一個與源文件同名的純文本文件到工程目錄下即可)。
  該目錄下的若干文件基本上是測試了Osip的一些基本功能函數,例如URI解析之類,可以大概瞭解一下oSIP的語法規範和調用方法,同時也能校驗一下之前編譯的OSIP開發庫能否正常使用,成功完成本項工作後,可以進入下一步具體的oSIP的使用學習了。
------------------------------------------------------
  由於oSIP是比較底層的SIP協議棧實現,新手較難上手,而官方的示例大都是一些僞代碼,需要有實際的例子程序參考學習,而最好的例子就是同樣官方發佈的oSIP的擴展開發庫exosip2,使用exoSIP可以很方便地快速創建一個完整的SIP程序(只針對性地適用於SIP終端開發用,所以我們這裏只是用它快速開發一個SIP終端,用來更方便地學習oSIP,要想真正掌握SIP的開發,需要掌握oSIP並熟讀RFC文檔才行,exoSIP不是我們的最終學習目的),通過成功編譯運行一個自己動手開發出的程序,再由淺入深應該是初學都最好的學習方法通過對使用exosip開發庫的使用創建自己的SIP程序,熟悉後再一個函數一個函數地深入學習exosip提供的接口函數,就可以深入理解osip 了,達到間接學習oSIP的目的,同時也能從eXoSIP中學習到正確使用oSIP的良好的編程風格和語法格式。
  而要成功編譯ExoSIP,似乎許多人被難住了,直接在XP-sp2上,用VC6,雖然你使用了eXoSIP推薦的winsock2.h,但是會得到一個sockaddr_storage結構不能識別的錯誤,因爲vc6自帶的開發庫太古董了,需要升級系統的Platform SDK,下載地址如下:
http://www.microsoft.com/msdownl ... PSP2FULLInstall.htm(VC6的支持已經停止,這是VC6能使用的最新SDK)
  成功安裝後編譯前需加OSIP_MT宏,以啓用線程庫,否則在程序中使用eXoSIP庫時會出錯,而編譯時也會得到許多函數未定義的Warning提示,編譯得到exosip2.lib供我們使用,當然,在此之前需要成功編譯了osip2和osipparser2,而在之後的實際使用時,發現oSIP也需要增加OSIP_MT宏,否則OSIP_MT調用oSIP的線程庫時會出錯,所以我們需要重新編譯oSIP了:),因爲eXosip是基於oSIP的(同上方式創建靜態和動態鏈接庫工程,並需在Link中手工添加oSIP和oSIPparser的lib庫)。
------------------------------------------------------
  創建新工程,可以是任意工程,我們從最簡單的Win32控制檯程序開始,爲了成功使用oSIP,我們需要引用相關庫,調用相關頭文件,經過多次試驗,發現需要引用如下的庫:
         exosip2.lib osip2.lib osipparser2.lib WSock32.Lib IPHlpApi.Lib WS2_32.Lib Dnsapi.lib
  其中,除了我們上面編譯得到的三個oSIP庫外,其它庫都是系統庫,其中有一些是新安裝的Platform SDK所新提供的。
  至此,我們有了一個簡單的開發環境了,可以充分利用網上大量的以oSIP爲基礎的代碼片段和官方說明文檔開始具體函數功能的測試和使用了:)
------------------------------------------------------
  我們先進行一個簡單的純SIP信令(不帶語音連接建立)的UAC的SIP終端的程序開發的試驗(即一個只能作爲主叫不能作爲被叫的的SIP軟電話模型),我們創建一個MFC應用程序,對話框模式,照上面的說明,設置工程包含我們上面得到的oSIP的相關開發庫及SDK的一些開發庫,並且由於默認LIBC的衝突,需要排除MSVCRT[D]開發庫(其中D代表Debug模式下,沒有D表示Release模式下),直接使用eXosip的幾個主要函數就可以創建一個基本的SIP軟電話模型。

  其主要流程爲:
  初始化eXosip庫-啓動事件監聽線程-向SIP Proxy註冊-向某SIP終端(電話號碼)發起呼叫-建立連接-結束連接

  初始化代碼:
        int ret = 0;
        ret = eXosip_init ();
        eXosip_set_user_agent("##YouToo0.1");
        if(0 != ret)
        {
                AfxMessageBox("Couldn't initialize eXosip!\n");
                return false;
        }
        ret = eXosip_listen_addr (IPPROTO_UDP, NULL, 0, AF_INET, 0);
        if(0 != ret)
        {
                eXosip_quit ();
                AfxMessageBox("Couldn't initialize transport layer!\n");
                return false;
        }

  啓動事件監聽線程:
        AfxBeginThread(sip_uac,(void *)this);

  向SIP Proxy註冊:
        eXosip_clear_authentication_info();
        eXosip_add_authentication_info(uname, uname, upwd, "md5", NULL); 
        real_send_register(30);  /* 自定義函數代碼請見源碼 */

  發起呼叫(構建假的SDP描述,實際軟電話使用它構建RTP媒體連接):
        osip_message_t *invite = NULL;  /* 呼叫發起消息體 */
        int i = eXosip_call_build_initial_invite (&invite, dest_call, source_call, NULL, "## YouToo test demo!");
        if (i != 0)
        {
                AfxMessageBox("Intial INVITE failed!\n");
        }
        char localip[128];
        eXosip_guess_localip (AF_INET, localip, 128);
        snprintf (tmp, 4096,
                "v=0\r\n"
                "o=josua 0 0 IN IP4 %s\r\n"
                "s=conversation\r\n"
                "c=IN IP4 %s\r\n"
                "t=0 0\r\n"
                "m=audio %s RTP/AVP 0 8 101\r\n"
                "a=rtpmap:0 PCMU/8000\r\n"
                "a=rtpmap:8 PCMA/8000\r\n"
                "a=rtpmap:101 telephone-event/8000\r\n"
                "a=fmtp:101 0-11\r\n", localip, localip, "9900");
        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 ();                               

  掛斷或取消通話:
        int ret;
        ret = eXosip_call_terminate(call_id, dialog_id); 
        if(0 != ret)
        {
                AfxMessageBox("hangup/terminate Failed!");
        }

  可以看到非常簡單,再藉助於oRTP和Mediastreamer開發庫,來快速爲我們的SIP軟電話增加RTP和與系統語音API接口交互及語音編碼功能,即可以快速開發出一個可用的SIP軟電話,關於oRTP和Mediastreamer的相關介紹不是本文重點,將在有空的時候考慮增加相應使用教程,文章前提到的地方可以下載基本可用的完整SIP軟電話的VC源碼工程文件供參考使用,完全CopyLeft,歡迎轉載,但請在轉載時註明作者信息,謝謝!

第二階段:
---------------------------------------------------
  得到了一個SIP軟電話模型後,我們可以根據軟電話的實際運行表現(結合用Ethereal抓包分析)來進行代碼的分析,以達到利用eXoSIP來輔助我們學習oSIP的最終目的(如要快速開發一個可用的SIP軟電話,請至前面提到的論壇去下載使用oRTP和Mediastreamer快速搭建的一個基本完整可用的SIP軟電話##YouToo 0.1版本的VC源碼工程文件作參考)。

  現在從eXosip的初始化函數開始入手,來分析oSIP的使用,這是第二階段,第三階段就是深入學習oSIP的源碼了,但大多數情況下應該沒有必要了,因爲在第二階段就有部分涉及到第三階段的工作了,而且oSIP的源碼也就大多是一些SIP數據的語法解析和狀態機的實現,能深入理解了SIP協議後,這些只是一種實現方式,沒必要完全去接受,而是可以用自己的方式和風格來實現一套,比如,更輕量化更有適用目的性的方式,oSIP則只起參考作用了。

  eXosip_init()是eXosip的初始化函數,我們來看看它的內部實現:
  首行是定義的 osip_t *osip,這在oSIP的官方手冊裏我們看到,所有使用oSIP的程序都要在最開始處聲明一個osip_t的指針,並使用osip_init(&osip)來初始化這個指針,銷燬這個資源使用osip_release(osip)即可。
  我們可以在代碼中看到很多OSIP_TRACE,這是調試輸出宏調用了函數osip_trace,可以用ENABLE_TRACE宏來打開調試以方便我們開發調試。
  其它就是很多的eXosip_t的全局變量eXosip的一些初始化操作,包括最上面的memset (&eXosip, 0, sizeof (eXosip))完全清空和下面的類似eXosip.user_agent = osip_strdup ("eXosip/" EXOSIP_VERSION)的exosip變量的一些初始值設置,其中有一個eXosip.j_stop_ua = 0應該是一個狀態機開關,後面可以看到很多代碼檢測這個變量來決定是否繼續流程處理,默認置成了0表示現在exosip的處理流程是就緒的,即ua是not stop的。
  
  osip_set_application_context (osip, &eXosip)是比較有意思的,它讓下面的eXosip_set_callbacks (osip)給osip設置大量的回調函數時,能讓osip能訪問到eXosip這個全局變量中設置的大量程序運行時交互的信息,相當於我們在VC下開啓一個線程時,給線程傳入的一個void指針指向我們的MFC應用程序的當前dialog對象實例,可以用void *osip_get_application_context (osip_t * osip)這個函數來取出指針來使用,不過好象exosip中並沒有用到它,可能是留給個人自已擴展的吧:)
  
  還能看到初始化代碼前面有一段WIN32平臺下的SOCK的初始化代碼,可以知道eXosip是用的原生的winsock api函數,也就是我們可能以前學過的用VC和WINAPI寫sock程序時(不是MFC),用到的那段SOCK初始代碼,還有一段有意思的代碼,就是jpipe()函數,它們返回的是一個管道,一個有2個整型數值的數組(一個進一個出),查看其代碼發現,非WIN32平臺是直接使用的pipe系統函數,而WIN32下則是用一對TCP的本地SOCK連接來模擬的管道,一個SOCK寫一個SOCK讀,這段代碼是比較有參考價值的:)
j = 50;
while (aport++ && j-- > 0)
{
  raddr.sin_port = htons ((short) aport);
  if (bind (s, (struct sockaddr *) &raddr, sizeof (raddr)) < 0)
  {
    OSIP_TRACE (osip_trace (__FILE__, __LINE__, OSIP_WARNING, NULL,
    "Failed to bind one local socket %i!\n", aport));
  } else
  break;
}
含義即,依次檢測50個端口,從static int aport = 10500;即10500~10550端口找出一個可用的本地端口來綁定listen模擬pipe的一對sock。
  eXosip_set_callbacks (osip)沒有什麼好看的,無非是和oSIP官方文檔介紹的一樣,設置一大堆的回調函數,關鍵是回調函數的實現,這也是許多初學者使用oSIP被卡殼的主要原因,不知道oSIP構建的程序是怎樣跑起來的,隨便選幾個回調函數看一下eXosip是怎樣實現的,有許多是形如下文的函數:
static void
cb_sndbye (int type, osip_transaction_t * tr, osip_message_t * sip)
{
  OSIP_TRACE (osip_trace
  (__FILE__, __LINE__, OSIP_INFO3, NULL, "cb_sndbye (id=%i)\r\n",
  tr->transactionid));
}
  即,只是打印一下調試,並沒有完整實現什麼功能,我們學習時,完全可以用相同的方法,定義一大堆回調函數,並不忙想怎麼完全實現,先都是隻打印一下調試信息,看具體的應用邏輯根據抓包測試分析和看調試看程序走到了哪一步,調用了哪一個回調,來明白具體回調函數要實現什麼用途,再來實現代碼就方便多了,當然,如果看透了RFC文檔,應該從字面就能知道各個回調函數的用途了,這是後話,不是誰都能快速完全看懂RFC的,所以我們要參考eXosip:)
  
  我們對其中的重要的回調函數進行逐個的分析:
  ---------------------------
  osip_set_cb_send_message (osip, &cb_snd_message) SIP消息發送回調函數
  這個函數可能是最重要的回調函數之一,消息發送,包括請求消息和迴應消息,一般情況下,狀態機的狀態就是由它控制的,發起一個消息初始化一個狀態機,迴應一個消息對狀態機修改,終結消息發送結束狀態機……
  看cb_snd_message的函數實現,要以發現,其主要代碼是對參數中的要發送的消息osip_message_t * sip進行分析,找出消息要發送的真實char *host,int port的值(這些參數可以省略,但要發送消息肯定需要host和port,所以要從sip中解析),最後根據sip中解析出的傳輸方式是TCP還是UDP選擇最終進行消息發送處理的函數cb_udp_snd_message,cb_tcp_snd_message處理(它們的參數一致,即本函數只是補全一些省略的參數並對消息進行合法性檢查)。
  **畢竟eXosip是一個通用的開發庫,它考慮了要支持TCP,UDP,TCPs,IPV4,IPV6,WIN32,*nix,WINCE等等多樣化的複雜環境,所以,我們可以略過我們暫時不需要的部分,比如,IPV6相關的代碼實現等。
  
  由於我們大多數情況下SIP是用的UDP,所以先來看一下cb_udp_snd_message的實現,它從全局變量exosip中獲取可用的sock,並盡最大能力解析出host和port(??難道前面的函數還不夠解析徹底??如最終仍無port信息則默認設置爲5060),使用osip_message_to_str (sip, &message, &length)函數將要發送的格式化的SIP消息轉換成能用SOCK傳輸的簡單數據併發送即完成消息發送,代碼中有許多複雜的環境探測和錯誤控制等等等等,我們可以暫時不用過多關注,可以繼續向下,結尾處有一個keeplive相關代碼,從代碼字面分析,可能是SIP的Register消息的自動重發相關代碼,可以在後面再細化分析。
  cb_tcp_snd_essage的函數實現要比上文的udp的實現簡單很多,主要是環境探測錯誤控制方面,因爲畢竟tcp是穩定連接的,對比一下代碼,可以看到主要流程還是將SIP消息轉換後,發送到從SIP消息中解析出的host和port對應的目標。
  
  看完兩個函數,可以知道,eXosip需要有兩個sock,是一個數組,0是給UDP用的,1是給TCP用的,要用SOCK當然要初始化,就是下文要介紹的eXosip的網絡相關的初始化了,上面的exosip_init可以看成是這個開發庫的系統初始化吧:) 
  至些,我們應該知道了oSIP開發的SIP應用程序的消息是從哪裏發出的吧,對了,就是從這個回調函數裏,所謂萬事開頭難,就象開發WIN32應用程序時,找到了WIN32程序的main函數入口下面的工作就好辦了,下面就都是爲一些事件消息開發對應的處理函數而已了:)

  osip_set_kill_transaction_callback 事務終結回調函數
  對應ICT,IST,NICT,NIST客戶/服務器註冊/非註冊事務狀態機的終結,主要是使用osip_remove_transaction (eXosip.j_osip, tr)將當前tr事務刪除,再加上一系列的清理工作,其中,NICT即客戶端的非Invite事務的清理比較複雜一些,要處理的內容也比較多,可以根據實際應用的情況進行有必要的清理工作:)

  cb_transport_error 傳輸失敗處理回調
  
對應於上面說到的四種事務狀態機,如果它們在處理時失敗,則在這時進行統一處理。
  從代碼可知,只是在NOTIFY,SUBSCRIBE,OPTION操作失敗才進行處理,其它錯誤可直接忽略。

  osip_set_message_callback 消息發送處理回調
  根據type不同,表示不同的消息發送狀態
  OSIP_XXX_AGAIN 重發相關消息
  OSIP_ICT_INVITE_SENT 發起呼叫
  OSIP_ICT_ACK_SENT ACK迴應
  OSIP_NICT_REGISTER_SENT 發起註冊
  OSIP_NICT_BYE_SENT BYE發出
  OSIP_NICT_CANCEL_SENT Cancel發出
  OSIP_NICT_INFO_SENT,OSIP_NICT_OPTIONS_SENT,OSIP_NICT_SUBSCRIBE_SENT,OSIP_NICT_NOTIFY_SENT,OSIP_NICT_UNKNOWN_REQUEST_SENT
  我們可以看到,eXosip沒有對它們作任何處理,我們可以根據自己需要,比如,重發2xx消息前記錄一下日誌之類的,擴展一下retransmission的處理方式,發起Invite前記錄一下通話日誌等等。

  OSIP_ICT_STATUS_1XX_RECEIVED uac收到1xx消息,一般是表示對端正在處理中,這時,主要是設置一下事務狀態機的狀態值,並對會話中的osip的一些參數根據返回值進行相應設置,裏面有許多條件判斷,但我們常用的一般是100,180,183的判斷而已,暫時可以忽略裏面複雜的判斷代碼。
  OSIP_ICT_STATUS_2XX_RECEIVED uac收到2xx消息,這裏主要跟蹤一下Register情況下的2xx,表示註冊成功,這時會更新一下exosip的註冊字段值,以便讓eXosip能自動維護uac的註冊,BYE的2xx迴應是終結消息,Invite的2xx迴應,則主要是初始化一下會話相關的數據,表示已成功建立連接。
  其它4xx,5xx,6xx則分別是對應的處理,根據實現情況進行概要的查看即可。
  report_event (je, sip)是代碼中用來進行事件處理的一個函數,跟蹤後發現,其最終是使用了我們上文提到的jpipe管道,以便在狀態機外實時觀測狀態機內的處理信息。
  
  OSIP_NIST_STATUS_XXX_SENT即對應於上面的uac的處理,這裏是uas的對應的消息處理,相比較於uac簡單一點。

  前面簡單介紹了一下大量的回調函數及它們的概要處理邏輯,可能會比較混亂,暫時不用管它,只需要記得一個大概的形象,知道一個SIP處理程序是通過osip_set_cb_send_message回調函數來實現真實地發送各種SIP消息,並且SIP的標準事務模型是由oSIP實現好了,我們只需要給不同的事務狀態設置不同的回調處理函數來處理事務,具體的狀態變化和內部邏輯不用管就可以了。

  下面來說一下消息處理回調函數用到的SOCK的初始化函數,即我們上面說的除了系統初始化外的網絡初始化函數eXosip_listen_addr:
  從上文知道了,系統將初始化兩個SOCK,一個UDP一個TCP,但查看代碼發現還有第三個,TCPs的,但好象還不能實用,現在不管它,代碼首先是根據傳輸是UDP還是TCP來設置對應的數組值,並且如果沒有提供IP地址和端口號,系統會自動取出本機網絡接口並創建可用的SOCK(http_port的方式暫不用考慮)。
  SOCK初始化後,如何開始SIP事務的呢?看到這個調用eXosip.j_thread = (void *) osip_thread_create (20000, _eXosip_thread, NULL),對的,這裏啓用了一個線程,即,eXosip是調用oSIP的線程函數(沒用系統提供的線程函數,是爲了跨平臺)進行事務處理的狀態機邏輯是在一個線程中處理的,這樣就明白了爲什麼一直沒能看到順序執行下來的程序啓動代碼了,接下去看,線程實際處理函數是_eXosip_thread,這裏面的代碼中,我們看到了上文提到的狀態機控制開關變量while (eXosip.j_stop_ua == 0),即,當j_stop_ua設置爲1時,osip_thread_exit ()結束事務處理即程序終結,再接下去看,_eXosip_execute是最終的處理函數了,而且它在程序未終結情況下是一直邏輯在執行,注意,要啓用oSIP的多線程宏OSIP_MT。
  
  看到_eXosip_execute的代碼中有很多時間函數和變量,仔細看,除去一些控制代碼,主要處理函數是eXosip_read_message (1, lower_tv.tv_sec, lower_tv.tv_usec),即取出消息,1表示只取出一條消息,其代碼量非常的大,但同樣的,其中也許多的控制代碼和錯誤檢測代碼,我們在查看時可以暫時忽略掉它們。
  eXosip_read_message讀取消息時,即沒有采用sock的block也沒有用非block方式,而是採用了select方式,具體應用可查詢fd_set相關文檔。
  根據jpipe_read (eXosip.j_socketctl, buf2, 499),我們可以估計,buf2中應該是保存的我們的控制管道的數據,具體作用至些還沒有表現出來,應該是用來反映一些狀態機內部的警示之類的信息,實際的SIP的處理的狀態機的數據是存放在buf中,使用_eXosip_recvfrom獲取的,獲取後sipevent = osip_parse (buf, i)解析,使用osip_find_transaction_and_add_event (eXosip.j_osip, sipevent)來查詢事件對應的事務狀態機,找到後就如同其註解所說明的,/* handled by oSIP ! */,即我們上文設置的那一大堆回調函數,至此,我們知道了整個SIP應用所處理的大概流程了。
  如果沒有找到事務狀態機呢?直接丟棄嗎?不是的,如果這是一個迴應消息,但沒有事務狀態機處理它,那它是一個錯誤的,要進行清理後才能丟棄,而如果是一個請求,那更不能丟棄了,因爲UAS事務狀態機要由它來啓動創建的(迴應消息表示本地發出了請求消息,即UAC行爲,事務狀態機應是由啓動UAC的代碼初始化啓動的),整個邏輯應該是很簡單的,但eXosip的實現代碼卻非常多,可見其花了非常多的精力在保證會話的穩定性和應付網絡複雜情況上,我們可以對其進行大量的精簡來構建滿足我們需求的代碼實現。
  先來看錯誤的迴應消息的處理函數eXosip_process_response_out_of_transaction,可以看到其代碼就是一大堆的賦值語句,XXX= NULL,即將一大堆的運行時變量清空,再調用osip_event_free清空事件,或者就是一些複雜的情況下,需要通過解析現在的運行時數據,從中分析出“可能”的正在等待迴應的對端,併發送相關終結通知消息等等,可以根據實際需要進行簡化。
  請求事件的處理eXosip_process_newrequest,首先是對事件進行探測,MSG_IS_INVITE、MSG_IS_ACK、MSG_IS_REQUEST……,對事件進行所屬狀態機分類,隨後使用_eXosip_transaction_init (&transaction,(osip_fsm_type_t) tx_type,eXosip.j_osip, evt->sip)根據探測結果進行狀態機初始化,實際調用的是osip_transaction_init,初始化後即將事件入狀態機osip_transaction_add_event (transaction, evt),由狀態機自動處理後調用相應回調函數處理邏輯了。當然,eXosip爲方便快速開發SIP終端應用,在下面又添加了許多自動化的處理代碼,來和我們在回調函數中設置的處理代碼相區分。

  線程調用的事件處理函數代碼最後是
if (eXosip.keep_alive > 0)
{
  _eXosip_keep_alive ();
}
  這段代碼印證了上文提到了,keep_alive是用來設置是否自動重新註冊,由_eXosip_keep_alive函數來實現自動將eXosip全局變量中保存的註冊消息解析後自動根據需要重新向SIP服務器發起Register註冊。
  同樣,因爲註冊消息發起是UAC的行爲,將它放在這裏,可以看出來所有事件消息的事務狀態機處理都是在這裏,只不過這裏只創建UAS的事務狀態機,UAC的事務狀態機的創建則要繼續到下面找了,從我們的YouToo軟電話代碼中可知,發起呼叫和發起註冊分別調用了eXosip_call_send_initial_invite,eXosip_register_send_register這兩個函數(另外用到的兩個build函數則是分別構建這兩個send函數要發送的SIP消息),查看這兩個函數可知,UAC的事務處理狀態機是在這裏進行初始化的。
  eXosip_register_send_register中可以看到是_eXosip_transaction_init (&transaction, NICT, eXosip.j_osip, reg)初始化UAC狀態機,實際也同UAS是調用的osip_transaction_init函數,同樣使用osip_transaction_add_event (transaction, sipevent)將事件入狀態機,狀態機隨後將自動處理調用相應回調函數處理邏輯了。
  另有osip_new_outgoing_sipmessage(reg),表示發送消息,到這裏,我們應該可以理解,真實的發送操作,是要到由狀態機處理後,調用了消息發送回調函數才真正地將註冊消息發送出去的。
  同註冊消息發送,它是NICT狀態機,呼叫消息的發送是ICT,由eXosip_call_send_initial_invite處理,_eXosip_transaction_init (&transaction, ICT, eXosip.j_osip, invite)初始化了狀態機,之前還有一個eXosip_call_init是用來初始化eXosip的一些參數的,暫時不管它,同樣osip_new_outgoing_sipmessage (invite)發送呼叫消息,但實際還是要狀態機處理後調用消息發送回調函數真實發送呼叫請求函數的,osip_transaction_add_event (transaction, sipevent)則標準地,將事件入狀態機,狀態機將能處理隨後的應用邏輯調用相應的回調函數了。

  好了,作了這麼多的分析,我們瞭解了eXosip是怎樣調用oSIP來形成被我能方便地再次調用的了,可以看到,爲了實現最大限度的跨平臺和兼容性,代碼中有大量的測試代碼,宏定義和錯誤再處理代碼,看起來非常吃力,但瞭解了其主要的調用框架:
  初始化,回調函數設置,UAC和UAS事務處理狀態機的啓動,事件處理流程等,就可以基本明白了oSIP各個函數的主要作用和正確的用法了,下一步,可以參考eXosip來針對某個應用,去除掉大量暫時用不到的代碼,來構建一個簡單的SIP軟電話和SIP服務器,來進一步深入oSIP學習應用了。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章