分类 wifidog认证 下的文章

wifidog认证源码分析Lighttpd1.4.20源码分析之插件系统(2)---插件的加载和初始化(2)

前面提到了main函数调用plugins_call_init函数对所有插件进行初始化,下面接着介绍
plugins_call_init函数在plugin.c文件中:

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> 1 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;
}

整个函数中,这个宏是重点:

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> 1 #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,它定义在下面的枚举结构中:

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> 1 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,在执行完后面那些宏调用之后,会形成一个如下的表:
1.png

从表中可以看出,插件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函数定义为,

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->1 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.wifidog.pro/2015/04/17/wifidog%E8%AE%A4%E8%AF%81%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90lighttpd%E6%8F%92%E4%BB%B6%E5%8A%A0%E8%BD%BD%E5%88%9D%E5%A7%8B%E5%8C%96-2.html 整理编辑,转载请注明出处

wifidog认证源码分析Lighttpd1.4.20源码分析之插件系统(2)---插件的加载和初始化(1)

前面讲了lighttpd插件系统的接口,下面我们来看看插件是怎么加载 和初始化的。
lighttpd的插件是以动态链接库的形式存在的。在服务器启动的时候,在初始化阶段将所有插件都加载进来。在server.c中的main函数中,加载插件是调用plugins_load函数:

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->1 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函数的实现:

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> 1 #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模块中:

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> 1 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函数对所有插件进行初始化:

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->1 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;
}

本文章由 http://www.wifidog.pro/2015/04/17/wifidog%E8%AE%A4%E8%AF%81%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90lighttpd%E5%8A%A0%E8%BD%BD%E5%92%8C%E5%88%9D%E5%A7%8B%E5%8C%96-1.html 整理编辑,转载请注明出处

wifidog认证源码分析Lighttpd1.4.20源码分析之插件系统(1)---plugin结构体和插件接口

在lighttpd中,使用插件的形式来增加服务的功能。同时,lighttpd提供了一个插件的公共接口给开发者,方便第三方提供额外的插件。Lighttpd的插件接口主要提供在plugin.h文件中。其中,plugin结构体是最核心的部分。
plugin结构体的定义如下:

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> 1 
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文件。
在plugin.h中,plugin结构体的定义后面还有一堆的函数声明:

int plugins_load(server * srv);
void plugins_free(server * srv);

这两个很明显是加载和释放插件函数。

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> 1 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函数是处理一些配置问题,暂不讨论。
在plugin.h文件中还定义了一些宏:

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> 1 #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()

#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文件中的结构体:

typedef struct 
{
    PLUGIN_DATA;
} plugin_data;

PLUGIN_DATA是一个宏,定义为:#define PLUGIN_DATA size_t id。
这个结构体用来存放插件所需要使用的数据,这个结构体作为plugin结构体中函数指针的最后一个参数:void p_d传入对应的函数中。在plugin.c结构体中plugin_data的定义很简单,仅仅包含一个数据成员id。在mod_.c/h文件中,同样也包含有plugin_data结构体的定义。如:mod_cgi.c中,

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->1 typedef struct {
    PLUGIN_DATA;
    buffer_pid_t cgi_pid;
    buffer *tmp_buf;
    buffer *parse_response;
    plugin_config **config_storage;
    plugin_config conf;
} plugin_data;

