青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

那誰的技術博客

感興趣領域:高性能服務器編程,存儲,算法,Linux內核
隨筆 - 210, 文章 - 0, 評論 - 1183, 引用 - 0
數據加載中……

lighttpd1.4.18代碼分析(四)--處理監聽fd的流程

前面介紹了lighttpd使用的watcher-worker模型, 它對IO事件處理的封裝, 現在可以把這些結合起來看看這大概的流程.

首先, 服務器創建監聽socket, 然后在server.c中調用函數network_register_fdevents將監聽socket注冊到IO事件處理器中:
int network_register_fdevents(server *srv) {
    size_t i;

    
if (-1 == fdevent_reset(srv->ev)) {
        
return -1;
    }

    
/* register fdevents after reset */
    
for (i = 0; i < srv->srv_sockets.used; i++) {
        server_socket 
*srv_socket = srv->srv_sockets.ptr[i];

        fdevent_register(srv
->ev, srv_socket->fd, network_server_handle_fdevent, srv_socket);
        fdevent_event_add(srv
->ev, &(srv_socket->fde_ndx), srv_socket->fd, FDEVENT_IN);
    }
    
return 0;
}
在這里, 調用函數fdevent_register注冊fd到IO事件處理器中, 對于服務器監聽fd而言,
它在fdnode中的回調函數handler是函數network_server_handle_fdevent, 而ctx則是srv_socket.
接著調用函數fdevent_event_add, 其中傳入的第三個參數是FDEVENT_IN, 也就是當該fd上有可讀數據時觸發調用, 對于所有監聽的fd而言,
有可讀事件就意味著有新的連接到達.

然后服務器創建子進程worker, 服務器父進程自己成為watcher, 自此下面的工作由子進程進行處理,
每個子進程所完成的工作都是一樣的.有的書上說有多個進程在等待accept連接的時候會造成所謂的驚群現象,在lighttpd的代碼中,
沒有看到在accaept之前進行加鎖操作, 這是否會造成驚群不得而知.

現在, 在IO事件處理器中僅有一個fd等待觸發, 就是前面注冊的監聽fd, 我們看看當一個連接到來的時候處理的流程, 首先看我們曾經說過的
輪詢fd進行處理的主循環:
        // 輪詢FD
        if ((n = fdevent_poll(srv->ev, 1000)) > 0) {
            
/* n is the number of events */
            
int revents;
            
int fd_ndx;

            fd_ndx 
= -1;
            
do {
                fdevent_handler handler;
                
void *context;
                handler_t r;

                
// 獲得處理這些事件的函數指針 fd等

                
// 獲得下一個fd在fdarray中的索引
                fd_ndx  = fdevent_event_next_fdndx (srv->ev, fd_ndx);
                
// 獲得這個fd要處理的事件類型
                revents = fdevent_event_get_revent (srv->ev, fd_ndx);
                
// 獲取fd
                fd      = fdevent_event_get_fd     (srv->ev, fd_ndx);
                
// 獲取回調函數
                handler = fdevent_get_handler(srv->ev, fd);
                
// 獲取處理相關的context(對server是server_socket指針, 對client是connection指針)
                context = fdevent_get_context(srv->ev, fd);

                
/* connection_handle_fdevent needs a joblist_append */
                
// 進行處理
                switch (r = (*handler)(srv, context, revents)) {
                
case HANDLER_FINISHED:
                
case HANDLER_GO_ON:
                
case HANDLER_WAIT_FOR_EVENT:
                
case HANDLER_WAIT_FOR_FD:
                    
break;
                
case HANDLER_ERROR:
                    
/* should never happen */
                    SEGFAULT();
                    
break;
                
default:
                    log_error_write(srv, __FILE__, __LINE__, 
"d", r);
                    
break;
                }
            } 
while (--> 0);
當一個連接到來的時候, 調用fdevent_poll返回值是1, 因為這個函數的返回值表示的是有多少網絡IO事件被觸發了, 接著由于n>0, 進入循環中
獲得被觸發的fd, 回調函數, 以及ctx指針, 在這里由于是監聽fd被觸發, 那么返回的回調函數是前面提到的network_server_handle_fdevent,
接著就要調用這個函數處理IO事件了:
// 這個函數是處理server事件的函數, 與connection_handle_fdevent對應
handler_t network_server_handle_fdevent(void *s, void *context, int revents) {
    server     
*srv = (server *)s;
    server_socket 
*srv_socket = (server_socket *)context;
    connection 
*con;
    
int loops = 0;

    UNUSED(context);

    
if (revents != FDEVENT_IN) {
        log_error_write(srv, __FILE__, __LINE__, 
"sdd",
                
"strange event for server socket",
                srv_socket
->fd,
                revents);
        
return HANDLER_ERROR;
    }

    
/* accept()s at most 100 connections directly
     *
     * we jump out after 100 to give the waiting connections a chance 
*/
    
// 一次最多接受100個鏈接
    for (loops = 0; loops < 100 && NULL != (con = connection_accept(srv, srv_socket)); loops++) {
        handler_t r;

        
// 這里馬上進入狀態機中進行處理僅僅對應狀態為CON_STATE_REQUEST_START這一段
        
// 也就是保存連接的時間以及設置一些計數罷了
        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;
        }
    }
    
return HANDLER_GO_ON;
}
我給這段代碼加了一些注釋, 有幾個地方做一些解釋:
1)UNUSED(context)是一個宏, 擴展開來就是( (void)(context) ), 實際上是一段看似無用的代碼, 因為沒有起什么明顯的作用, 是一句廢話,
在這個函數中, 實際上沒有使用到參數context, 如果在比較嚴格的編譯器中, 這樣無用的參數會產生一條警告, 說有一個參數沒有使用到, 加上了
這么一句無用的語句, 就可以避免這個警告.那么, 有人就會問了, 為什么要傳入這么一個無用的參數呢?回答是, 為了滿足這個接口的需求,
來看看回調函數的類型定義:
typedef handler_t (*fdevent_handler)(void *srv, void *ctx, int revents);
這個函數指針要求的第二個參數是一個ctx指針, 對于監聽fd的回調函數network_server_handle_fdevent而言, 它是無用的, 但是對于處理連接fd
的回調函數而言, 這個指針是有用的.

