首頁(yè) >> 新聞

在Vovida的基礎(chǔ)上實(shí)現(xiàn)自己的SIP協(xié)議棧(一)

盧政 2003/08/01

寫在前面的話

  不少通訊方面的同好已經(jīng)讀了我在去年歲末撰寫的《如何用OpenH323開(kāi)發(fā)自己的H.323協(xié)議棧》,大都給予了很高的評(píng)價(jià),甚至可以說(shuō)是好評(píng)如潮,說(shuō)來(lái)慚愧,我只不過(guò)把十幾個(gè)人的工作進(jìn)行了整理和歸納而已,事實(shí)上我自己的代碼只有很少的一部分(主要在H.245/H.235部分),后來(lái)很多朋友向我索要RTH323的測(cè)試版本一直未果,我在這里說(shuō)明一下,由于該軟件的使用和二次開(kāi)發(fā)的權(quán)利已經(jīng)被某歐洲公司所買斷,所以我已經(jīng)無(wú)權(quán)發(fā)布測(cè)試代碼,如有不便敬請(qǐng)大家原諒.

  在我們開(kāi)發(fā)RTH323之際,我已經(jīng)開(kāi)始注意SIP協(xié)議了,并且根據(jù)RFC2543設(shè)計(jì)了不少實(shí)驗(yàn)代碼,因?yàn)楫?dāng)時(shí)的開(kāi)發(fā)有一個(gè)H.323-SIP的翻譯網(wǎng)關(guān)的需求,不過(guò)后來(lái)這個(gè)計(jì)劃又取消了,也從這個(gè)時(shí)候開(kāi)始我逐漸對(duì)SIP有了比較濃厚的興趣,只是并沒(méi)有做什么實(shí)際的工作,僅僅初步了解了一下協(xié)議的整體構(gòu)造.

  直到去年年底,我接觸了Vovida的開(kāi)放原碼的SIP系統(tǒng)--Vocal以后,我決定開(kāi)始系統(tǒng)的了解SIP的整個(gè)構(gòu)造,我個(gè)人認(rèn)為Vocal是一個(gè)非常典型的SIP系統(tǒng),里面包含了所有構(gòu)造電信級(jí)呼叫中心,區(qū)域網(wǎng)關(guān)以及中繼網(wǎng)關(guān)的所有內(nèi)容,而且代碼清晰,比較易于改造;于是我花費(fèi)了大概6個(gè)月的時(shí)間閱讀了整個(gè)Vocal系統(tǒng)的代碼,并在很多重要的地方做了標(biāo)注.正好在今年的5月份,我的公司有構(gòu)造一個(gè)大型的Voice/Video IP企業(yè)呼叫中心的計(jì)劃,我就把我對(duì)Vocal的研究成果提交給公司,方案得到通過(guò),現(xiàn)在我的公司正在和國(guó)內(nèi)某個(gè)知名大學(xué)合作準(zhǔn)備在現(xiàn)有Vocal基礎(chǔ)上改造一個(gè)企業(yè)級(jí)的視頻電話呼叫平臺(tái)

  不過(guò)我個(gè)人而言,對(duì)這個(gè)計(jì)劃不是非常的滿意,由于時(shí)間和金錢上的限制,大部分的電訊級(jí)補(bǔ)充服務(wù)第一階段沒(méi)有實(shí)現(xiàn)的,只可能在第二階段去實(shí)現(xiàn)該計(jì)劃,時(shí)間可能要持續(xù)很長(zhǎng),所以我也很希望有其他的開(kāi)發(fā)公司參與完成這個(gè)方面的開(kāi)發(fā),當(dāng)然我們也可以為有興趣的公司提供技術(shù)咨詢或者展開(kāi)合作。

  我寫這本文的目的在于公布我個(gè)人對(duì)Vocal這個(gè)開(kāi)放原代碼系統(tǒng)的一些研究成果,當(dāng)然里面有很多地方?jīng)]有說(shuō)得非常清楚,本身要用文字來(lái)闡述程序的設(shè)計(jì)思想就是一個(gè)非常困難的事情,所以我在里面大量的繪制了很多圖表,來(lái)幫助讀者閱讀本文,這次公布的是有關(guān)UA端的內(nèi)容,后續(xù)在本文中將介紹Vocal系統(tǒng)中的Provision Server; Marshal Server; Redirect Server; HeartBeat Server:Policy Server:CDR Server:Network Manager:Feature Server:以及各種協(xié)議的Translator,讀者需要具備對(duì)SIP,H.323,MGCP,QoS的基本了解,以及對(duì)Java,XML, Call Processing Language,C++的知識(shí),在以下章節(jié)中不會(huì)對(duì)這些基本的知識(shí)做太詳細(xì)的介紹。

  預(yù)計(jì)本文全部刊登完畢大概需近一年的時(shí)間,我希望有軟件開(kāi)發(fā)公司來(lái)和我合作完成這篇文章或者是共同從事Vocal系統(tǒng)的二次開(kāi)發(fā)工作。

  從這半年的閱讀Vocal的過(guò)程中我體會(huì)到Vocal系統(tǒng)的商業(yè)應(yīng)用價(jià)值非常大,有人在論壇上和我討論:改造別人的應(yīng)用平臺(tái)的難度和重新開(kāi)發(fā)一個(gè)應(yīng)用的難度哪個(gè)更大,雙方都各置一詞,就我個(gè)人覺(jué)得,大型的商業(yè)軟件如果按照開(kāi)放原代碼來(lái)進(jìn)行改造,特別是一些基礎(chǔ)平臺(tái)(例如操作系統(tǒng)),速度肯定是比從零開(kāi)始要快很多的,RTH323就是一個(gè)成功的范例,這也是國(guó)內(nèi)很多軟件廠商的基本運(yùn)行模式,特別對(duì)于一些人員,技術(shù),資金都不是非常充裕的公司這可能也是唯一的方案,但是開(kāi)放原代碼的軟件大部分的效率非常低下,而且代碼冗余,注釋比較少,可讀性非常差,甚至還有一些致命錯(cuò)誤(Vocal中的Feature Server中就有這樣的錯(cuò)誤,往往可以造成系統(tǒng)崩潰)所以這樣做的前提條件就是能把握住工作的重點(diǎn),能及時(shí)發(fā)現(xiàn)并排除問(wèn)題,這樣才可能把開(kāi)放原代碼改造成高效率的商業(yè)應(yīng)用。

