lighttpd-1.4.39 : fdevents and Event Handler

重要参考:
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     select
Unix         poll       poll
Linux 2.4+   rt-signals linux-rtsig
Linux 2.6+   epoll      linux-sysepoll
Solaris      /dev/poll  solaris-devpoll
FreeBSD, ... 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文件.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值