Lighttpd1.4.20源码分析之状态机(1)---状态机总览

http://www.cnblogs.com/kernel_hcy/archive/2010/03/24/1694203.html

前面讲了lighttpd的fdevent系统,从这一篇开始,我们将进入lighttpd的状态机。状态机可以说是lighttpd最核心的部分。lighttpd将一个连接在不同的时刻分成不同的状态,状态机则根据连接当前的状态,决定要对连接进行的处理以及下一步要进入的状态。下面这幅图描述了lighttpd的状态机:

(在lighttpd源码文件夹中的doc目录中有个state.dot文件,通过dot命令可以生成上面的图:dot -Tpng state.dot -o state.png。)     图中的各个状态对应于下面的一个枚举类型:

1 typedef enum
 2 {
 3     CON_STATE_CONNECT,             //connect 连接开始
 4      CON_STATE_REQUEST_START,     //reqstart 开始读取请求
 5      CON_STATE_READ,             //read 读取并解析请求
 6      CON_STATE_REQUEST_END,         //reqend 读取请求结束
 7      CON_STATE_READ_POST,         //readpost 读取post数据
 8      CON_STATE_HANDLE_REQUEST,     //handelreq 处理请求
 9     CON_STATE_RESPONSE_START,     //respstart 开始回复
10     CON_STATE_WRITE,             //write 回复写数据
11     CON_STATE_RESPONSE_END,     //respend 回复结束
12     CON_STATE_ERROR,             //error 出错
13     CON_STATE_CLOSE             //close 连接关闭
14 } connection_state_t;

    在每个连接中都会保存这样一个枚举类型变量,用以表示当前连接的状态。connection结构体的第一个成员就是这个变量。     在连接建立以后,在connections.c/connection_accpet()函数中,lighttpd会调用connection_set_state()函数,将新建立的连接的状态设置为CON_STATE_REQUEST_START。在这个状态中,lighttpd记录连接建立的时间等信息。     下面先来说一说整个状态机的核心函数───connections.c/ connection_state_machine()函数。函数很长,看着比较吓人。。。其实,这里我们主要关心的是函数的主体部分:while循环和其中的那个大switch语句,删减之后如下:

1 int connection_state_machine(server * srv, connection * con)
 2 {
 3     int done = 0, r;
 4     while (done == 0)
 5     {
 6         size_t ostate = con -> state;
 7         int b;
 8         //这个大switch语句根据当前状态机的状态进行相应的处理和状态转换。
 9         switch (con->state)
10         {
11         case CON_STATE_REQUEST_START:    /* transient */
12         case CON_STATE_REQUEST_END:    /* transient */
13         case CON_STATE_HANDLE_REQUEST:
14         case CON_STATE_RESPONSE_START:
15         case CON_STATE_RESPONSE_END:    /* transient */
16         case CON_STATE_CONNECT:
17         case CON_STATE_CLOSE:
18         case CON_STATE_READ_POST:
19         case CON_STATE_READ:
20         case CON_STATE_WRITE:
21         case CON_STATE_ERROR:    /* transient */
22         default:
23             break;
24         }//end of switch(con -> state) ...
25         if (done == -1)
26         {
27             done = 0;
28         }
29         else if (ostate == con->state)
30         {
31             done = 1;
32         }
33     }
34     return 0;
35 }

    程序进入这个函数以后,首先根据当前的状态进入对应的switch分支执行相应的动作。然后,根据情况,进入下一个状态。跳出switch语句之后,如果连接的状态没有改变,说明连接读写数据还没有结束,但是需要等待IO事件,这时,跳出循环,等待IO事件。对于done==-1的情况,是在CON_STATE_HANDLE_REQUEST状态中的问题,后面再讨论。如果在处理的过程中没有出现需要等待IO事件的情况,那么在while循环中,连接将被处理完毕并关闭。 接着前面的话题,在建立新的连接以后,程序回到network.c/network_server_handle_fdevent()函数中的for循环在中后,lighttpd对这个新建立的连接调用了一次connection_state_machine()函数。如果这个连接没有出现需要等待IO事件的情况,那么在这次调用中,这个连接请求就被处理完毕。但是实际上,在连接第一次进入CON_STATE_READ状态时,几乎是什么都没做,保持这个状态,然后跳出了while循环。在循环后面,还有一段代码:

1  switch (con->state)
 2     {
 3     case CON_STATE_READ_POST:
 4     case CON_STATE_READ:
 5     case CON_STATE_CLOSE:
 6         fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_IN);
 7         break;
 8     case CON_STATE_WRITE:
 9         if (!chunkqueue_is_empty(con->write_queue) &&
10             (con->is_writable == 0)&& (con->traffic_limit_reached == 0))
11         {
12             fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_OUT);
13         }
14         else
15         {
16             fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
17         }
18         break;
19     default:
20         fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
21         break;
22     }

这段代码前面已经介绍过,这个连接的连接fd被加入到fdevent系统中,等待IO事件。当有数据可读的时候,在main函数中,lighttpd调用这个fd对应的handle函数,这里就是connection_handle_fdevent()函数。这个函数一开始将连接加入到了joblist(作业队列)中。前面已经说过,这个函数仅仅做了一些标记工作。程序回到main函数中时,执行了下面的代码:

1 for (ndx = 0; ndx < srv->joblist->used; ndx++)
 2         {
 3             connection *con = srv->joblist->ptr[ndx];
 4             handler_t r;
 5             connection_state_machine(srv, con);
 6             switch (r = plugins_call_handle_joblist(srv, con))
 7             {
 8             case HANDLER_FINISHED:
 9             case HANDLER_GO_ON:
10                 break;
11             default:
12                 log_error_write(srv, __FILE__, __LINE__, "d", r);
13                 break;
14             }
15             con->in_joblist = 0;//标记con已经不在队列中。
16         }

这段代码就是对joblist中的所有连接,依次对其调用connection_state_machine()函数。在这次调用中,连接开始真正的读取数据。lighttpd调用connection_handle_read_state()函数读取数据。在这个函数中,如果数据读取完毕或出错,那么连接进入相应的状态,如果数据没有读取完毕那么连接的状态不变。(PS:在connection_handle_read_state()读取的数据其实就是HTTP头,在这个函数中根据格式HTTP头的格式判断HTTP头是否已经读取完毕,包括POST数据。)上面说到,在connection_state_machile()函数的while循环中,如果连接的状态没有改变,那么将跳出循环。继续等待读取数据。 读取完数据,连接进入CON_STATE_REQUEST_END。在这个状态中lighttpd对HTTP头进行解析。根据解析的结果判断是否有POST数据。如果有,则进入CON_STATE_READ_POST状态。这个状态的处理和CON_STATE_READ一样。如果没有POST数据,则进入CON_STATE_HANDLE_REQUEST状态。在这个状态中lighttpd做了整个连接最核心的工作:处理连接请求并准备response数据。 处理完之后,连接进入CON_STATE_RESPONSE_START。在这个状态中,主要工作是准备response头。准备好后,连接进入CON_STATE_WRITE状态。显然,这个状态是向客户端回写数据。第一次进入WRITE状态什么都不做,跳出循环后将连接fd加入fdevent系统中并监听写事件(此时仅仅是修改要监听的事件)。当有写事件发生时,和读事件一样调用connection_handle_fdevent函数做标记并把连接加入joblist中。经过若干次后,数据写完。连接进入CON_STATE_RESPONSE_END状态,进行一些清理工作,判断是否要keeplive,如果是则连接进入CON_STATE_REQUEST_START状态,否则进入CON_STATE_CLOSE。进入CLOSE后,等待客户端挂断,执行关闭操作。这里顺便说一下,在将fd加到fdevent中时,默认对每个fd都监听错误和挂断事件。 连接关闭后,connection结构体并没有删除,而是留在了server结构体的connecions成员中。以便以后再用。 关于joblist有一个问题。在每次将连接加入的joblist中时,通过connection结构体中的in_joblist判断是否连接已经在joblist中。但是,在joblist_append函数中,并没有对in_joblist进行赋值,在程序的运行过程中,in_joblist始终是0.也就是说,每次调用joblist_append都会将连接加入joblist中,不论连接是否已经加入。还有,当连接已经处理完毕后,程序也没有将对应的connection结构体指针从joblist中删除,虽然这样不影响程序运行,因为断开后,对应的connection结构体的状态被设置成CON_STATE_CONNECT,这个状态仅仅是清理了一下chunkqueue。但这将导致joblist不断增大,造成轻微的内存泄漏。在最新版(1.4.26)中,这个问题依然没有修改。     就先说到这。后面将详细介绍各个状态的处理。

分类: Lighttpd源码分析

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏儿童编程

我不是算命先生,却对占卜有了疑惑——如何论证“占卜前提”的正确与否

事出有因,我对《周易》感兴趣了很多年。只是觉得特别有趣,断断续续学习了一些皮毛。这几天又偶然接触到了《梅花易数》,觉得很是精彩,将五行八卦天干地支都串联了起来。...

15310
来自专栏儿童编程

一张导图梳理欧洲简史梗概

3.1K30
来自专栏儿童编程

一张图理清《梅花易数》梗概

学《易经》的目的不一定是为了卜卦,但是了解卜卦绝对能够让你更好地了解易学。今天用一张思维导图对《梅花易数》的主要内容进行概括,希望能够给学友们提供帮助。

32240
来自专栏儿童编程

声音功能让儿童编程更有创造性

导读:Scratch中声音功能非常强大,除了常规的音效,你甚至可以模拟各种乐器的各个发音、设置节拍、休止……如果你愿意,甚至可以用它创作一个交响乐。我们可以引导...

13840
来自专栏儿童编程

什么样的人生才是有意义的人生——没有标准的标准答案

【导读】其实我们可以跳出这个小圈圈去更加科客观地看一下这个世界。在夜晚的时候我们仰望天空,浩瀚的宇宙中整个地球只是一粒浮尘,何况地球上一个小小的人类?在漫长的历...

1.8K50
来自专栏儿童编程

《动物魔法学校》儿童学编程Scratch之“外观”部分

导读:本文通过一个案例《动物魔法学校》来学习Scratch语言的“外观”部分。之后通过一系列其他功能的综合运用对作品功能进行了扩展。

19240
来自专栏儿童编程

天干地支五行八卦的对应关系

19690
来自专栏Ken的杂谈

【系统设置】CentOS 修改机器名

18130
来自专栏FSociety

SQL中GROUP BY用法示例

GROUP BY我们可以先从字面上来理解,GROUP表示分组,BY后面写字段名,就表示根据哪个字段进行分组,如果有用Excel比较多的话,GROUP BY比较类...

5.2K20
来自专栏儿童编程

儿童创造力教育与编程教育的碰撞——MIT雷斯尼克教授最新理论梗概

儿童编程教育已经在我国各一线二线城市疯狂出现,颇有“烂大街”的趋势。我们不禁要问很多很多问题:

22370

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励