1. PJSIP簡介
PJSIP的實現是爲了能在嵌入式設備上高效實現SIP/VOIP.其主要特徵包括:
1).極具移植性.(Extremely portable)
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[基礎框架庫]
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_EXCEPTION異常,固然,由於支持異常處理,也可使用其它方式讓上層程序靈活的定義異常的處理.
這是異常處理的基本出發點,可是這有大量的爭論,緣由是這改變了程序的正常流程,誰能去保證這種流程是用戶所須要的呢,所以C++中的異常處理飽受爭議,請酌情使用]
h). 能夠在後端使用任何的內存分配器.默認狀況下是使用malloc/free管理內存池的塊,
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]
這沒什麼好說的.
#include <pjlib.h>html
#define THIS_FILE "main.cpp"編程
int main()後端
{數組
char errmsg[PJ_ERR_MSG_SIZE];緩存
pj_caching_pool cp;安全
pj_status_t status;服務器
status=pj_init();網絡
if(status!=PJ_SUCCESS)數據結構
{架構
pj_strerror(status,errmsg,sizeof(errmsg));
fprintf(stderr,"pj_init error\n");
return 1;
}
//create factory
pj_caching_pool_init(&cp,NULL,1024*1024);
pj_pool_t *pool;
//create pool
pool=pj_pool_create(&cp.factory,"pool1",4000,4000,NULL);
if(!pool)
{
fprintf(stderr,"error createing pool\n");
return 1;
}
//alloc some size memory
void *p;
p=pj_pool_alloc(pool,(pj_rand()+1)%512);
//free the pool
pj_pool_release(pool);
//free the factory
pj_caching_pool_destroy(&cp);
return 0;
}
2.
PJSIP
基於一個開放的、成熟的SIP開源庫進行開發不但能夠大大提升效率,也可加強與其餘的SIP系統的兼容性。PJSIP是用C編寫的,至關優秀的一個SIP協議棧,其主要特徵包括:
1. 極具移植性。支持的平臺有Windows、Windows Mobile、Linux、Unix、MacOS X、RTEMS、Symbian OS等。
2. 很是小的存儲空間。包含完整SIP功能的代碼庫僅150K。
3. 高性能。採用優秀的內存分配機制,運行速度快。
4. 支持衆多SIP特徵和擴展。好比IM、presence、event subscription、call transfer、PIDF等。
5. 豐富的SIP文檔和範例。
PJSIP 開源庫由一系列功能庫所組成,如圖1所示,PJLIB 是系統抽象層,PJLIB-UTIL提供有用的工具函數,PJNATH 解決NAT 穿越問題,PJMEDIA 和PJMEDIA-CODEC 負責SDP 協商、媒體編碼和媒體傳輸,PJSIP 是核心SIP 協議棧,PJSIP-SIMPLE 實現Presence和即時消息,PJSIP-UA 提供SIP 用戶代理庫,PJSUA 位於最高層,整合了下層模塊的所有功能。PJSIP 的每一個功能庫根據其所在的層次以及負責的功能都提供了豐富的編程接口,方便開發人員使用。
PJSIP 協議棧內部包含多個SIP 消息處理層,以下圖2所示,從下往上依次TRANSPORT層、ENDPOINT 層、TRANSACTION 層、UA 層和DIALOG 層。每一個消息處理層以模塊的形式註冊到協議棧中,開發者也能夠編寫並添加本身的消息處理模塊,對SIP 消息進行解析或修改。當TRANSPORT MANAGER 收到SIP 消息包時,會把該SIP EVENT 通知上層的ENDPOINT,而ENDPOINT 會找到對應的接收者,先把EVENT 傳給TRANSACTION LAYER,而後再傳給UA LAYER(傳遞的順序由每一個模塊的優先權決定),若是 UA LAYER指定要處理TRANSACTION 的EVENT,TRANSACTION LAYER 也會把解析後的EVENT傳給UA LAYER。
PJSIP是一個高度封裝的庫,實際上它是經過PJSUA子庫來實現應用的。一個完整的PJSUA生命週期,首先須要初始化,經過函數init()來實現。在這個函數中,將建立代理、初始化變量和堆棧,以及建立一個UDP傳輸並在最後啓動代理;第二步將爲UA添加用戶,若是須要的話,還要向服務器註冊用戶;當用戶添加成功後,此時能夠創建一個呼叫鏈接,發起會話;當會話鏈接成功後,就可使用SRTP協議實時傳輸加密後的數據,進行通話。最後的過程是掛起或銷燬呼叫。
1.1.1 通訊結構圖
Sip協議棧的中心環節就是Endpoint,被封裝成了pjsip_endpoint類型來使用。來看看它的特性和職責吧。
1.它採用內存池來爲全部的sip組件分配內存。
2.它有一個時間堆,爲全部的sip組件分配時間。
3.它還有一個通信協議管理模塊,這個管理模塊包括通信協議的管理,以及對消息的解析的打印。還記得sip消息的結構嗎?
4.它還有一個單一的PJLIB的io隊列,這個io隊列什麼功能呢?就是對處理不完的網絡事件放到隊列中。
5.它還有一個線程安全的輪詢函數。用於來對應用程序的線程進行事件和socket事件的輪詢。
6.它管理着pjsip模塊,pjsip模塊不建立任何線程。Pjsip是擴展協議棧的主要方法,並不侷限於消息的解析和顯示。
7.從通信管理模塊接收消息,而且把消息分發到各個模塊。
下面具體介紹一下這些功能。
全部組件內存的分配都有endpoint進行,這樣就能夠保證線程的安全,保證整個應用體系執行統一的決定。這些決定不少,舉一個例子吧。例如,緩存。
Endpoint提供了兩個函數來分配和釋放內存池
.pjsip_endpt_create_poll()
.pjsip_endpt_create_poll()
對於endpoint的建立能夠用pjsip_endpt_create()函數。
當調用這個函數以後,endpoint就被建立了,建立內存池的工廠由應用程序指定來讓endpoint使用。這個內存工廠指針將存在於endpoint的生命週期中,endpoint就用他來建立和釋放內存池。
Endpoint經過控制一個定時器堆來管理定時器。全部組件的定時器的建立和管理都由endpoint進行。
提供的函數有
.pjsip_endpt_schedule_timer();
.pjsip_endpt_cancel_timer();
當endpoint的輪詢函數被調用的時候,endpoint來檢查時間的終結。
Endpoint提供了一個函數叫作pjsip_endpt_handle_events()來檢查時間和網絡時間。應用程序能夠肯定它多久等到網絡事件的發生。