wifidog源码分析Lighttpd1.4.20源码分析之fdevent系统(3) -----使用(2)

接着上文介绍的函数fdevent_linux_sysepoll_event_add 讲解,首先看函数的第三个参数events,他是一个整型,其没以为对应一种IO事件。
上面fdevent_event_add()函数的额第三个参数是FDEVENT_IN,这是一个宏

/*
 * 用于标记文件描述符的状态
 */
 #define FDEVENT_IN     BV(0)     //文件描述符是否可写
 #define FDEVENT_PRI    BV(1)      //不阻塞的可读高优先级的数据 poll
 #define FDEVENT_OUT    BV(2)     //文件描述符是否可读
#define FDEVENT_ERR    BV(3)     //文件描述符是否出错
#define FDEVENT_HUP    BV(4)     //已挂断 poll
#define FDEVENT_NVAL   BV(5)     //描述符不引用一打开文件 poll

其中BV也是一个宏,定义在settings.c文件中:

#define BV(x) (1 << x)

其作用就是将一个整数变量第x位置1,其余为0。
那么上面FDEVENT_XX的宏定义就是定义了一系列者养的整数,通过这些宏的或操作,可以在一个整数中用不同的位表示不同的事件。这些宏和struct epoll_event结构体中的events变量的对应的宏定义对应(有点绕。。。)。说白了就是和epoll.h中的枚举EPOLL_EVENTS对应。
这个函数的主要工作就是设置一些值然后调用epoll_ctl函数。虽然函数的名称是event_add,但是如果第二个参数fde_ndx不等于-1(那肯定就等于后面的参数fd),那这个时候可以肯定fd已经在epoll的监测中了,这时候不再是将fd增加到epoll中,而是修改其要监听的IO事件,就是最后一个参数表示的事件。fdevent_linux_sysepoll_event_add()增加成功后返回fd,在fdevent_event_add中,将这个返回值赋给fde_ndx,所以,fde_ndx==fd。
至此,监听fd的注册流程已经全部结束了,由于当有连接请求是,监听fd的表现是有数据课读,因此,只监听其FDEVENT_IN事件。注册之后,监听fd就开始等待连接请求。
接着,进程执行到了下面的语句:

//启动事件轮询。底层使用的是IO多路转接。

if ((n = fdevent_poll(srv->ev, 1000)) > 0)
{
            /*
             * nn是事件的数量(服务请求啦,文件读写啦什么的。。。)
             */
            int revents;
            int fd_ndx = -1;
            /**
             * 这个循环中逐个的处理已经准备好的请求,知道所有的请求处理结束。
             */
            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);
                /*
                 * connection_handle_fdevent needs a joblist_append
                 */
                /**
                 * 这里,调用请求的处理函数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:
                    SEGFAULT();
                    break;
                default:
                    log_error_write(srv, __FILE__, __LINE__, "d", r);
                    break;
                }
            }while (--n > 0);
        }
        else if (n < 0 && errno != EINTR)
        {
            log_error_write(srv, __FILE__, __LINE__, "ss","fdevent_poll failed:", strerror(errno));
        }
}

  这段语句是worker子进程的工作重心所在。首先调用fdevent_poll()函数等待IO事件发生,如果没有IO事件,程序会阻塞在这个函数中。如果有fd发生了IO事件,则从fdevent_poll函数中返回,返回值是发生了IO事件的fd的数量。接着,程序进入do-while循环,循环中对每个fd,调用一些列fdevent系统的接口函数,最后调用event_handler处理IO事件。
  fdevent_poll()函数调用fdevents结构体中的poll,最终调用的是epoll_wait()函数。epoll_wait()函数将发生了IO事件的fd对应的epoll_evet结构体实例的存储在fdevents结构体的epoll_events数组成员中。fdevent_event_next_fdndx函数返回epoll_events数组中下一个元素的下标,fdevent_event_get_revent函数调用ev->event_get_revent()获得fd发生的IO事件,最终调用的是:

static int fdevent_linux_sysepoll_event_get_revent(fdevents * ev, size_t ndx)
{
    int events = 0, e;
    e = ev->epoll_events[ndx].events;
    if (e & EPOLLIN)
        events |= FDEVENT_IN;
    if (e & EPOLLOUT)
        events |= FDEVENT_OUT;
    if (e & EPOLLERR)
        events |= FDEVENT_ERR;
    if (e & EPOLLHUP)
        events |= FDEVENT_HUP;
    if (e & EPOLLPRI) //有紧急数据到达(带外数据)
        events |= FDEVENT_PRI;
    return e;
}

  这个函数就做了一个转换。fdevent_get_handler和fdevent_get_context返回fd对应的fdnode中的handler和ctx。这几个函数都简单,这里不再列出源代码。
  最后,在switch语句中调用fd对应的handler函数处理事件。对于监听fd,调用的函数为:

/**
 * 这个是监听socket的IO事件处理函数。
 * 只要的工作就是建立和客户端的socket连接。只处理读事件。在处理过程中,
 * 每次调用这个函数都试图一次建立100个连接,这样可以提高效率。
 */
