slighttpd基于lighttpd架构的Server项目实战—状态机机制回顾

Posted jiange_zh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了slighttpd基于lighttpd架构的Server项目实战—状态机机制回顾相关的知识,希望对你有一定的参考价值。

有限状态机FSM(Finite State Machine)

关于状态机的一个极度确切的描述是它是一个有向图形,由一组节点和一组相应的转移函数组成。状态机通过响应一系列事件而“运行”。每个事件都在属于“当前” 节点的转移函数的控制范围内,其中函数的范围是节点的一个子集。函数返回“下一个”(也许是同一个)节点。这些节点中至少有一个必须是终态。当到达终态, 状态机停止。

传统应用程序的控制流程基本是顺序的:遵循事先设定的逻辑,从头到尾地执行。很少有事件能改变标准执行流程;而且这些事件主要涉及异常情况。“命令行实用程序”是这种传统应用程序的典型例子。

另一类应用程序由外部发生的事件来驱动——换言之,事件在应用程序之外生成,无法由应用程序或程序员来控制。具体需要执行的代码取决于接收到的事件,或者它相对于其他事件的抵达时间。所以,控制流程既不能是顺序的,也不能是事先设定好的,因为它要依赖于外部事件。

显然,必须采取不同的技术来处理这些情况。它能处理任何顺序的事件,并能提供有意义的响应——即使这些事件发生的顺序和预计的不同。有限状态机正是为了满足这方面的要求而设计的。

lighttpd的状态机机制简要回顾

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

这里写图片描述

状态机机制体现了清晰的逻辑,并且其设计可以让我们更好地将服务器主体与插件结合起来,共同来完成请求的处理与响应。

下面我们将对lighttpd的状态机进行简要的回顾,具体的讨论,可以参见以下博文:

Lighttpd1.4.20源码分析 笔记 状态机与插件

Lighttpd1.4.20源码分析 笔记 状态机之请求处理

Lighttpd1.4.20源码分析 笔记 状态机之response

Lighttpd1.4.20源码分析 笔记 状态机之错误处理和连接关闭

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

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

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

状态定义如下:

typedef enum
{
    CON_STATE_CONNECT,             //connect 连接开始
     CON_STATE_REQUEST_START,     //reqstart 开始读取请求
     CON_STATE_READ,             //read 读取并解析请求
     CON_STATE_REQUEST_END,         //reqend 读取请求结束
     CON_STATE_READ_POST,         //readpost 读取post数据
     CON_STATE_HANDLE_REQUEST,     //handelreq 处理请求
    CON_STATE_RESPONSE_START,     //respstart 开始回复
    CON_STATE_WRITE,             //write 回复写数据
    CON_STATE_RESPONSE_END,     //respend 回复结束
    CON_STATE_ERROR,             //error 出错
    CON_STATE_CLOSE             //close 连接关闭
} connection_state_t;

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

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

int connection_state_machine(server * srv, connection * con)
{
    int done = 0, r;
    while (done == 0)
    {
        size_t ostate = con -> state;
        int b;
        //根据当前状态机的状态进行相应的处理和状态转换。
        switch (con->state)
        {
        case CON_STATE_REQUEST_START:    /* transient */
        //do something
        case CON_STATE_REQUEST_END:    /* transient */
        //do something
        case CON_STATE_HANDLE_REQUEST:
        //do something
        case CON_STATE_RESPONSE_START:
        //do something
        case CON_STATE_RESPONSE_END:    /* transient */
        //do something
        case CON_STATE_CONNECT:
        //do something
        case CON_STATE_CLOSE:
        //do something
        case CON_STATE_READ_POST:
        //do something
        case CON_STATE_READ:
        //do something
        case CON_STATE_WRITE:
        //do something
        case CON_STATE_ERROR:    /* transient */
        //do something
        default:
        //do something
            break;
        }//end of switch(con -> state) ...
        if (done == -1)
        {
            done = 0;
        }
        else if (ostate == con->state)
        {
            done = 1;
        }
    }
    /* something else */
    return 0;
}

可以看到,事实上,状态机的主体就是一个switch语句,它根据不同的state进入相应的分支,进行事件的处理,在一个状态处理结束时,会通过调用connection_set_state()函数来设置新的状态,从而推动状态机的运转。

在lighttpd中,各个状态所做的工作总结如下:

【CON_STATE_CONNECT】

清除待读取队列中的数据-chunkqueue_reset(con->read_queue);
置con->request_count = 0。

【CON_STATE_REQUEST_START】  /*transient */

过渡状态;
记录事件起始时间;
con->request_count++(一次长连接最多可以处理的请求数量是有限制的);
转移到CON_STATE_READ状态。

【CON_STATE_READ】和【CON_STATE_READ_POST】

调用connection_handle_read_state(srv,con);
服务器从连接读取HTTP头并存放在con->requeset.request中。
两者的区别:POST的数据量比较大,可能需要临时文件来存储。

【CON_STATE_REQUEST_END】    /*transient */

调用http_request_parse(srv, con)解析请求;
函数首先解析Request line,解析出来的结果存放在
con->request.http_method, 
con->request.http_version和
con->request.uri中;
解析完request line后,开始分析header lines。
将field name和value保存到con->request.headers中。
解析完后判断是否有POST数据,有则进入CON_STATE_READ_POST状态,
否则转移到CON_STATE_HANDLE_REQUEST状态。

【CON_STATE_HANDLE_REQUEST】

本状态需要决定如何处理请求;
该状态调用http_response_prepare函数,根据返回值进行相应的处理。
如果函数返回HANDLER_FINISHED,且con->mode!=DIRECT(事件已被插件接管),
则直接进入CON_STATE_RESPONSE_START。
否则lighttpd会做一些处理后再进入CON_STATE_RESPONSE_START状态。
如果函数返回了HANDLER_WAIT_FOR_FD或
HANDLER_WAIT_FOR_EVENT,
状态依旧会停留在CON_STATE_HANDLE_REQUEST,等待事件或数据。
如果函数返回了HANDLER_ERROR,进入到CON_STATE_ERROR状态。

【CON_STATE_RESPONSE_START】

调用connection_handle_write_prepare(srv,con);
根据客户端请求的method来设置response的headers;
状态机进入CON_STATE_WRITE状态。

【CON_STATE_WRITE】

调用connection_handle_write(srv,con);
将响应写回给客户端,注意,数据可能一次发送不完。
如果数据发送完毕,状态机进入CON_STATE_RESPONSE_END状态。

【CON_STATE_RESPONSE_END】

通知所有插件连接处理完毕;
如果是长连接,重新回到CON_STATE_REQUEST_START;
否则通知所有插件连接关闭;
执行connection_close(srv, con);
和connection_reset(srv, con);
连接关闭。

【CON_STATE_ERROR】   /* transient */

调用插件handle_request_done;
调用插件handle_connection_close;
执行connection_close将连接关闭。

【CON_STATE_CLOSE】

调用connection_close(srv, con);
将连接关闭,
注意,这里服务器主动关闭连接,使用shutdown而不是close。

好了,回顾完lighttpd的状态机机制之后,下一节中,我们将把状态机机制引入到我们的项目当中!~

以上是关于slighttpd基于lighttpd架构的Server项目实战—状态机机制回顾的主要内容,如果未能解决你的问题,请参考以下文章

slighttpd基于lighttpd架构的Server项目实战—http-parser

slighttpd基于lighttpd架构的Server项目实战—状态机机制回顾

slighttpd基于lighttpd架构的Server项目实战(10)—插件&动态库

slighttpd基于lighttpd架构的Server项目实战—预备知识之Http

基于 Serverless 架构的编程学习小工具

基于 Serverless 架构的编程学习小工具