目 錄

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

一.引言

  在各種的IP網(wǎng)絡(luò)多媒體通訊協(xié)議中,當(dāng)前在市場(chǎng)上占據(jù)主流位置的應(yīng)當(dāng)算是ITU的H.323和IETF的SIP兩個(gè)協(xié)議,目前在單純的話音市場(chǎng),MGCP協(xié)議由于有大規(guī)模的用戶擴(kuò)容能力應(yīng)用也正呈現(xiàn)上升的趨勢(shì),在2000年以前市場(chǎng)上占主流的主要是H.323協(xié)議,然而SIP協(xié)議由于它避免了復(fù)雜的原語(yǔ)(ASN.1)分析,它的應(yīng)用也在2000年以后也得到高速的普及,甚至有超過(guò)H.323的趨勢(shì),成為H.323最有力的競(jìng)爭(zhēng)對(duì)手,當(dāng)然,由于SIP協(xié)議的一些固有缺陷(下面將會(huì)詳細(xì)介紹這些缺陷),這種情況在未來(lái)的幾年可能不會(huì)出現(xiàn),不過(guò)對(duì)于中等規(guī)模的多媒體通訊業(yè)務(wù)(每小時(shí)接入60000門)應(yīng)用而言,采用SIP不失為一個(gè)方便,快捷的開(kāi)發(fā)策略。

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

  對(duì)于一個(gè)Vocal系統(tǒng)的用戶而言,Vocal同樣為其提供了以下的一些能力:

1. 通過(guò)Web來(lái)配置整個(gè)Vocal系統(tǒng);
2. 使用SNMP網(wǎng)管來(lái)檢測(cè)整個(gè)系統(tǒng)和呼叫組網(wǎng)的狀態(tài);
3. 可以定義一個(gè)用戶的呼叫特性列表(相當(dāng)于H.323系列中的H.450補(bǔ)充協(xié)議部分);
4. 授權(quán)檢查;
5. 廣告信息;
6. 基于RSVP的簡(jiǎn)單QoS保證。

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

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

  這一節(jié)和本文的內(nèi)容似乎沒(méi)有太大的關(guān)系,不過(guò)筆者認(rèn)為作為市場(chǎng)主流的H.323和SIP之間需要做一個(gè)相關(guān)性的比較,以免使很多讀者在選擇協(xié)議的時(shí)候陷入歧途。

  雖然Vocal在SIP的應(yīng)用上已經(jīng)可以算是一個(gè)成功的實(shí)例,所以目前單純以Vocal作為主體開(kāi)發(fā)SIP的多媒體通訊系統(tǒng)從理論上是可行的,但是事實(shí)上目前所有的VOIP商業(yè)系統(tǒng)都是以H.323為主體,兼容SIP協(xié)議,似乎還沒(méi)有一個(gè)廠商在實(shí)際上支持SIP(Cisco好象有類似的產(chǎn)品,不過(guò)應(yīng)用前景似乎不是非常明朗),首先從市場(chǎng)上來(lái)看,H.323的系統(tǒng)已經(jīng)有大量的投資,應(yīng)用也非常普遍,SIP相對(duì)比較新,似乎不夠成熟;從市場(chǎng)上來(lái)看,越來(lái)越多的附加服務(wù)將成為應(yīng)用的主流,SIP領(lǐng)域相對(duì)來(lái)說(shuō)比H.323能夠提供更多,更靈活的服務(wù),而且在信令的互通性上有更加多的優(yōu)勢(shì),當(dāng)然H.323也能夠保證其他解決方案之間的互用性;但是,目前MGCP協(xié)議已經(jīng)得到了大量的工業(yè)支持,簡(jiǎn)單的終端和更加復(fù)雜完善的呼叫控制方式讓它得到了更多的應(yīng)用,很可能會(huì)成為SIP的潛在競(jìng)爭(zhēng)者。

  其次我們從ITU和IETF的條款保證上來(lái)看,IETF所制定的草案一開(kāi)始都闡述一個(gè)讓人失望的觀點(diǎn):本草案作為參考資料是不合適的,除非在"制定和完善中",這樣在協(xié)議成熟和完全理解期間必然會(huì)把一些工作引入誤區(qū),特別是某些協(xié)議被更新和升級(jí)期間。相對(duì)而言,作為官方實(shí)體的ITU制定的協(xié)議一旦實(shí)施就不再輕易的改變,因此在發(fā)布協(xié)議前,已經(jīng)對(duì)協(xié)議作了很長(zhǎng)時(shí)間的互通性測(cè)試。

  最后從技術(shù)上來(lái)看,SIP和H.323在技術(shù)實(shí)現(xiàn)上有很大的不同:

  a.開(kāi)發(fā)速度:SIP當(dāng)然的優(yōu)于H.323協(xié)議太簡(jiǎn)單了,不過(guò)如果H.323原語(yǔ)部分可以比較好的解析的話,事實(shí)上兩者開(kāi)發(fā)速度相差不大。

  b.多播:在這個(gè)方面IETF具有優(yōu)勢(shì),有非常強(qiáng)大的應(yīng)用經(jīng)驗(yàn)的,SIP已經(jīng)設(shè)計(jì)在很多多播的骨干網(wǎng)絡(luò)上,h.323v1,v2要使用多單播同時(shí)進(jìn)行的方式才能完成,不過(guò)H.323V3版本多播的支持就已經(jīng)非常不錯(cuò)了。

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

  d.對(duì)于SIP而言,所有的消息都采用文本編碼,所以SIP消息非常簡(jiǎn)單,這樣在開(kāi)發(fā)的時(shí)候簡(jiǎn)單的網(wǎng)絡(luò)檢測(cè)就可以調(diào)試,反觀H.323協(xié)議采用了PER或者BER的二進(jìn)制編碼方式,信令不是非常直觀。

  e.系統(tǒng)資源的消耗上,SIP可以說(shuō)是開(kāi)銷驚人,每次服務(wù)器發(fā)出通告的時(shí)候,都需要建立一個(gè)監(jiān)聽(tīng)套接字,這樣的結(jié)果勢(shì)必造成大量的閑置套接字,假設(shè)在建立一個(gè)完整的Proxy/Register/RTP Gateway/三者和而為一的園區(qū)出口網(wǎng)關(guān)的時(shí)候,資源上勢(shì)必會(huì)非常的緊張,這個(gè)是不能不予以考慮的問(wèn)題。相反H.323在打開(kāi)邏輯通道的情況下(OpenLogicalChannel消息)只建立一個(gè)套接字。