2) 在函數的前面, 首先要判斷傳入的event事件是否是FDEVENT_IN, 也就是說, 只可能在fd有可讀數據的時候才觸發該函數, 其它的情況都是錯誤.

3)函數在最后進入一個循環, 循環的最多次數是100次, 并且當connection_accept函數返回NULL時也終止循環, 也就是說, 當監聽fd被觸發時,
服務器盡量的去接收新的連接, 最多接收100個新連接, 這樣有一個好處, 假如服務器監聽fd是每次觸發只接收一個新的連接, 那么效率是比較低的,
不如每次被觸發的時候"盡力"的去接收, 一直到接收了100個新的連接或者沒有可接收的連接之后才返回.接著來看看負責接收新連接的函數
connection_accept做了什么:
// 接收一個新的連接
connection *connection_accept(server *srv, server_socket *srv_socket) {
    
/* accept everything */

    
/* search an empty place */
    
int cnt;
    sock_addr cnt_addr;
    socklen_t cnt_len;
    
/* accept it and register the fd */

    
/**
     * check if we can still open a new connections
     *
     * see #1216
     
*/

    
// 如果正在使用的連接數大于最大連接數 就返回NULL
    if (srv->conns->used >= srv->max_conns) {
        
return NULL;
    }

    cnt_len 
= sizeof(cnt_addr);

    
if (-1 == (cnt = accept(srv_socket->fd, (struct sockaddr *&cnt_addr, &cnt_len))) {
        
switch (errno) {
        
case EAGAIN:
#if EWOULDBLOCK != EAGAIN
        
case EWOULDBLOCK:
#endif
        
case EINTR:
            
/* we were stopped _before_ we had a connection */
        
case ECONNABORTED: /* this is a FreeBSD thingy */
            
/* we were stopped _after_ we had a connection */
            
break;
        
case EMFILE:
            
/* out of fds */
            
break;
        
default:
            log_error_write(srv, __FILE__, __LINE__, 
"ssd""accept failed:", strerror(errno), errno);
        }
        
return NULL;
    } 
else {
        connection 
*con;

        
// 當前使用的fd數量+1
        srv->cur_fds++;

        
/* ok, we have the connection, register it */
        
// 打開的connection+1(這個成員貌似沒有用)
        srv->con_opened++;

        
// 獲取一個新的connection
        con = connections_get_new_connection(srv);

        
// 保存接收到的fd
        con->fd = cnt;
        
// 索引為-1
        con->fde_ndx = -1;
#if 0
        gettimeofday(
&(con->start_tv), NULL);
#endif
        
// 注冊函數指針和connection指針
        fdevent_register(srv->ev, con->fd, connection_handle_fdevent, con);

        
// 狀態為可以接收請求
        connection_set_state(srv, con, CON_STATE_REQUEST_START);

        
// 保存接收連接的時間
        con->connection_start = srv->cur_ts;
        
// 保存目標地址
        con->dst_addr = cnt_addr;
        buffer_copy_string(con
->dst_addr_buf, inet_ntop_cache_get_ip(srv, &(con->dst_addr)));
        
// 保存server_socket指針
        con->srv_socket = srv_socket;

        
// 設置一下接收來的FD, 設置為非阻塞
        if (-1 == (fdevent_fcntl_set(srv->ev, con->fd))) {
            log_error_write(srv, __FILE__, __LINE__, 
"ss""fcntl failed: ", strerror(errno));
            
return NULL;
        }
#ifdef USE_OPENSSL
        
/* connect FD to SSL */
        
if (srv_socket->is_ssl) {
            
if (NULL == (con->ssl = SSL_new(srv_socket->ssl_ctx))) {
                log_error_write(srv, __FILE__, __LINE__, 
"ss""SSL:",
                        ERR_error_string(ERR_get_error(), NULL));

                
return NULL;
            }

            SSL_set_accept_state(con
->ssl);
            con
->conf.is_ssl=1;

            
if (1 != (SSL_set_fd(con->ssl, cnt))) {
                log_error_write(srv, __FILE__, __LINE__, 
"ss""SSL:",
                        ERR_error_string(ERR_get_error(), NULL));
                
return NULL;
            }
        }
#endif
        
return con;
    }
}
拋開出錯處理這部分不解釋, 一旦出錯, 就返回NULL指針, 這時可以終止上面那個循環接收新連接的過程,下面重點看看接收了一個新的連接之后需要
做哪些事情, 在上面的代碼中我加了一些簡單的注釋, 下面加一些更加詳細些的解釋:
1)要將服務器已經接收的fd數量(成員cur_fds)加一, 這個數量用于判斷是否可以接收新的連接的, 超過一定的數量時, 服務器就暫停接收,
等一些fd釋放之后才能繼續接收

