1. 程式人生 > >Lighttpd原始碼分析之狀態機與外掛

Lighttpd原始碼分析之狀態機與外掛

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;

下面就是lighttpd的狀態機:

這裡寫圖片描述

在每個連線中都會儲存這樣一個狀態機,用以表示當前連線的狀態。

在連線建立以後,在connections.c/connection_accpet()函式中,lighttpd呼叫connection_set_state()函式,將新建立的連線的狀態設定為CON_STATE_REQUEST_START。在這個狀態中,lighttpd記錄連線建立的時間等資訊。

整個狀態機的核心函式是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 */

    /* 將fd加入到fdevent系統中,等待IO事件。
     * 當有資料可讀的時候,在main函式中,lighttpd呼叫這個fd對應的handle函式,
     * 這裡就是connection_handle_fdevent()函式。
     * 這個函式一開始將連線加入到了joblist(作業佇列)中。
     */
    switch (con->state)
    {
    case CON_STATE_READ_POST:
    case CON_STATE_READ:
    case CON_STATE_CLOSE:
        fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_IN);
        break;
    case CON_STATE_WRITE:
        /* request write-fdevent only if we really need it
         * - if we have data to write
         * - if the socket is not writable yet
         */
        if (!chunkqueue_is_empty(con->write_queue) &&
            (con->is_writable == 0)&& (con->traffic_limit_reached == 0))
        {
            fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_OUT);
        }
        else
        {
            fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
        }
        break;
    default:
        fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
        break;
    }
    return 0;
}

這個函式首先根據當前的狀態進入對應的switch分支執行相應的動作,然後根據情況進入下一個狀態。

跳出switch語句之後,如果連線的狀態沒有改變,說明連線讀寫資料還沒有結束,但是需要等待IO事件,這時跳出迴圈,等待IO事件。

如果在處理的過程中不需要等待IO事件,那麼在while迴圈中,連線將被處理完畢並關閉。

在我們的main函式中,之前討論過,在一個while迴圈中,處理超時,處理IO時間,之後有下面這段程式碼:

      for (ndx = 0; ndx < srv->joblist->used; ndx++) {
            connection *con = srv->joblist->ptr[ndx];
            handler_t r;

            connection_state_machine(srv, con);

            switch(r = plugins_call_handle_joblist(srv, con)) {
            case HANDLER_FINISHED:
            case HANDLER_GO_ON:
                break;
            default:
                log_error_write(srv, __FILE__, __LINE__, "d", r);
                break;
            }

            con->in_joblist = 0;
        }

這段程式碼對joblist中的所有連線依次呼叫connection_state_machine()函式進行處理。

下面說明下各狀態的主要內容:

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);
CON_STATE_REQUEST_END    /*transient */
http_request_parse(srv, con);
解析請求,若是POST請求則轉移到CON_STATE_READ_POST狀態,
否則轉移到CON_STATE_HANDLE_REQUEST狀態。
CON_STATE_HANDLE_REQUEST
http_response_prepare(srv, con);
函式中呼叫
handle_uri_raw;
handle_uri_clean;
handle_docroot;
handle_physical;
handle_subrequest_start;
handle_subrequest。
如果函式返回了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);
CON_STATE_WRITE
connection_handle_write(srv,con);
CON_STATE_RESPONSE_END
呼叫外掛的handle_request_done介面。
如果是長連線,重新回到CON_STATE_REQUEST_START;否則呼叫外掛的handle_connection_close介面。
執行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);將連線關閉。

以上是狀態機的概況。

總覽了狀態機,我們知道狀態機會針對相應的階段對事件進行處理,那麼狀態機是如何處理這些事件的?

事實上,對於事件的處理,一部分是由lighttpd完成的,而一部分是由外掛完成的。外掛中那些負責事件處理的介面分佈在某幾個狀態中。我們只需在外掛的各個階段完成指定工作並返回相應的返回值,就可以促使狀態機完成狀態切換,完成事件的整套處理流程,並最終由lighttpd完成事件的響應。

在外掛中,我們可以編寫程式碼來註冊lighttpd提供的回撥介面,lighttpd在初始化階段、狀態機執行階段、退出階段會分別呼叫這些回撥函式,完成外掛的例項化,初始化,連線重置,事件處理,外掛釋放等功能。

要了解lighttpd對外掛的呼叫方式,需要明白一個概念:事件接管。