f. SIP沒(méi)有會(huì)議控制能力,所以僅僅只能做到點(diǎn)對(duì)點(diǎn)的媒體通訊,而H.323一開(kāi)始就考慮了會(huì)議功能,其中還包含了H.332會(huì)議控制協(xié)議。(Vocal提供了一個(gè)Conferencing Server可以做普通的會(huì)議控制)。

g. 基于無(wú)線的網(wǎng)絡(luò)而言,H.323有很大優(yōu)勢(shì),由于信令采用了二進(jìn)制編碼,所以比較適合手持設(shè)備實(shí)現(xiàn),而SIP由于采用了文本方式就沒(méi)有這樣的能力。

三.本文的主要內(nèi)容

  和RTH323的介紹一樣,我在下面將會(huì)盡量詳細(xì)的分析一下Vocal的整個(gè)原代碼,當(dāng)然不可能做到完全系統(tǒng)地向大家展示Vocal的精妙之處,其實(shí)我自己對(duì)這個(gè)協(xié)議棧還是有相當(dāng)多不了解的地方,希望大家能對(duì)我們的研究提出寶貴意見(jiàn),后續(xù)的文章將會(huì)以連載的方式對(duì)Vocal進(jìn)行介紹,順序如下:

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:

  我們現(xiàn)在可以開(kāi)始進(jìn)入Vovida的第一個(gè)實(shí)體的介紹--User Agent


(點(diǎn)擊放大)

User Agent

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

1. UaOverView.gif:
  對(duì)于UA中全部的主要類的關(guān)系描述,主要是展現(xiàn)了一些比較重要的基類。
2. UaSimpleStatesComplet.gif
  UA端的一個(gè)簡(jiǎn)單的呼叫和應(yīng)答的全部命令和狀態(tài)的交互示意圖。
3. Ua-States.gif
  UA狀態(tài)遷移的示意圖。
  另外在UA中我們會(huì)把基本的SIPStack的一些調(diào)用做一下詳細(xì)的介紹,所以篇幅可能會(huì)比較長(zhǎng)。

