原文:http://www.cnblogs.com/kernel_hcy/archive/2010/03/11/1683809.html
在lighttpd中,使用插件(plugin
)的形式来增加服务的功能。同时,lighttpd提供了一个插件的公共接口给开发者,方便第三方提供额外的插件。lighttpd的插件接口主要提供在plugin.h文件中。其中,plugin结构体是最核心的部分。
plugin结构体的定义如下:
typedef struct {
size_t version; // 插件的版本
buffer *name; /* name of the plugin */
void *(* init) ();
handler_t (* set_defaults) (server *srv, void *p_d);
handler_t (* cleanup) (server *srv, void *p_d);
/* is called ... */
handler_t (* handle_trigger) (server *srv, void *p_d); /* once a second */
handler_t (* handle_sighup) (server *srv, void *p_d); /* at a signup */
handler_t (* handle_uri_raw) (server *srv, connection *con, void *p_d); /* after uri_raw is set */
handler_t (* handle_uri_clean) (server *srv, connection *con, void *p_d); /* after uri is set */
handler_t (* handle_docroot) (server *srv, connection *con, void *p_d); /* getting the document-root */
handler_t (* handle_physical) (server *srv, connection *con, void *p_d); /* mapping url to physical path */
handler_t (* handle_request_done) (server *srv, connection *con, void *p_d); /* at the end of a request */
handler_t (* handle_connection_close)(server *srv, connection *con, void *p_d); /* at the end of a connection */
handler_t (* handle_joblist) (server *srv, connection *con, void *p_d); /* after all events are handled */
handler_t (* handle_subrequest_start)(server *srv, connection *con, void *p_d);
/* when a handler for the request * has to be found */
handler_t (* handle_subrequest) (server *srv, connection *con, void *p_d); /* */
handler_t (* connection_reset) (server *srv, connection *con, void *p_d); /* */
void *data;
/* dlopen handle */
void *lib;
} plugin;
可以看出,在结构体plugin的设计中,作者使用了面向对象
的思想。plugin结构体就是一个虚基类,其中的数据成员,如name,version等都是子类公有的。而随后的一系列函数指针则是虚函数,这些函数指针在plugin结构体中并没有进行赋值,要求所有的子类必须对其进行赋值。不同的子类对这些函数指针赋不同的值,在进行函数调用的时候就可以实现多态
。
另外,c语言毕竟不支持面向对象,因此,在通过c实现面向对象的时候大多情况先是要靠人的理解,而不是语言上的约束。如,这里说plugin结构体是一个虚基类,实际上所有的子类都是这个结构体的实例,而子类的实例只有一个,也就是他自己。这就和C++中的子类不同了。
在plugin结构体中,version成员比较重要。很明显,这个成员标记这个插件的版本。在plugin结构体中定义的那一系列函数指针是插件的对外接口,也就是插件对lighttpd的接口,lighttpd只知道这些接口,通过调用这些接口来完成工作。随着lighttpd的不断改进,这些接口可能满足不了服务器的要求,因此要对其进行改进,这样就有可能造成以前开发的插件无法使用。通过version成员,在加载插件的时候判断这个插件是否符合当前服务器的版本,也就是接口是否相符。如果不相符,则不加载插件,这样就可以避免由于接口的不相符造成服务器的崩溃等问题。
这些函数指针在lighttpd的文档中被称作’hooks’。分为serverwide hooks和connectionwide hooks,serverwide hooks是有服务器调用的,主要处理一些初始化等辅助的工作,包括:init,cleanup, set_defaults, handle_trigger和handle_sighup。connectionwide hooks主要是面向连接的,在处理连接的时候调用这些hooks完成相应的工作。这些hooks大部分在函数http_response_prepare()中被调用。
至于这些hooks是在哪被调用,都完成哪些功能,在后面分析具体的插件的时候会详细介绍。有兴趣的读者可以阅读lighttpd源码包中doc文件夹下的plugins文件(lighttpd-1.4.37/doc/outdated/plugins.txt
)。
在plugin.h中,plugin结构体的定义后面还有一堆的函数声明:
// 这两个很明显是加载和释放插件函数。
int plugins_load(server *srv);
void plugins_free(server *srv);
handler_t plugins_call_handle_uri_raw(server *srv, connection *con);
handler_t plugins_call_handle_uri_clean(server *srv, connection *con);
handler_t plugins_call_handle_subrequest_start(server *srv, connection *con);
handler_t plugins_call_handle_subrequest(server *srv, connection *con);
handler_t plugins_call_handle_request_done(server *srv, connection *con);
handler_t plugins_call_handle_docroot(server *srv, connection *con);
handler_t plugins_call_handle_physical(server *srv, connection *con);
handler_t plugins_call_handle_connection_close(server *srv, connection *con);
handler_t plugins_call_handle_joblist(server *srv, connection *con);
handler_t plugins_call_connection_reset(server *srv, connection *con);
handler_t plugins_call_handle_trigger(server *srv);
handler_t plugins_call_handle_sighup(server *srv);
handler_t plugins_call_init(server *srv);
handler_t plugins_call_set_defaults(server *srv);
handler_t plugins_call_cleanup(server *srv);
/* 这一系列的plugins_call_XXXXX函数则是插件对外的接口。也就是说,lighttpd服务器通过这些函数,调用插件进行工作。lighttpd在调用插件的时候并不知道到底调用的是哪些插件,而仅仅调用上面的函数。这些函数再调用相应的插件的函数,从而完成工作。具体怎么调用插件的函数,放在后面的文章中介绍。 最后面的config_XXXXXX函数是处理一些配置问题,暂不讨论 */
int config_insert_values_global(server *srv, array *ca, const config_values_t *cv);
int config_insert_values_internal(server *srv, array *ca, const config_values_t *cv);
int config_setup_connection(server *srv, connection *con);
int config_patch_connection(server *srv, connection *con, comp_key_t comp);
int config_check_cond(server *srv, connection *con, data_config *dc);
int config_append_cond_match_buffer(connection *con, data_config *dc, buffer *buf, int n);
在plugin.h文件中还定义了一些宏:
#define SERVER_FUNC(x) \
static handler_t x(server *srv, void *p_d)
#define CONNECTION_FUNC(x) \
static handler_t x(server *srv, connection *con, void *p_d)
#define INIT_FUNC(x) \
static void *x(void)
#define FREE_FUNC SERVER_FUNC
#define TRIGGER_FUNC SERVER_FUNC
#define SETDEFAULTS_FUNC SERVER_FUNC
#define SIGHUP_FUNC SERVER_FUNC
#define SUBREQUEST_FUNC CONNECTION_FUNC
#define JOBLIST_FUNC CONNECTION_FUNC
#define PHYSICALPATH_FUNC CONNECTION_FUNC
#define REQUESTDONE_FUNC CONNECTION_FUNC
#define URIHANDLER_FUNC CONNECTION_FUNC
前面的三个宏(SERVER_FUNC, CONNECTION_FUNC和INIT_FUNC)定义了函数签名的模板。后面的一系列宏和plugin结构体中的函数指针对应,确定这些函数指针所对应的函数签名。
在进行插件开发的时候,插件中的函数签名要使用上面的宏来生成。这样可以保证接口的统一
。
最后,还要提一下plugin.c文件中的plugin_data
结构体:
#define PLUGIN_DATA size_t id
typedef struct {
PLUGIN_DATA;
} plugin_data;
这个结构体用来存放插件所需要使用的数据,这个结构体作为plugin结构体中函数指针的最后一个参数:void p_d传入对应的函数中。在plugin.c结构体中plugin_data的定义很简单,仅仅包含一个数据成员id。在mod_.c/h文件中,同样也包含有plugin_data结构体的定义。如:mod_cgi.c中,
typedef struct {
PLUGIN_DATA;
buffer_pid_t cgi_pid;
buffer *tmp_buf;
buffer *parse_response;
plugin_config **config_storage;
plugin_config conf;
} plugin_data;
这些定义有一个共通的特点,那就是第一个成员都是PLUGIN_DATA。这又是一个技巧。所有这些plugin_data相当于是plugin.c中plugin_data的子类。这些子类开始的部分和父类相同,这就允许子类的指针转换成父类指针,然后再转换回去,并保证数据不会丢失。这样,lighttpd所面对的插件数据接口是plugin.c中定义的plugin_data,当lighttpd在调用插件中的函数,并把数据传进去的时候,插件可以再把数据的类型还原回去。这样,对于lighttpd,所面对的数据接口就只有一个,插件所需要的数据可以不对lighttpd公开,这就很好的隐藏了数据。同时也简化了lighttpd的复杂度,提高了程序的扩展性。
收获:lighttpd通过宏模拟了面向对象中的继承、多态。
原文:http://www.cnblogs.com/kernel_hcy/archive/2010/03/11/1683814.html
前面讲了lighttpd插件系统的接口,下面我们来看看插件是怎么加载和初始化的。
lighttpd的插件是以动态链接库
的形式存在的。在服务器启动的时候,在初始化阶段将所有插件都加载进来。在server.c的main函数中,调用plugins_load函数来加载插件:
if (plugins_load(srv)) {
log_error_write(srv, __FILE__, __LINE__, "s",
"loading plugins finally failed");
plugins_free(srv);
server_free(srv);
return -1;
}
请读者注意一下这个函数调用的位置。这个函数是在服务器的初始化阶段进行调用的,并且该函数就在这调用了一次,其他地方没有再被调用过。虽然插件是以动态链接库的形式存在,但这些库是在服务器启动阶段一次性加载完毕,如果想再增加插件,只能配置好配置文件后重新启动服务器。
下面看一看plugins_load函数的实现:
#ifdef LIGHTTPD_STATIC
int plugins_load(server * srv)
{
plugin *p;
#define PLUGIN_INIT(x)\
p = plugin_init(); \
if (x ## _plugin_init(p)) { \
log_error_write(srv, __FILE__, __LINE__, "ss",#x, "plugin init failed" ); \
plugin_free(p); \
return -1;\
}\
plugins_register(srv, p);
#include "plugin-static.h"
return 0;
}
#else
//动态链接
int plugins_load(server * srv)
{
plugin *p;
int (*init) (plugin * pl);
const char *error;
size_t i;
for (i = 0; i < srv->srvconf.modules->used; i++)
{
//获得动态链接库的名称。
data_string *d = (data_string *) srv->srvconf.modules->data[i];
char *modules = d->value->ptr;
//库所在目录
buffer_copy_string_buffer(srv->tmp_buf, srv->srvconf.modules_dir);
buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN("/"));
//拼接库的名称。
buffer_append_string(srv->tmp_buf, modules);
buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN(".so"));
p = plugin_init();
//linux调用函数dlopen加载动态库
if (NULL == ( p->lib = dlopen(srv->tmp_buf->ptr, RTLD_NOW | RTLD_GLOBAL)))
{
log_error_write(srv, __FILE__, __LINE__, "sbs", "dlopen() failed for:", srv->tmp_buf, dlerror());
plugin_free(p);
return -1;
}
//调用动态库中的XXX_plugin_init函数。
//XXX是库的名称
buffer_reset(srv->tmp_buf);
buffer_copy_string(srv->tmp_buf, modules);
buffer_append_string_len(srv->tmp_buf, CONST_STR_LEN("_plugin_init"));
#if 1
//调用dlsym函数获得XXX_plugin_init函数的地址。
init = (int (*)(plugin *)) (intptr_t) dlsym(p->lib, srv->tmp_buf->ptr);
#else
//这句没有用
*(void **) (&init) = dlsym(p->lib, srv->tmp_buf->ptr);
#endif
if ((error = dlerror()) != NULL)
{
log_error_write(srv, __FILE__, __LINE__, "s", error);
plugin_free(p);
return -1;
}
//初始化插件
//在初始化的过程中,模块将自己所有的对外接口函数的入口地址都存入到p中。
if ((*init) (p))
{
log_error_write(srv, __FILE__, __LINE__, "ss", modules, "plugin init failed");
plugin_free(p);
return -1;
}
#if 0
log_error_write(srv, __FILE__, __LINE__, "ss", modules,
"plugin loaded");
#endif
plugins_register(srv, p);
}
return 0;
}
#endif //end of #ifdef LIGHTTPD_STATIC
上面的函数中删除了处理在windows下加载动态链接库的部分。有兴趣的读者可以自行查看源代码。下面全部讨论在linux下的实现。
这个函数作者编写了两个版本,从宏LIGHTTPD_STATIC可以看出,作者貌似是想提供一个加载静态链接库的版本。但是,该版本的函数并没有什么实质性的实现(在最新的版本中(1.4.26),该函数依然没有实现)。我们主要来看看后面的动态链接库的版本。
加载的过程如下:
(1)从配置文件中读取到动态链接库所在的文件夹和动态库的名称。
(2)创建一个plugin结构体的实例。
(3)调用dlopen函数加载动态库。在plugin结构体中保存返回的句柄。
(4)通过dlsym函数获得XXXXXX_plugin_init函数的地址。其中XXXXXX是配置文件中定义的这个插件的内容。
(5)调用XXXXXX_plugin_init函数。
(6)调用plugins_register函数注册插件。
XXXXXX_plugin_init函数在插件中定义,对这个插件进行初始化。其中,最重要的部分就是对plugin结构体中那一系列的函数指针进行赋值。如,mod_cgi模块中:
int mod_cgi_plugin_init(plugin * p)
{
p->version = LIGHTTPD_VERSION_ID;
p->name = buffer_init_string("cgi");
p->connection_reset = cgi_connection_close_callback;
p->handle_subrequest_start = cgi_is_handled;
p->handle_subrequest = mod_cgi_handle_subrequest;
#if 0
p->handle_fdevent = cgi_handle_fdevent;
#endif
p->handle_trigger = cgi_trigger;
p->init = mod_cgi_init;
p->cleanup = mod_cgi_free;
p->set_defaults = mod_fastcgi_set_defaults;
p->data = NULL;
return 0;
}
plugins_register函数是将plugin结构体的指针存放在server结构体的plugins数组中。
插件加载完毕之后,main函数有处理了一些初始化的工作,然后,调用plugins_call_init函数对所有插件进行初始化:
if (HANDLER_GO_ON != plugins_call_init(srv))
{
log_error_write(srv, __FILE__, __LINE__, "s",
"Initialization of plugins failed. Going down.");
plugins_free(srv);
network_close(srv);
server_free(srv);
return -1;
}
plugins_call_init函数在plugin.c文件中:
handler_t plugins_call_init(server * srv)
{
size_t i;
plugin **ps;
ps = srv->plugins.ptr;
/* * fill slots */
srv->plugin_slots = calloc(PLUGIN_FUNC_SIZEOF, sizeof(ps));
for (i = 0; i < srv->plugins.used; i++)
{
size_t j;
/* * check which calls are supported */
plugin *p = ps[i];
/** * 对所有的plugin进行登记造册。这个宏在后文中着重讲解。 */
#define PLUGIN_TO_SLOT(x, y) \
if (p->y) { \
plugin **slot = ((plugin ***)(srv->plugin_slots))[x]; \
if (!slot) { \
slot = calloc(srv->plugins.used, sizeof(*slot));\
((plugin ***)(srv->plugin_slots))[x] = slot; \
} \
for (j = 0; j < srv->plugins.used; j++) { \
if (slot[j]) continue;\
slot[j] = p;\
break;\
}\
}
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_CLEAN, handle_uri_clean);
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_RAW, handle_uri_raw);
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_REQUEST_DONE, handle_request_done);
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE, handle_connection_close);
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_TRIGGER, handle_trigger);
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SIGHUP, handle_sighup);
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST, handle_subrequest);
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST_START, handle_subrequest_start);
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_JOBLIST, handle_joblist);
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_DOCROOT, handle_docroot);
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_PHYSICAL, handle_physical);
PLUGIN_TO_SLOT(PLUGIN_FUNC_CONNECTION_RESET, connection_reset);
PLUGIN_TO_SLOT(PLUGIN_FUNC_CLEANUP, cleanup);
PLUGIN_TO_SLOT(PLUGIN_FUNC_SET_DEFAULTS, set_defaults);
#undef PLUGIN_TO_SLOT
//对插件进行初始化,调用其初始化函数
if (p->init)
{
if (NULL == (p->data = p->init()))
{
log_error_write(srv, __FILE__, __LINE__, "sb", "plugin-init failed for module", p->name);
return HANDLER_ERROR;
}
/* * used for con->mode,DIRECT==0,plugins above that */
((plugin_data *) (p->data))->id = i + 1;
//这里检测插件的版本是否和当前服务器的版本相同。
//这里保证如果以后插件的接口发生了改变,不会造成服务器崩溃。
if (p->version != LIGHTTPD_VERSION_ID)
{
log_error_write(srv, __FILE__, __LINE__, "sb","plugin-version doesn't match lighttpd-version for", p->name);
return HANDLER_ERROR;
}
}
else
{
p->data = NULL;
}
}
return HANDLER_GO_ON;
}
整个函数中,这个宏是重点:
#define PLUGIN_TO_SLOT(x, y) \
if (p->y) { \
plugin **slot = ((plugin ***)(srv->plugin_slots))[x]; \
if (!slot) { \
slot = calloc(srv->plugins.used, sizeof(*slot));\
((plugin ***)(srv->plugin_slots))[x] = slot; \
} \
for (j = 0; j < srv->plugins.used; j++) { \
if (slot[j]) continue;\
slot[j] = p;\
break;\
}\
}
在结构体server中,plugin_slots是一个void指针。在这个宏中可以看到,plugin_slots被转换成了plugin结构体的三级指针。朝前看:
srv->plugin_slots = calloc(PLUGIN_FUNC_SIZEOF, sizeof(ps));
plugin_slots是一个存放ps类型数据的数组,数组的长度为PLUGIN_FUNC_SIZEOF。PLUGIN_FUNC_SIZEOF在后面说明。ps的类型是plugin结构体的二级指针。在上面的宏中,我们看到,plugin_slots是一个数组,随后的if分支中可以看到,plugin_slots的元素也是数组。因此,plugin_slots是一个二维数组,数组中的元素是plugin结构体的指针,并且,plugin_slots是动态创建的。
下面在来看PLUGIN_FUNC_SIZEOF,它定义在下面的枚举结构中:
typedef enum
{
PLUGIN_FUNC_UNSET,
PLUGIN_FUNC_HANDLE_URI_CLEAN,
PLUGIN_FUNC_HANDLE_URI_RAW,
PLUGIN_FUNC_HANDLE_REQUEST_DONE,
PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE,
PLUGIN_FUNC_HANDLE_TRIGGER,
PLUGIN_FUNC_HANDLE_SIGHUP,
PLUGIN_FUNC_HANDLE_SUBREQUEST,
PLUGIN_FUNC_HANDLE_SUBREQUEST_START,
PLUGIN_FUNC_HANDLE_JOBLIST,
PLUGIN_FUNC_HANDLE_DOCROOT,
PLUGIN_FUNC_HANDLE_PHYSICAL,
PLUGIN_FUNC_CONNECTION_RESET,
PLUGIN_FUNC_INIT,
PLUGIN_FUNC_CLEANUP,
PLUGIN_FUNC_SET_DEFAULTS,
PLUGIN_FUNC_SIZEOF
} plugin_t;
从枚举结构的名字可以看出,这个枚举类型定义了插件的功能的类型,对应着plugin结构体中那些函数指针。而最后一个量,PLUGIN_FUNC_SIZEOF,根据枚举类型的特点,正好是上面所定义的类型的数量。这是一个很常用的技巧,这样可以保证在增加类型的时候,保证程序中可以得到正确的类型数量,而不要去改动那些需要类型数量的代码。
接着回到上面的宏,这个宏有两个参数,有后面的使用可以看出,第一个参数x是枚举类型plugin_t,第二个参数x对应的在plugin结构体中函数指针的名称。
因此,上面的宏的作用就是:根据参数x所指定的插件功能类型,判断插件p中是否含有功能x,也就是指针p->y是否非NULL。如果包含功能x,则将指针p添加到数组plugin_slots的第x行中。内层的if语句是为了判断plugin_slots的第x行是否存在,不存在则创建之。for循环是为了将p添加到plugin_slots的第x行的末尾。
例如,插件*p1, *p2, *p3,在执行完后面那些宏调用之后,会形成一个如下的表:
从表中可以看出,插件p1包含所有的功能,也就是实现了plugin结构体中函数指针对应的所有函数。插件p2不包含功能HANDLE_SUBREQUEST,HANDLE_SUBREQUEST_START和HANDLE_REQUEST_DONE功能,插件p3中不包含
HANDLE_TRIGGER,HANDLE_SIGHUP和CONNECTION_RESET功能。当然,这仅仅是举个例子。
上面的例子中的表就是plugins_slots。这个宏和后面的宏调用可以看成是给所有的插件“登记造册”。通过plugins_slots数组,可以快速的确定某个功能都有那些插件实现了,这方便后面的插件的调用。
完成这些宏调用后,初始化函数测试插件是否定义了init函数。如果定义了则调用之。这里的init函数和前面加载函数中的XXX_plugin_init函数不一样。XXX_plugin_init初始化函数是初始化plugin结构体,核心工作是对plugin结构体中的函数指针进行赋值。而init函数则是初始化这个插件对应的plugin_data结构体,分配数据空间,初始化成员变量并返回其指针。
如:mod_cgi.c的init函数定义为,
INIT_FUNC(mod_cgi_init)
{
plugin_data *p;
p = calloc(1, sizeof(*p));
assert(p);
p->tmp_buf = buffer_init();
p->parse_response = buffer_init();
return p;
}
返回的指针存放在plugin结构体中的data成员中。然后对data中的id进行赋值。接着,检查插件的版本是否和当前服务器的版本相同。
如果插件没有定义init函数,则data赋值NULL。
至此,插件的加载和初始化工作全部完成了。下面总结一下整个加载和初始化的过程:
(1)根据配置文件从相应的目录中加载插件的动态连接库。
(2)获得插件动态库中XXX_plugin_init函数的入口地址并调用之。此函数对plugin结构体进行赋值。
(3)在server结构体中注册插件。
(4)调用plugins_call_init初始化插件。
(5)通过上面那个宏及后面一系列的宏调用,将插件登记造册,记录在server结构体的plugins_slots成员中。plugins_slot是一个二维数组,数组成员是plugin结构体指针。
(6)最后调用插件的init函数初始化各自的plugin_data结构体。
下一篇中,将介绍一下plugin.c中的宏PLUGIN_TO_SLOT。
http://www.cnblogs.com/kernel_hcy/archive/2010/03/14/1685777.html
前面讲了lighttpd插件系统的加载和初始化,这一篇中,将介绍一下plugin.c中的宏PLUGIN_TO_SLOT。
在将PLUGIN_TO_SLOT宏之前,我们先来看看lighttpd中插件系统的对外接口。这个接口所对的“外”指的是lighttpd服务器。前面已经提到,在运行的过程中,lighttpd不知道所加载的插件都是干什么用的,只知道这些插件所实现的接口,也就是在plugin结构体中那些函数指针有哪些对于某个插件是NULL,哪些是具体的函数地址。
既然lighttpd只知道这些,那么它又是怎样调用这些插件的呢?
答案就在plugin.h文件中的下面一系列函数声明:
handler_t plugins_call_handle_uri_raw(server * srv, connection * con);
handler_t plugins_call_handle_uri_clean(server * srv,connection * con);
handler_t plugins_call_handle_subrequest_start(server * srv,connection * con);
handler_t plugins_call_handle_subrequest(server * srv,connection * con);
handler_t plugins_call_handle_request_done(server * srv,connection * con);
handler_t plugins_call_handle_docroot(server * srv,connection * con);
handler_t plugins_call_handle_physical(server * srv,connection * con);
handler_t plugins_call_handle_connection_close(server * srv,connection * con);
handler_t plugins_call_handle_joblist(server * srv,connection * con);
handler_t plugins_call_connection_reset(server * srv,connection * con);
handler_t plugins_call_handle_trigger(server * srv);
handler_t plugins_call_handle_sighup(server * srv);
handler_t plugins_call_init(server * srv);
handler_t plugins_call_set_defaults(server * srv);
handler_t plugins_call_cleanup(server * srv);
这些函数就是插件系统对外的接口。在运行过程中,lighttpd靠调用上面的这些函数调用插件。比如:在server.c的main函数中,就调用了plugins_call_set_defaults函数:
if (HANDLER_GO_ON != plugins_call_set_defaults(srv))
{
log_error_write(srv, __FILE__, __LINE__, "s",
"Configuration of plugins failed. Going down.");
plugins_free(srv);
network_close(srv);
server_free(srv);
return -1;
}
如果你使用ctags+vim看代码,当在这些函数的调用处想跳转到定义时发现ctags没有找到这些函数的定义。难道这些函数没有实现?这显然是不会的。其实,这正是由于本文的重点───PLUGIN_TO_SLOT宏造成的。
打开plugin.c文件,会发现这几行代码:
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_CLEAN, handle_uri_clean)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_RAW, handle_uri_raw)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_REQUEST_DONE,handle_request_done)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_CONNECTION_CLOSE,handle_connection_close)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST, handle_subrequest)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SUBREQUEST_START,handle_subrequest_start)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_JOBLIST, handle_joblist)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_DOCROOT, handle_docroot)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_PHYSICAL, handle_physical)
PLUGIN_TO_SLOT(PLUGIN_FUNC_CONNECTION_RESET, connection_reset)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_TRIGGER, handle_trigger)
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_SIGHUP, handle_sighup)
PLUGIN_TO_SLOT(PLUGIN_FUNC_CLEANUP, cleanup)
PLUGIN_TO_SLOT(PLUGIN_FUNC_SET_DEFAULTS, set_defaults)
再看看PLUGIN_TO_SLOT宏的源码:
#define PLUGIN_TO_SLOT(x, y) \
handler_t plugins_call_##y(server *srv, connection *con) {\
plugin **slot;\
size_t j;\
if (!srv->plugin_slots) return HANDLER_GO_ON;\
slot = ((plugin ***)(srv->plugin_slots))[x];\
if (!slot) return HANDLER_GO_ON;\
for (j = 0; j < srv->plugins.used && slot[j]; j++) { \
plugin *p = slot[j];\
handler_t r;\
switch(r = p->y(srv, con, p->data)) {\
case HANDLER_GO_ON:\
break;\
case HANDLER_FINISHED:\
case HANDLER_COMEBACK:\
case HANDLER_WAIT_FOR_EVENT:\
case HANDLER_WAIT_FOR_FD:\
case HANDLER_ERROR:\
return r;\
default:\
log_error_write(srv, __FILE__, __LINE__, "sbs", #x, p->name, "unknown state");\
return HANDLER_ERROR;\
}\
}\
return HANDLER_GO_ON;\
}
这下明白了吧。上面那些函数是由这些宏调用模板化生成的。由于这些函数的代码具有很高的相似度(仅仅是调用的插件函数不同),通过宏模板进行生成,可以节约大量的代码,同时又不容易出错。这类似于C++中的模板。注:C语言预处理器运算符##为宏扩展提供了一种连接实际参数的手段。如果替换文本中的参数与##相邻,则改参数将被实际参数替换,##与前后的空白符将被删除,并对替换后的结果重新扫描。(摘自:C语言程序设计 K&R)在这里,plugins_call_和实参y拼接成函数名。
下面我们着重分析一下PLUGIN_TO_SLOT宏的内容:
根据后面的宏调用可以看出,参数x是plugin_t枚举类型,y是plugin结构体中函数指针成名的名字。在宏调用中,x和y是相对应的。
PLUGIN_TO_SLOT宏首先通过参数y拼接出函数名。
如:这个宏调用
PLUGIN_TO_SLOT(PLUGIN_FUNC_HANDLE_URI_CLEAN, handle_uri_clean),拼接得到的函数名为plugins_call_handle_uri_clean,加上参数和返回值,正好是plugin.h中的函数handler_t plugins_call_handle_uri_clean(server * srv, connection * con)。其他的以此类推。
这条语句slot = ((plugin *)(srv->plugin_slots))[x];通过宏参数x得到plugin_slots的第x列。plugin_slots的结构在前面的文章中已经讲解过了。不熟悉的读者可以再回头看看。这列中包含所有具有参数x所对应的功能的插件的指针。也就是,plugin结构体的成员变量y不为NULL的所有plugin实例的指针。接着,在for循环中,调用这些插件的y函数,就是这句:switch(r = p->y(srv, con, p->data))。后面就是一些返回值和错误处理了。
读者也许早就发现在plugin.c文件中有两个PLUGIN_SLOT宏。猛地一看没有任何差别。确实,着两个宏基本上都一样,只有一点不同:第二个宏的switch语句中调用y函数时,参数少了一个con:switch(r = p->y(srv, p->data))。这是他们唯一的差别。读者可以看看plugin结构体中的函数指针,有四个是两个参数的(server * srv, void p_d),其他都是三个参数(server srv, connection * con, void *p_d)。
在这里有一个很有意思的问题。我们注意到,在所有plugins_call_XXX函数中,由于都是通过上面的PLUGIN_TO_SLOT宏生成的。那么,这些函数在被lighttpd进行调用的时候,无论来的请求想要干什么,lighttpd都会逐一调用所有插件对应的函数。这就有一个问题了:如果所调用的第一个插件所具有的功能不是这个连接所想要的功能,这不就出错了么?但是反过来想想,既然要隐藏插件的所有细节,那么lighttpd也就无从知道哪些插件是干什么的。因此,对与一个连接,lighttpd也就不会知道使用哪个插件进行处理。因此,这样做也是没办法的事情。虽然这样会影响服务器的效率,毕竟每次都调用了很多无用的函数,但却有助于服务器的扩展。效率换扩展性,关键要把握一个度。
那么lighttpd无法确定连接该用哪个插件来处理,那么插件自己就必须知道哪些连接是自己该处理的。这涉及到连接的细节处理,我们先暂且放一放。从下一篇开始,将讲解一下fd event系统,在分析具体的连接的处理时,在讲解这个问题。
下一篇,介绍lighttpd中的fdevents结构体….
收获:可以使用C语言的来模拟面向对象:继承(宏)、虚函数(函数指针)、虚机类(包含公共成员变量,函数指针)、多态(函数指针)、模板(宏)、私有成员(static关键字)、暴露的借口放在头文件中。