【slighttpd】基于lighttpd架构的Server项目实战(9)—状态机

上一节中,我们简单回顾了lighttpd的状态机机制,在本节中,我们将把状态机机制引入到我们的项目当中~

我们的状态机大体上效仿lighttpd的状态机,不过有一些改动:

typedef enum
{
    CON_STATE_CONNECT,
    CON_STATE_REQUEST_START,
    CON_STATE_READ,
    CON_STATE_REQUEST_END,
    CON_STATE_HANDLE_REQUEST,
    CON_STATE_RESPONSE_START,
    CON_STATE_WRITE,
    CON_STATE_RESPONSE_END,
    CON_STATE_ERROR
} connection_state_t;

在这里,我们暂对post不做特殊处理,因此所有request数据都将在CON_STATE_READ阶段读进来。

另外,我们的close阶段隐含在了Connection的析构函数当中,因此也不拿出来作为一个状态,所以我们的状态如上所述。

我们整体看看状态机部分的代码:

bool Connection::StateMachine()
{
    request_state_t req_state;

    while (true)
    {
        switch (con_state)
        {
            case CON_STATE_CONNECT:
                ResetConnection();
                break;

            case CON_STATE_REQUEST_START:
                ++con_req_cnt;
                WantRead();
                SetState(CON_STATE_READ);
                break;

            case CON_STATE_READ:
                req_state = GetParsedRequest();
                if (req_state == REQ_ERROR) 
                {
                    return false;
                } 
                else if (req_state == REQ_IS_COMPLETE) 
                {
                    SetState(CON_STATE_REQUEST_END);
                    break;
                }
                else 
                {
                    return true;
                }
                break;

            case CON_STATE_REQUEST_END:
                NotWantRead();
                SetState(CON_STATE_HANDLE_REQUEST);
                break;

            case CON_STATE_HANDLE_REQUEST:
                PrepareResponse();
                SetState(CON_STATE_RESPONSE_START);
                break;

            case CON_STATE_RESPONSE_START:
                WantWrite();
                SetState(CON_STATE_WRITE);
                break;

            case CON_STATE_WRITE:
                con_outbuf += http_response.GetResponse();
                SetState(CON_STATE_RESPONSE_END);
                break;

            case CON_STATE_RESPONSE_END:
                NotWantWrite(); //设置flag表示不想读,但是如果缓冲区还有数据,仍发送,发送完毕再注销写事件
                delete http_req_parsed;
                http_req_parsed = NULL;
                http_response.ResetResponse();
                SetState(CON_STATE_REQUEST_START);
                break;

            case CON_STATE_ERROR:
                http_response.ResetResponse();
                SetErrorResponse();
                con_outbuf += http_response.GetResponse();
                if (con_outbuf.empty()) //错误信息写出去之后才关闭连接
                    return false;
                return true;

            default:
                return false;
        }
    }
    return true;
}

下面我们分别谈一谈各个阶段:

【CON_STATE_CONNECT】
重置连接,这一个状态将在引入连接池后发挥作用。

case CON_STATE_CONNECT:
    ResetConnection();
    break;

【CON_STATE_REQUEST_START】
过渡状态,为读取请求做准备工作;
con_req_cnt表示当前连接处理的请求次数,后面可能用于限制一次长连接所处理的请求数;
之后开始监听读事件,进入CON_STATE_READ。

case CON_STATE_REQUEST_START:
    ++con_req_cnt;
    WantRead();
    SetState(CON_STATE_READ);
    break;

【CON_STATE_READ】
这个阶段开始读数据,其中调用GetParsedRequest(),该函数从请求队列中获取一个已经解析完的请求并存放到数据成员http_req_parsed中,如果请求队列为空,则对读取的inbuf进行解析,如果是一个完整的请求,则获取该请求,否则返回REQ_NOT_COMPLETE,继续读取;
如果获得一个完整的请求,进入CON_STATE_REQUEST_END状态;

case CON_STATE_READ:
          req_state = GetParsedRequest();
          if (req_state == REQ_ERROR) 
          {
                    return false;
          } 
          else if (req_state == REQ_IS_COMPLETE) 
          {
                    SetState(CON_STATE_REQUEST_END);
                    break;
          }
          else 
          {
                    return true;
          }
          break;

【CON_STATE_REQUEST_END】
结束读取请求,注销读事件。

case CON_STATE_REQUEST_END:
          NotWantRead();
          SetState(CON_STATE_HANDLE_REQUEST);
          break;

【CON_STATE_HANDLE_REQUEST】
请求已经解析完了,在这里需要决定如何处理请求;
我们调用PrepareResponse(),它根据解析的结果,决定给用户返回怎样的回复,并填充http_response结构体;

case CON_STATE_HANDLE_REQUEST:
          PrepareResponse();
          SetState(CON_STATE_RESPONSE_START);
          break;

【CON_STATE_RESPONSE_START】
过渡阶段,开始写事件的监听。

case CON_STATE_RESPONSE_START:
          WantWrite();
          SetState(CON_STATE_WRITE);
          break;

【CON_STATE_WRITE】
开始写,调用http_response.GetResponse(),将http_response结构体的数据合成响应字符串,追加到outbuf中用于发送。

case CON_STATE_WRITE:
          con_outbuf += http_response.GetResponse();
          SetState(CON_STATE_RESPONSE_END);
          break;

【CON_STATE_RESPONSE_END】
调用NotWantWrite()表示不想写了,在这里,我们并没有真正注销写事件,而是只设置一个标识,以后不再触发新的写事件,但是留在输出缓冲区的数据仍继续发送,当缓冲区没数据之后,才真正注销写事件;
下面是写事件回调函数中的部分代码:

con->con_outbuf.erase(con->con_outbuf.begin(), con->con_outbuf.begin() + ret);
if (con->con_outbuf.size() == 0 && !con->con_want_write)
{
          con->UnsetWriteEvent();
}

只有缓冲区为空且服务器不想写时才真正注销写事件;

之后做一些清理工作,进入CON_STATE_REQUEST_START状态(keep-alive)。

case CON_STATE_RESPONSE_END:
          NotWantWrite(); //设置flag表示不想读,但是如果缓冲区还有数据,仍发送,发送完毕再注销写事件
          delete http_req_parsed;
          http_req_parsed = NULL;
          http_response.ResetResponse();
          SetState(CON_STATE_REQUEST_START);
          break;

【CON_STATE_ERROR】
出错时进入这个状态,设置错误码响应(500),之后需要将错误码发出去,因此在outbuf为空时才返回false,否则保持write事件。

case CON_STATE_ERROR:
          http_response.ResetResponse();
          SetErrorResponse();
          con_outbuf += http_response.GetResponse();
          if (con_outbuf.empty())   //错误信息写出去之后才关闭连接
                    return false;
          return true;

加入状态机后,我们的整个逻辑就清晰了许多!
接下来我们将开始添加插件模块,之后将插件与状态机结合起来,可以很明显的看到状态机带来的便利~

你可能感兴趣的:(http,server,架构,lighttpd,状态机)