在Vovida的基礎上實現自己的SIP協議棧(一)

 在Vovida的基礎上實現自己的SIP協議棧(一)

盧政 2003/08/01

 

寫在前面的話

  不少通訊方面的同好已經讀了我在去年歲末撰寫的《如何用OpenH323開發自己的H.323協議棧》,大都給予了很高的評價,甚至可以說是好評如潮,說來慚愧,我只不過把十幾個人的工作進行了整理和歸納而已,事實上我自己的代碼只有很少的一部分(主要在H.245/H.235部分),後來很多朋友向我索要RTH323的測試版本一直未果,我在這裏說明一下,由於該軟件的使用和二次開發的權利已經被某歐洲公司所買斷,所以我已經無權發佈測試代碼,如有不便敬請大家原諒.

  在我們開發RTH323之際,我已經開始注意SIP協議了,並且根據RFC2543設計了不少實驗代碼,因爲當時的開發有一個H.323-SIP的翻譯網關的需求,不過後來這個計劃又取消了,也從這個時候開始我逐漸對SIP有了比較濃厚的興趣,只是並沒有做什麼實際的工作,僅僅初步瞭解了一下協議的整體構造.

  直到去年年底,我接觸了Vovida的開放原碼的SIP系統--Vocal以後,我決定開始系統的瞭解SIP的整個構造,我個人認爲Vocal是一個非常典型的SIP系統,裏面包含了所有構造電信級呼叫中心,區域網關以及中繼網關的所有內容,而且代碼清晰,比較易於改造;於是我花費了大概6個月的時間閱讀了整個Vocal系統的代碼,並在很多重要的地方做了標註.正好在今年的5月份,我的公司有構造一個大型的Voice/Video IP企業呼叫中心的計劃,我就把我對Vocal的研究成果提交給公司,方案得到通過,現在我的公司正在和國內某個知名大學合作準備在現有Vocal基礎上改造一個企業級的視頻電話呼叫平臺

  不過我個人而言,對這個計劃不是非常的滿意,由於時間和金錢上的限制,大部分的電訊級補充服務第一階段沒有實現的,只可能在第二階段去實現該計劃,時間可能要持續很長,所以我也很希望有其他的開發公司參與完成這個方面的開發,當然我們也可以爲有興趣的公司提供技術諮詢或者展開合作。

  我寫這本文的目的在於公佈我個人對Vocal這個開放原代碼系統的一些研究成果,當然裏面有很多地方沒有說得非常清楚,本身要用文字來闡述程序的設計思想就是一個非常困難的事情,所以我在裏面大量的繪製了很多圖表,來幫助讀者閱讀本文,這次公佈的是有關UA端的內容,後續在本文中將介紹Vocal系統中的Provision Server; Marshal Server; Redirect Server; HeartBeat Server:Policy Server:CDR Server:Network Manager:Feature Server:以及各種協議的Translator,讀者需要具備對SIP,H.323,MGCP,QoS的基本瞭解,以及對Java,XML, Call Processing Language,C++的知識,在以下章節中不會對這些基本的知識做太詳細的介紹。

  預計本文全部刊登完畢大概需近一年的時間,我希望有軟件開發公司來和我合作完成這篇文章或者是共同從事Vocal系統的二次開發工作。

  從這半年的閱讀Vocal的過程中我體會到Vocal系統的商業應用價值非常大,有人在論壇上和我討論:改造別人的應用平臺的難度和重新開發一個應用的難度哪個更大,雙方都各置一詞,就我個人覺得,大型的商業軟件如果按照開放原代碼來進行改造,特別是一些基礎平臺(例如操作系統),速度肯定是比從零開始要快很多的,RTH323就是一個成功的範例,這也是國內很多軟件廠商的基本運行模式,特別對於一些人員,技術,資金都不是非常充裕的公司這可能也是唯一的方案,但是開放原代碼的軟件大部分的效率非常低下,而且代碼冗餘,註釋比較少,可讀性非常差,甚至還有一些致命錯誤(Vocal中的Feature Server中就有這樣的錯誤,往往可以造成系統崩潰)所以這樣做的前提條件就是能把握住工作的重點,能及時發現並排除問題,這樣纔可能把開放原代碼改造成高效率的商業應用。

目 錄

一.楔子
二.H.323和SIP之間的差異
三.本文的主要內容
1.User Agent的簡介
2.UA部分主要程序部分的介紹
2.1 主程序:\SIP\UA\UA.cxx
2.2 創建一個User Agent的實體
2. 3 HeartLessProxy的創建
2.4 讓User Agent Run起來
2. 5 HeartLessProxy Run方法的實現
2. 5. 1 WorkerThread的Run方法
2.5.1.1 processSipEvent
2. 5. 1. 2 processUaDeviceEvent
2.5.1.3 processUaDigitEvent
2. 5. 2 SipThread的Run方法
2. 6 在User Agent中的四個重要實例的Run方法
2. 6. 1 媒體設備啓動
2.6.2 啓動RTP線程,用於對RTP/RTCP包的接收和發送管理;
2. 6. 3 合法用戶列表的獲取(Redirection Server專用)
2. 6. 4 監測線程:
2. 6. 5 自動呼叫
3.開始一個呼叫和等待對方呼叫:
3. 1 系統創建StateIdle狀態:
3. 2 開始一個呼叫:
3. 2. 1 OpStartCall主程序部分:
3. 2. 2 取得鍵盤的事件
3. 2. 3 狀態機(State)對各個操作(Operator)的處理過程:
3. 2. 4 開始一個呼叫所經歷的各種操作(Operator)
3. 2. 5 如何進入待機狀態(Idle狀態)
3. 2. 6 如何開始撥號並且開始一個呼叫:
3. 2. 6. 1 OpStartDialTone本地發送撥號音;
3. 2. 6. 2 OpAddDigit輸入電話號碼開始撥號:
3. 2. 6. 3 OpStopDialTone;
3. 2. 6. 4 OpInviteUrl建立一個INVITE消息並且發送到被叫;
3.2.7 進入Trying狀態
3. 2. 7. 1 OpStartTimer啓動每個事件的定時器:
3. 2. 7. 2 掛機事件的檢測機制
3. 2. 7. 3 OpStartRingbackTone向被叫進行鈴聲回放。
3.2.7.4 OpReDirect進行重定向服務的操作
3.2.7.5 授權檢查
3.2.7.6 OpFarEndAnswered處理接收到的OK迴應
3.2.7.7 在Vocal中如何實現RSVP資源預留協議
3.2.8用戶處於通話的StateInCall狀態:
3. 2. 8. 1 OpStartAudioDuplex主叫打開RTP通道
3. 2. 8. 2 處理RTP/RTCP包:
3.2.8.3 ACK消息的處理過程OpAck
3. 2. 8. 4 OpConfTargetOk多方會議檢測:
3.2.9 呼叫等待
3. 2. 9. 1 呼叫等待的詳細描述:
3. 2. 9. 2 操作之間存在的競爭
3. 2. 9. 3 呼叫中所涉及模塊介紹
3.3 等待對方的呼叫
3.3.1 OpRing等待對方的振鈴消息
3. 3. 2 OpStartRinging開始響鈴
3. 3. 3 OpRingingInvite處理又一個INVITE消息(呼叫等待)
3. 3. 4 OpAnswerCall被叫打開媒體通道開始通訊
3.3.5 回到StateInCall狀態
4.如何在改造現有的終端使之能傳遞視頻流。
4.1一個H.261+的Codec的基本構造
4. 2 增加視頻能力所需要做的工作

一.引言

  在各種的IP網絡多媒體通訊協議中,當前在市場上佔據主流位置的應當算是ITU的H.323和IETF的SIP兩個協議,目前在單純的話音市場,MGCP協議由於有大規模的用戶擴容能力應用也正呈現上升的趨勢,在2000年以前市場上佔主流的主要是H.323協議,然而SIP協議由於它避免了複雜的原語(ASN.1)分析,它的應用也在2000年以後也得到高速的普及,甚至有超過H.323的趨勢,成爲H.323最有力的競爭對手,當然,由於SIP協議的一些固有缺陷(下面將會詳細介紹這些缺陷),這種情況在未來的幾年可能不會出現,不過對於中等規模的多媒體通訊業務(每小時接入60000門)應用而言,採用SIP不失爲一個方便,快捷的開發策略。

  在各種的VOIP開放原碼的開發項目中,Vovida的基於SIP協議的VoCAL(Vovida Open CommunucAtion Library)不僅僅是在基於SIP的開放原代碼協議棧中是最爲龐大而且完善的,甚至在所有的原碼開放的多媒體通訊協議棧中同樣也是完善而且全面的,目前發佈的VOCAL1.4.0主要支持RFC2543,據稱在新版本的Vocal1.5.0將支持RFC3261協議;Vocal提供了基本的SIP呼叫控制和切換,例如:用戶註冊和登記,呼叫初始化,修改呼叫特性,或者重新定義呼叫特性,終止呼叫;以及一些用戶的基本呼叫特性:例如呼叫前轉,呼叫等待,呼叫阻塞,呼叫轉移,語音郵件等等。

  對於一個Vocal系統的用戶而言,Vocal同樣爲其提供了以下的一些能力:

1. 通過Web來配置整個Vocal系統;
2. 使用SNMP網管來檢測整個系統和呼叫組網的狀態;
3. 可以定義一個用戶的呼叫特性列表(相當於H.323系列中的H.450補充協議部分);
4. 授權檢查;
5. 廣告信息;
6. 基於RSVP的簡單QoS保證。

  同時,VOCAL也提供了詳細的文檔和SDK包以給用戶做二次開發,用戶可以在C++,以及Call Processing Language(CPL),Java Telephony API上開發自己的應用。

二.H.323和SIP之間的差異:

  這一節和本文的內容似乎沒有太大的關係,不過筆者認爲作爲市場主流的H.323和SIP之間需要做一個相關性的比較,以免使很多讀者在選擇協議的時候陷入歧途。

  雖然Vocal在SIP的應用上已經可以算是一個成功的實例,所以目前單純以Vocal作爲主體開發SIP的多媒體通訊系統從理論上是可行的,但是事實上目前所有的VOIP商業系統都是以H.323爲主體,兼容SIP協議,似乎還沒有一個廠商在實際上支持SIP(Cisco好象有類似的產品,不過應用前景似乎不是非常明朗),首先從市場上來看,H.323的系統已經有大量的投資,應用也非常普遍,SIP相對比較新,似乎不夠成熟;從市場上來看,越來越多的附加服務將成爲應用的主流,SIP領域相對來說比H.323能夠提供更多,更靈活的服務,而且在信令的互通性上有更加多的優勢,當然H.323也能夠保證其他解決方案之間的互用性;但是,目前MGCP協議已經得到了大量的工業支持,簡單的終端和更加複雜完善的呼叫控制方式讓它得到了更多的應用,很可能會成爲SIP的潛在競爭者。

  其次我們從ITU和IETF的條款保證上來看,IETF所制定的草案一開始都闡述一個讓人失望的觀點:本草案作爲參考資料是不合適的,除非在"制定和完善中",這樣在協議成熟和完全理解期間必然會把一些工作引入誤區,特別是某些協議被更新和升級期間。相對而言,作爲官方實體的ITU制定的協議一旦實施就不再輕易的改變,因此在發佈協議前,已經對協議作了很長時間的互通性測試。

  最後從技術上來看,SIP和H.323在技術實現上有很大的不同:

  a.開發速度:SIP當然的優於H.323協議太簡單了,不過如果H.323原語部分可以比較好的解析的話,事實上兩者開發速度相差不大。

  b.多播:在這個方面IETF具有優勢,有非常強大的應用經驗的,SIP已經設計在很多多播的骨幹網絡上,h.323v1,v2要使用多單播同時進行的方式才能完成,不過H.323V3版本多播的支持就已經非常不錯了。

  c.地址的運用上SIP使用Url上的機制非常靈活,這樣可以讓SIP以一種非常靈活的方式重定向到非SIP服務器上去,被另外一個SIP呼叫的SIP終端也能重定向到某個網頁或者是電子郵件地址。對於H.323而言,命名的機制就非常混亂了,從ASN.1的文件我們可以看到有h323-ID,url-ID,transport-ID,email-ID,partynumber等等。

  d.對於SIP而言,所有的消息都採用文本編碼,所以SIP消息非常簡單,這樣在開發的時候簡單的網絡檢測就可以調試,反觀H.323協議採用了PER或者BER的二進制編碼方式,信令不是非常直觀。

  e.系統資源的消耗上,SIP可以說是開銷驚人,每次服務器發出通告的時候,都需要建立一個監聽套接字,這樣的結果勢必造成大量的閒置套接字,假設在建立一個完整的Proxy/Register/RTP Gateway/三者和而爲一的園區出口網關的時候,資源上勢必會非常的緊張,這個是不能不予以考慮的問題。相反H.323在打開邏輯通道的情況下(OpenLogicalChannel消息)只建立一個套接字。