handler_t network_server_handle_fdevent(void *s, void *context, int revents)
{
    server *srv = (server *) s;
    server_socket *srv_socket = (server_socket *) context;
    connection *con;
    int loops = 0;
    UNUSED(context);
    /*
     * 只有fd事件是FDEVENT_IN时,才进行事件处理。
     */
    if (revents != FDEVENT_IN)
    {
        log_error_write(srv, __FILE__, __LINE__, "sdd", "strange event for server socket", srv_socket->fd, revents);
        return HANDLER_ERROR;
    }
    /*
     * accept()s at most 100 connections directly we jump out after 100 to give the waiting connections a chance
     *一次监听fd的IO事件,表示有客户端请求连接,对其的处理就是建立连接。建立连接后并不急着退出函数,
     * 而是继续尝试建立新连接,直到已经建立了100次连接。这样可以提高效率。
     */
    for (loops = 0; loops < 100 && NULL != (con =connection_accept(srv, srv_socket)); loops++)
    {
        handler_t r;
        //根据当前状态,改变con的状态机,并做出相应的动作。
        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;
}

  监听fd有IO事件,表示有客户端请求连接,对其的处理就是建立连接。在这个函数中,建立连接后并不急着退出,而是继续尝试建立新连接,直到已经建立了100次连接。这样可以提高效率。connection_accept()函数接受连接请求并返回一个connection结构体指针。接着对这个连接启动状态机(状态机可是很有意思的。。。)。然后把连接加到作业队列中。
  这里有一个问题,如果连接迟迟达不到100个,那么程序就会阻塞在这个函数中,这样对于已经建立的连接也就没法进行处理。这怎么办?读者不要忘了,在将监听fd注册到fdevent系统时,其被设置成了非阻塞的,因此,如果在调用accept()函数时没有连接请求,那么accept()函数会直接出错返回,这样connection_accept就返回一个NULL,退出了for循环。
到这,fdevent系统对于监听fd的处理就完成了一个轮回。处理完IO事件以后fd接着在epoll中等待下一次事件。
  下一篇中回介绍一些fdevent系统对连接fd(accept函数的返回值)的处理,以及超时连接的处理。

本文章由 http://www.wifidog.pro/2015/04/21/wifidog%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90lighttpd%E4%BD%BF%E7%94%A8-2.html 整理编辑,转载请注明出处

wifidog源码分析Lighttpd1.4.20源码分析之fdevent系统(3) -----使用(1)

前面讲了lighttpd的fdevent系统的初始化过程。这篇要看一看lighttpd是怎样使用fdevent系统的。讲解的过程中,会详细的分析fdevent的源代码。
首先还是从server.c的main函数入手。在程序的初始化过程中,当完成fdevent的初始化之后,第一个需要fdevent处理的事情就是将在初始化网络的过程中得到的监听fd(socket函数的返回值)注册的fdevent系统中。调用的是network_register_fdevents()函数,定义在network.c文件中:

/**
 * 在fd events系统中注册监听socket。
 * 这个函数在子进程中被调用。
 */
 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);
        fdevent_event_add(srv->ev, &(srv_socket->fde_ndx), srv_socket->fd, FDEVENT_IN);
    }
    return 0;
}

函数的重点是for循环,它遍历所有的监听fd并将其注册到fdevent系统中。在初始化网络的过程中,调用socket函数之后,将其返回值(监听fd)保存在server结构体的srv_sockets成员中,这个成员是一个server_socket_array结构体,而server_socket_array结构体是server_socket结构体的指针数组。server_socket结构体定义如下:

typedef struct
{
        sock_addr addr;           //socket fd对应的的地址。
         int fd;                     //socket()函数返回的监听fd
         int fde_ndx;                 //和fd相同。
         buffer *ssl_pemfile;
        buffer *ssl_ca_file;
        buffer *ssl_cipher_list;
        unsigned short ssl_use_sslv2;
        unsigned short use_ipv6;    //标记是否使用ipv6
         unsigned short is_ssl;
        buffer *srv_token;
#ifdef USE_OPENSSL
        SSL_CTX *ssl_ctx;
 #endif
        unsigned short is_proxy_ssl;
} server_socket;

这里我们主要看前三个成员,前两个成员很简单,对于第三个成员,作者的本意应该是保存fd对应的fdnode在fdevents结构体中fdarray数组中的下标,但是程序在存储fdnode时候是以fd最为下标存储的(后面的fdevent_register函数中),所以通常fde_ndx==fd。
下面看一看fdevent_register()函数,在fdevent.c中定义:

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;
    ev->fdarray[fd] = fdn; //使用文件描述符作为数组的下标。可以将查询
 //的时间变为 O(1)
     return 0;
}

在这个函数中,创建了一个fdnode结构体的实例,然后对其成员赋值。最后,以fd为下标将这个实例存如fdevents结构体中的fdarray数组中。关于第三个参数:fdevent_handler handler,这是一个函数指针,其定义为typedef handler_t(*fdevent_handler) (void *srv, void *ctx, int revents)。这个函数指针对应XXX_handle_fdevent()类型的函数。比如network.c/ network_server_handle_fdevent() ,connections.c/ connection_handle_fdevent()。这些函数的作用是在fdevent系统检测到fd有IO事件发生时,处理这些IO事件。比如,network_server_handle_fdevent()处理监听fd(socket函数的返回值)发生的IO事件,connection_handle_fdevent()处理连接fd(accept函数的返回值)发生的IO事件。除了上面的两个函数,还有stat_cacahe.c/stat_cache_handle_fdevent(),mod_cgi.c/cgi_handle_fdevent(),mod_fastcgi.c/ fcgi_handle_fdevent(),mod_proxy.c/ proxy_handle_fdevent()和mod_scgi.c/scgi_handle_fdevent()等。在后面的讲解中,主要围绕network_server_handle_fdevent()和connection_handle_fdevent(),其他的函数有兴趣的读者可以自行查看。
接着,在for循环中调用(fdevent.c)fdevent_event_add()函数:

int fdevent_event_add(fdevents * ev, int *fde_ndx, int fd, int events)
{
    int fde = fde_ndx ? *fde_ndx : -1;
    if (ev->event_add)
        fde = ev->event_add(ev, fde, fd, events)
    if (fde_ndx)
        *fde_ndx = fde;
    return 0;
}

函数中调用了fdevents结构体中event_add函数指针对应的函数。前面我们已经假设系统使用epoll,那么我们就去看看fdevent_linux_sysepoll.c中的fdevent_linux_sysepoll_event_add()函数,这个函数的地址在初始化的时候被赋给fdevents中的event_add指针:

static int fdevent_linux_sysepoll_event_add(fdevents * ev, int fde_ndx, int fd, int events)
{
    struct epoll_event ep;
    int add = 0;
    if (fde_ndx == -1) //描述符不在epoll的检测中,增加之。
         add = 1;
    memset(&ep, 0, sizeof(ep));
    ep.events = 0;
    /**
     * 在ep中设置需要监听的IO事件。
     * EPOLLIN : 描述符可读。
     * EPOLLOUT :描述符可写。
     * 其他的事件还有:EPOLLRDHUP , EPOLLPRI, EPOLLERR, EPOLLHUP, EPOLLET, EPOLLONESHOT等。
     */
    if (events & FDEVENT_IN)
        ep.events |= EPOLLIN;
    if (events & FDEVENT_OUT)
        ep.events |= EPOLLOUT;
    /*
     * EPOLLERR :描述符发生错误。
     * EPOLLHUP :描述符被挂断。通常是连接断开。
     */
    ep.events |= EPOLLERR | EPOLLHUP /* | EPOLLET */ ;
    ep.data.ptr = NULL;
    ep.data.fd = fd;
    /*
     * EPOLL_CTL_ADD : 增加描述符fd到ev->epoll_fd中,并关联ep中的事件到fd上。
     * EPOLL_CTL_MOD : 修改fd所关联的事件。
     */
    if (0 != epoll_ctl(ev->epoll_fd, add ?EPOLL_CTL_ADD : EPOLL_CTL_MOD, fd, &ep))
    {
        fprintf(stderr, "%s.%d: epoll_ctl failed: %s, dying\n",__FILE__,__LINE__, strerror(errno));
        SEGFAULT();
        return 0;
    }
    return fd;
}

