1. 程式人生 > >【slighttpd】基於lighttpd架構的Server專案實戰(8)—狀態機機制回顧

【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原始碼分析 筆記 狀態機之請求處理

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的狀態機機制之後,下一節中,我們將把狀態機機制引入到我們的專案當中!~