在mod_cml.h中:

Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->1 typedef struct {
    PLUGIN_DATA;
    buffer *basedir;
    buffer *baseurl;
    buffer *trigger_handler;
    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.wifidog.pro/2015/04/16/wifidog%E8%AE%A4%E8%AF%81%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90Lighttpd-plugin%E7%BB%93%E6%9E%84%E4%BD%93%E5%92%8C%E6%8F%92%E4%BB%B6%E6%8E%A5%E5%8F%A3-1.html 整理编辑,转载请注明出处

wifidog认证源码分析lighttpd1.4.20源码分析 -----工作模型

lighttpd的工作模型很简单──一个主进程加多个工作进程的多进程模型,也就是所谓的watcher-worker模型。
  整个程序的入口(main函数)在server.c文件中。在main函数的开始部分必然是处理参数和各种繁杂的初始化工作。其中有两个地方要重点看一起。第一个是下面的语句:

if (test_config) //没有进行任何测试。。。
{
     printf("Syntax OK\n");
}

这个If语句是为了判断配置文件的语法是否合法。但是,明显,没有进行任何的测试,而仅仅是输出了一句话。
  第二个是函数daemonize()。这个函数的作用是使程序进入daemon。函数的详细内容如下:

static void daemonize(void)
{
        /*
         * 忽略和终端读写有关的信号
         */
#ifdef SIGTTOU
        signal(SIGTTOU, SIG_IGN);
#endif    
#ifdef SIGTTIN
        signal(SIGTTIN, SIG_IGN);
#endif
#ifdef SIGTSTP
        signal(SIGTSTP, SIG_IGN);
#endif
        if (0 != fork()) /* 产生子进程,父进程退出 */
            exit(0);
        if (-1 == setsid())/* 设置子进程的设置ID */
            exit(0);
        signal(SIGHUP, SIG_IGN);/* 忽略SIGHUP信号 */
        if (0 != fork())/* 再次产生子进程,父进程退出 */
            exit(0);
        if (0 != chdir("/"))/* 更改工作目录为根目录 */
            exit(0);
}

  这里作者使用了标准的产生*nix daemon的方法。两次调用fork并退出父进程。具体的原因读者可以参阅《Unix环境高级编程》(APUE)中有关daemon的讲解部分。
  顺着main函数继续向下走,沿途的各种初始化工作尽可忽略。下面的语句是本文的重点!

* 下面程序将产生多个子进程。这些子进程成为worker,
     * 也就是用于接受处理用户的连接的进程。而当前的主进程将
     * 成为watcher,主要工作就是监视workers的工作状态,
     * 当有worker因为意外而退出时,产生新的worker。
     * 在程序退出时,watcher负责停止所有的workers并清理资源。
     */
        int child = 0; //用来标记这个进程是子进程还是父进程。
           //当子进程返回到这个while循环的开始的时候,由于标记
        //进程是子进程,流程直接跳出while循环执行后面的程序。
        //而对于父进程,则继续产生子进程。
        while (!child && !srv_shutdown && !graceful_shutdown)
        {
            if (num_childs > 0) //watcher继续产生worker
            {
                switch (fork())
                {
                case -1:    //出错
                    return -1;
                case 0:     //子进程进入这个case
                    child = 1;
                    break;
                default:    //父进程进入这个case
                    num_childs--;
                    break;
                }
            } 
            else         //watcher
            {
                /**
                 * 当产生了足够的worker时,watcher就在这个while
                 * 中不断的循环。
                 * 一但发现有worker退出(进程死亡),立即产生新的worker。
                 * 如果发生错误并接受到SIGHUP信号,向所有的进程
                 *(父进程及其子进程)包括自己发送SIGHUP信号。
                 * 并退出。
                 */
                int status;

                if (-1 != wait(&status))
                {
                    /** 
                     * one of our workers went away 
                     */
                    num_childs++;
                } 
                else
                {
                    switch (errno)
                    {
                    case EINTR:
                        /**
                         * if we receive a SIGHUP we have to close our 
                         * logs ourself as we don't 
                         * have the mainloop who can help us here
                         */
                        if (handle_sig_hup)
                        {
                            handle_sig_hup = 0;
                            log_error_cycle(srv);
                            /**
                             * forward to all procs in the process-group
                             * 向所有进程发送SIGHUP信号。(父进程及其子进程)
                             * we also send it ourself
                             */
                            if (!forwarded_sig_hup)
                            {
                                forwarded_sig_hup = 1;
                                kill(0, SIGHUP);
                            }
                        }
                        break;
                    default:
                        break;
                    }end of switch (errno)...
                }//end of if (-1 != wait(&status)) ...
            }//end of if (num_childs > 0)...
        }// end of while(!child...

在正常的运行过程中,watcher进程是不会退出上面的while循环。一旦退出了这个循环,那么也就意为着整个程序退出了。
另外,woker的数量可以在配置文件中进行配置。
子进程,也就是worker退出了上面的while循环后就开始处理连接请求等各种工作。
在子进程的一开始,还是各种初始化工作,包括fd时间处理器的初始化(fdevent_init(srv->max_fds + 1, srv->event_handler)),stat cache初始化(stat_cache_init())等。子进程工作在一个大while循环中。
while的工作流程如下:
1、判断连接是否断开。如果断开,则调用处理程序进行处理并重新开始新一轮的日志记录。
2、判断是否接受到了alarm函数发出的信号。接受到信号后,判断服务器记录的时间是否和当前时间相同。如果相同,说明时间还没有过一秒,继续处理连接请求。如果不相同,则时间已经过了一秒。那么,服务器则触发插件,清理超时连接,清理stat-cache缓存。这理里面最重要的是处理超时连接。程序中通过一个for循环查询所有的连接,比较其idle的时间和允许的最大idle时间来判断连接是否超时。如果连接超时,则让连接进入出错的状态(connection_set_state(srv, con, CON_STATE_ERROR);)。
3、判断服务器socket连接是否失效。如果失效了,则在不是服务器过载的情况下将所有连接重新加入的fdevent中。为什么服务器socket会失效呢?可以看到,在后面判断出服务器过载后,即标记了socket连接失效。srv->sockets_disabled = 1;
4、如果socket没有失效,判断服务器是否过载。如果过载了,则关闭所有连接,清理服务器并退出服务器。
5、分配文件描述符。
6、启动事件轮询。等待各种IO时间的发生。包括文件读写,socket请求等。
7、一旦有事件发生,调用相应的处理函数进行处理。
8、最后,检查joblist中是否有未处理的job并处理之。
至此,一次循环结束了。然后,从头开始继续循环直到服务器关闭。

在处理IO事件的时候,程序进入下面的循环:

do
{
        fdevent_handler handler; //事件处理函数指针
        void *context;
        handler_t r;
        //获得下一个事件的标号
        fd_ndx = fdevent_event_next_fdndx(srv->ev, fd_ndx);
        //获得这个事件的具体类型。
revents = fdevent_event_get_revent(srv->ev, fd_ndx);
        //获得事件对应的文件描述符
fd = fdevent_event_get_fd(srv->ev, fd_ndx);
        //获得事件处理函数的指针
handler = fdevent_get_handler(srv->ev, fd);
        //获得事件的环境
context = fdevent_get_context(srv->ev, fd);
        /**
         * 这里,调用请求的处理函数handler处理请求!
         * 这才是重点中的重点!!
         */
        switch (r = (*handler) (srv, context, revents))
        {
        case HANDLER_FINISHED:
        case HANDLER_GO_ON:
        case HANDLER_WAIT_FOR_EVENT:
        case HANDLER_WAIT_FOR_FD:
            break;
        case HANDLER_ERROR:
            /*
             * should never happen 
             */
            SEGFAULT();
            break;
        default:
            log_error_write(srv, __FILE__, __LINE__, "d", r);
            break;
        }
    }while (—n > 0);

这个循环是worker进程的核心部分。这里由于作者对IO系统的出色的封装。我们不须要理解这些函数的作用就可知道连接的处理流程。在程序中,作者使用回调函数轻松的解决掉了处理工种事件时判断处理函数的问题。代码优雅而高效。在lighttpd中,作者使用了大量的回调函数,这使得代码清晰易懂,效率也很高。
  有一点值得注意。程序在处理连接超时的时候是每一秒中轮询所有的连接,判断其是否超时。这必然会降低服务器的效率。在实际的运行中,确实会对lighttpd的效率造成一定的影响。
  lighttpd使用的watcher-worker模型虽然简单,但是在实际的运行过程中也非常的高效。有的时候,简单并不一定就不高效。

本文章由 http://www.wifidog.pro/2015/04/16/wifidog%E8%AE%A4%E8%AF%81%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90lighttpd%E5%B7%A5%E4%BD%9C%E6%A8%A1%E5%9E%8B.html 整理编辑,转载请注明出处