本文章由 http://www.wifidog.pro/2015/04/21/wifidog%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90lighttpd%E4%BD%BF%E7%94%A8-1.html 整理编辑,转载请注明出处

wifidog源码分析Lighttpd1.4.20源码分析之fdevent系统(2)-----初始化(2)

接着上文的struct ev_map结构,继续朝下走,

if (srv->srvconf.event_handler->used == 0)
    {
        srv->event_handler = event_handlers[0].et;
    }
    else
    {
        for (i = 0; event_handlers[i].name; i++)
        {
            if (0 == strcmp(event_handlers[i].name, srv->srvconf.event_handler->ptr))
            {
                srv->event_handler = event_handlers[i].et;
                break;
            }
        }
    }

srv->srvconf.event_handler->used为0说明配置文件中没有配置所使用的多路IO系统。

跑个题,在配置文件中是如下进行设置的:

## set the event-handler (read the performance
##section in the manual)
server.event-handler = "freebsd-kqueue" # needed on OS X

所设置的值就是上面定义的那个数组中元素的第二个成员(字符串)的值。
如果配置文件中没有配置,那么就使用上面定义的数组的第一个元素的值。这也就是为什么定义的时候那个排序很有意思的原因。
如果配置文件中有配置,那就按配置文件的来喽。(上面的代码删除了处理出错的部分)
从congif_set_defaults()函数回来,继续在main函数中走。
下面的语句设置最大描述符数量:

if (srv->event_handler == FDEVENT_HANDLER_SELECT)
    {
        /*
         * select的硬限制。减去200是为了增加安全性,防止出现段错误。
         */
        srv->max_fds = FD_SETSIZE - 200;
    }
    else
    {
        srv->max_fds = 4096;
    }

由于select()函数一次能处理的fd数量有限制,所以要特殊处理。FD_SETSIZT在select.h中定义。其他情况最大描述符数量都设置为4096.
后面还有一个设置最大fd数量的地方:
//根据实际设置情况,重新设置max_fds。

if (srv->event_handler == FDEVENT_HANDLER_SELECT)
{
    srv->max_fds = rlim.rlim_cur < FD_SETSIZE - 200 ?
rlim.rlim_cur : FD_SETSIZE - 200;
}
else
{
    srv->max_fds = rlim.rlim_cur;
}

这个是根据当前用户的限制来的。
当程序产生子进程后,在子进程中执行的第一条语句就是初始化fdevent系统:

if (NULL == (srv->ev = fdevent_init(srv->max_fds + 1,
srv->event_handler)))
    {
        log_error_write(srv, __FILE__, __LINE__, "s",
"fdevent_init failed");
        return -1;
    }

进入fdevent_init()函数:

/**
 * 初始化文件描述符事件数组fdevent
 */