下面是一些所使用到的SIP基本的類的介紹:
  HeartlessProxy :創(chuàng)建了一個(gè)容納呼叫的"容器",和SIP的消息堆棧,以及WorkThread和SipThread(用于對(duì)SIP消息的隊(duì)列處理),由HeartlessProxy::Run()方法調(diào)用這寫線程的Run方法,使他們啟動(dòng)。該類的初始化是用一個(gè)Builder的基本類對(duì)它進(jìn)行實(shí)例化。

  BasicProxy:由源自HeartlessProxy,它讓系統(tǒng)使用HeartBeat機(jī)制,在這個(gè)類中創(chuàng)建了三個(gè)HeartBeat類型的線程:HeartbeatTxThread,HeartbeatRxThread,HouseKeepingThread,不過(guò)暫時(shí)在Ua中都沒(méi)有應(yīng)用到,一般是用在HeartBeat Server中(注:HeartBeat的機(jī)制就是指在Vocal的Server集群通過(guò)多播端口向HeartBeat Server發(fā)送HeartBeat數(shù)據(jù)報(bào),如果在指定的時(shí)間內(nèi)沒(méi)有收到該數(shù)據(jù)報(bào),那么認(rèn)為該服務(wù)器處于Down狀態(tài),由HeartbeatServer發(fā)送狀態(tài)消息到SNMP網(wǎng)管,同時(shí)啟動(dòng)備份設(shè)備,這個(gè)機(jī)制類似于BGP,EIGRP協(xié)議中的后備路由方案)。

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

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

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

  Feature: Feature是一個(gè)狀態(tài)(State)容器,用于裝載各種狀態(tài),從所有的狀態(tài)上來(lái)說(shuō),F(xiàn)eature是所有State的集合,F(xiàn)eature,State,Operator之間的關(guān)系是一種容器包含的關(guān)系,在Feature::process()中會(huì)調(diào)用State::process()來(lái)完成各個(gè)狀態(tài)的處理,它會(huì)返回下一個(gè)在容器內(nèi)要處理的狀態(tài)(Sate)。.

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

  Operator : Operator則是一個(gè)基本類,Operator::process() 是一個(gè)虛函數(shù),他需要其他的Operator子類對(duì)它進(jìn)行實(shí)例化,它實(shí)質(zhì)上也是描述各種操作的一個(gè)基本類。

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

  SipEvent: 從SipProxyEvent上繼承,用于描述各種SIP消息,使用中當(dāng)SipThread收到一個(gè)SipMsg的時(shí)候創(chuàng)建一個(gè)SipEvent,同樣SipEvent也會(huì)安裝在輸出的FIFO中(Outputfifo)。

  DeviceEvnet: 從SipProxyEvent上繼承,用于描述本地設(shè)備事件。

  TimerEvent:從SipProxyEvent上繼承,在設(shè)定的時(shí)鐘超時(shí)的時(shí)候產(chǎn)生該事件。

  CallContainer: 是CallInfo類的容器類。

  CallInfo: 是一個(gè)基本類,用于對(duì)呼叫的各種信息的描述集合,任何一個(gè)SipProxyEvent都包含了一個(gè)CallInfo。

  CallProcessingQueue: 一個(gè)用于裝載各種SipProxyEvent消息的FiFO隊(duì)列,在構(gòu)造HeartLessProxy的它被創(chuàng)建,WorkerThread對(duì)他中間的消息進(jìn)行排隊(duì)處理。

  FeatureThread:被Marshal Server調(diào)用用于發(fā)送接受subscribe/Notify消息對(duì),得到合法的用戶的列表和呼叫特性(在后面介紹marshal和Feature Server的時(shí)候會(huì)詳細(xì)介紹)。

  ResGwDeviece: 所有設(shè)備的基本類,用于描述所有的設(shè)備,當(dāng)然它中間的很多屬性需要具體的設(shè)備進(jìn)行實(shí)例化。


(點(diǎn)擊放大)

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

  UserAgent: 用于描述一個(gè)基本的用戶代理,它通過(guò)Run方法啟動(dòng)以后打開(kāi)了用戶端的RTP通道和設(shè)備媒體設(shè)備處理進(jìn)程,并且啟動(dòng)了HeartlessProxy的Run方法,開(kāi)始啟動(dòng)SIP消息處理線程。

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

  RTPThread: 用于處理RTP/RTCP會(huì)話。

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

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

  SipTransceiver: SIP消息的發(fā)送和接收器的描述,包含有一個(gè)接收緩沖隊(duì)列和發(fā)送緩沖隊(duì)列。

  UaBuilder: Builder類在Ua端的描述,繼承了Builder的各種描述,這個(gè)是UA端的一個(gè)重要的類,它負(fù)責(zé)構(gòu)建各種SIP消息事件,并且在這里包含了UA的注冊(cè)和各種狀態(tài)機(jī)的初始化和實(shí)例化過(guò)程。

  UaConfiguration: CFG文件的描述類。

  UaCallInfo:屬于CallInfo的子類,包含了UA的各種工作狀態(tài),可以讓不同狀態(tài)下的所有所有的Call操作(發(fā)送和接收),使用一個(gè)相同的狀態(tài)機(jī)。

  RegisterManager:用于跟蹤處理UA端的注冊(cè)。

  LoadGenThread:檢測(cè)線程,用于大量的呼叫時(shí)候?qū)ο到y(tǒng)的檢測(cè).


