1、lighttpd1.4.18 代码分析( 一)-watcher,worker 模型 lighttpd 是目前非常流行的 web 服务器,很多流量非常大的网站(如 youtube)使用的就是lighttpd,它的代码量不多,但是设计巧妙,效率高, 功能完备(这是它将来能取代 Apache 的重要因素),编码风格优美, 是学习网络编程,熟悉 http 服务器编写的良好范例.在我初学网络编程的时候,就是看的 lighttpd 的源码进行学习,在其中学到了不少的技巧 .我打算将这些写出来与别人分享,可能开始比较杂乱 ,也不会作完全的分析,因为很多部分的代码我也没有看过,写一点是一点吧.我进行阅读和分析
2、的 lighttpd 版本是 1.4.18.lighttpd 采用的是多进程+ 多路复用 (如 select,epoll)的网络模型,它对多路复用 IO 操作的封装将作为下一个专题的内容,本次将讲解它所采用的多进程模型.lighttpd 中的配置文件有一项 server.max-worker 配置的是服务器生成的工作进城数.在lighttpd 中, 服务器主进程被称为 watcher(监控者),而由这个主进程创建出来的子进程被称为 woker(工作者 ),而 woker 的数量正是由上面提到的配置项进行配置的.watcher 创建并且监控 woker 的代码如下所示, 我觉得这是一段很巧妙的代
3、码,在我阅读了这段代码之后, 到目前为止,我所写的所有服务器采用的都是类似 lighttpd 的 watcher-woker 多进程模型, 这段代码在 src 目录中 server.c 文件的 main 函数中:#ifdef HAVE_FORK/* start watcher and workers */num_childs = srv-srvconf.max_worker;if (num_childs 0) int child = 0;while (!child case 0:child = 1;break;default:num_childs-;break; else int status
4、;if (-1 != wait( else switch (errno) case EINTR:/* if we receive a SIGHUP we have to close our logs ourself as we dont * have the mainloop who can help us here*/if (handle_sig_hup) handle_sig_hup = 0;log_error_cycle(srv);/* forward to all procs in the process-group* * we also send it ourself*/if (!f
5、orwarded_sig_hup) forwarded_sig_hup = 1;kill(0, SIGHUP);break;default:break;/* for the parent this is the exit-point */if (!child) /* * kill all children too */if (graceful_shutdown) kill(0, SIGINT); else if (srv_shutdown) kill(0, SIGTERM);log_error_close(srv);network_close(srv);connections_free(srv
6、);plugins_free(srv);server_free(srv);return 0;#endif首先,woker 的数量保存在变量 num_childs 中, 同时另一个变量 child 是一个标志位, 为 0时是父进程(watcher), 为 1 时则是子进程 (worker), 下面进入一个循环:while (!child 而子进程是 worker, 是具体执行服务器操作的工作者 , 在被创建完毕之后退出循环, 去做下面的事情 .而如果父进程退出这个循环 , 那么一定是srv_shutdown 或者 graceful_shutdown 之一变为了非零值, 所以在循环外, 还要进行判
7、断, 如果是父进程, 那么就是服务器程序要退出了 , 最后作一些清理的工作 .用伪码表示这部分代码就是:如果(是父进程 而且 当前没有要求终止服务器 ) 就一直循环下去如果还有未创建的子进程创建出一个子进程如果是子进程, 那么根据最上面的循环条件退出这个循环.如果是父进程, 那么将未创建的子进程数量 - 1否则 就是没有未创建的子进程一直保持睡眠, 一旦发现有子进程退出父进程就苏醒 , 将未创建的子进程数量 + 1;父进程的代码永远不会执行到这个循环体之外, 一旦发生, 就是因为要终止服务器的运行, 如果这种情况发生, 就进行最后的一些清理工作 .在这之后, 各子进程分道扬镳 , 各自去进行自
8、己的工作, 互不干扰.这也是我非常喜欢多进程编程的原因, 少了多线程编程中考虑到数据同步等麻烦的事情, 要考虑的事情相对而言简单的多了.关于多进程 VS 多线程的话题, 不在这里多加阐述了.lighttpd1.4.18 代码分析( 二)-fdevents 结构体解析 lighttpd 中采取了类似于 OO 中面向对象的方式封装了各种平台对网络 IO 的事件处理,这其中包括:OS Method Config Valueall select selectUnix poll pollLinux 2.4+ rt-signals linux-rtsigLinux 2.6+ epoll linux-sys
9、epollSolaris /dev/poll solaris-devpollFreeBSD, . kqueue freebsd-kqueueNetBSD kqueue kqueue所有的网络 IO 事件都要满足这个数据结构的要求, 这个名为 fdevents 的结构体可以看成是 OO 中虚拟基类, 而每种具体的实现则可以看成是继承并且实现了该虚拟基类中纯虚函数的派生类:typedef struct fdevents fdevent_handler_t type;fdnode *fdarray;size_t maxfds;#ifdef USE_LINUX_SIGIOint in_sigio;in
10、t signum;sigset_t sigset;siginfo_t siginfo;bitset *sigbset;#endif#ifdef USE_LINUX_EPOLLint epoll_fd;struct epoll_event *epoll_events;#endif#ifdef USE_POLLstruct pollfd *pollfds;size_t size;size_t used;buffer_int unused;#endif#ifdef USE_SELECTfd_set select_read;fd_set select_write;fd_set select_error
11、;fd_set select_set_read;fd_set select_set_write;fd_set select_set_error;int select_max_fd;#endif#ifdef USE_SOLARIS_DEVPOLLint devpoll_fd;struct pollfd *devpollfds;#endif#ifdef USE_FREEBSD_KQUEUEint kq_fd;struct kevent *kq_results;bitset *kq_bevents;#endif#ifdef USE_SOLARIS_PORTint port_fd;#endifint
12、(*reset)(struct fdevents *ev);void (*free)(struct fdevents *ev);int (*event_add)(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 n
13、dx);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;该结构体中包含了一些公有的参数, 也就是类似 OO 中虚拟基类中的成员变量, 无论是哪个派生类继承之后都会有这部分的成员,如最前面的几个成员:fdevent_handler_t type;fdnode *fdarray;size_t maxfds;其中 type 是如下类型
14、的枚举 :typedef enum FDEVENT_HANDLER_UNSET,FDEVENT_HANDLER_SELECT,FDEVENT_HANDLER_POLL,FDEVENT_HANDLER_LINUX_RTSIG,FDEVENT_HANDLER_LINUX_SYSEPOLL,FDEVENT_HANDLER_SOLARIS_DEVPOLL,FDEVENT_HANDLER_FREEBSD_KQUEUE,FDEVENT_HANDLER_SOLARIS_PORT fdevent_handler_t;, 其实也就是各个不同 IO 事件类型.fdarray 是一个存放 fdnode *类型的数组
15、:typedef struct _fdnode fdevent_handler handler;void *ctx;int fd;struct _fdnode *prev, *next; fdnode;这个结构体中, handler 是一个回调函数, 在事件触发时进行回调( 后面我们会讲到), ctx 是一个 context, 根据不同的 fd 进行区分.fd 就不必多说了, 是 socket fd, 可能是服务器监听所有的 fd, 也可能是 accept 之后与client 相关的 fd.再后面的两个参数, 将这个结构体变为了一个链表中的一个节点.maxfds, 这个成员用于存放可以处理的最
16、大数量.除了这些公有成员外, 根据编译时的预编译宏 , 该结构体还有其它的成员 , 这些成员就相当于 OO 中具体每个派生类自己私有的成员.在这个结构体的最后, 是一组函数指针 , 也就是 OO 中的纯虚函数, 每个派生类都要根据这些接口自己进行实现,我分别给加上了一些简单的注释 :/ 重置某个 fdeventsint (*reset)(struct fdevents *ev);/ 释放 fdevents 指针void (*free)(struct fdevents *ev);/ 向 fdevents 中添加一个 fd, events 表示这个 fd 对哪些事件感兴趣int (*event_a
17、dd)(struct fdevents *ev, int fde_ndx, int fd, int events);/ 向 fdevents 中删除一个 fdint (*event_del)(struct fdevents *ev, int fde_ndx, int fd);/ 根据 fd 在 fdevents 中的 fdarray 中的 index, 获取该 fd 目前对哪些事件感兴趣int (*event_get_revent)(struct fdevents *ev, size_t ndx);/ 根据 fd 在 fdevents 中的 fdarray 中的 index, 获取要进行处理的
18、 fdint (*event_get_fd)(struct fdevents *ev, size_t ndx);/ 获取下一个需要进行处理的 fd 在 fdarray 中的 indexint (*event_next_fdndx)(struct fdevents *ev, int ndx);/ 轮询, timeout_ms 是超时参数, 单位是微秒int (*poll)(struct fdevents *ev, int timeout_ms);/ 这个接口一般都没有实现, 为 NULLint (*fcntl_set)(struct fdevents *ev, int fd);在上面的函数中,
19、参数 int events 的值为以下几种:#define BV(x) (1 x)#define FDEVENT_IN BV(0)#define FDEVENT_PRI BV(1)#define FDEVENT_OUT BV(2)#define FDEVENT_ERR BV(3)#define FDEVENT_HUP BV(4)#define FDEVENT_NVAL BV(5)从这里可以看到, 具体判断某个事件是否发生 , 需要采用的掩码操作进行判断 , 而不是一般的比较操作, 如:if (events & FDEVENT_IN) 而不是 if (events = FDEVENT_IN)因此, 一个 fd, 当前所关注的事件类型可以不止一个而是有多个.了解了这个结构体之后, 也就了解了 lighttpd 中每种网络 IO 事件必须处理的事件类型及它们对外的接口.下一节开始讲解在 lighttpd 中是如何使用这个事件处理器的.
Copyright © 2018-2021 Wenke99.com All rights reserved
工信部备案号:浙ICP备20026746号-2
公安局备案号:浙公网安备33038302330469号
本站为C2C交文档易平台,即用户上传的文档直接卖给下载用户,本站只是网络服务中间平台,所有原创文档下载所得归上传人所有,若您发现上传作品侵犯了您的权利,请立刻联系网站客服并提供证据,平台将在3个工作日内予以改正。