fdevents *fdevent_init(size_t maxfds, fdevent_handler_t type)
{
    fdevents *ev;

    //内存被初始化为0
    ev = calloc(1, sizeof(*ev));

    //分配数组
    ev->fdarray = calloc(maxfds, sizeof(*ev->fdarray));
    ev->maxfds = maxfds;

    //根据设定的多路IO的类型进行初始化。
    switch (type)
    {
    case FDEVENT_HANDLER_POLL:
        if (0 != fdevent_poll_init(ev))
        {
            fprintf(stderr, "%s.%d: event-handler poll failed\n", __FILE__, __LINE__);
            return NULL;
        }
        break;
    case FDEVENT_HANDLER_SELECT:
        if (0 != fdevent_select_init(ev))
        {
            fprintf(stderr, "%s.%d: event-handler select failed\n", __FILE__, __LINE__);
            return NULL;
        }
        break;
    case FDEVENT_HANDLER_LINUX_RTSIG:
        if (0 != fdevent_linux_rtsig_init(ev))
        {
            fprintf(stderr, "%s.%d: event-handler linux-rtsig failed, try to set server.event-handler = \"poll\" or \"select\"\n",
                    __FILE__, __LINE__);
            return NULL;
        }
        break;
    case FDEVENT_HANDLER_LINUX_SYSEPOLL:
        if (0 != fdevent_linux_sysepoll_init(ev))
        {
            fprintf(stderr, "%s.%d: event-handler linux-sysepoll failed, try to set server.event-handler = \"poll\" or \"select\"\n",
                    __FILE__, __LINE__);
            return NULL;
        }
        break;
    case FDEVENT_HANDLER_SOLARIS_DEVPOLL:
        if (0 != fdevent_solaris_devpoll_init(ev))
        {
            fprintf(stderr, "%s.%d: event-handler solaris-devpoll failed, try to set server.event-handler = \"poll\" or \"select\"\n",
                    __FILE__, __LINE__);
            return NULL;
        }
        break;
    case FDEVENT_HANDLER_FREEBSD_KQUEUE:
        if (0 != fdevent_freebsd_kqueue_init(ev))
        {
            fprintf(stderr, "%s.%d: event-handler freebsd-kqueue failed, try to set server.event-handler = \"poll\" or \"select\"\n",
                    __FILE__, __LINE__);
            return NULL;
        }
        break;
    default:
        fprintf(stderr, "%s.%d: event-handler is unknown, try to set server.event-handler = \"poll\" or \"select\"\n",
                __FILE__, __LINE__);
        return NULL;
    }

    return ev;
}

fdevent_init()函数根据fdevent_handler_t的值调用相应的初始化函数。我们前面已经假设系统使用epoll,那么进入

fdevent_linux_sysepoll_init()函数:
int fdevent_linux_sysepoll_init(fdevents * ev)
{
    ev->type = FDEVENT_HANDLER_LINUX_SYSEPOLL;
#define SET(x) \
    ev->x = fdevent_linux_sysepoll_##x;
    SET(free);
    SET(poll);
    SET(event_del);
    SET(event_add);
    SET(event_next_fdndx);
    SET(event_get_fd);
    SET(event_get_revent);
    //创建epoll
    if (-1 == (ev->epoll_fd = epoll_create(ev->maxfds)))
    {
        fprintf(stderr, "%s.%d: epoll_create failed (%s), try 
to set server.event-handler = \"poll\" or \"select\"\n",
                __FILE__, __LINE__, strerror(errno));
        return -1;
    }
    //设置epoll_fd为运行exec()函数时关闭。
    if (-1 == fcntl(ev->epoll_fd, F_SETFD, FD_CLOEXEC))
    {
        fprintf(stderr,    "%s.%d: epoll_create failed (%s), try 
to set server.event-handler = \"poll\" or \"select\"\n",
                __FILE__, __LINE__, strerror(errno));
        close(ev->epoll_fd);
        return -1;
    }
    //创建fd事件数组。在epoll_wait函数中使用。存储发生了IO事件的fd和
//对应的IO事件。
    ev->epoll_events = malloc(ev->maxfds * 
sizeof(*ev->epoll_events));
    return 0;
}

函数中首先通过SET宏对fdevents结构体中的函数指针赋值。然后创建epoll,最后做一些设置。
至此fdevent的初始化工作已经全部完成了。
额,没想到初始化讲了这么多。还是等下一篇再讲使用吧。。。

本文章由 http://www.wifidog.pro/2015/04/20/wifidog%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90Lighttpd%E5%88%9D%E5%A7%8B%E5%8C%96-2.html 整理编辑,转载请注明出处

wifidog源码分析Lighttpd1.4.20源码分析之fdevent系统(2)-----初始化(1)

前面讲了lighttpd的fdevents结构体以及fdevent系统的对外接口,这一篇将解析一下fdevent系统初始化。
C程序在进行真正的编译之前都要进行预编译,那么,我们就先来看看fdevent系统中的一些宏。首先是fdevent.h开头的一些宏:

#if defined(HAVE_EPOLL_CTL) && defined(HAVE_SYS_EPOLL_H)
# if defined HAVE_STDINT_H
#  include <stdint.h>
# endif
# define USE_LINUX_EPOLL
# include <sys/epoll.h>
 #endif



 #if defined HAVE_POLL && (defined(HAVE_SYS_POLL_H) || defined(HAVE_POLL_H))