(點(diǎn)擊放大)

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

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

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

main( int argc, char* argv[] )
{
… …
//是否把當(dāng)前的UA設(shè)置為守護(hù)進(jìn)程。由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")
);
}
//這里是打開(kāi)配置文件,我們?cè)谶@里使用的配置文件暫時(shí)定為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());
}
}
//創(chuàng)建一個(gè)Uabuilder類,它在類狀態(tài)圖中的位置可以參考Uaoverview.gif中它的位置,
Sptr < UaBuilder > uaBuilder = new UaBuilder;
//在這里創(chuàng)建一個(gè)用戶代理,我們要確定它在本地的偵聽(tīng)和發(fā)送的端口,我們同樣從CFG文
//件中得到。
UserAgent ua( uaBuilder, Data( UaConfiguration::instance()->getLocalSipPort() ).convertInt() );
ua.run();
if ( UaCommandLine::instance()->getBoolOpt( "voicemail" ) )
{
//下面的兩個(gè)Run我們暫時(shí)不定義,在SNMP網(wǎng)管的時(shí)候?qū)⒔榻BHearterBeat的時(shí)候在做//詳細(xì)的闡述(所謂的HeartBeat技術(shù)是在多播口上定時(shí)發(fā)送heartbeat消息,以通知目前//端點(diǎn)的狀態(tài),類似于BGP協(xié)議中的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
}
//加入U(xiǎn)A端到本地的運(yùn)行隊(duì)列里面
ua.join();
return 0;
} // ua main()

  看完了UA的主程序,我們可以看到,目前在UA端的部分的主要工作就是創(chuàng)建一個(gè)User Agent的實(shí)體UserAgent然后調(diào)用Run方法讓它運(yùn)行,那么我們看一下UserAgent這個(gè)關(guān)鍵類的一些基本情況:


(點(diǎn)擊放大)

2.2 創(chuàng)建一個(gè)User Agent的實(shí)體:

  在創(chuàng)建一個(gè)User Agent的實(shí)體的同時(shí)還有一個(gè)非常重要的實(shí)體HeartLesProxy,用于處理SIP的各種消息,并且開(kāi)啟后臺(tái)工作線程;它的創(chuàng)建過(guò)程我們稍后做詳細(xì)介紹。

UserAgent::UserAgent( Sptr uaBuilder, unsigned short sipPort, Data appName )
: HeartLessProxy( uaBuilder, sipPort, appName )
{
const char* useDevice = "NULL"; // default to NULL_HARDWARE
… …
//用什么聲音設(shè)備?在這里我們暫時(shí)定為Sound Card好了,如果要是用Quicknet來(lái)集成
//各種壓縮算法當(dāng)然更好,不過(guò)價(jià)格也高了一些,下面都以Sound Card作為標(biāo)準(zhǔn)介紹。
if( UaCommandLine::instance()->getBoolOpt( "soundcard" ) )
{
useDevice = "SOUNDCARD";
}
//LOAD GENERATION是一個(gè)檢測(cè)線程,可以在屏幕上打印各種命令的往復(fù)消息,以及系統(tǒng)的各//種統(tǒng)的狀態(tài)。
if(! UaConfiguration::instance()->getLoadGenOn())
{
// Create devices only if load gen is turned OFF.
//設(shè)備實(shí)例化,并且向帶入設(shè)備名稱以及所要處理的消息隊(duì)列,在實(shí)例化的過(guò)程中會(huì)//打開(kāi)一個(gè)聲卡設(shè)備,并且將這個(gè)聲卡設(shè)備綁定兩個(gè)輸入的命令,輸出命令的FIFO
//隊(duì)列當(dāng)中,(inputQ,和outputQ)詳細(xì)可以參看SoundCardDevice的建構(gòu)函數(shù),它//闡述了如何綁定這兩個(gè)隊(duì)列inputQ,outputQ,并且初始化ResGwDevice(所有的聲音//設(shè)備的父類)。
UaDevice::instance( useDevice, myCallProcessingQueue );
if( ! (strcmp(useDevice, "NONE") == 0) )
{
cpLog( LOG_DEBUG, "Create RTP Thread" );
//創(chuàng)建一個(gè)RTP包處理線程用于對(duì)RTP Packet的處理
rtpThread = new RtpThread( UaDevice::instance() );
assert( rtpThread != 0 );
}
//這里調(diào)用了sound Card的設(shè)備消息處理線程的創(chuàng)立,用于處理與聲卡設(shè)備相關(guān)的各種消//息.
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" );
//這里建立一個(gè)向FS發(fā)送消息的線程,關(guān)于這個(gè)部分的內(nèi)容在Feature //Server的部分再做詳細(xì)介紹.
featureThread = new FeatureThread( subManager );
assert( featureThread != 0 );
uaBuilder->setSubscribeManager( subManager );
}
}
else
{
… …
}

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

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

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