對於每個事件,都有一個mode欄位(con->mode)。該欄位的定義:

typedef enum { DIRECT, EXTERNAL } connection_type;

連線物件有一個欄位mode用來標識該連線是最初由伺服器accept產生的客戶端連線還是外掛產生的其他輔助連線,當mode=DIRECT時表示對應連線由lighttpd伺服器accept產生,mode!=DIRECT時表示對應連線是由外掛產生的。

事件(con)初始化時mode是DIRECT;connection_reset(srv,con);

lighttpd在大部分流程中會在入口檢查到mode != DIRECT時直接返回GO_ON。即:此事件由使用者外掛接管,lighttpd不參與。

使用者編寫的外掛應通過將mode置為外掛自身的ID達到接管的作用。外掛ID是在外掛載入時由外掛的載入順序確定的,是外掛的唯一標識。

使用者編寫外掛在每個介面的一開始應該判斷mode是否等於自身的ID,若相等才能繼續執行,否則直接退出,返回GO_ON。

瞭解了以上概念之後,我們就可以理解lighttpd對外掛的呼叫方式了:

在lighttpd需要呼叫外掛某一個階段的介面函式時,會對所有外掛註冊在該處的介面順序呼叫,順序與外掛載入順序相同。例如:呼叫uri_raw介面,會先呼叫A外掛的mod_A_uri_raw,然後呼叫B外掛的mod_B_uri_raw,直到將所有已載入外掛這個位置的介面全部呼叫完成。但實際處理這次事件通常只有一個外掛,即外掛ID與mode相同的那個外掛。

因此,假設在CON_STATE_HANDLE_REQUEST狀態,lighttpd呼叫了外掛的handle_uri_raw介面,但是我們有多個外掛,每個外掛都註冊了handle_uri_raw這個介面,lighttpd也能辨別出要使用哪個外掛。

如果外掛在處理事件的過程中,想讓lighttpd接管,還需要把mode置為DIRECT才行。

以上是lighttpd狀態機和外掛的總覽概況。

相關推薦

Lighttpd原始碼分析狀態外掛

Lighttpd啟動時完成了一系列初始化操作後,就進入了一個包含11個狀態的有限狀態機中。 每個連線都是一個connection例項(con),狀態的切換取決於con->state。 lighttpd經過初步處理後將con的基本資訊初始化,而外掛對事件的處理就是針對c

Lighttpd1.4.20原始碼分析 筆記 狀態請求處理

lighttpd請求處理的過程: 1.伺服器與客戶端建立連線後,連線進入CON_STATE_REQUEST_START狀態,伺服器做一些標記,如連線開始的時間等。 2.連線進入CON_STATE_READ狀態,伺服器從連線讀取HTTP頭並存放在con->

雲客Drupal8原始碼分析臨時儲存訊息服務

前言:臨時儲存與訊息服務之間並沒有什麼直接關聯,由於她們都是系統基礎元件,內容也比較簡單,為後續主題做準備,所以放在一起講解。 臨時儲存概述: 臨時儲存用來儲存一些臨時性的資料,超期後會被刪除,比如節點在儲存前的預覽資料,她和快取不一樣,她是臨時性的、不能被重建的資料,依據

分散式訊息佇列RocketMQ原始碼分析2 -- BrokerNameServer心跳機制

我們知道,Kafka是通過ZK的臨時節點來監測Broker的死亡的。當一個Broker掛了之後,ZK上面對應的臨時節點被刪除,同時其他Broker收到通知。 那麼在RocketMQ中,對應的NameServer是如何判斷一個Broker的死亡呢? 有興趣朋友

Lighttpd1.4.20源代碼分析 筆記 狀態錯誤處理和連接關閉

全部 階段 內存 and ces ons keep ren log 這裏所說的錯誤有兩種: 1.http協議規定的錯誤,如404錯誤。 2.server執行過程中的錯誤。如write錯誤。 對於http協議規定的錯誤,這裏的“錯誤”是針對clien

symfony原始碼分析容器的生成使用

symfony 的容器是有一個編譯過程的,框架初始化的時候會執行Symfony\Component\HttpKernel\Kernel::initializationContainer ,這個方法會對程式碼進行檢查,看是否需要生成新的容器程式碼。如果需要 Symfony 會將各個類的依賴關係通過

雲客Drupal8原始碼分析外掛系統(下)

