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

前面讲了lighttpd的fdevent系统,从这一篇开始,我们将进入lighttpd的状态机。状态机可以说是lighttpd最核心的部分。lighttpd将一个连接在不同的时刻分成不同的状态,状态机则根据连接当前的状态,决定要对连接进行的处理以及下一步要进入的状态。下面这幅图描述了lighttpd的状态机:
 2010032422082570.png
(在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)中,这个问题依然没有修改。
    就先说到这。后面将详细介绍各个状态的处理。

 

转载于:https://www.cnblogs.com/kernel_hcy/archive/2010/03/24/1694203.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值