//創(chuàng)建一個(gè)SIP消息收發(fā)器的實(shí)體,在這個(gè)實(shí)體的構(gòu)建里主要是把收發(fā)SIP消息的TCP/UDP
//的收發(fā)通道創(chuàng)建(SipUdpConnection和SipUdpConnection)。同時(shí)會(huì)構(gòu)造一個(gè)SNMP的
//SipAgent.他的主要作用是向SNMP網(wǎng)關(guān)發(fā)送SNMP消息,描述網(wǎng)絡(luò)的運(yùn)行狀態(tài)
if ( filterOn == true )
{
mySipStack = new SipTransceiverFilter(applName, defaultSipPort, nat, aContext);
}
else
{
mySipStack = new SipTransceiver(applName, defaultSipPort, nat, aContext);
}
myBuilder->setSipStack(mySipStack);
//創(chuàng)建一個(gè)SIP消息的解析線程 。
mySipThread = new SipThread(mySipStack, myCallProcessingQueue);

… …
}

2.4 讓User Agent Run起來(lái):

  構(gòu)建User Agent的工作已經(jīng)完畢,現(xiàn)在應(yīng)該讓調(diào)用它的Run方法了;從下面的程序可以看到,Run方法的調(diào)用,讓整個(gè)程序進(jìn)入一種"Idle"的狀態(tài),等待命令輸入和狀態(tài)的產(chǎn)生,這個(gè)過(guò)程我們可以看到在Ua.CXX的Main程序中調(diào)用(ua.run())。

Void UserAgent::run()
{
//調(diào)用HeartLessProxy的Run方法,稍后做詳細(xì)的介紹
HeartLessProxy::run();
… …
deviceThread->run(); //調(diào)用SoundcardDevice::hardwareMain(0)
… …
rtpThread->run();//調(diào)用SoundCardDevice::processRTP()進(jìn)行RTP流的處理
… …
//在這里向FS發(fā)送隊(duì)列(myQ = new Fifo < Sptr < SubscribeMsg > >)中的各種消息,不
//過(guò)在Ua1001.cfg中,參數(shù)Subscribe_on設(shè)置為OFF所以,本章我們對(duì)FS暫不予以分析,
//在最后一章詳細(xì)分析FS的時(shí)候回著重分析它.
featureThread->run();//調(diào)用subscribeManager::subscribeMain()
… …
//后臺(tái)監(jiān)測(cè)線程開(kāi)啟.
loadGenThread->run();//調(diào)用LoadGenMonitor::lgMain()

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

(未完待續(xù))

作者供稿 CTI論壇編輯

作者聯(lián)系方法:lu_zheng@21cn.com

在Vovida的基礎(chǔ)上實(shí)現(xiàn)自己的SIP協(xié)議棧(二)


分類信息:     文摘