2) 調用函數connections_get_new_connection返回一個connection指針, 用于保存新到的連接, 獲得這個指針之后要保存接收這個連接的時間
(成員connection_start中), 保存新到連接的地址(成員dst_addr和dst_addr_buf中), 此外還要保存一個server指針, 并且調用函數fdevent_fcntl_set
將該fd設置為非阻塞的, 最后別忘了要調用fdevent_register函數將該fd注冊到IO事件處理器中, 另外該fd當前的狀態通過connection_set_state設置為
CON_STATE_REQUEST_START, 這是后面進入狀態機處理連接的基礎.

了解了這個函數的處理過程, 回頭看看上面的循環:
    for (loops = 0; loops < 100 && NULL != (con = connection_accept(srv, srv_socket)); loops++) {
        handler_t r;

        
// 這里馬上進入狀態機中進行處理僅僅對應狀態為CON_STATE_REQUEST_START這一段
        
// 也就是保存連接的時間以及設置一些計數罷了
        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;
        }
    }
我們已經分析完了函數connection_accept, 當一個新的連接調用這個函數成功返回的時候, 這個循環執行函數connection_state_machine
進行處理.這是一個非常關鍵的函數, 可以說, 我們后面講解lighttpd的很多筆墨都將花費在這個函數上, 這也是我認為lighttpd實現中最精妙的
地方之一, 在這里我們先不進行講解, 你所需要知道的是, 在這里, connection_state_machine調用了函數fdevent_event_add, 傳入的事件參數仍然是
FDEVENT_IN, 也就是說, 對于新加入的fd, 它所首先關注的IO事件也是可讀事件.

我們大體理一理上面的流程, 省略去對watcher-worker模型的描述:
創建服務器監聽fd-->
調用fdevent_register函數將監聽fd注冊到IO事件處理器中-->
調用fdevent_event_add函數添加FDEVENT_IN到監聽fd所關注的事件中-->

當一個新的連接到來時:
IO事件處理器輪詢返回一個>0的值-->
IO事件處理返回被觸發的fd, 回調函數, ctx指針,在這里就是監聽fd,回調函數則是network_server_handle_fdevent->
調用監聽fd注冊的回調函數network_server_handle_fdevent-->
network_server_handle_fdevent函數盡力接收新的連接, 除非已經接收了100個新連接, 或者沒有新連接到來-->
對于新到來的連接, 同樣是調用fdevent_register函數將它注冊到IO事件處理器中, 同樣調用fdevent_event_add函數添加該fd所關注的事件是FDEVENT_IN

