上一节中,我们简单回顾了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;
加入状态机后,我们的整个逻辑就清晰了许多!
接下来我们将开始添加插件模块,之后将插件与状态机结合起来,可以很明显的看到状态机带来的便利~