分类 wifidog原理 下的文章

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 整理编辑,转载请注明出处

wifidog HTTP Lighttpd1.4.20源码分析之buffer.c(h)--------字符串内存管理(1)

  在web服务器中,通常要设计很多字符串的处理。比如客户端请求的 URI地址、发送的 query参数、post 提交的数据等等都是一串字符。因此,提供对字符串的灵活高效的处理,对lighttpd的效率至关重要。
  在lighttpd中,buffer提供了对字符串的处理。在buffer.h中,有如下的数据结构定义:
  //定义buffer

typedef struct 
{
    char *ptr;     //指向存储空间,一个字符串组
    size_t used;     //buffer中数据的长度
    size_t size;     //buffer的长度
} buffer;

  上面的结构体定义了lighttpd中,对字符串处理的基本结构。其具体含义如上。
Code

//定义buffer数组
typedef struct 
{
    buffer **ptr;     //buffer指针数组
    size_t used;     //buffer数组中数据的个数
    size_t size;     //buffer数组的大小
} buffer_array;
/*
 * 这个比较有意思
 */
typedef struct 
{
    char *ptr;
    size_t offset;    /* input-pointer */
    size_t used;    /* output-pointer */
    size_t size;
}read_buffer

这个结构体比较有意思,具体干什么的我还没有发现。。。不过从其定义中猜测,应该和输入输出缓冲有关。

围绕buffer结构体和buffer_array结构体,在buffer.h中定义了很多操作函数,其具体的作用将在下文中一一说明。其中比较有意思有技巧的函数还将就其实现代码进行分析。大部分的函数都很简单,读者可以自行阅读。
首先是buffer_array的操作函数:
1、buffer_array *buffer_array_init(void);
  初始化一个buffer_array,返回其指针并分配空间。
2、void buffer_array_free(buffer_array * b);
  释放b指向的buffer_array的空间。
3、void buffer_array_reset(buffer_array * b);
重置buffer_array。并递归重置数组中的数据。
4、buffer *buffer_array_append_get_buffer(buffer_array * b);
  返回数组中第一个未使用的buffer结构体的指针。如果数组已满,则对数组进行扩容,并初始化第一个为使用的buffer的指针。

下面是buffer的操作函数:
1、buffer *buffer_init(void);
初始化一个buffer。
2、buffer *buffer_init_buffer(buffer * b);
用b初始化一个buffer。相当于复制b。
3、buffer *buffer_init_string(const char *str);
用str初始化一个buffer。把str指向的字符串复制到buffer中。
4、void buffer_free(buffer * b);
释放buffer的空间。
5、void buffer_reset(buffer * b);
这个比较有意思。重置b所指向的buffer结构。一般情况下,都是把buffer数据区中ptr指向的字符数组的第一个元素ptr[0]设置为’\0’,然后把buffer的size设置为0。但当buffer的大小超过BUFFER_MAX_REUSE_SIZE时,则直接释放buffer的ptr指向的空间并把size设置为0。
6、int buffer_prepare_copy(buffer * b, size_t size);
为复制准备size大小的空间。如果b的空间大于size则仅仅将b的使用空间used设置为0.如果b的空间小于size,则重新分配size大小的空间。另外,为了防止内存碎片,在每次重新分配空间时,都将所分配的空间凑成BUFFER_PIECE_SIZE的整数倍:

1 b->size += BUFFER_PIECE_SIZE - (b->size % BUFFER_PIECE_SIZE);

7、int buffer_prepare_append(buffer * b, size_t size);
  为追加size大小的数据准备空间。操作和上一个函数查不多。
8、int buffer_copy_string(buffer * b, const char *s);
  将字符串s复制到b中。

Code
int buffer_append_string(buffer *b, const char *s)
{
    size_t s_len;
    if (!s || !b) return -1;
    s_len = strlen(s);
    buffer_prepare_append(b, s_len + 1);
    /*
     * 如果buffer中原来有数据(字符串),那么最后一个字符是NULL,
     * 在复制的时候,要覆盖这个字符。
      * 但当buffer为空时,就不需要覆盖NULL字符,因此,需要加一,
     * 以便和有数据的情况下处理相同。
     */
    if (b->used == 0)
        b->used++;
    //覆盖原来数据最后一个字符NULL,同时,也将s中的NULL复制到b中。
    memcpy(b->ptr + b->used - 1, s, s_len + 1);
    b->used += s_len;
    return 0;
}

9、int buffer_copy_string_len(buffer * b, const char *s, size_t s_len);
  将字符串s复制到b中。s_len是s的长度。s被看作是一个不以'\0'结尾的字符串,s_len是s的长度。最终b中的数据以'\0'结尾。也就是说,如果s的结尾是'\0',那么,最终,b中的数据末尾有两个'\0',而且b中used表示的数据长度,包括其中一个'\0'!
10、int buffer_copy_string_buffer(buffer * b, const buffer * src);
  将src中的数据复制到b中。
11、int buffer_copy_string_hex(buffer * b, const char *in ,size_t in_len);
  将字符串In转化成十六进制形式,复制到b中。
12、int buffer_copy_long(buffer * b, long val);
  将val以字符串的形式复制到b中。
13、int buffer_copy_memory(buffer * b, const char *s, size_t s_len);
  复制s指向的内存区域中的数据到b中。
14、int buffer_append_string(buffer * b, const char *s);
  将字符串s追加大b中。
15、int buffer_append_string_len(buffer * b, const char *s, size_t s_len);
  将字符串s追加到b中。s_len为s的长度。
  具体的处理与上面的复制函数差不多。
16、int buffer_append_string_buffer(buffer * b, const buffer * src);
  将src的数据追加到b中。
17、int buffer_append_string_lfill(buffer * b, const char *s, size_t maxlen);
  这个函数在buffer.c中没有实现。
18、int buffer_append_string_rfill(buffer * b, const char *s, size_t maxlen);
  将字符串s追加到b中。其中maxlen为字符串s的最大长度。如果
  字符串s的长度小于maxlen,那么追加空格,使其长度达到maxlen。在 函数实现中。如果s的长度大于maxlen,则可能溢出。。。
19、int buffer_append_long_hex(buffer * b, unsigned long len);
  将无符号长整型value转化成对应的十六进制的字符串形式。并将字符串复制到b中。其中涉及到数值转16进制的问题。代码如下:

Code
static const char hex_chars[] = "0123456789abcdef";
int buffer_append_long_hex(buffer *b, unsigned long value) 
{
    char *buf;
    int shift = 0;
    unsigned long copy = value;
    //计算十六进制表示的value的长度
    while (copy) 
    {
        copy >>= 4;
        shift++;
    }
    if (shift == 0)
        shift++;
    /*
     * 保证追加的字符串为偶数位。
     * 如若不是偶数位,则在最前面加一个'0'.
     */
    if (shift & 0x01)
        shift++;
    buffer_prepare_append(b, shift + 1);//最后一个'\0'
    if (b->used == 0)
        b->used++;
    //buf指向开始存放的位置
    buf = b->ptr + (b->used - 1);
    b->used += shift;
    /*
     * 每四位一组,转化value为十六进制形式的字符串
     */
    shift <<= 2;
    while (shift > 0) 
    {
        shift -= 4;
        *(buf++) = hex_chars[(value >> shift) & 0x0F];
    }
    *buf = '\0';
    return 0;
}

20、int buffer_append_long(buffer * b, long val);
  将val以字符串的形式追加大b中。

  下面的宏定义使用来处理off_t和long类型。
  如果long和off_t相同,则用处理long的函数来处理off_t。如果不相同,则另行定义off_t的处理函数。

Code
#if defined(SIZEOF_LONG) && (SIZEOF_LONG == SIZEOF_OFF_T)
#define buffer_copy_off_t(x, y)        buffer_copy_long(x, y)
#define buffer_append_off_t(x, y)    buffer_append_long(x, y)
#else
int buffer_copy_off_t(buffer * b, off_t val);
int buffer_append_off_t(buffer * b, off_t val);
#endif

22、int buffer_append_memory(buffer * b, const char *s, size_t s_len);
  将s指向的内存区的数据复制到b中,s_len是s的长度。
23、char *buffer_search_string_len(buffer * b, const char *needle, size_t len);
  判断b中是否含有字符串needle,needle的长度为len。如果存在,则返回needle在b中的指针位置,否则返回NULL
24、int buffer_is_empty(buffer * b);
  判断b是否为空。
25、int buffer_is_equal(buffer * a, buffer * b);
  判断a和b中的数据是相同。
26、int buffer_is_equal_right_len(buffer * a, buffer * b, size_t len);
  判断b1和b2中,最右边的len个字符是否相同。
27、int buffer_is_equal_string(buffer * a, const char *s, size_t b_len);
  b中的数据是否等于s,b_len为s的长度。
28、int buffer_caseless_compare(const char *a, size_t a_len,const char *b, size_t b_len);
  比较字符串a和b,忽略大小写。

Code
/** 
* simple-assumption:
* most parts are equal and doing a case conversion needs time
* 假设比较的部分相同的较多且大小写转换需要时间。 
*/
int buffer_caseless_compare(const char *a, size_t a_len, const char *b, size_t b_len) 
{
        size_t ndx = 0, max_ndx;
        size_t *al, *bl;
        size_t mask = sizeof(*al) - 1;
        al = (size_t *)a;
        bl = (size_t *)b;
                /* 一开始,将字符串数组转化成size_t类型的数组,通过比较size_t类型来比较是否相同 */
        /* libc的字符串比较函数也使用了相同的技巧,可有效的加快比较速度 */
        /* 检查a1和b1的位置是否对齐(size_t的类型长度的倍数?) ? */
        if ( ((size_t)al & mask) == 0 &&
                     ((size_t)bl & mask) == 0 ) 
        {
            /* 确定比较的长度 */
            max_ndx = ((a_len < b_len) ? a_len : b_len) & ~mask;

            for (; ndx < max_ndx; ndx += sizeof(*al)) 
                       {
                if (*al != *bl) break;
                al++; bl++;
            }
        }
        /* 相同的部分比较完毕 */
        /* 开始比较字符串,并忽略大小写 */
        a = (char *)al;
        b = (char *)bl;
        max_ndx = ((a_len < b_len) ? a_len : b_len);
        for (; ndx < max_ndx; ndx++) 
               {
            char a1 = *a++, b1 = *b++;
            /*
                'A'的二进制表示为0100 0001,'a'的二进制表示为0110 0001,
                大写字母比小写字母的ASCII值小了32。
                通过或上一个32,可以使所有的字母全部转换成大写字母。
            */
            if (a1 != b1) 
                       {
                if ((a1 >= 'A' && a1 <= 'Z') && (b1 >= 'a' && b1 <= 'z'))
                    a1 |= 32;
                else if ((a1 >= 'a' && a1 <= 'z') && (b1 >= 'A' && b1 <= 'Z'))
                    b1 |= 32;
                if ((a1 - b1) != 0) return (a1 - b1);
            }
        }
        /* all chars are the same, and the length match too。 they are the same */
        if (a_len == b_len) return 0;
        /* if a is shorter then b, then b is larger */
        return (a_len - b_len);
}

本文章由 http://www.wifidog.pro/2015/04/15/wifidog%E8%AE%A4%E8%AF%81lighttpd-%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86-1.html 整理编辑,转载请注明出处

wifidog自带HTTP 服务器 Lighttpd1.4.20源码分析之array.c(h) -----通用数组(2)

上文讲解到
array.h中还有一个定义:

 typedef struct {
  DATA_UNSET;
  array *value;
 } data_array;