以上, 就是lighttpd監聽fd處理新連接的大體流程.
我們知道, fd分為兩種:一種是服務器自己創建的監聽fd, 負責監聽端口, 接收新到來的連接;
另一種, 就是由監聽fd調用accept函數返回的連接fd, 這兩種fd在處理時都會注冊到IO事件處理器中(調用fdevent_register函數),
同時添加它們所關注的IO事件(可讀/可寫等)(調用fdevent_event_add函數).

也就是說,對IO事件處理器而言, 它并不關注所處理的fd是什么類型的, 你要使用它, 那么就把你的fd以及它的回調函數注冊到其中, 同時添加你所關注的IO事件是什么, 當一個fd所關注的IO事件被觸發時, IO事件處理器自動會根據你所注冊的回調函數進行回調處理, 這是關鍵點, 如果你沒有明白, 請回頭看看前面提到的IO事件處理器.

這些的基礎就是我們前面提到IO事件處理器, 前面我們提到過, lighttpd對IO事件處理的封裝很漂亮, 每個具體實現都按照接口的規范進行處理.
我們在講解時, 也沒有涉及到任何一個具體實現的細節, 這也是因為lighttpd的封裝很好, 以至于我們只需要了解它們對外的接口不需要深入細節就
可以明白其運行的原理.在本節中, 我們結合IO事件處理器, 對上面提到的第一種fd也就是監聽fd的處理流程做了介紹, 在后面的內容中, 將重點講解對
連接fd的處理.


posted on 2008-09-03 11:17 那誰 閱讀(3868) 評論(3)  編輯 收藏 引用 所屬分類: 網絡編程 、服務器設計 、Linux/Unixlighttpd

評論

# re: lighttpd1.4.18代碼分析(四)--處理監聽fd的流程  回復  更多評論   

mark 有時間好好研究!
2008-09-03 18:43 | 浪跡天涯

# re: lighttpd1.4.18代碼分析(四)--處理監聽fd的流程  回復  更多評論   

寫得不錯
補充一下,在c++中無作用的語句(像上面那個拙劣的UNUSED宏)是會有警告的(C不會?),一個可選的做法是把形參的名字注釋掉,如
void foo( int /*arg*/ ){
}
甚至可以
void foo( int /*arg*/ = 0 ){
}
一個實用的例子可以參見SGI版STL的std::allocator<T>::dealloate()的實現
2008-09-08 00:48 | 踏雪赤兔

# re: lighttpd1.4.18代碼分析(四)--處理監聽fd的流程  回復  更多評論   

