前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Lighttpd1.4.20源码分析之状态机(1)---状态机总览

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

作者头像
bear_fish
发布2018-09-19 16:02:05
6850
发布2018-09-19 16:02:05
举报

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。)     图中的各个状态对应于下面的一个枚举类型:

代码语言:javascript
复制
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语句,删减之后如下:

代码语言:javascript
复制
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循环。在循环后面,还有一段代码:

代码语言:javascript
复制
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函数中时,执行了下面的代码:

代码语言:javascript
复制
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源码分析

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2016年08月03日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档