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 -- Broker與NameServer心跳機制
我們知道,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原始碼分析之-SimpleChannelInboundHandler與ChannelInboundHandlerAdapter詳解(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原始碼分析之DecisionTree與GBDT
我們在前面的文章講過,在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原始碼分析之Spring與Mybatis整合MapperScannerConfigurer處理過程原始碼分析
前面文章分析了這麼多關於Mybatis原始碼解析,但是我們最終使用的卻不是以前面文章的方式,編寫自己mybatis_config.xml,而是最終將配置融合在spring的配置檔案中。有了前面幾篇部落格的分析,相信這裡會容易理解些關於Mybatis的初始化及
Realm原始碼分析之copyToRealm與copyToRealmOrUpdate
createObject 在Realm原始碼分析之Writes中已經詳細追蹤過createObject的執行流程,此處不再贅述。 createObject有如下的兩個過載方法,區別是如果Model沒有指明主鍵使用前者,否則使用後者: createObjec
netty原始碼分析之-EventLoop與執行緒模型(1)
執行緒模型確定來程式碼的執行方式,我們總是必須規避併發執行可能會帶來的副作用,所以理解netty所採用的併發模型的影響很重要。netty使用了被稱為事件迴圈的EventLoop來執行任務來處理在連線的生命週期內發生的事件 執行緒模型 對於Even
Mybatis深入原始碼分析之Mapper與介面繫結原理原始碼分析
緊接上篇文章:Mybatis深入原始碼分析之SqlSessionFactoryBuilder原始碼分析,這裡再來分析下,Mappe