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 |
|
|