程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> Lighttpd1.4.20源碼分析之狀態機(1)---狀態機總覽

Lighttpd1.4.20源碼分析之狀態機(1)---狀態機總覽

編輯:關於.NET

前面講了lighttpd的fdevent系統,從這一篇開始,我們將進入lighttpd的狀態機。狀態機可以說是lighttpd最核心的部分。lighttpd將一個連接在不同的時刻分成不同的狀態,狀態機則根據連接當前的狀態,決定要對連接進行的處理以及下一步要進入的狀態。下面這幅圖描述了lighttpd的狀態機:

(在lighttpd源碼文件夾中的doc目錄中有個state.dot文件,通過dot命令可以生成上面的圖:dot -Tpng state.dot -o state.png。)

圖中的各個狀態對應於下面的一個枚舉類型:

1 typedef enum
2 {
3   CON_STATE_CONNECT,       //connect 連接開始
4    CON_STATE_REQUEST_START,   //reqstart 開始讀取請求
5    CON_STATE_READ,       //read 讀取並解析請求
6    CON_STATE_REQUEST_END,     //reqend 讀取請求結束
7    CON_STATE_READ_POST,     //readpost 讀取post數據
8    CON_STATE_HANDLE_REQUEST,   //handelreq 處理請求
9   CON_STATE_RESPONSE_START,   //respstart 開始回復
10   CON_STATE_WRITE,       //write 回復寫數據
11   CON_STATE_RESPONSE_END,   //respend 回復結束
12   CON_STATE_ERROR,       //error 出錯
13   CON_STATE_CLOSE       //close 連接關閉
14 } connection_state_t;

在每個連接中都會保存這樣一個枚舉類型變量,用以表示當前連接的狀態。connection結構體的第一個成員就是這個變量。

在連接建立以後,在connections.c/connection_accpet()函數中,lighttpd會調用 connection_set_state()函數,將新建立的連接的狀態設置為CON_STATE_REQUEST_START。在這個狀態中,lighttpd記錄連接建立的時間等信息。

下面先來說一說整個狀態機的核心函數───connections.c/ connection_state_machine()函數。函數很長,看著比較嚇人。。。其實,這裡我們主要關心的是函數的主體部分:while循環和其中的那個大switch語句,刪減之後如下:

1 int connection_state_machine(server * srv, connection * con)
2 {
3   int done = 0, r;
4   while (done == 0)
5   {
6     size_t ostate = con -> state;
7     int b;
8     //這個大switch語句根據當前狀態機的狀態進行相應的處理和狀態轉換。
9     switch (con->state)
10     {
11     case CON_STATE_REQUEST_START:  /* transient */
12     case CON_STATE_REQUEST_END:  /* transient */
13     case CON_STATE_HANDLE_REQUEST:
14     case CON_STATE_RESPONSE_START:
15     case CON_STATE_RESPONSE_END:  /* transient */
16     case CON_STATE_CONNECT:
17     case CON_STATE_CLOSE:
18     case CON_STATE_READ_POST:
19     case CON_STATE_READ:
20     case CON_STATE_WRITE:
21     case CON_STATE_ERROR:  /* transient */
22     default:
23       break;
24     }//end of switch(con -> state) ...
25     if (done == -1)
26     {
27       done = 0;
28     }
29     else if (ostate == con->state)
30     {
31       done = 1;
32     }
33   }
34   return 0;
35 }

程序進入這個函數以後,首先根據當前的狀態進入對應的switch分支執行相應的動作。然後,根據情況,進入下一個狀態。跳出switch語句之後,如果連接的狀態沒有改變,說明連接讀寫數據還沒有結束,但是需要等待IO事件,這時,跳出循環,等待IO事件。對於done==-1的情況,是在 CON_STATE_HANDLE_REQUEST狀態中的問題,後面再討論。如果在處理的過程中沒有出現需要等待IO事件的情況,那麼在while循環中,連接將被處理完畢並關閉。

接著前面的話題,在建立新的連接以後,程序回到 network.c/network_server_handle_fdevent()函數中的for循環在中後,lighttpd對這個新建立的連接調用了一次connection_state_machine()函數。如果這個連接沒有出現需要等待IO事件的情況,那麼在這次調用中,這個連接請求就被處理完畢。但是實際上,在連接第一次進入CON_STATE_READ狀態時,幾乎是什麼都沒做,保持這個狀態,然後跳出了while循環。在循環後面,還有一段代碼:

1 switch (con->state)
2   {
3   case CON_STATE_READ_POST:
4   case CON_STATE_READ:
5   case CON_STATE_CLOSE:
6     fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_IN);
7     break;
8   case CON_STATE_WRITE:
9     if (!chunkqueue_is_empty(con->write_queue) &&
10       (con->is_writable == 0)&& (con->traffic_limit_reached == 0))
11     {
12       fdevent_event_add(srv->ev, &(con->fde_ndx), con->fd, FDEVENT_OUT);
13     }
14     else
15     {
16       fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
17     }
18     break;
19   default:
20     fdevent_event_del(srv->ev, &(con->fde_ndx), con->fd);
21     break;
22   }

這段代碼前面已經介紹過,這個連接的連接fd被加入到fdevent系統中,等待IO事件。當有數據可讀的時候,在main函數中,lighttpd調用這個fd對應的handle函數,這裡就是connection_handle_fdevent()函數。這個函數一開始將連接加入到了joblist(作業隊列)中。前面已經說過,這個函數僅僅做了一些標記工作。程序回到main函數中時,執行了下面的代碼:

1 for (ndx = 0; ndx < srv->joblist->used; ndx++)
2     {
3       connection *con = srv->joblist->ptr[ndx];
4       handler_t r;
5       connection_state_machine(srv, con);
6       switch (r = plugins_call_handle_joblist(srv, con))
7       {
8       case HANDLER_FINISHED:
9       case HANDLER_GO_ON:
10         break;
11       default:
12         log_error_write(srv, __FILE__, __LINE__, "d", r);
13         break;
14       }
15       con->in_joblist = 0;//標記con已經不在隊列中。
16     }

這段代碼就是對joblist中的所有連接,依次對其調用connection_state_machine()函數。在這次調用中,連接開始真正的讀取數據。lighttpd調用connection_handle_read_state()函數讀取數據。在這個函數中,如果數據讀取完畢或出錯,那麼連接進入相應的狀態,如果數據沒有讀取完畢那麼連接的狀態不變。(PS:在connection_handle_read_state()讀取的數據其實就是HTTP頭,在這個函數中根據格式HTTP頭的格式判斷HTTP頭是否已經讀取完畢,包括POST數據。)上面說到,在 connection_state_machile()函數的while循環中,如果連接的狀態沒有改變,那麼將跳出循環。繼續等待讀取數據。

讀取完數據,連接進入CON_STATE_REQUEST_END。在這個狀態中lighttpd對HTTP頭進行解析。根據解析的結果判斷是否有POST 數據。如果有,則進入CON_STATE_READ_POST狀態。這個狀態的處理和CON_STATE_READ一樣。如果沒有POST數據,則進入 CON_STATE_HANDLE_REQUEST狀態。在這個狀態中lighttpd做了整個連接最核心的工作:處理連接請求並准備response數據。

處理完之後,連接進入CON_STATE_RESPONSE_START。在這個狀態中,主要工作是准備response頭。准備好後,連接進入CON_STATE_WRITE狀態。顯然,這個狀態是向客戶端回寫數據。第一次進入WRITE狀態什麼都不做,跳出循環後將連接fd加入 fdevent系統中並監聽寫事件(此時僅僅是修改要監聽的事件)。當有寫事件發生時,和讀事件一樣調用 connection_handle_fdevent函數做標記並把連接加入joblist中。經過若干次後,數據寫完。連接進入 CON_STATE_RESPONSE_END狀態,進行一些清理工作,判斷是否要keeplive,如果是則連接進入 CON_STATE_REQUEST_START狀態,否則進入CON_STATE_CLOSE。進入CLOSE後,等待客戶端掛斷,執行關閉操作。這裡順便說一下,在將fd加到fdevent中時,默認對每個fd都監聽錯誤和掛斷事件。

連接關閉後,connection結構體並沒有刪除,而是留在了server結構體的connecions成員中。以便以後再用。

關於joblist有一個問題。在每次將連接加入的joblist中時,通過connection結構體中的in_joblist判斷是否連接已經在 joblist中。但是,在joblist_append函數中,並沒有對in_joblist進行賦值,在程序的運行過程中,in_joblist始終是0.也就是說,每次調用joblist_append都會將連接加入joblist中,不論連接是否已經加入。還有,當連接已經處理完畢後,程序也沒有將對應的connection結構體指針從joblist中刪除,雖然這樣不影響程序運行,因為斷開後,對應的connection結構體的狀態被設置成 CON_STATE_CONNECT,這個狀態僅僅是清理了一下chunkqueue。但這將導致joblist不斷增大,造成輕微的內存洩漏。在最新版(1.4.26)中,這個問題依然沒有修改。

就先說到這。後面將詳細介紹各個狀態的處理。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved