PJSIP開發指南


一、通用設計

1.1   架構

1.1.1        通信圖

下面的圖展示了SIP消息在PJSIP組件間從后端到前端如何傳遞的。

 

 

1.1.2        類圖

下面的圖顯示類視圖

 

 

 

 

 

 

1.2   Endpoint

SIP 協議棧的核心是SIP endpoint,它由透明的pjsip_endpoint的表示,endpoint具有下面的屬性和職責

l  內存儲工廠,為所有的SIP組件分配內存

l  具備定時器堆實列,為所有的SIP組件調度定時器

l  傳輸管理起實例,傳輸管理器負責傳輸SIP消息並且控制消息的解析和打印。

l  擁有單實例PJLIB io 隊列,IO隊列采用proactor模式分發事件。

l  提供了線程安全的輪詢功能,應用程序的線程能夠查詢定時器和socket事件(PJSIP自身不常見任何線程)

l  管理PJSIP模塊,PJSIP模塊主要以為着擴展SIP stack,而不僅僅限於消息的解析和打印。

l  從傳輸模塊接收SIP消息,並且分發到各個模塊中。

 

1.2.1        內存池分配和釋放

為了保證線程的安全性以及在整個應用中策略強一致性,所有的SIP內存都需要在endpoint中完成分配。比如:內存池緩存,未使用的內存被保留在以后使用而不是銷毀。

Endpoint提供下面的函數分配和釋放內存池

pjsip_endpt_create_pool()

pjsip_endpt_release_pool()

當endpoint被pjsip_endpt_create()創建時,應用一定要指明由endpoint使用的內存池工廠。在整個生命周期內Endpoint持有內存池工廠的指針,將來備用創建和釋放內存。

1.2.2        定時器管理

Endpoint 擁有一個獨立定時器堆實例,所有SIP組件的定時器創建和調度都需要通過endpoint 完成。Endpoint提供下面的函數管理定時器

pjsip_endpt_schedule_timer()

pjsip_endpt_cancel_timer()

endpoint的poll函數被調用時,檢查定時器是否過期。

1.2.3         輪詢棧

Endpoint 提供獨立的函數(pjsip_endpt_handle_events())為了檢查定時器和網絡事件的出現,應用可以指定准備等待多久去檢查事件的出現。

Pjsip 棧從不創建線程,整個棧的運行過程都依賴於應用所創建的線程,不管是API被調用或者是輪詢函數被調用。

輪詢功能優化等待時間依賴於定時器堆的內容,比如:當它找到定時器將在下個5S過期,他等待socket事件的時間,將不會比5S長,在無網絡事件出現時,應用程序等待超過5S是沒有必要的,當然每個平台的定時器的精度是不一樣的。

1.3   線程安全和線程的兼容性

1.3.1        線程安全

線程安全的討論是比較麻煩的事情,但是,幸運的是,下面的設計原則,在整個協議中的應用都保持了一致性。

對象一定是線程安全的,但是數據結構一定不是線程安全的。

看到這個主題,很自然的想到,對象和簡單數據結構的區別不是那么清晰,但是有一些例子可以提供,可能更明白寫。

數據結構的例子:

PJLIB的數據結構,比如:

l  鏈表、數組、哈希表、字符串、內存池。

l  SIP消息的元素,URLs、header fields和SIP消息

這些數據結構並不是線程安全的,數據結構的線程安全由包含他們的對象保證。如果數據結構是線程安全的,將會嚴重的影響協議棧的性能並且消耗操作系統的資源。

相比之下,PJSIP的對象一定是線程安全的,比如:

l  PJLIB 對象,比如ioqueue

l  PJSIP 對象,比如: endpoint、transactions、dialogs等。

 

 

1.3.2        復雜性

更糟糕的是,一些對象在頭文件中暴露了他們的聲明(pjsip_transaction 和 pjsip_dialog)。

雖然API暴露可以保證線程的安全性,應用在訪問數據結構前,必須通過pj_mutex_lock獲取到對象的互斥鎖。

更糟糕的是,dialog暴露的不同的API鎖定dialog,應用程序應該調用pjsip_dlg_inc_lock和pjsip_dlg_dec_lock() 替代pj­_mutex_lock和pj_mutex_unlock。兩種處理方式區別是:

Dialog的inc/dec保證了dialog將不會被銷毀在函數調用時。而pj_mutex_unlock會因為dialog銷毀了而導致層序crash。

考慮下面的例子:

pj_mutext_lock(dlg->mutex)

pjsip_dlg_end_session(dlg,…)

pj_mutex_unlock(dlg->mutex)

在上面的例子中,應用可能會crash,因為pjsip_dlg_end_session可能會銷毀dialog在某些情況下。例如:INVITE事務沒有被應答,事務會被馬上銷毀,導致dialog被立刻銷毀。Dialog

Inc/dec可以通過增加dialog的引用計數,防止這類情況的發生。再end_session時,dialog 不會被銷毀,dialog 可能會在dec_lock函數中被銷毀。所以正確的調用順序應該如下:

Pjsip_dlg_inc_lock(dlg)

Pjsip_dlg_end_session(dlg)

Pjsip_dlg_dec_lock

最最糟糕的是,鎖的調用順序一定是正確的,否則引起死鎖。比如:應用想去鎖dialog和dialog 的 transaction,應用必須獲得dialog mutex在獲取transaction mutex之前,否則當其他的線程以相反的順序拿鎖時,將會引起死鎖。

1.3.3         解決辦法

幸運的是,應用程序很少會直接獲取對象的鎖,所以上面的問題很少會發生。應用應該使用對象的API訪問對象,API會對象檢查,保證了lock的正確性,避免了死鎖和crash的產生。

應用程序的回調被對象調用,這些回調被調用時,對象的鎖已經獲取到了。所以應用能夠安全的獲取對象的數據結構,不必要獲取對象的鎖。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM