lighttpd-1.4.20源码分析

lighttpd-1.4.20源码分析 以后的更新都在这个地址 http://bbs3.chinaunix.net/thread-1369986-1-1.html

网络编程讨论QQ群号:45438969

0  写在前面


对于轻量级web服务器lighttpd,我个人就不做多的介绍,大家自己在网上查查。我要说的是,从这开始,我将陆续写一些有关lighttpd源码分析的资料。
对于lighttpd源码(版本是1.4.20),我断断续续看了很久,啃了不少rfc文档,也查了不少资料,对着源码一边分析一边注释。1.4.20版本137个源码文件注释了其中的一大半(有40几个模块文件,我没有分析全部,只看了其中的几个模块,因为每一个模块是一个单独的功能,模块之间联系不是很大,但是模块文件有统一的结构,分析清楚了其中的一个,其它的就可以类推分析理解)。但这是好些天以前的事情了,最近一段时间,人懒了些,天天看动漫,把这些东西都忘记的差不多了。今天又翻出来,想想干脆自己再整理一下,形成文字,一来文字比代码更容易理解,说不定下次工作的时候可以再看看,二来嘛,最开始分析lighttpd的时候得到过CU上众多网友(特别是网友converse)的帮助,整理了发过来,如果能给大家一点点帮助也就算是谢谢各位网友了。:)
因为lighttpd源码较多,文档整理的过程中可能会前后引用,因此我就给各部分编个号,便于对应查找。另外,为了更集中的表达和理解lighttpd源码思路,文档里不会有太多的代码,各位在看代码的时候最好同时把lighttpd源码打开。

1  分析准备

1.1 lighttpd源码准备及相关网站
       lighttpd官方网站:http://www.lighttpd.net/,最新的稳定版本为1.4.20,开发版本为1.5.01.5的同以前的版本相比变大很大(相关信息可以查询该站点以及相关链接),而我这里分析的是1.4.20
相关连接:
       http://www.lighttpd.net/download/lighttpd-1.4.20.tar.gz
       http://www.lighttpd.net/download/lighttpd-1.4.20.tar.bz2
       http://www.lighttpd.net/download
       http://www.lighttpd.net/
       http://blog.lighttpd.net/
       http://redmine.lighttpd.net/wiki/lighttpd/Devel

1.2 lighttpd源码分析工具
我个人用的是windows系统,使用的分析工具是Source Insight,感觉还是很方便。如果你没有特别的要求,不妨也试试这个工具。


1.3 其它资料
       web服务器源码分析涉及许多rfc文档。下面提供了下载rfc文档的地址:
       http://www.mayan.cn/np/
       http://www.mayan.cn/comnet/tcpip/RFC-all.zip

2   lighttpd主工作模型分析

2.1 查找程序入口点
对于在一个包含众多源文件的工程中查找程序入口点,应从工程的makefile文件着手。lighttpd-1.4.20源文件包中提供了两个文件(Makefile.amMakefile.in),可由这两个文件利用autoconfautomake等生成makefile文件(刚刚接触的话,这个过程也许有些复杂,这不是我讲解的主要内容,在此略过。)。通过分析makefile文件中的文件依赖,逐步从程序反推到其对应的源代码文件。
编译lighttpd-1.4.20源码,将生成lemonlighttpdlighttpd-angelproc_openspawn-fcgi以及一些模块库文件,其中的lighttpd程序是lighttpd服务器的主程序,也就是我们分析的重点,其入口点对应的源程序文件为server.c
如果要运行lighttpd服务器,则可以在命令行下输入如下命令:
lighttpd -f /home/lenky/source/lighttpd-1.4.20/lenky/conf
-f选项指定其后的参数为lighttpd配置文件路径。lighttpd配置文件如何写,可以参考lighttpd-1.4.20源文件包提供的例子(x:/.../lighttpd-1.4.20/doc/lighttpd.conf)。

2.2 lighttpd主工作模型
2.2.1服务器模型
       lighttpd作者将lighttpd设计为多进程模型,一个监控进程(作者称其为watcher)和一些实际接受客户端请求并作出响应的工作进程(作者称它们为workers)。工作进程最大数目可由用户在配置文件里设置,比如设置工作进程为4,则在配置文件里加入一行“server.max-worker = 4”即可。如果用户在配置文件里没有对server.max-worker做设置,则max-worker0[1],此时没有监控进程,而工作进程数目为1
首先用函数内局部变量num_childs记录待创建的工作进程数目(初始值为用户配置的最大工作进程数目),同时申明一个child标志变量,初始值为0表示为父进程。父进程进入循环处理:如果当前进程数目没有达到最大工作进程数目,父进程则fork一个子进程,根据fork函数的返回值设置child标志变量,子进程内child1,其将跳出while循环往下执行,作为工作进程响应服务客户端请求,父进程内num_childs自减1表示剩余的待创建的工作进程数目。如果全部工作进程都已创建,父进程则调用了wait函数阻塞自己,由wait自动分析当前进程是否有某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;同时将子进程的退出状态保存在status变量内,并且将局部变量num_childs自增1,表示又可以新创建一个工作进程(这将在下一个循环时进行工作进程的fork创建);如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。
要父进程跳出while循环只有两种可能,一种可能是srv_shutdown 值为1,另一种可能是graceful_shutdown值为1。一旦父进程跳出while循环则表示服务器程序要退出了, 于是作一些清理的工作,包括终止所有的工作进程、关闭log日志记录、释放内存资源等。
如果简化成伪码的话,则如下所示:
如果(是父进程而且当前没有要求终止服务器)
{
如果还有未创建的工作子进程
   {
         fork创建出一个子进程
如果是子进程, child标志变量为1,根据最上面的循环条件将跳出该循环往下执行
如果是父进程, 那么将剩余的待创建的子进程数量减少1
   }
否则就是没有未创建的子进程
   {
父进程保持睡眠, 但是一旦发现有子进程退出,父进程就苏醒, 将待创建的子进程数数目增1,下一个while循环将创建新的工作子进程
   }
}
如果(是父进程)
{
父进程执行到这,表示服务器程序要退出了, 于是作一些清理的工作,包括终止所有的工作进程、关闭log日志记录、释放内存资源等。
}
以下是工作子进程的执行代码


2.2.2 工作进程服务模型
工作进程运行在一个大循环内,只要srv_shutdown不为真,即只要用户没有终止服务器进程,工作进程就持续运行。
首先判断是否收到HUP信号,如果收到则
       1,重置HUP信号收到标志handle_sig_hup0
       2,调用各个插件的HUP信号处理函数。plugins_call_handle_sighup函数是在plugin.h中申明,在plugin.c中通过宏扩展来定义的,具体细节以后再讲,此处只要知道工作进程通过plugins_call_handle_sighup来调用各个模块的HUP信号处理函数(如果该模块设置有对HUP信号进行处理的函数)。
       3,重新打开log日志文件,因为并没有重新读取配置文件,所以此处仅仅是重新打开原来的log日志文件,并写入一个表征收到过HUP信号的log记录。

接下来判断是否收到ALRM信号,如果收到则:
       1,重置ALRM信号收到标志handle_sig_alarm0
       2,比较当前时间和服务器上次记录时间,如果不相等则进行如下处理:
调用各个插件的ALRM信号处理函数。plugins_call_handle_trigger函数是在plugin.h中申明,在plugin.c中通过宏扩展来定义的,具体细节以后再讲,此处只要知道工作进程通过plugins_call_handle_trigger来调用各个模块的ALRM信号处理函数(如果该模块设置有对ALRM信号进行处理的函数)。
更新服务器记录时间,便于下次比较使用。
调用stat_cache_trigger_cleanup对文件状态缓存器中节点进行清除操作,删除一些比较旧的节点(文件状态缓存节点最后使用时间距离现在时间超过2秒)。
处理超时连接,也就是说lighttpd中对连接的超时处理比较简单, 就是在每隔一秒触发的定时器中查看当前的所有连接, 看它们的时间是否已经超过了最长的生存期, 如果是就关闭连接。具体来看,首先是一个for循环遍历当前的所有连接。对每一个连接,判断其当前状态并根据不同状态设置的超时值进行超时判断,如果超时将连接状态设置为CON_STATE_ERROR,接下来调用connection_state_machine函数进行状态机的状态转换操作。lighttpd中采用了所谓的状态机(state-engine)去处理每一个连接,状态机中的每一种节点表示连接当时所处的状态,包括connectreqstartreadreqendreadposthandlereq:respstartwriterespenderrorclose11个状态,这些状态之间的切换图如下图所示[2],至于具体与之对应的状态切换条件以及操作细节以后再讲。

处理传输速度限制,假设某一时刻平均传输速度达到了用户设置的最大值,于是停止发送数据(con->traffic_limit_reached1,如果为0if判断为假,不做任何处理)。随着时间的推移,平均传输速度会变小(相当于分子不变,分母变大),只要平均传输速度小于用户设置的最大值就继续发送数据(con->traffic_limit_reached设为0,同时调用状态机切换函数)。

根据当前的资源利用情况禁用或启用server sockets服务。
禁用:当文件描述符(当前文件描述符和等待文件描述符之和)大于0.9倍服务器最大(系统允许或用户设置)文件描述符数目或当前连接大于最大(系统允许或用户设置)连接数目或收到终止服务器指令时就需要禁用server sockets服务,此时逐个删除该工作进程上的所有在socket描述符上的事件监听器(通过fdevent_event_del函数)。另外,如果是收到关闭服务器则需要注销事件监听器结构(主要是释放内存空间,防止内存泄露)并且关闭文件描述符、删除附属文件。
启用:当文件描述符(当前文件描述符和等待文件描述符之和)小于0.8倍服务器最大(系统允许或用户设置)文件描述符数目并且当前连接小于0.9倍最大(系统允许或用户设置)连接数目并且终止服务器标志为0时就需要启用server sockets服务,此时逐个添加该工作进程上的所有在socket描述符上的事件监听器(通过fdevent_event_add函数)。

接下来查看如果有待处理的文件描述符,则通过状态机切换函数进行处理,为了合理利用资源,程序会保证至少有16个空闲文件描述符。

程序再接下来就是通过fdevent_poll -> epoll_wait(以USE_LINUX_EPOLL为例)来轮询I/O事件的发生,其中等待I/O事件发生的超时值timeout_ms=1000milliseconds,即1秒。如果在等待的这1秒内有I/O事件发生,则返回的n值记录事件数目,随后用一个do-while循环对每一个发生的I/O事件进行处理。
下面以伪码讲解这个do-while循环:
DO{
获得发生了I/O事件的文件描述符在fdarray中的索引
获得该文件描述符上发生的I/O事件类型
获得该文件描述符
获得I/O事件处理的回调函数
获得I/O事件处理的上下文环境

调用回调函数进行I/O事件处理,并传入相关参数。
}WHILEI/O事件未处理完)



2.3 注
[1] 如果用关键字max_worker搜索全部源文件,只找到三处,分别为
Base.h:     unsigned short max_worker;
Configfile.c:     cv[13].destination = &(srv->srvconf.max_worker);
Server.c:   num_childs = srv->srvconf.max_worker;
base.h里的是定义,configfile.c是获取用户配置,如果用户没有对server.max-worker做设
置,则不会改变srv->srvconf.max_worker的值。server.c是使用max_worker值。那在哪儿把srv->srvconf.max_worker的值设置为0的呢?注意server.c源文件里的server_init函数里的server *srv = calloc(1, sizeof(*srv));,我们知道calloc区别于malloc函数的功能就在于其会将所分配的内存空间中的每一位都初始化为零,也就是说,如果你是为字符类型或整数类型的元素分配内存,那么这些元素将保证会被初始化为0;如果你是为指针类型的元素分配内存,那么这些元素通常会被初始化为空指针;如果你为实型数据分配内存,则这些元素会被初始化为浮点型的零。因此srv结构体的unsigned short类型的max_worker元素其初值就为0

[2] 该状态图可以通过如下操作获得:
1,安装GraphViz
ubuntu系统的话可以在命令行下输入如下命令自动安装:
apt-cache search graphviz
sudo aptitude install graphviz graphviz-dev graphviz-doc libgraphviz-dev
2,把如下代码(这段代码在lighttpd官方网站在线帮助文档中可以找到)拷贝到一个文件,比如HTTPStates.dot
#!graphviz
digraph state {
   edge [color=green];
   connect -> reqstart -> read -> reqend -> handlereq -> respstart -> write -> respend -> connect;
   edge [color=grey];
   reqend -> readpost -> handlereq [ label="POST" ];
   edge [ color=blue]
   respend -> reqstart [ label="keep-alive" ];
   edge [ color=lightblue]
   handlereq -> handlereq [ label="sub-request" ];
   edge [style=dashed, color=red];
   error -> close -> connect;
   error -> connect;
   handlereq -> error;
   read -> error;
   readpost -> error;
   write -> error;
   connect [shape=box];
}
3,在命令行下执行下列命令生成HTTPStates.png就是所需的图。
dot -Tpng HTTPStates.dot -o HTTPStates.png

2.3 参考文献
http://bbs.chinaunix.net/viewthread.php?tid=1251434

你可能感兴趣的:(Lighttpd源码分析,lighttpd,工作,graphviz,服务器,plugins,makefile)