1. 程式人生 > >介紹一個開源的SIP(VOIP)協議庫PJSIP

介紹一個開源的SIP(VOIP)協議庫PJSIP

err 是否 創建 sample 程序 null strerror status family

本文系轉載,出處不可考。

假設你對SIP/VoIP技術感興趣,哪希望你不要錯過:),假設你對寫出堪稱優美的Code感興趣
,那麽你也不可錯過:)

這期間我想分析一下一個實際的協議棧的設計到實現的相關技術,算是自己的一個學習經
歷記錄.

最初選擇這個庫做分析的原因非常easy,文檔齊全:),其他良好的特征則是慢慢發現的:)

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
當然,也能夠選擇:
#include
#include
這樣的分離的方式,只是。簡單介紹其間,還是使用第一種吧:),畢竟,你不須要確認到你所
需的函數或者數據結構詳細到哪個詳細的頭文件:)

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天,又沒工具可在開發板上使
用,哪個痛苦,想自殺:(]
原因非常easy,你的內存都是從內存池中獲取的,就算你沒有釋放你獲取的內存,僅僅要你記得
把內存池destroy,那麽內存還是會還給系統.

還有設計帶來的一些其他益處,比方可用性和靈活性:
e).內存泄漏更easy被跟蹤.
這是由於你的內存是在指定的內存池中分配的,僅僅要能非常快定位到內存池,內存泄漏的偵
測就方便多了.
f).設計上從內存池中獲取內存這一操作是非線程安全的.
原因是設計者覺得內存池被上層對象所擁有,線程安全應該由上層對象去保證,這種話
,沒有鎖的問題會讓內存分配變得很的快.
g).內存池的行為像C++中的new的行為,當內存池獲取內存chunks會拋出PJ_NO_MEMORY_EX
CEPTION異常,當然,由於支持異常處理,也能夠使用其他方式讓上層程序靈活的定義異常的
處理.
[這是異常處理的基本出發點,可是這有大量的爭論,原因是這改變了程序的正常流程,誰能
去保證這樣的流程是用戶所須要的呢,因此C++中的異常處理飽受爭議,請酌情使用]
h). 能夠在後端使用不論什麽的內存分配器.默認情況下是使用malloc/free管理內存池的塊,
可是應用程序也能夠指定自己的策略(strategy),比如從一個全局存儲空間分配內存.

恩,要知道,不論什麽事務都是兩面的(頗為佩服創造出“雙贏”這個詞的語言天才, 只是。文
字遊戲對於技術人員不能說是件好事情:(),好了,使用時,不要覺得這個內存池是哪種"per
fect"的技術,要記得"不論什麽設計,都是在各種限制條件中的一個折中,對於‘戴著鐐銬的舞蹈
‘,除了‘舞蹈‘,也不要忘記‘鐐銬‘哦",不要忘了告誡:):
告誡[Caveats]:
a).使用合適的大小來初始化內存池.
使用內存池時,須要指定一個初始內存池大小, 這個值是內存池的初始值,假設你想要高
性能,要慎重選擇這個值哦,太大的化會浪費內存,過小又會讓內存池自身頻繁的去添加內存
,顯然這兩種情況都不可取.
b). 註意,內存池僅僅能添加,而不能被縮小(shrink),由於內存池沒有函數把內存chunks釋
放還給系統,這就要去內存池的構造者和使用者明白使用內存.

恩,主要的原理都差點兒相同了,後面我們來看看怎樣使用這個內存池.

5.2內存池的使用[Using Memory Pool]
內存池的使用相當的簡單,扳個手指頭就搞定了,假設你看明確了上面的原理和特征:)

a).創建內存池工廠[Create Pool Factory]
上面不是提及內存池的內部分配策略以及異常處理方式麽, 事實上這就是指定這個的:)

當然,不須要你每一個內存池都自己取指定策略和異常處理方式,PJLIB已經有了一個默認的
實現:Caching Pool Factory,這個內存池工廠的初始化使用函數pj_caching_pool_init()


b).創建內存池[Create The Pool]
使用pj_pool_create(),其參數分別為內存工廠(Pool Factory),內存池的名字(name),初
始時的大小以及增長時的大小.

c).依據須要分配內存[Allocate Memory as Required]
然後,你就能夠使用pj_pool_alloc(), pj_pool_calloc(), 或pj_pool_zalloc()從指定
的內存池依據須要去獲取內存了:)

d).Destroy內存池[Destroy the Pool]
這實際上是把預分配的內存還給系統.

e).Destroy內存池工廠[Destroy the Pool Factory]
這沒什麽好說的.

非常easy吧:)
作者在文檔中給出了一個樣例:
例如以下:
#include

#define THIS_FILE "pool_sample.c"

static void my_perror(const char *title, pj_status_t status)
{
char errmsg[PJ_ERR_MSG_SIZE];

pj_strerror(status, errmsg, sizeof(errmsg));
PJ_LOG(1,(THIS_FILE, "%s: %s [status=%d]", title, errmsg, status));
}

static void pool_demo_1(pj_pool_factory *pfactory)
{
unsigned i;
pj_pool_t *pool;

// Must create pool before we can allocate anything
pool = pj_pool_create(pfactory, // the factory
"pool1", // pool‘s name
4000, // initial size
4000, // increment size
NULL); // use default callback.
if (pool == NULL) {
my_perror("Error creating pool", PJ_ENOMEM);
return;
}

// Demo: allocate some memory chunks
for (i=0; i<1000; ++i) {
void *p;

p = pj_pool_alloc(pool, (pj_rand()+1) % 512);

// Do something with p
...

// Look! No need to free p!!
}

// Done with silly demo, must free pool to release all memory.
pj_pool_release(pool);
}

int main()
{
pj_caching_pool cp;
pj_status_t status;

// Must init PJLIB before anything else
status = pj_init();
if (status != PJ_SUCCESS) {
my_perror("Error initializing PJLIB", status);
return 1;
}

// Create the pool factory, in this case, a caching pool,
// using default pool policy.
pj_caching_pool_init(&cp, NULL, 1024*1024 );

// Do a demo
pool_demo_1(&cp.factory);

// Done with demos, destroy caching pool before exiting app.
pj_caching_pool_destroy(&cp);

return 0;
}

我就不解釋了:)

介紹一個開源的SIP(VOIP)協議庫PJSIP