以下內容僅是一個預覽,完整內容請見文尾: 至此本系列對外掛的介紹全部完成,涵蓋了系統外掛的所有知識 全文目錄(全文10476字): 例項化外掛 外掛對映Plugin mapping 外掛上下文  

swoole_process原始碼分析查詢佇列狀態

swoole_process提供的statQueue用於統計佇列狀態,其返回一個數組,裡面包括了訊息總個數和總的位元組數。 array swoole_process->statQueue();

netty原始碼分析-SimpleChannelInboundHandlerChannelInboundHandlerAdapter詳解(6)

每一個Handler都一定會處理出站或者入站(也可能兩者都處理)資料,例如對於入站的Handler可能會繼承SimpleChannelInboundHandler或者ChannelInboundHandlerAdapter,而SimpleChannelIn

docker原始碼分析容器日誌處理log-driver實現

子程序:由一個程序(父程序)建立的程序,整合父程序大部分屬性,同時可以被父程序守護和管理。 (2) 你需要知道關於程序產生日誌的形式: 程序產生

Yarn原始碼分析旅---總體架構---概述總體架構

歡迎大家討論,我也是接觸時間不長,有問題歡迎大家指正。歡迎轉載,轉載請註明出處 Haddoop 1.0的不足與Hadoop2.0的產生         學習和研究過Hadoop1.0的人都應該知道,在Hadoop1.0中,使用了Master\Slave的架構模式,jobTr

LDD3原始碼分析硬體通訊&中斷處理

作者:劉昊昱 編譯環境:Ubuntu 10.10 核心版本:2.6.32-38-generic-pae LDD3原始碼路徑:examples/short/ 本分析LDD3第9和第10章的示例程式碼short。short涉及的主要知識點有通過I/O埠或I/O記憶體操作裝

Flink-CEP論文原始碼解讀狀態狀態轉換

Flink CEP的論文與設計 Flink的CEP設計與實現重度參考了論文《Efficient Pattern Matching over Event Streams》。下面我們就來結合論文談談Flink CEP的設計。 這篇論文探討的話題是如何在事件流

spark mllib原始碼分析DecisionTreeGBDT

我們在前面的文章講過,在spark的實現中,樹模型的依賴鏈是GBDT-> Decision Tree-> Random Forest,前面介紹了最基礎的Random Forest的實現,在此基礎上我們介紹Decision Tree和GBDT的實現

雲客Drupal8原始碼分析外掛系統(上)

各位《雲客drupal8原始碼分析》系列的讀者: 本系列一直以每週一篇的速度進行部落格原創更新,希望幫助大家理解drupal8底層原理,並縮短學習時間,但自《外掛系統(上)》主題開始部落格僅釋出前言和目錄,這是因為雲客在思考一個問題:drupal在國外如此流行但在國內卻很小

Prometheus 實戰於原始碼分析API聯邦

在進行原始碼講解關於prometheus還有一些配置和使用,需要解釋一下。首先是API的使用,prometheus提供了一套HTTP的介面 curl http://localhost:9090/api/v1/query?query=go_goroutine

Mybatis原始碼分析SpringMybatis整合MapperScannerConfigurer處理過程原始碼分析

        前面文章分析了這麼多關於Mybatis原始碼解析,但是我們最終使用的卻不是以前面文章的方式,編寫自己mybatis_config.xml,而是最終將配置融合在spring的配置檔案中。有了前面幾篇部落格的分析,相信這裡會容易理解些關於Mybatis的初始化及

Realm原始碼分析copyToRealmcopyToRealmOrUpdate

createObject 在Realm原始碼分析之Writes中已經詳細追蹤過createObject的執行流程,此處不再贅述。 createObject有如下的兩個過載方法,區別是如果Model沒有指明主鍵使用前者,否則使用後者: createObjec

netty原始碼分析-EventLoop執行緒模型(1)

執行緒模型確定來程式碼的執行方式,我們總是必須規避併發執行可能會帶來的副作用,所以理解netty所採用的併發模型的影響很重要。netty使用了被稱為事件迴圈的EventLoop來執行任務來處理在連線的生命週期內發生的事件 執行緒模型 對於Even

Mybatis深入原始碼分析Mapper介面繫結原理原始碼分析

緊接上篇文章:Mybatis深入原始碼分析之SqlSessionFactoryBuilder原始碼分析,這裡再來分析下,Mappe