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文件.
- lighttpd-1.4.39 : fdevents and Event Handler
- android event listener and event handler
- lighttpd-1.4.39 : watcher and worker
- lighttpd-1.4.39 : array
- lighttpd-1.4.39 : buffer
- lighttpd-1.4.39 : alarm
- lighttpd-1.4.39 : plugin
- lighttpd-1.4.39 : connection
- lighttpd-1.4.39 : mod_staticfile
- lighttpd-1.4.39 : state machine
- event generator,event dispatcher,event handler.
- Event Handler in Laszlo
- JavaScript -- event handler
- Java pogramming : Event Handler
- nagios event handler配置
- PHP and Lighttpd for Android
- PreviewXXX event and XXX event
- Event Manager and Event Listener
- Matrix Mathematics
- 1025: [SCOI2009]游戏 线性筛素数+DP
- 1046. Shortest Distance (20)
- Shiro简介
- Visual Studio 2008 可扩展性开发(四):添加新的命令
- lighttpd-1.4.39 : fdevents and Event Handler
- LeetCode Valid Sudoku
- oracle数据库学习(二)
- sklearn sparse matrix 的构造方法
- HDOJ 1005 Number Sequence
- 解决“Git Windows客户端保存用户名与密码”的问题
- DLL的使用
- java概述
- MIPS构架简介