f. SIP沒有會議控制能力,所以僅僅只能做到點對點的媒體通訊,而H.323一開始就考慮了會議功能,其中還包含了H.332會議控制協議。(Vocal提供了一個Conferencing Server可以做普通的會議控制)。

g. 基於無線的網絡而言,H.323有很大優勢,由於信令採用了二進制編碼,所以比較適合手持設備實現,而SIP由於採用了文本方式就沒有這樣的能力。

三.本文的主要內容

  和RTH323的介紹一樣,我在下面將會盡量詳細的分析一下Vocal的整個原代碼,當然不可能做到完全系統地向大家展示Vocal的精妙之處,其實我自己對這個協議棧還是有相當多不瞭解的地方,希望大家能對我們的研究提出寶貴意見,後續的文章將會以連載的方式對Vocal進行介紹,順序如下:

3.1 Vovida User Agent:
3.2 Vovida Provision Server:
3.3 Marshal Server;
3.4 Redirect Server;
3.5 HeartBeat Server:
3.6 Policy Server:
3.7 CDR Server:
3.8 Network Manager:
3.9 Feature Server:
3.10 Translator Server:

  我們現在可以開始進入Vovida的第一個實體的介紹--User Agent


(點擊放大)

User Agent

1.User Agent的簡介:
  User Agent是描述一個普通的用戶終端,用戶代理,以下都簡稱UA端。本身來說UA端的代碼在Linux或者是Windows上都可以編譯運行。在Vocal中資料最詳細是User Agent的介紹了,有關UA描述的所有的代碼部分部分集中在\SIP\UA目錄下面,SIP的Stack軟件主要集中在\SIP\SIPSTACK,SIP消息和狀態的基類描述主要集中在\SIP\BASE;大家如果對SIP的狀態和命令不是非常熟悉的話,可以進入\SIP\UA\目錄下瀏覽以下的幾個線圖:

1. UaOverView.gif:
  對於UA中全部的主要類的關係描述,主要是展現了一些比較重要的基類。
2. UaSimpleStatesComplet.gif
  UA端的一個簡單的呼叫和應答的全部命令和狀態的交互示意圖。
3. Ua-States.gif
  UA狀態遷移的示意圖。
  另外在UA中我們會把基本的SIPStack的一些調用做一下詳細的介紹,所以篇幅可能會比較長。

