147
技術社區[雲棲]
Partysip的插件技術研究
Partysip的插件技術研究
之Partysip框架優化方案
介紹:
本文是《Partysip框架優化計劃》的一部分,著重研究Partysip插件技術,並在此基礎上嚐試優化。
整體上說Partysip雖然沒有使用OO的思想去設計,但是還是盡量“封裝”獨立的函數操作,所以研究和理解還是比較方便,對於每個聲明結構體都會有一組相關的函數對其操作,這些操作大部分是名字上的差異,其執行操作是對結構體內變量賦值,修改操作,以及結構體的init和free操作,試想如果Partysip采用OO設計思想進行設計,將是非常容易理解。
對於Partysip插件,我們是按照程序啟動時引入插件的過程來理解,並逐步理解插件的實現和調用,在文章最後給出實現一個插件所需要完成的基本操作。現在假設我們已經具有插件,這裏我們所說的插件其實就是按照Partysip框架格式定義的動態鏈接庫(dll)。
插件的加載過程
Partysip中插件加載在main.c文件中的main_load_plugins()函數內實現,在加載Partysip的插件之前,Partysip框架已經做了一些初始化,關於這方麵的知識,以後會再講。在main_load_plugins函數內,大部分代碼我們不用考慮,加載插件的主要函數是psp_plugin_load函數,通過循環調用psp_plugin_load函數實現對所有插件的加載,該函數在psp_plugin.h文件內聲明,在psp_plugin.c文件內定義。
關於插件操作其他函數也是在這兩個文件內聲明和定義,後麵將詳細介紹psp_plugin.h內的函數。在psp_plugin_load函數調用ppl_dso_load函數(在ppldso.h文件內定義),在ppl_dso_load函數內調用係統函數LoadLibraryEx加載動態鏈接庫,這樣我們就把插件“插入”Partysip中,這和在程序中引入一個其他dll是沒有區別的。Partysip經過諸多轉換後,會把dll的句柄保存在ppl_dso_handle_t類型的結構體內,在XXX_plugin(XXX是插件的名稱,每個插件都有這樣的一個全局結構體)結構體內有一個ppl_dso_handle_t類型指針指向這個變量,最後資源釋放時也是通過ppl_dso_handle_t這個結構體進行資源釋放,釋放時是通過全局結構XXX_plugin來釋放的,這個結構體在psp_plugin_take_ownership函數內注冊,Partysip在加載控件,主要通過XXX_plugin這個結構體,調用plugin_init函數,關於這個函數的作用以後會提到。
加載動態鏈接庫dll(插件)成功後,我們回到psp_plugin_load函數,這個函數內聲明兩個重要變量
1:dso_handle,變量類型為ppl_dso_handle_t,他的原型為
struct ppl_dso_handle_t
{
void *handle;
const char *errormsg;
};
是用來記錄加載dll的句柄和錯誤信息的(如果加載失敗)。
2:sym,變量類型為ppl_dso_handle_sym_t,他的原型是typedef void *ppl_dso_handle_sym_t;,這個類型更多是基於將來擴展考慮而這樣定義的。當加載dll後用dso_handle記錄加載dll的句柄信息和錯誤信息。然後通過調用ppl_dso_sym函數,獲取dll中的特定結構體的指針(我們做插件時,必須創建一個這樣的全局結構體變量,結構體變量名必須為插件名+ _plugin,比如UDP插件中聲明udp_plugin結構體)。結構體指針的類型為psp_plugin_t。這個結構體的主要作用是記錄插件的相關信息,我們在後麵還將詳細介紹這個結構體。獲取這個結構指針後,再次分別調用ppl_dso_sym (&sym, dso_handle, "plugin_init"),ppl_dso_sym (&sym, dso_handle, "plugin_start"),ppl_dso_sym (&sym, dso_handle, "plugin_release"),分別獲取加載插件中的plugin_init,plugin_start,plugin_release三個函數地址(後麵我們將詳細介紹其作用),通過這個函數(*plug)->plugin_init (name_config)調用插件的plugin_init函數,這個函數內做插件的相關注冊操作,具體操作參考下邊的函數講解。以上各個過程中如果出現錯誤,馬上調用ppl_dso_unload函數,通過FreeLibrary函數釋放插件。在Partysip,Osip中,函數返回值為0,表示執行函數成功,-1表示失敗。
3:關於線程,在《Partysip框架優化計劃》一文中提到,Partysip會為每個插件創建一個線程的觀點是不正確的。經過仔細分析Partysip框架,Partysip會創建三個線程(啟動參數中包含i選項,i表示為interface,用戶界麵的意思,主界麵線程,加上這個線程Partysip一共創建4個線程。如果Osip庫采用多線程方式編譯,osip至少還會創建4個線程)。這三個線程分別為mythread_resolv,mythread_tlp,mythread_sfp。這三個線程分別監聽三個管道,Partysip為了通用向並沒有使用windows下的CreatePipe等函數,而是創建了從10500開始的三個TCP端口,當有消息到來時(比如q,退出)通過這幾個端口來傳輸。關閉這三個線程的作用。
製作插件
Partysip插件就是滿足Partysip框架要求的動態鏈接庫(dll),滿足Partysip框架的插件至少要滿足一下五點
1:插件內定義全局變量XXX_plugin(XXX為插件名稱),此變量類型為psp_plugin_t。
這個結構體原型為
struct psp_plugin_t
{
int plug_id;// 加載後的插件ID
char *name; // 插件名字
char *version; // 插件版本
char *description; // 插件描述信息
#define PLUGIN_TLP 0x01
#define PLUGIN_IMP 0x02
#define PLUGIN_UAP 0x04
#define PLUGIN_SFP 0x16
int flag;
/*... */
int owners; /* number of attached plugins */
ppl_dso_handle_t *dso_handle; // 加載插件後的插件句柄
int (*plugin_init) (); // 以下為三個回調函數,分別執行初始化,開始,釋放數據操作。
int (*plugin_start) ();
int (*plugin_release) ();
};
這個結構體的作用是記錄Partysip插件的相關信息,比如插件的名字,插件的ID,插件的版本,插件的描述信息,插件的三個回調函數,插件的句柄等。變量XXX_plugin中的一些成員變量會在插件加載的Partysip框架中修改。比如owners,dso_handle,plugin_init等,下麵是UDP插件udp_plugin的例子
psp_plugin_t PSP_PLUGIN_DECLARE_DATA udp_plugin = {
0, /* uninitialized */
"Udp plugin",
"0.5.1",
"plugin for receiving and sending UDP message",
PLUGIN_TLP,
0, /* number of owners is always 0 at the begining */
NULL, /* future place for the dso_handle */
&plugin_init,
&plugin_start,
&plugin_release
};
2:聲明plugin_init 函數,其聲明原型為
PSP_PLUGIN_DECLARE (int) plugin_init (char *name_config),
PSP_PLUGIN_DECLARE是一個宏,聲明為
#define PSP_PLUGIN_DECLARE(type) __declspec(dllimport) type __stdcall
表示plugin_init為一個導出函數。名字,函數參數,返回值不能修改。
Plugin_int函數的主要作用是,初始化插件內部的一些操作,變量的申請,係統參數的獲取操作,係統資源的獲取,還有一個重要作用就是,注冊回調函數(hook機製),涉及到回調函數執行的優先級別等信息。參數name_config傳遞配置文件路徑的字符串,這個函數在加載完成插件後調用。
3:聲明plugin_start函數,函數原型為PSP_PLUGIN_DECLARE (int) plugin_start ()
調用完成plugin_init之後,在合適的位置執行此函數,這個函數目前隻是作為保留函數使用,一般都是隻是return -1;
4:聲明plugin_release 函數,函數原型為PSP_PLUGIN_DECLARE (int) plugin_release ()
函數的作用,主要用於卸載插件時,調用這個函數,釋放由plugin_init中申請的內存空間,關閉係統資源等。
5:聲明注冊hook函數
插件中hook函數執行順序(psp_plug)
/** run this hook first, before ANYTHING */
#define PSP_HOOK_REALLY_FIRST (-10)
/** run this hook first */
#define PSP_HOOK_FIRST 0
/** run this hook somewhere */
#define PSP_HOOK_MIDDLE 10
/** run this hook after every other hook which is defined*/
#define PSP_HOOK_LAST 20
/** run this hook last, after EVERYTHING */
#define PSP_HOOK_REALLY_LAST 30
#define PSP_HOOK_FINAL 40
/* How to call plugins:
1: detect which method or response code is used
2: call the list of method in the PSP_HOOK_REALLY_FIRST list
3: call the list of method in the PSP_HOOK_FIRST
4: ...
5: ...
So: For performance reason, this list should be ready to use:
table[0] for any REQUEST (other than INVITE)
table[1] for INVITE
table[2] for REGISTER
table[3] for BYE
...
table[1] contains all the hook related to INVITE:
for irp:
*/
/* HOW PLUGINS ARE IMPLEMENTED:
1: The core layer is asked to load a plugin named "$name"
2: Do a dlopen of the shared library
3: Do dlsym to access some methods:
plugin_configure
-> used to init internal state of plugin
-> plugin store its "func_tab" in the *_plugin structure
....
*/
小結:
以上這些隻是基於Partysip框架的基本了解,由於剛接觸Partysip難免有錯誤,或者理解偏差的地方,歡迎大家指出。
部分縮寫詞說明(部分)
縮寫
|
原組成詞
|
說明
|
備注
|
psp
|
Partysip
|
psp是Partysip縮寫
|
|
tlp
|
Transport layer Processing
|
tlp傳輸層處理
|
Transport layer Processing
|
sfp
|
State Full Processing
|
sfp全狀態處理
|
State Full Processing
(全狀態處理)
|
ict
|
Invite Client Transaction
|
ict客戶端邀請事務
|
Invite Client Transaction
(邀請客戶端事務)
|
nict
|
Non-Invite Client Transaction
|
Nict非客戶端要求事務
|
Non Invite Client Transaction(非邀請客戶端事務)
|
ist
|
Invite Server Transaction
|
Ist服務器端要求事務
|
Invite Server Transaction
|
nist
|
Non Invite Server Transaction
|
Nist非服務器端要求事務
|
Non Invite Server Transaction(非邀請服務器端事務)
|
snd
|
Send
|
|
|
inc
|
|
|
|
rcv
|
receive
|
|
|
最後更新:2017-04-02 00:06:17