这个定义了一个array类型的数据,也就是说,通用数组中存放的数据可以数通用数组,这样可以形成多维的通用数组。
在array.h中定义了如下的通用数组操作函数:
1、array *array_init(void);
初始化数组,分配空间。
2、array *array_init_array(array * a);
用数组a来初始化一个数组。也就是得到一个a的深拷贝。
3、void array_free(array * a);
释放数组。释放所有空间。
4、void array_reset(array * a);
重置data中的所有数据(调用UNSET类型数据中的reset函数),并将used设为0。相当于清空数组。
5、int array_insert_unique(array * a, data_unset * str);
将str插入到数组中。
6、data_unset *array_pop(array * a);
弹出data中的最后一个元素,返回奇指针,data中的最后一个位置设为NULL。
7、int array_print(array * a, int depth);
打印数组中的内容。depth参数用于在打印多维数组时,实现缩进。
8、a_unset *array_get_unused_element(array * a, data_type_t t);
返回第一个未使用的数据,也就是used位置的数据,这个数据不在数组中,返回这个数据指针后,将data[unsed]设为NULL。可能返回NULL。
9、data_unset *array_get_element(array * a, const char *key);
根据key值,返回数组中key值与之相同的数据
10、data_unset *array_replace(array * a, data_unset * du);
如果数组中有与du的key值相同的数据,则用du替换那个数据,并返回那个数据的指针。如果不存在,则把du插入到数组中。(调用data_insert_unique函数)
11、 int array_strcasecmp(const char *a, size_t a_len, const char *b, size_t b_len);
这个函数并没用实现,仅仅给出了上面的定义。也许这个是用来比较两个字符串,并且可能会忽略大小写。
12、void array_print_indent(int depth);
根据depth打印空白,实现缩进。
13、size_t array_get_max_key_length(array * a);
返回数组中最长的key的长度。

另外,在array.c中定义了一个辅助函数static intarray_get_index(array *a, const char *key, size_t keylen, int *rndx)。这个函数的作用是通过key值,查找数据,返回其在数组data中的下标位置,并通过参数rndx返回其下标在数组sorted中的位置。
函数的定义如下:

Code
static int array_get_index(array *a, const char *key, size_t keylen, int *rndx) 
{
    /*参数keylen是key的长度*/
    int ndx = -1;
    int i, pos = 0;
    if (key == NULL) return -1;
    /* try to find the string */
    /* 
* sorted数组是个下标数组,存放的是排好序的输入元素的下标,
* 相当于一个排好序的数组。
     * 利用sorted数组进行二分查找。
     * 若找到,返回元素在data数组中的位置,并通过rndx返回
* 其在sorted数组中的位置。
     * 若没有找到,通过rndx返回此元素在sorted中的位置,并返回-1
     */
/* pos中存放的是元素在数组data中的位置 */
/*  
当data的空间不够时,通用数组每次为data增加16个空间,第一次初始化时,
data的长度为16。因此,size始终是16的倍数。
used可以为任何数值,当然要大于等于0,小于size。
而next_power_of_2是大于used最小的2的倍数,如used=5,那么
next_power_of_2就等于8。
这样,used始终大于等于next_power_of_2的1/2。
*/
/*
 在这儿的二分搜索中,next_power_of_2是个很有创意的技巧。
next_power_of_2类似于一个标杆,利用这个标杆进行二分搜索可以减少很多
出错的几率,也使程序更加易懂。效率上当然没有什么损失。下面的程序读者可
自行看看,并不是很难。
 */
    for (i = pos = a->next_power_of_2 / 2; ; i >>= 1) 
{
 int cmp;
 if (pos < 0) {
 pos += i;
 } else if (pos >= (int)a->used) {
 pos -= i;
 } else {
 /* 比较两个元素的key值 */
 cmp = buffer_caseless_compare(key, keylen
, a->data[a->sorted[pos]]->key->ptr
, a->data[a->sorted[pos]]->key->used
);
 if (cmp == 0) {
 /* found */
 ndx = a->sorted[pos];
 break;
 } else if (cmp < 0) {/* 所找数据在前半部分 */
 pos -= i;
 } else { /*  所找数据在后半部分*/
 pos += i;
 }
 }
 if (i == 0) break;
    }
    if (rndx) *rndx = pos;
    return ndx;
}