下面是一些所使用到的SIP基本的類的介紹:
  HeartlessProxy :創建了一個容納呼叫的"容器",和SIP的消息堆棧,以及WorkThread和SipThread(用於對SIP消息的隊列處理),由HeartlessProxy::Run()方法調用這寫線程的Run方法,使他們啓動。該類的初始化是用一個Builder的基本類對它進行實例化。

  BasicProxy:由源自HeartlessProxy,它讓系統使用HeartBeat機制,在這個類中創建了三個HeartBeat類型的線程:HeartbeatTxThread,HeartbeatRxThread,HouseKeepingThread,不過暫時在Ua中都沒有應用到,一般是用在HeartBeat Server中(注:HeartBeat的機制就是指在Vocal的Server集羣通過多播端口向HeartBeat Server發送HeartBeat數據報,如果在指定的時間內沒有收到該數據報,那麼認爲該服務器處於Down狀態,由HeartbeatServer發送狀態消息到SNMP網管,同時啓動備份設備,這個機制類似於BGP,EIGRP協議中的後備路由方案)。

  SipThread:SipThread源自ThreadIf(Thread Interface),主要作用是接收並且在sipstack對所收到的SIP消息排隊,並且對接收的SipMsg(SIP消息)產生相應的Sip本地處理事件SipEvent,並且把他們放置在一個Fifo隊列中等待處理,SipThread::thread()是循環處理的線程。

  WorkerThread:和SipThread一樣,它也是源自ThreadIf,主要作用在於接受並且列隊處理上面SipThread收到的Sip本地處理事件SipEvent。

  Builder:是一個基本類,它由WorkerThread調用,在這個類中包含了針對用戶代理的CallContainer(包含所收到的各種呼叫信息)類的指針,從代碼上看Builder在HeartLessProxy/BasicProxy被創建的時候創建。

  Feature: Feature是一個狀態(State)容器,用於裝載各種狀態,從所有的狀態上來說,Feature是所有State的集合,Feature,State,Operator之間的關係是一種容器包含的關係,在Feature::process()中會調用State::process()來完成各個狀態的處理,它會返回下一個在容器內要處理的狀態(Sate)。.

  State: State是Operator的集合容器, State::process() 調用 Operator::process().和上面所描述的一樣Operator返回在容器內的下一個操作(Operator)。

  Operator : Operator則是一個基本類,Operator::process() 是一個虛函數,他需要其他的Operator子類對它進行實例化,它實質上也是描述各種操作的一個基本類。

  SipProxyEvent: 這個是一個基本類,用於描述各種SIP的事件信息,包括各種SIP消息和各種本地的設備消息,同時它包含了SIP消息的輸出隊列指針,使用的時候,可以把他載入輸出的FIFO中。

  SipEvent: 從SipProxyEvent上繼承,用於描述各種SIP消息,使用中當SipThread收到一個SipMsg的時候創建一個SipEvent,同樣SipEvent也會安裝在輸出的FIFO中(Outputfifo)。

  DeviceEvnet: 從SipProxyEvent上繼承,用於描述本地設備事件。

  TimerEvent:從SipProxyEvent上繼承,在設定的時鐘超時的時候產生該事件。

  CallContainer: 是CallInfo類的容器類。

  CallInfo: 是一個基本類,用於對呼叫的各種信息的描述集合,任何一個SipProxyEvent都包含了一個CallInfo。

  CallProcessingQueue: 一個用於裝載各種SipProxyEvent消息的FiFO隊列,在構造HeartLessProxy的它被創建,WorkerThread對他中間的消息進行排隊處理。

  FeatureThread:被Marshal Server調用用於發送接受subscribe/Notify消息對,得到合法的用戶的列表和呼叫特性(在後面介紹marshal和Feature Server的時候會詳細介紹)。

  ResGwDeviece: 所有設備的基本類,用於描述所有的設備,當然它中間的很多屬性需要具體的設備進行實例化。


(點擊放大)

