如果你對SIP/VoIP技術感興趣,哪希望你不要錯過,如果你對寫出堪稱優美的Code感興趣 ,那麼你也不可錯過這期間我想分析一下一個實際的協議棧的設計到實現的相關技術,算是自己的一個學習經 曆記錄. 最初選擇這個庫做分析的原因很簡單,文檔齊全,其它良好的特徵則是慢慢發現的 www.pjsip.org 1. PJSIP簡介 PJSIP的實現是為了能在嵌入式裝置上高效實現SIP/VOIP.其主要特徵包括: 1).極具移植性.(Extremely portable) 當前可支援平台包括: * Win32/x86 (Win95/98/ME, NT/2000/XP/2003, mingw). * arm, WinCE and Windows Mobile. * Linux/x86, (user mode and as kernel module(!)). * Linux/alpha * Solaris/ultra. * MacOS X/powerpc * RTEMS (x86 and powerpc). 正移植到: * Symbian OS 2).非常小的足印.(Very small footprint) 官方宣稱編譯後的庫<150Kb,我在PC上編譯後加上strip後大概173Kb,這對於嵌入 式裝置,是個好訊息 3).高效能.(High performance) 這點我們後面可以看看是否如作者宣稱的 4).支援眾多的特徵.(Many features) 這點可以從http://www.pjsip.org/sip_media_features.htm#sip_features看出. 5).充足的SIP文檔.(Extensive SIP documentation) 這是我最初選擇該庫的原因,當然不是最終的原因,最終的原因是它的code 2. PJSIP的組成. 其實說是PJSIP不是特別貼切,這個庫實際上是幾個部分組成的. 1).PJSIP - Open Source SIP Stack[開源的SIP協議棧] 2).PJMEDIA - Open Source Media Stack[開源的媒體棧] 3).PJNATH - Open Source NAT Traversal Helper Library[開源的NAT-T輔助庫] 4).PJLIB-UTIL - Auxiliary Library[協助工具輔助庫] 5).PJLIB - Ultra Portable Base Framework Library[基礎架構庫] 而在最上層庫的目錄分為(可以使用tree -d -L 1 查看) $TOP/build [包含Makefile] $TOP/build.symbian [針對symbian的Makefile] $TOP/pjlib [參考上面] $TOP/pjlib-util [參考上面] $TOP/pjnath [參考上面] $TOP/pjmedia [參考上面] $TOP/pjsip [參考上面] $TOP/pjsip-apps $TOP/third_party 而在每個子目錄,可以看到分為: bin [編譯後產生的二進位檔案] build [Makefile] build/output build/wince-evc4 docs [doxygen的文檔,用doxygen docs/doxygen.cfg產生] include [標頭檔] lib [編譯後產生的庫] src [原始碼] 3. PJLIB簡介 要理解好PJSIP,就不得不先說說PJLIB,PJLIB算的上是這個庫中最基礎的庫,正是這個 庫的優美實現,才讓PJSIP變得如此優越。 PJLIB提供了一系列特徵,這是我們下面分析的重點,涉及到: 1).非動態記憶體分配[No Dynamic Memory Allocations] 實現了記憶體池,擷取記憶體是從與分配的記憶體池中擷取,高效能程式多會自己構造記憶體池 ,後面我們會解釋該記憶體池的使用以及基本的原理。根據作者的比較,是常規的 malloc( )/free()函數的30倍。 2).OS抽象[Operating System Abstraction] 實現OS抽象的根本原因在與可移植性,毋庸置疑. 涉及到: a).線程[Threads.] b).執行緒區域儲存[Thread Local Storage.] c).互斥[Mutexes.] d).號誌[Semaphores.] e).原子變數[Atomic Variables.] f).臨屆區[Critical sections.] g).鎖對象[Lock Objects.] h).事件對象[Event Object.] i).時間管理[Time Data Type and Manipulation.] j).高解析的時間戳記[High Resolution Timestamp.] 等等,這些我們後面分析代碼時一一看來 3).低層的網路相關IO[Low-Level Network I/O] 這涉及到: a).Socket抽象[Socket Abstraction.] b).網路位址解析[Network Address Resolution.] c).實現針對Socket的select API[Socket select() API.] 4).時間管理[Timer Management] 這主要涉及到兩個部分,一個時定時器的管理,還有就是時間解析的精度(舉例說來,就 是能精確到哪個時間等級,比如 POSIX sleep(),就只能以秒為單位,而使用select()則可 以實現毫秒層級的計時) 5).各種資料結構[Various Data Structures] 主要有: a).針對字串的操作[String Operations] b).數組輔助[Array helper] c).Hash表[Hash Tabl] d).鏈表[Linked List] e).紅黑平衡樹[Red/Black Balanced Tree] 6).異常處理[Exception Construct] 使用的是TRY/CATCH,知道C++/JAVA之類物件導向語言的人看過會宛而一笑 7).LOG機制[Logging Facility] 很顯然,一個良好的程式,好的LOG機制不可少。這能很方便的讓你去偵錯工具,對此我 是深有體會,任何時候,不要忘記“好的程式,是架構出來的;而能跑的程式,是調試出 來的:)” 8).隨機數以及GUID的產生[Random and GUID Generation] GUID指的是"globally unique identifier",只是一個標識而已,比如說你的省份證, 算的上是一個GUID,當然,準確說來是“china unique identifier”. 看了這麼多的特徵列舉,是不是很完備,的確。 總算是初步列舉完了PJLIB的基本特徵了,後面我們來說說它的使用與實現: 4. PJLIB的使用 有了上述介紹,是不是很想知道這個庫的使用,沒關係,我們慢慢說來 首先是標頭檔和編譯出來的庫的位置,這就不必多說了,除非你沒有使用過手動編譯的庫 ,如果不太瞭解步驟,google一下,啊 1).為了使用這個庫,需要使用: #include <pjlib.h> 當然,也可以選擇: #include <pj/log.h> #include <pj/os.h> 這種分離的方式,不過,簡介其間,還是使用第一種吧:),畢竟,你不需要確認到你所 需的函數或者資料結構具體到哪個具體的標頭檔 2).確保在使用PJLIB之前調用 pj_init()來完成PJLIB庫使用前說必須的一些初始化. 這是一個必不可少的步驟. ~~~~~~~~~~~~~~~~~~~~~~~ 3).使用PJLIB的一些建議 作者對使用PJLIB的程式提出了一些建議,包括如下 : a).不要使用ANSI C[Do NOT Use ANSI C] 觀點很明確,ANSI C並不會讓程式具有最大的移植性,應該使用PJSIP庫所提供的響 應機制來實現你所需要的功能. b).使用pj_str_t取代C風格的字串[Use pj_str_t instead of C Strings] 原因之一是移植性,之二則是PJLIB內建的pj_str_t相關操作會更快(效能). c).從記憶體池分配記憶體[Use Pool for Memory Allocations] 這很明顯,如果你知道為什麼會使用記憶體池的話(提示一下,效能以及易用性:)) d).使用PJLIB的LOG機製做文字顯示[Use Logging for Text Display] 很明顯 還有些關於移植的一些問題,不在我們的討論範圍,如果你需要移植到其它平台或者 環境,請參考http://www.pjsip.org/pjlib/docs/html/porting_pjlib_pg.htm 5. PJLIB的使用以及原理 終於開始提及實現原理以及具體的編碼了:),前面的列舉還真是個瑣碎的事情,還是奔主題 來:). 5.1快速記憶體池[Fast Memory Pool] 前面說過,使用記憶體池的原因在於效能的考慮,原因是C風格的malloc()以及C++風格的new 操作在高效能或即時條件下表現並不太好,原因在於效能的瓶頸在於記憶體片段問題. 下面列舉其優點與需要主要的問題: 優點: a).不像其它記憶體池,允許分配不同尺寸的chunks. b).快速. 記憶體chunks擁有O(1)的複雜度,並且操作僅僅是指標的算術運算,其間不需要使用鎖住任 何互斥量. c).有效使用記憶體. 除了可能因為記憶體對齊的原因會浪費很少的記憶體外,記憶體的使用效率非常高. d).可預防記憶體流失. 在C/C++程式中如果出現記憶體流失問題,其尋找過程哪個艱辛,不足為外人道也:( [曾經有次用別人的Code,出現了記憶體流失,在開發板上尋找N天,又沒工具可在開發板上使 用,哪個痛苦,想自殺~~~ 原因很簡單,你的記憶體都是從記憶體池中擷取的,就算你沒有釋放你擷取的記憶體,只要你記得 把記憶體池destroy,那麼記憶體還是會還給系統. 還有設計帶來的一些其它益處,比如可用性和靈活性: e).記憶體流失更容易被跟蹤. 這是因為你的記憶體是在指定的記憶體池中分配的,只要能很快定位到記憶體池,記憶體流失的偵 測就方便多了. f).設計上從記憶體池中擷取記憶體這一操作是非安全執行緒的. 原因是設計者認為記憶體池被上層對象所擁有,安全執行緒應該由上層對象去保證,這樣的話 ,沒有鎖的問題會讓記憶體配置變得非常的快. g).記憶體池的行為像C++中的new的行為,當記憶體池擷取記憶體chunks會拋出PJ_NO_MEMORY_EX CEPTION異常,當然,因為支援異常處理,也可以使用其它方式讓上層程式靈活的定義異常的 處理. [這是異常處理的基本出發點,但是這有大量的爭論,原因是這改變了程式的正常流程,誰能 去保證這種流程是使用者所需要的呢,因此C++中的異常處理飽受爭議,請酌情使用] h). 可以在後端使用任何的記憶體 Clerk.預設情況下是使用malloc/free管理記憶體池的塊, 但是應用程式也可以指定自己的策略(strategy),例如從一個全域儲存空間分配記憶體. 恩,要知道,任何事務都是兩面的(頗為佩服創造出“雙贏”這個詞的語言天才, 不過,文 字遊戲對於技術人員不能說是件好事情:(),好了,使用時,不要認為這個記憶體池是哪種"per fect"的技術,要記得"任何設計,都是在各種限制條件中的一個折中,對於'戴著鐐銬的舞蹈 ',除了'舞蹈',也不要忘記'鐐銬'哦",不要忘了告誡: 告誡[Caveats]: a).使用合適的大小來初始化記憶體池. 使用記憶體池時,需要指定一個初始記憶體池大小, 這個值是記憶體池的初始值,如果你想要高 效能,要謹慎選擇這個值哦,太大的化會 |