在上面列出的函数中,还有一个函数要重点讲解一下,也是最复杂的一个函数:int array_insert_unique(array *a, data_unset *str)。这个函数将数据str插入到数组中,当并不是单纯的插入,如果数组中存在key于str相同的数据,则把str的内容拷贝到这个数据中。

Code
int array_insert_unique(array *a, data_unset *str) {
    int ndx = -1;
    int pos = 0;
    size_t j;
    /* generate unique index if neccesary */
    if (str->key->used == 0 || str->is_index_key) {
               buffer_copy_long(str->key, a->unique_ndx++);
                   str->is_index_key = 1;
    }
    /* 在数组中查找与str具有相同key的数据 */
    if (-1 != (ndx = array_get_index(a, str->key->ptr, str->key->used, &pos))) 
        {
            /* 找到,复制 */
             if (a->data[ndx]->type == str->type) 
              {
                       str->insert_dup(a->data[ndx], str);
              } 
              else 
              {
                       fprintf(stderr, "a\n");
              }
              return 0;
    }
    /* 当数组的长度大于最大值时,不进行插入,并返回-1 */
    if (a->used+1 > INT_MAX) {
                    /* we can't handle more then INT_MAX entries: see array_get_index() */
                   return -1;
    }

    if (a->size == 0) {
               /* 数组为空 */
               /* 初始data的长度为16 */
               a->size   = 16;
               a->data   = malloc(sizeof(*a->data)     * a->size);
               a->sorted = malloc(sizeof(*a->sorted)   * a->size);
               assert(a->data);
               assert(a->sorted);
               for (j = a->used; j < a->size; j++) 
                            a->data[j] = NULL;
    } 
        else if (a->size == a->used) 
        {
          /* data已经满了,对data进行扩容,增加16个空间。 */
          /* 这就是为什么size一定是16的倍数 */
               a->size  += 16;
               a->data   = realloc(a->data, sizeof(*a->data) * a->size);
               a->sorted = realloc(a->sorted, sizeof(*a->sorted) * a->size);
               assert(a->data);
               assert(a->sorted);
               for (j = a->used; j < a->size; j++)
                             a->data[j] = NULL;
    }
    ndx = (int) a->used;
    a->data[a->used++] = str;
    /*
               在上面调用函数array_get_index的时候,
              已将str应该在数组sorted中位置存放在了pos中。 
        */
    if (pos != ndx /* 要插入的位置在中部 */&&((pos < 0) /* 在开始位置插入 */
                            ||buffer_caseless_compare(str->key->ptr
                                          , str->key->used
                                          , a->data[a->sorted[pos]]->key->ptr
                                          , a->data[a->sorted[pos]]->key->used
                            ) > 0)) 
        {
               /* 判断当前pos所对应的元素是否比str小,若是,这pos后移一位 */
               pos++;
    }
    /* 移动sorted数组中后面的数据,腾出位置。 */
    if (pos != ndx) {
               memmove(a->sorted + (pos + 1), a->sorted + (pos), (ndx - pos) * sizeof(*a->sorted));
    }
    /* insert */
    a->sorted[pos] = ndx;
    /* 如果used==next_power_of_2时,扩展next_power_of_2 */
    if (a->next_power_of_2 == (size_t)ndx) 
              a->next_power_of_2 <<= 1;
    return 0;
}

其他函数都很简单,读者可自己查看。另外,print函数虽然复杂,但对整个程序的意义不大,读者可自行查看。

总结:
  Lighttpd中的通用数组的设置主要是使用的面向对象的思想,使数组具有很好的扩展性和适应性。通用数组中二分查找的实现也是一个特色。还有就是使用sorted数组只对data中的数据的下标排序,这也是一个很有用的技巧。
  以上就是我的一点拙见,还望读者网友对疏漏之处进行批评指正。

本文章由 http://www.wifidog.pro/2015/04/13/wifidog-lighttpd%E5%88%86%E6%9E%90%E9%80%9A%E7%94%A8%E6%95%B0%E7%BB%84-2.html 整理编辑,转载请注明出处