生活随笔
收集整理的這篇文章主要介紹了
Lighttpd源码分析之状态机与插件
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
Lighttpd啟動時完成了一系列初始化操作后,就進入了一個包含11個狀態的有限狀態機中。
每個連接都是一個connection實例(con),狀態的切換取決于con->state。
lighttpd經過初步處理后將con的基本信息初始化,而插件對事件的處理就是針對con進行的,它拿到con后按照業務需要進行相應處理,然后再交還給lighttpd,lighttpd根據con中的信息完成響應。
狀態定義如下:
[cpp] view plain
copy typedef?enum??{??????CON_STATE_CONNECT,????????????????????CON_STATE_REQUEST_START,????????????CON_STATE_READ,????????????????????CON_STATE_REQUEST_END,????????????????CON_STATE_READ_POST,????????????????CON_STATE_HANDLE_REQUEST,???????????CON_STATE_RESPONSE_START,???????????CON_STATE_WRITE,???????????????????CON_STATE_RESPONSE_END,???????????CON_STATE_ERROR,???????????????????CON_STATE_CLOSE???????????????}?connection_state_t;??
下面就是lighttpd的狀態機:
在每個連接中都會保存這樣一個狀態機,用以表示當前連接的狀態。
在連接建立以后,在connections.c/connection_accpet()函數中,lighttpd調用connection_set_state()函數,將新建立的連接的狀態設置為CON_STATE_REQUEST_START。在這個狀態中,lighttpd記錄連接建立的時間等信息。
整個狀態機的核心函數是connections.c/ connection_state_machine()函數。
函數的主體部分刪減之后如下:
[cpp] view plain
copy 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:????????????????????????case?CON_STATE_REQUEST_END:????????????????????????case?CON_STATE_HANDLE_REQUEST:????????????????????case?CON_STATE_RESPONSE_START:????????????????????case?CON_STATE_RESPONSE_END:????????????????????????case?CON_STATE_CONNECT:????????????????????case?CON_STATE_CLOSE:????????????????????case?CON_STATE_READ_POST:????????????????????case?CON_STATE_READ:????????????????????case?CON_STATE_WRITE:????????????????????case?CON_STATE_ERROR:????????????????????????default:????????????????????????break;??????????}??????????if?(done?==?-1)??????????{??????????????done?=?0;??????????}??????????else?if?(ostate?==?con->state)??????????{??????????????done?=?1;??????????}??????}????????????????????????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:???????????????????????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時間,之后有下面這段代碼:
[cpp] view plain
copy 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()函數進行處理。
下面說明下各狀態的主要內容:
[cpp] view plain
copy CON_STATE_CONNECT??清除待讀取隊列中的數據-chunkqueue_reset(con->read_queue);??置con->request_count?=?0。(本次連接還未處理過請求)??CON_STATE_REQUEST_START????記錄事件起始時間;??con->request_count++(一次長連接最多可以處理的請求數量是有限制的);??轉移到CON_STATE_READ狀態。????CON_STATE_READ和CON_STATE_READ_POST??connection_handle_read_state(srv,con);??CON_STATE_REQUEST_END??????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?????調用插件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源码分析之状态机与插件的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。