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