@踏雪赤兔
兄臺為什么說那個是拙劣的宏呢?
2011-04-19 16:27 | zhanglistar
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            亚洲图片在线| 午夜在线视频一区二区区别| 欧美国产日本| 欧美xxxx在线观看| 欧美日韩福利在线观看| 国产精品v欧美精品v日本精品动漫| 欧美日韩伦理在线| 国产日韩在线不卡| 91久久精品一区| 亚洲午夜激情网页| 久久一二三国产| 国产精品一区二区你懂得 | 国产精品成人在线| 国产精品免费看片| 伊人成年综合电影网| 日韩视频第一页| 午夜视频在线观看一区二区| 久久精品99久久香蕉国产色戒 | aa级大片欧美| 久久九九国产| 欧美激情亚洲一区| 国产亚洲欧美色| 91久久精品一区二区别| 亚洲欧美日韩人成在线播放| 麻豆精品一区二区av白丝在线| 亚洲人成亚洲人成在线观看图片 | 一本色道久久99精品综合| 亚洲男人天堂2024| 麻豆精品精品国产自在97香蕉| 亚洲免费电影在线| 久久青草久久| 国产日本亚洲高清| 亚洲精品综合精品自拍| 久久久天天操| 亚洲综合大片69999| 欧美高清自拍一区| 黄色国产精品| 午夜亚洲福利| 一本大道久久a久久精二百| 久久午夜精品一区二区| 国产精品免费观看在线| 99热在线精品观看| 久久一区国产| 欧美亚洲日本国产| 欧美午夜电影网| 亚洲精品视频在线| 欧美va天堂| 久久riav二区三区| 国产欧美日韩在线视频| 一本色道久久88精品综合| 亚洲国产欧美久久| 美日韩丰满少妇在线观看| 国产丝袜一区二区| 久久精品91久久久久久再现| 亚洲一区二区精品在线观看| 国产精品av免费在线观看| 一区二区欧美国产| av成人免费| 国产精品国产一区二区| 欧美精品在欧美一区二区少妇| 在线免费观看日本欧美| 老司机免费视频一区二区| 欧美有码视频| 亚洲精品日日夜夜| 免费成人网www| 久久国产高清| 韩国精品主播一区二区在线观看| 久久不射网站| 欧美亚洲综合在线| 激情亚洲网站| 免费视频一区| 欧美精品在线播放| 在线一区二区三区四区五区| 亚洲精品在线视频| 国产精品进线69影院| 亚洲欧美成人在线| 欧美一级在线视频| 伊人久久亚洲美女图片| 亚洲高清二区| 欧美日韩在线视频一区| 欧美在线日韩精品| 欧美一区二区三区喷汁尤物| 极品av少妇一区二区| 亚洲精品色婷婷福利天堂| 欧美日韩精品三区| 久久精品国产成人| 狂野欧美性猛交xxxx巴西| 日韩视频一区二区三区在线播放免费观看 | 亚洲美女精品久久| 久久久久久久久综合| 一本色道久久综合精品竹菊| 一区二区三区国产在线观看| 国产一区二区精品久久91| 欧美凹凸一区二区三区视频| 欧美精品尤物在线| 久久国产一区二区三区| 亚洲欧美激情四射在线日 | 欧美一区二粉嫩精品国产一线天| 亚洲欧美日韩在线播放| 亚洲日本欧美| 亚洲欧美视频在线观看| 亚洲乱亚洲高清| 亚洲欧美日韩另类精品一区二区三区| 亚洲国产精品欧美一二99| 一区二区三区欧美在线| 在线观看中文字幕不卡| 亚洲性视频网站| 国产精品午夜在线观看| 老鸭窝毛片一区二区三区| 欧美精品自拍偷拍动漫精品| 久久综合九色综合欧美狠狠| 欧美精品性视频| 欧美精品成人一区二区在线观看| 久久精品亚洲乱码伦伦中文| 欧美一区二区免费视频| 日韩亚洲国产精品| 久久久午夜电影| 亚洲综合大片69999| 欧美黄色免费| 久久午夜精品一区二区| 国产美女扒开尿口久久久| 亚洲人成精品久久久久| 亚洲国产美女| 久久综合给合久久狠狠色| 欧美在线关看| 国产亚洲欧美色| 亚洲性夜色噜噜噜7777| 亚洲欧美日韩一区二区三区在线| 欧美电影资源| 亚洲国产精品一区二区第四页av| 狠狠色香婷婷久久亚洲精品| 亚洲欧美一区二区原创| 亚洲男同1069视频| 欧美性猛交视频| 亚洲视频axxx| 午夜国产精品视频免费体验区| 欧美性片在线观看| 亚洲美女中出| 亚洲在线中文字幕| 国产精品福利在线观看| 亚洲视频成人| 欧美一区二区三区日韩| 国产日韩专区| 久久精品免费观看| 久久免费观看视频| 精品51国产黑色丝袜高跟鞋| 久久国产视频网| 欧美二区在线| 亚洲美女在线国产| 国产精品免费一区二区三区在线观看| 99亚洲伊人久久精品影院红桃| 亚洲午夜激情在线| 国产精品chinese| 欧美一区二区三区在线观看视频| 久久精品人人做人人爽| 国产一区二区三区在线播放免费观看| 久久精品官网| 亚洲国产精品一区在线观看不卡 | 国产精品va在线播放| 制服诱惑一区二区| 久久―日本道色综合久久| 激情综合自拍| 欧美日韩国产高清视频| 亚洲欧美制服另类日韩| 亚洲福利视频网站| 欧美日韩视频专区在线播放 | 欧美日韩美女在线| 亚洲男人天堂2024| 噜噜噜躁狠狠躁狠狠精品视频| 亚洲国产第一页| 欧美午夜精品理论片a级按摩| 久久精品人人做人人综合| 亚洲精品一区二区三区婷婷月| 99国产精品视频免费观看| 久久精品国产亚洲高清剧情介绍 | 亚洲视频网站在线观看| 久久一区二区三区超碰国产精品| 亚洲高清免费视频| 国产精品久久777777毛茸茸| 欧美一区二区三区男人的天堂| 亚洲高清三级视频| 性亚洲最疯狂xxxx高清| 亚洲高清视频一区二区| 国产精品美女久久久| 麻豆精品网站| 欧美中在线观看| 99亚洲视频| 亚洲国产精品v| 久久国产精品99国产| 中日韩视频在线观看| 国内精品美女在线观看| 欧美午夜三级| 欧美国产日本韩| 久久五月天婷婷| 欧美专区在线观看一区| 亚洲在线成人精品| 在线中文字幕一区| 亚洲精品视频在线| 亚洲高清在线精品| 9色精品在线|