Lighttpd源码分析之状态机与插件

来源:互联网 发布:算法工程师面试题 编辑:程序博客网 时间:2024/06/06 08:44

Lighttpd启动时完成了一系列初始化操作后,就进入了一个包含11个状态的有限状态机中。

每个连接都是一个connection实例(con),状态的切换取决于con->state。

lighttpd经过初步处理后将con的基本信息初始化,而插件对事件的处理就是针对con进行的,它拿到con后按照业务需要进行相应处理,然后再交还给lighttpd,lighttpd根据con中的信息完成响应。

状态定义如下:

[cpp] view plain copy
  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;  

下面就是lighttpd的状态机:

这里写图片描述

在每个连接中都会保存这样一个状态机,用以表示当前连接的状态。

在连接建立以后,在connections.c/connection_accpet()函数中,lighttpd调用connection_set_state()函数,将新建立的连接的状态设置为CON_STATE_REQUEST_START。在这个状态中,lighttpd记录连接建立的时间等信息。

整个状态机的核心函数是connections.c/ connection_state_machine()函数。

函数的主体部分删减之后如下:

[cpp] view plain copy
  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.         //根据当前状态机的状态进行相应的处理和状态转换。  
  9.         switch (con->state)  
  10.         {  
  11.         case CON_STATE_REQUEST_START:    /* transient */  
  12.         //do something  
  13.         case CON_STATE_REQUEST_END:    /* transient */  
  14.         //do something  
  15.         case CON_STATE_HANDLE_REQUEST:  
  16.         //do something  
  17.         case CON_STATE_RESPONSE_START:  
  18.         //do something  
  19.         case CON_STATE_RESPONSE_END:    /* transient */  
  20.         //do something  
  21.         case CON_STATE_CONNECT:  
  22.         //do something  
  23.         case CON_STATE_CLOSE:  
  24.         //do something  
  25.         case CON_STATE_READ_POST:  
  26.         //do something  
  27.         case CON_STATE_READ:  
  28.         //do something  
  29.         case CON_STATE_WRITE:  
  30.         //do something  
  31.         case CON_STATE_ERROR:    /* transient */  
  32.         //do something  
  33.         default:  
  34.         //do something  
  35.             break;  
  36.         }//end of switch(con -> state) ...  
  37.         if (done == -1)  
  38.         {  
  39.             done = 0;  
  40.         }  
  41.         else if (ostate == con->state)  
  42.         {  
  43.             done = 1;  
  44.         }  
  45.     }  
  46.     /* something else */  
  47.   
  48.     /* 将fd加入到fdevent系统中,等待IO事件。 
  49.      * 当有数据可读的时候,在main函数中,lighttpd调用这个fd对应的handle函数, 
  50.      * 这里就是connection_handle_fdevent()函数。 
  51.      * 这个函数一开始将连接加入到了joblist(作业队列)中。 
  52.      */  
  53.     switch (con->state)  
  54.     {  
  55.     case CON_STATE_READ_POST:  
  56.     case CON_STATE_READ:  
  57.     case CON_STATE_CLOSE:  
  58.         fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_IN);  
  59.         break;  
  60.     case CON_STATE_WRITE:  
  61.         /* request write-fdevent only if we really need it 
  62.          * - if we have data to write 
  63.          * - if the socket is not writable yet 
  64.          */  
  65.         if (!chunkqueue_is_empty(con->write_queue) &&  
  66.             (con->is_writable == 0)&& (con->traffic_limit_reached == 0))  
  67.         {  
  68.             fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_OUT);  
  69.         }  
  70.         else  
  71.         {  
  72.             fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);  
  73.         }  
  74.         break;  
  75.     default:  
  76.         fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);  
  77.         break;  
  78.     }  
  79.     return 0;  
  80. }  

这个函数首先根据当前的状态进入对应的switch分支执行相应的动作,然后根据情况进入下一个状态。

跳出switch语句之后,如果连接的状态没有改变,说明连接读写数据还没有结束,但是需要等待IO事件,这时跳出循环,等待IO事件。

如果在处理的过程中不需要等待IO事件,那么在while循环中,连接将被处理完毕并关闭。

在我们的main函数中,之前讨论过,在一个while循环中,处理超时,处理IO时间,之后有下面这段代码:

[cpp] view plain copy
  1. for (ndx = 0; ndx < srv->joblist->used; ndx++) {  
  2.       connection *con = srv->joblist->ptr[ndx];  
  3.       handler_t r;  
  4.   
  5.       connection_state_machine(srv, con);  
  6.   
  7.       switch(r = plugins_call_handle_joblist(srv, con)) {  
  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.   
  16.       con->in_joblist = 0;  
  17.   }  

这段代码对joblist中的所有连接依次调用connection_state_machine()函数进行处理。

下面说明下各状态的主要内容:

[cpp] view plain copy
  1. CON_STATE_CONNECT  
  2. 清除待读取队列中的数据-chunkqueue_reset(con->read_queue);  
  3. 置con->request_count = 0。(本次连接还未处理过请求)  
  4. CON_STATE_REQUEST_START  /*transient */  
  5. 记录事件起始时间;  
  6. con->request_count++(一次长连接最多可以处理的请求数量是有限制的);  
  7. 转移到CON_STATE_READ状态。  
  8.   
  9. CON_STATE_READ和CON_STATE_READ_POST  
  10. connection_handle_read_state(srv,con);  
  11. CON_STATE_REQUEST_END    /*transient */  
  12. http_request_parse(srv, con);  
  13. 解析请求,若是POST请求则转移到CON_STATE_READ_POST状态,  
  14. 否则转移到CON_STATE_HANDLE_REQUEST状态。  
  15. CON_STATE_HANDLE_REQUEST  
  16. http_response_prepare(srv, con);  
  17. 函数中调用  
  18. handle_uri_raw;  
  19. handle_uri_clean;  
  20. handle_docroot;  
  21. handle_physical;  
  22. handle_subrequest_start;  
  23. handle_subrequest。  
  24. 如果函数返回了HANDLER_FINISHED,且con->mode!=DIRECT(事件已经被我们的业务插件接管),  
  25. 则直接进入CON_STATE_RESPONSE_START。  
  26. 否则lighttpd会做一些处理后再进入CON_STATE_RESPONSE_START状态。  
  27. 如果函数返回了HANDLER_WAIT_FOR_FD或  
  28. HANDLER_WAIT_FOR_EVENT,  
  29. 状态依旧会停留在CON_STATE_HANDLE_REQUEST,等待事件或数据。  
  30. 如果函数返回了HANDLER_ERROR,进入到CON_STATE_ERROR状态。  
  31. CON_STATE_RESPONSE_START  
  32. connection_handle_write_prepare(srv,con);  
  33. CON_STATE_WRITE  
  34. connection_handle_write(srv,con);  
  35. CON_STATE_RESPONSE_END  
  36. 调用插件的handle_request_done接口。  
  37. 如果是长连接,重新回到CON_STATE_REQUEST_START;否则调用插件的handle_connection_close接口。  
  38. 执行connection_close(srv, con);和connection_reset(srv, con);将连接关闭。  
  39. CON_STATE_ERROR   /* transient */  
  40. 调用插件handle_request_done;  
  41. 调用插件handle_connection_close;  
  42. 执行connection_close将连接关闭。  
  43. CON_STATE_CLOSE  
  44. connection_close(srv, con);将连接关闭。  

以上是状态机的概况。

总览了状态机,我们知道状态机会针对相应的阶段对事件进行处理,那么状态机是如何处理这些事件的?

事实上,对于事件的处理,一部分是由lighttpd完成的,而一部分是由插件完成的。插件中那些负责事件处理的接口分布在某几个状态中。我们只需在插件的各个阶段完成指定工作并返回相应的返回值,就可以促使状态机完成状态切换,完成事件的整套处理流程,并最终由lighttpd完成事件的响应。

在插件中,我们可以编写代码来注册lighttpd提供的回调接口,lighttpd在初始化阶段、状态机执行阶段、退出阶段会分别调用这些回调函数,完成插件的实例化,初始化,连接重置,事件处理,插件释放等功能。

要了解lighttpd对插件的调用方式,需要明白一个概念:事件接管。

对于每个事件,都有一个mode字段(con->mode)。该字段的定义:

typedef enum { DIRECT, EXTERNAL } connection_type;

连接对象有一个字段mode用来标识该连接是最初由服务器accept产生的客户端连接还是插件产生的其他辅助连接,当mode=DIRECT时表示对应连接由lighttpd服务器accept产生,mode!=DIRECT时表示对应连接是由插件产生的。

事件(con)初始化时mode是DIRECT;connection_reset(srv,con);

lighttpd在大部分流程中会在入口检查到mode != DIRECT时直接返回GO_ON。即:此事件由用户插件接管,lighttpd不参与。

用户编写的插件应通过将mode置为插件自身的ID达到接管的作用。插件ID是在插件加载时由插件的加载顺序确定的,是插件的唯一标识。

用户编写插件在每个接口的一开始应该判断mode是否等于自身的ID,若相等才能继续执行,否则直接退出,返回GO_ON。

了解了以上概念之后,我们就可以理解lighttpd对插件的调用方式了:

在lighttpd需要调用插件某一个阶段的接口函数时,会对所有插件注册在该处的接口顺序调用,顺序与插件加载顺序相同。例如:调用uri_raw接口,会先调用A插件的mod_A_uri_raw,然后调用B插件的mod_B_uri_raw,直到将所有已加载插件这个位置的接口全部调用完成。但实际处理这次事件通常只有一个插件,即插件ID与mode相同的那个插件。

因此,假设在CON_STATE_HANDLE_REQUEST状态,lighttpd调用了插件的handle_uri_raw接口,但是我们有多个插件,每个插件都注册了handle_uri_raw这个接口,lighttpd也能辨别出要使用哪个插件。

如果插件在处理事件的过程中,想让lighttpd接管,还需要把mode置为DIRECT才行。

以上是lighttpd状态机和插件的总览概况。