下面介紹一下在UA端所使用到的基本類:

  UserAgent: 用於描述一個基本的用戶代理,它通過Run方法啓動以後打開了用戶端的RTP通道和設備媒體設備處理進程,並且啓動了HeartlessProxy的Run方法,開始啓動SIP消息處理線程。

  DeviceThread: 用於處理各種媒體設備,以及輸入輸出設備的線程,將收到的設備消息放在CallProcessingQueue隊列中。

  RTPThread: 用於處理RTP/RTCP會話。

  SubScribemanager: 用於MS端發送subscribe消息到RS端,以及接收Notify消息,處理用戶的呼叫特性列表。

  UaCallContainer: 繼承CallContainer類,主要在Ua端使用,定義了UA呼叫的各種信息的集合。

  SipTransceiver: SIP消息的發送和接收器的描述,包含有一個接收緩衝隊列和發送緩衝隊列。

  UaBuilder: Builder類在Ua端的描述,繼承了Builder的各種描述,這個是UA端的一個重要的類,它負責構建各種SIP消息事件,並且在這裏包含了UA的註冊和各種狀態機的初始化和實例化過程。

  UaConfiguration: CFG文件的描述類。

  UaCallInfo:屬於CallInfo的子類,包含了UA的各種工作狀態,可以讓不同狀態下的所有所有的Call操作(發送和接收),使用一個相同的狀態機。

  RegisterManager:用於跟蹤處理UA端的註冊。

  LoadGenThread:檢測線程,用於大量的呼叫時候對系統的檢測.


(點擊放大)

  其實這幾個線圖對UA的描述還是非常粗糙,如果大家對UA端的代碼沒有任何的閱讀的話,看他們是完全看不懂的,這些只能是一些內部開發人員專用文檔而已,下面我們開始對UA原代碼部分做詳細的介紹:

2.UA部分主要程序部分的介紹:

2.1 主程序:\SIP\UA\UA.cxx
  主程序部分主要是根據CFG文件中的定義建立本地的呼叫和等待接收進程。

main( int argc, char* argv[] )
{
… …
//是否把當前的UA設置爲守護進程。由CFG文件確定。
if ( UaCommandLine::instance()->getBoolOpt( "daemon" ) )
{
// TODO set cpLog to use syslog
assert( Daemon() >= 0 );
}
… …
if (UaCommandLine::instance( ) -> getIntOpt( "retransmit" ))
{
SipTransceiver::reTransOn();
}
else
{
SipTransceiver::reTransOff();
}

if (UaCommandLine::instance()->getIntOpt("retransinitial") != 500 ||
UaCommandLine::instance()->getIntOpt("retransmax") != 4000 )
{
SipTransceiver::setRetransTime(
UaCommandLine::instance()->getIntOpt("retransinitial"),
UaCommandLine::instance()->getIntOpt("retransmax")
);
}
//這裏是打開配置文件,我們在這裏使用的配置文件暫時定爲Ua1001.cfg(以下均相同)
const string cfgStr = UaCommandLine::instance()->getStringOpt( "cfgfile" );
FILE *cfgFile = fopen( cfgStr.c_str(), "r");
if ( cfgFile == 0 )
{
cerr << "can not open " << cfgStr << endl;
cerr << "Usage: " << argv[0] << " " << appUsage << endl;
exit( 0 );
}
else
{
fclose( cfgFile );
UaConfiguration::instance( cfgStr );
}
// if the config file has a log level, do something about it
if(UaConfiguration::instance()->getLogFilename() != "")
{
int retval = cpLogOpen(
UaConfiguration::instance()->getLogFilename().c_str());
if(retval == 0)
{
cpLog(LOG_ALERT, "Could not open %s",
UaConfiguration::instance()->getLogFilename().c_str());
}
}
//創建一個Uabuilder類,它在類狀態圖中的位置可以參考Uaoverview.gif中它的位置,
Sptr < UaBuilder > uaBuilder = new UaBuilder;
//在這裏創建一個用戶代理,我們要確定它在本地的偵聽和發送的端口,我們同樣從CFG文
//件中得到。
UserAgent ua( uaBuilder, Data( UaConfiguration::instance()->getLocalSipPort() ).convertInt() );
ua.run();
if ( UaCommandLine::instance()->getBoolOpt( "voicemail" ) )
{
//下面的兩個Run我們暫時不定義,在SNMP網管的時候將介紹HearterBeat的時候在做//詳細的闡述(所謂的HeartBeat技術是在多播口上定時發送heartbeat消息,以通知目前//端點的狀態,類似於BGP協議中的Hello消息。
#if defined(HAS_VOICEMAIL)
cpLog( LOG_DEBUG, "UA is running as voicemail front end" );
if( !UaCommandLine::instance()->getBoolOpt("no_heartbeat") )
{
#if defined(HAS_HEARTBEAT)
// Create and start heartbeat transmit thread
Sptr < HeartbeatTxThread > heartbeatThread
= new HeartbeatTxThread(sipPort,
500,
(const char*)"226.2.2.5",
6000);
heartbeatThread->run();
heartbeatThread->join();
#endif
}
#else
cpLog( LOG_ERR, "UA is NOT compiled to run as voicemail front end" );
#endif
}
//加入UA端到本地的運行隊列裏面
ua.join();
return 0;
} // ua main()

  看完了UA的主程序,我們可以看到,目前在UA端的部分的主要工作就是創建一個User Agent的實體UserAgent然後調用Run方法讓它運行,那麼我們看一下UserAgent這個關鍵類的一些基本情況:


(點擊放大)

2.2 創建一個User Agent的實體:

  在創建一個User Agent的實體的同時還有一個非常重要的實體HeartLesProxy,用於處理SIP的各種消息,並且開啓後臺工作線程;它的創建過程我們稍後做詳細介紹。

UserAgent::UserAgent( Sptr uaBuilder, unsigned short sipPort, Data appName )
: HeartLessProxy( uaBuilder, sipPort, appName )
{
const char* useDevice = "NULL"; // default to NULL_HARDWARE
… …
//用什麼聲音設備?在這裏我們暫時定爲Sound Card好了,如果要是用Quicknet來集成
//各種壓縮算法當然更好,不過價格也高了一些,下面都以Sound Card作爲標準介紹。
if( UaCommandLine::instance()->getBoolOpt( "soundcard" ) )
{
useDevice = "SOUNDCARD";
}
//LOAD GENERATION是一個檢測線程,可以在屏幕上打印各種命令的往復消息,以及系統的各//種統的狀態。
if(! UaConfiguration::instance()->getLoadGenOn())
{
// Create devices only if load gen is turned OFF.
//設備實例化,並且向帶入設備名稱以及所要處理的消息隊列,在實例化的過程中會//打開一個聲卡設備,並且將這個聲卡設備綁定兩個輸入的命令,輸出命令的FIFO
//隊列當中,(inputQ,和outputQ)詳細可以參看SoundCardDevice的建構函數,它//闡述瞭如何綁定這兩個隊列inputQ,outputQ,並且初始化ResGwDevice(所有的聲音//設備的父類)。
UaDevice::instance( useDevice, myCallProcessingQueue );
if( ! (strcmp(useDevice, "NONE") == 0) )
{
cpLog( LOG_DEBUG, "Create RTP Thread" );
//創建一個RTP包處理線程用於對RTP Packet的處理
rtpThread = new RtpThread( UaDevice::instance() );
assert( rtpThread != 0 );
}
//這裏調用了sound Card的設備消息處理線程的創立,用於處理與聲卡設備相關的各種消//息.
deviceThread = new DeviceThread( UaDevice::instance() );
assert( deviceThread != 0 );

cpLog( LOG_DEBUG, "Create SubscribeManager" );
Sptr subManager = new SubscribeManager( mySipStack );

if ( UaConfiguration::instance()->getSubscribeOn() )
{
cpLog( LOG_DEBUG, "Create Feature Thread" );
//這裏建立一個向FS發送消息的線程,關於這個部分的內容在Feature //Server的部分再做詳細介紹.
featureThread = new FeatureThread( subManager );
assert( featureThread != 0 );
uaBuilder->setSubscribeManager( subManager );
}
}
else
{
… …
}

// 是否打開重傳機制?
if (UaCommandLine::instance( ) -> getBoolOpt( "retransmit" ) )
{
SipTransceiver::reTransOn();
}
else
{
SipTransceiver::reTransOff();
}

// 定義接收代理服務器(Proxy)發出的消息所儲存的容器
myCallContainer = new UaCallContainer;
assert( myCallContainer != 0 );
//綁定容器到用戶端
uaBuilder->setCallContainer( myCallContainer );
//設置SIP的消息堆棧
uaBuilder->setSipStack( mySipStack );
//開始向註冊服務器發送註冊(Register)消息。
uaBuilder->startRegistration();
}
2.3 HeartLessProxy的創建:
HeartLessProxy
(
const Sptr < Builder > builder,
unsigned short defaultSipPort,
Data applName,
bool filterOn,
bool nat,
SipAppContext aContext
)
{
myCallContainer = new CallContainer;

myBuilder = builder;
myBuilder->setCallContainer(myCallContainer);
//這裏創建了一個消息的輸出隊列,在前面的創建一個UserAgent的實體的過程中已經
//闡述過會把它綁定到相關的設備上去
myCallProcessingQueue = new Fifo < Sptr < SipProxyEvent > >;
//這裏創建一個WorkThread線程在該線程中的myBuilder->process(nextEvent)
//檢查消息隊列myFifo中的返回消息(調用Uabuilder->process進行檢查),從而
//得到返回的消息。
//很明顯,這裏新創建了一個 myWorkerThread工作線程,我們等一下就會看到如何把它Run
//起來
myWorkerThread = new WorkerThread(myCallProcessingQueue, myBuilder);

//創建一個SIP消息收發器的實體,在這個實體的構建裏主要是把收發SIP消息的TCP/UDP
//的收發通道創建(SipUdpConnection和SipUdpConnection)。同時會構造一個SNMP的
//SipAgent.他的主要作用是向SNMP網關發送SNMP消息,描述網絡的運行狀態
if ( filterOn == true )
{
mySipStack = new SipTransceiverFilter(applName, defaultSipPort, nat, aContext);
}
else
{
mySipStack = new SipTransceiver(applName, defaultSipPort, nat, aContext);
}
myBuilder->setSipStack(mySipStack);
//創建一個SIP消息的解析線程 。
mySipThread = new SipThread(mySipStack, myCallProcessingQueue);

… …
}

2.4 讓User Agent Run起來:

  構建User Agent的工作已經完畢,現在應該讓調用它的Run方法了;從下面的程序可以看到,Run方法的調用,讓整個程序進入一種"Idle"的狀態,等待命令輸入和狀態的產生,這個過程我們可以看到在Ua.CXX的Main程序中調用(ua.run())。

Void UserAgent::run()
{
//調用HeartLessProxy的Run方法,稍後做詳細的介紹
HeartLessProxy::run();
… …
deviceThread->run(); //調用SoundcardDevice::hardwareMain(0)
… …
rtpThread->run();//調用SoundCardDevice::processRTP()進行RTP流的處理
… …
//在這裏向FS發送隊列(myQ = new Fifo < Sptr < SubscribeMsg > >)中的各種消息,不
//過在Ua1001.cfg中,參數Subscribe_on設置爲OFF所以,本章我們對FS暫不予以分析,
//在最後一章詳細分析FS的時候回着重分析它.
featureThread->run();//調用subscribeManager::subscribeMain()
… …
//後臺監測線程開啓.
loadGenThread->run();//調用LoadGenMonitor::lgMain()

// User TimerEvent to kick start the load generator
… …
} // UserAgent::run

(未完待續)
發佈了14 篇原創文章 · 獲贊 1 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章