# define USE_POLL
# ifdef HAVE_POLL_H
#  include <poll.h>
# else
#  include <sys/poll.h>
# endif
# if defined HAVE_SIGTIMEDWAIT && defined(__linux__)
#  define USE_LINUX_SIGIO
#  include <signal.h>
# endif
 #endif

 #if defined HAVE_SELECT
# ifdef __WIN32
#  include <winsock2.h>
# endif
# define USE_SELECT
# ifdef HAVE_SYS_SELECT_H
#  include <sys/select.h>
# endif
 #endif

 #if defined HAVE_SYS_DEVPOLL_H && defined(__sun)
# define USE_SOLARIS_DEVPOLL
# include <sys/devpoll.h>
 #endif

 #if defined HAVE_SYS_EVENT_H && defined HAVE_KQUEUE
# define USE_FREEBSD_KQUEUE
# include <sys/event.h>
 #endif

 #if defined HAVE_SYS_PORT_H && defined HAVE_PORT_CREATE
# define USE_SOLARIS_PORT
# include <sys/port.h>
 #endif

通过上面的宏判断系统中是否有对应的多路IO系统,如果有,就定义对应的USE_XXX宏,用来方便后面程序的盘算。
预编译完这些宏以后,对于当前系统中有的多路IO系统,就会有对应的USE_XXX符号被定义。预编译器接着运行,将那些不需要的代码都忽略。由于fdevent.h中对所有可能的多路IO系统都定义了初始化函数:

int fdevent_select_init(fdevents * ev);
 int fdevent_poll_init(fdevents * ev);
 int fdevent_linux_rtsig_init(fdevents * ev);
 int fdevent_linux_sysepoll_init(fdevents * ev);
 int fdevent_solaris_devpoll_init(fdevents * ev);
 int fdevent_freebsd_kqueue_init(fdevents * ev);

因此,对于系统中没有的多路IO系统对应的初始化函数,预编译结束后,这些初始化函数被定义为报错函数。如epoll对应的为:

int fdevent_linux_sysepoll_init(fdevents * ev)
{
    UNUSED(ev);
    fprintf(stderr,    "%s.%d: linux-sysepoll not supported,
 try to set server.event-handler = \"poll\" or \"select\"\n",
             __FILE__, __LINE__);
    return -1;
}

等预编译器执行完后,进行真正的编译。这里,我们假设系统中只有epoll。
还是从server.c中的main函数开始。
首先,我们要看一看在配置中,有关fdevent的设置。进入configfile.c文件中的config_set_defaults()函数。函数的一开始就有这么一个定义:

struct ev_map
    {
        fdevent_handler_t et;
        const char *name;
    } event_handlers[] =
    {
        /*
         * - poll is most reliable - select works everywhere -
* linux-* are experimental
         */
#ifdef USE_POLL
        {FDEVENT_HANDLER_POLL, "poll"},
#endif
#ifdef USE_SELECT
        {FDEVENT_HANDLER_SELECT, "select"},
#endif
#ifdef USE_LINUX_EPOLL
        {FDEVENT_HANDLER_LINUX_SYSEPOLL, "linux-sysepoll"},
#endif
#ifdef USE_LINUX_SIGIO
        {FDEVENT_HANDLER_LINUX_RTSIG, "linux-rtsig"},
#endif
#ifdef USE_SOLARIS_DEVPOLL
        {FDEVENT_HANDLER_SOLARIS_DEVPOLL, "solaris-devpoll"},
#endif
#ifdef USE_FREEBSD_KQUEUE
        {FDEVENT_HANDLER_FREEBSD_KQUEUE, "freebsd-kqueue"},
        {FDEVENT_HANDLER_FREEBSD_KQUEUE, "kqueue"},
#endif
        {FDEVENT_HANDLER_UNSET, NULL}
    };

这个结构体定义了多路IO系统的类型和名称。那些宏的作用就不多说了。上面的语句定义了一个struct ev_map类型的数组。数组的内容是当前系统中存在的多路IO系统的类型和名称。这里排序很有意思,从注释中可以看出,poll排在最前因为最可靠,select其次因为支持最广泛,epoll第三因为是最好的。

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