lighttpd-1.4.39 : fdevents and Event Handler

来源:互联网 发布:mysql 1054 编辑:程序博客网 时间:2024/06/06 08:33

重要参考:
http://bbs.chinaunix.net/thread-1251434-2-1.html
http://bbs.chinaunix.net/thread-1251434-4-1.html
http://www.cnblogs.com/kernel_hcy/archive/2010/03/22/1691951.html
lighttpd对网络的I/O事件进行了封装,并且实现了几种事件处理器。可以通过配置文件来选择使用哪种事件处理器。可以使用的事件处理器有:select、poll、epoll等。

对网络I/O事件的封装

使用fdnode表示一个文件描述符相关的处理器、关心的事件等。当关心的事件events发生时,就使用相应的处理器handler来处理。

typedef struct _fdnode {    fdevent_handler handler; // 该描述符对应的处理器,回调函数,事件触发时进行回调    void *ctx;        // context,上下文相关    void *handler_ctx;    int fd;           // file descriptor : 文件描述符, 是socket fd, 可能是服务器监听的所有的fd(监听套接字), 也可能是accept之后与client相关的fd(已连接套接字)    int events;       // 对该fd的所关心的事件} fdnode;

如果该fd是服务器监听客户端连接的fd, 那么handler = network_server_handle_fdevent(在network.c文件中), ctx保存的就是server指针;如果该fd是accapt客户端连接之后的fd, 那么handler = connection_handle_fdevent(在connections.c文件中), ctx保存的就是connection指针.

对网络的I/O事件进行了封装,使用结构体 fdevents表示。
参考文章中的讲解非常到位,这里再次体现了面向对象思想。fdevents相当于一个虚基类,它包含了一些”public”成员:srv、type、fdarray、maxfds。派生类继承该虚基类后,都会包含这些public成员。
此外,根据编译时的预编译宏, 该结构体还能包含其它的成员, 这些成员就相当于OO中派生类自己私有的成员.
在这个结构体的最后, 是一组函数指针, 也就是OO中的纯虚函数, 每个派生类都要根据这些接口自己进行实现

typedef struct fdevents {    struct server *srv;    fdevent_handler_t type; // 事件处理器的类型    fdnode **fdarray; // 需要处理的“文件描述符”的数组    size_t maxfds;    // fdarray最大大小#ifdef USE_LINUX_EPOLL    int epoll_fd;    struct epoll_event *epoll_events;#endif#ifdef USE_POLL    struct pollfd *pollfds;    size_t size;    size_t used;    buffer_int unused;#endif#ifdef USE_SELECT    fd_set select_read;    fd_set select_write;    fd_set select_error;    fd_set select_set_read;    fd_set select_set_write;    fd_set select_set_error;    int select_max_fd;#endif#ifdef USE_SOLARIS_DEVPOLL    int devpoll_fd;    struct pollfd *devpollfds;#endif#ifdef USE_SOLARIS_PORT    port_event_t *port_events;#endif#ifdef USE_FREEBSD_KQUEUE    int kq_fd;    struct kevent *kq_results;#endif#ifdef USE_SOLARIS_PORT    int port_fd;#endif#ifdef USE_LIBEV    struct ev_loop *libev_loop;#endif    int (*reset)(struct fdevents *ev);    void (*free)(struct fdevents *ev);    int (*event_set)(struct fdevents *ev, int fde_ndx, int fd, int events);    int (*event_del)(struct fdevents *ev, int fde_ndx, int fd);    int (*event_get_revent)(struct fdevents *ev, size_t ndx);    int (*event_get_fd)(struct fdevents *ev, size_t ndx);    int (*event_next_fdndx)(struct fdevents *ev, int ndx);    int (*poll)(struct fdevents *ev, int timeout_ms);    int (*fcntl_set)(struct fdevents *ev, int fd);} fdevents;

事件处理器 :Event Handler

I/O多路复用技术有:select, poll, epoll (下面称为事件处理器,event-handler)。lighttpd可以通过配置文件来选择使用某种event handler, 例如:
server.event-handler = “linux-sysepoll”
表示事件处理器为 epoll。
lighttpd支持的事件处理器如下表所示:

============ ========== ===============OS           Method     Config Value============ ========== ===============all          select     selectUnix         poll       pollLinux 2.4+   rt-signals linux-rtsigLinux 2.6+   epoll      linux-sysepollSolaris      /dev/poll  solaris-devpollFreeBSD, ... kqueue     freebsd-kqueue============ ========== ===============

fdevent_handler_t表示事件处理器的类型:

typedef enum { FDEVENT_HANDLER_UNSET,        FDEVENT_HANDLER_SELECT, // select        FDEVENT_HANDLER_POLL,   // poll        FDEVENT_HANDLER_LINUX_SYSEPOLL, // epoll        FDEVENT_HANDLER_SOLARIS_DEVPOLL,        FDEVENT_HANDLER_SOLARIS_PORT,        FDEVENT_HANDLER_FREEBSD_KQUEUE,        FDEVENT_HANDLER_LIBEV} fdevent_handler_t;
  • 初始化

worker被创建好之后,就进入了I/O事件的处理过程。
首先是初始化一个fdevents结构体,通过函数fdevent_init完成。其中srv->event_handler表示handler的类型,如上所述,这是通过读取配置文件来获得的。fdevents创建好之后保存在srv->ev中。

srv->ev = fdevent_init(srv, srv->max_fds + 1, srv->event_handler)

如果我们选择的是select,那么最终会调用fdevent_select_init()来完成初始化操作。初始化的过程其实就是给函数指针赋值的过程(体现了多态),让函数指针指向select的相关函数(通过宏SET(X),使得代码更简洁,清晰)

int fdevent_select_init(fdevents *ev) {    ev->type = FDEVENT_HANDLER_SELECT;#define SET(x) \    ev->x = fdevent_select_##x;    SET(reset);    SET(poll);    SET(event_del);    SET(event_set);    SET(event_next_fdndx);    SET(event_get_fd);    SET(event_get_revent);    return 0;}


  • 注册

在服务器创建一个socket fd并且进行监听后, 要将该fd注册到fdevent中, 这样才能使用使用这个事件处理机制.在server.c文件的main函数中, 调用network_register_fdevents函数将所有监听的fd注册到事件处理器中。
当有客户端请求连接时,监听套接字的FDEVENT_IN事件发生,函数network_server_handle_fdevent被调用,该函数会accept此连接,然后将连接后的socket fd再次注册到fdevent中(connection_accept调用fdevent_register),处理器为connection_handle_fdevent

// network.c// 将worker的监听套接字注册到fdevent中,handler为network_server_handle_fdevent,关心的事件event为FDEVENT_IN,即:有客户端连接时,触发该事件,进而调用handler进行处理(这里就是network_server_handle_fdevent)int network_register_fdevents(server *srv) {    size_t i;    if (-1 == fdevent_reset(srv->ev)) {        return -1;    }    /* register fdevents after reset */    for (i = 0; i < srv->srv_sockets.used; i++) {        server_socket *srv_socket = srv->srv_sockets.ptr[i];        fdevent_register(srv->ev, srv_socket->fd, network_server_handle_fdevent, srv_socket); // 注册fd        fdevent_event_set(srv->ev, &(srv_socket->fde_ndx), srv_socket->fd, FDEVENT_IN); // 设置关心的事件    }    return 0;}// 服务器监听套接字的handler,当有客户端请求连接时被调用static handler_t network_server_handle_fdevent(server *srv, void *context, int revents) {    ...    if (0 == (revents & FDEVENT_IN)) { // 判断事件是否发生了        ...        return HANDLER_ERROR;    }    /* accept()s at most 100 connections directly     *     * we jump out after 100 to give the waiting connections a chance */    for (loops = 0; loops < 100 && NULL != (con = connection_accept(srv, srv_socket)); loops++) { // 接收该请求,并注册到fdevent中(srv->ev),handler为connection_handle_fdevent        handler_t r;        connection_state_machine(srv, con);        switch(r = plugins_call_handle_joblist(srv, con)) {        case HANDLER_FINISHED:        case HANDLER_GO_ON:            break;        default:            log_error_write(srv, __FILE__, __LINE__, "d", r);            break;        }    }    return HANDLER_GO_ON;}
// fdevent.c// 给描述符fd创建一个fdnode,然后加入到ev中int fdevent_register(fdevents *ev, int fd, fdevent_handler handler, void *ctx) {    fdnode *fdn;    fdn = fdnode_init();    fdn->handler = handler;    fdn->fd      = fd;    fdn->ctx     = ctx;    fdn->handler_ctx = NULL;    fdn->events  = 0;    ev->fdarray[fd] = fdn; // 使用fd作为下标进行索引    return 0;}int fdevent_event_set(fdevents *ev, int *fde_ndx, int fd, int events) {    int fde = fde_ndx ? *fde_ndx : -1;    if (ev->event_set) fde = ev->event_set(ev, fde, fd, events);    ev->fdarray[fd]->events = events;    if (fde_ndx) *fde_ndx = fde;    return 0;}


  • 事件处理

在将服务器监听fd注册到网络IO事件处理器中之后, 这个处理器就要开始循环处理了, 在server.c中的main.c函数中是这个轮询的主过程:
        if ((n = fdevent_poll(srv->ev, 1000)) > 0) { // 会调用前面设置好的srv->ev->poll进行处理(体现了“多态”)。其中n是事件个数            /* n is the number of events */            int revents;            int fd_ndx;#if 0            if (n > 0) {                log_error_write(srv, __FILE__, __LINE__, "sd",                        "polls:", n);            }#endif            // 循环处理n个事件            fd_ndx = -1;            do {                fdevent_handler handler;                void *context;                handler_t r;                fd_ndx  = fdevent_event_next_fdndx (srv->ev, fd_ndx); // 返回需要处理的fd的索引fd_ndx                if (-1 == fd_ndx) break; /* not all fdevent handlers know how many fds got an event */                // 通过该索引,得到事件revents,fd,handler,context                // 最终掉用 (*handler)(srv, context, revents)进行处理                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);                /* connection_handle_fdevent needs a joblist_append */#if 0                log_error_write(srv, __FILE__, __LINE__, "sdd",                        "event for", fd, revents);#endif                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);

简单的说, 这个过程就是:首先调用poll函数指针获取相关网络IO被触发的事件数, 保存在整型变量n中, 然后根据这个n值进行以下循环, 每次处理完n值减一, 为0之后退出, 这个循环的大致过程是: 首先获取下一个被触发的网络事件在fdnode数组中的索引, 接着根据该索引获取相关的事件类型, fd, 回调函数, contex, ,接着根据这些调用回调函数(也就是我们上面提到的函数 network_server_handle_fdevent和connection_handle_fdevent), 请注意, 在本节的最开始部分曾经提到过fdevent.h中声明的函数都是对外暴露的fdevent结构体”public函数”, 在上面这个轮询的过程中使用的正是这些”public函数”, 在这些”public函数”中再根据曾经初始化的函数指针进行调用, 实现了OO中所谓的”多态”.

以上就是通过fdevent结构体实现的网络IO处理器模型, 在这里体现如何使用C实现OO面向对象编程的种种常用技巧,不放在本节最后做一个总结:
1) fdevent结构体是一个虚拟基类, 其中的函数指针就是虚拟基类中的纯虚函数, 由具体实现去初始化之.fdevent结构体中的对象为所有派生类的公共成员, 而用各个预编译宏包围的成员则是各个派生类的私有成员.

2) 在fdevent.h中声明的函数可以理解为虚拟基类对外暴露的接口, 也就是public函数.

3) 各个具体的实现分别是各个实现C文件中的静态函数, 也就是派生类的private函数.

如果阅读到这里仍然对lighttpd中网络IO处理器模型有疑问, 可以具体参看前面提到的fdevent.h/c文件, 以及以fdevent_为前缀的c文件.

0 0
原创粉丝点击