【slighttpd】基於lighttpd架構的Server專案實戰(8)—狀態機機制回顧
轉載地址:https://blog.csdn.net/jiange_zh/article/details/50640270
有限狀態機FSM(Finite State Machine)
關於狀態機的一個極度確切的描述是它是一個有向圖形,由一組節點和一組相應的轉移函式組成。狀態機通過響應一系列事件而“執行”。每個事件都在屬於“當前” 節點的轉移函式的控制範圍內,其中函式的範圍是節點的一個子集。函式返回“下一個”(也許是同一個)節點。這些節點中至少有一個必須是終態。當到達終態, 狀態機停止。
傳統應用程式的控制流程基本是順序的:遵循事先設定的邏輯,從頭到尾地執行。很少有事件能改變標準執行流程;而且這些事件主要涉及異常情況。“命令列實用程式”是這種傳統應用程式的典型例子。
另一類應用程式由外部發生的事件來驅動——換言之,事件在應用程式之外生成,無法由應用程式或程式設計師來控制。具體需要執行的程式碼取決於接收到的事件,或者它相對於其他事件的抵達時間。所以,控制流程既不能是順序的,也不能是事先設定好的,因為它要依賴於外部事件。
顯然,必須採取不同的技術來處理這些情況。它能處理任何順序的事件,並能提供有意義的響應——即使這些事件發生的順序和預計的不同。有限狀態機正是為了滿足這方面的要求而設計的。
lighttpd的狀態機機制簡要回顧
狀態機可以說是lighttpd最核心的部分。lighttpd將一個連線在不同的時刻分成不同的狀態,狀態機則根據連線當前的狀態,決定要對連線進行的處理以及下一步要進入的狀態。下面這幅圖描述了lighttpd的狀態機:
狀態機機制體現了清晰的邏輯,並且其設計可以讓我們更好地將伺服器主體與外掛結合起來,共同來完成請求的處理與響應。
下面我們將對lighttpd的狀態機進行簡要的回顧,具體的討論,可以參見以下博文:
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, //restart開始讀取請求
CON_STATE_READ, //read讀取並解析請求
CON_STATE_REQUEST_END, //reqend讀取請求結束
CON_STATE_READ_POS, //readpost讀取post資料
CON_STATE_HANDLE_REQUEST, //handlereq處理請求
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的狀態機機制之後,下一節中,我們將把狀態機機制引入到我們的專案當中!~