• <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>

            那誰(shuí)的技術(shù)博客

            感興趣領(lǐng)域:高性能服務(wù)器編程,存儲(chǔ),算法,Linux內(nèi)核
            隨筆 - 210, 文章 - 0, 評(píng)論 - 1183, 引用 - 0
            數(shù)據(jù)加載中……

            lighttpd1.4.18代碼分析(四)--處理監(jiān)聽(tīng)fd的流程

            前面介紹了lighttpd使用的watcher-worker模型, 它對(duì)IO事件處理的封裝, 現(xiàn)在可以把這些結(jié)合起來(lái)看看這大概的流程.

            首先, 服務(wù)器創(chuàng)建監(jiān)聽(tīng)socket, 然后在server.c中調(diào)用函數(shù)network_register_fdevents將監(jiān)聽(tīng)socket注冊(cè)到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;
            }
            在這里, 調(diào)用函數(shù)fdevent_register注冊(cè)fd到IO事件處理器中, 對(duì)于服務(wù)器監(jiān)聽(tīng)fd而言,
            它在fdnode中的回調(diào)函數(shù)handler是函數(shù)network_server_handle_fdevent, 而ctx則是srv_socket.
            接著調(diào)用函數(shù)fdevent_event_add, 其中傳入的第三個(gè)參數(shù)是FDEVENT_IN, 也就是當(dāng)該fd上有可讀數(shù)據(jù)時(shí)觸發(fā)調(diào)用, 對(duì)于所有監(jiān)聽(tīng)的fd而言,
            有可讀事件就意味著有新的連接到達(dá).

            然后服務(wù)器創(chuàng)建子進(jìn)程worker, 服務(wù)器父進(jìn)程自己成為watcher, 自此下面的工作由子進(jìn)程進(jìn)行處理,
            每個(gè)子進(jìn)程所完成的工作都是一樣的.有的書上說(shuō)有多個(gè)進(jìn)程在等待accept連接的時(shí)候會(huì)造成所謂的驚群現(xiàn)象,在lighttpd的代碼中,
            沒(méi)有看到在accaept之前進(jìn)行加鎖操作, 這是否會(huì)造成驚群不得而知.

            現(xiàn)在, 在IO事件處理器中僅有一個(gè)fd等待觸發(fā), 就是前面注冊(cè)的監(jiān)聽(tīng)fd, 我們看看當(dāng)一個(gè)連接到來(lái)的時(shí)候處理的流程, 首先看我們?cè)?jīng)說(shuō)過(guò)的
            輪詢fd進(jìn)行處理的主循環(huán):
                    // 輪詢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;

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

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

                            
            /* connection_handle_fdevent needs a joblist_append */
                            
            // 進(jìn)行處理
                            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);
            當(dāng)一個(gè)連接到來(lái)的時(shí)候, 調(diào)用fdevent_poll返回值是1, 因?yàn)檫@個(gè)函數(shù)的返回值表示的是有多少網(wǎng)絡(luò)IO事件被觸發(fā)了, 接著由于n>0, 進(jìn)入循環(huán)中
            獲得被觸發(fā)的fd, 回調(diào)函數(shù), 以及ctx指針, 在這里由于是監(jiān)聽(tīng)fd被觸發(fā), 那么返回的回調(diào)函數(shù)是前面提到的network_server_handle_fdevent,
            接著就要調(diào)用這個(gè)函數(shù)處理IO事件了:
            // 這個(gè)函數(shù)是處理server事件的函數(shù), 與connection_handle_fdevent對(duì)應(yīng)
            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個(gè)鏈接
                for (loops = 0; loops < 100 && NULL != (con = connection_accept(srv, srv_socket)); loops++) {
                    handler_t r;

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

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

            3)函數(shù)在最后進(jìn)入一個(gè)循環(huán), 循環(huán)的最多次數(shù)是100次, 并且當(dāng)connection_accept函數(shù)返回NULL時(shí)也終止循環(huán), 也就是說(shuō), 當(dāng)監(jiān)聽(tīng)fd被觸發(fā)時(shí),
            服務(wù)器盡量的去接收新的連接, 最多接收100個(gè)新連接, 這樣有一個(gè)好處, 假如服務(wù)器監(jiān)聽(tīng)fd是每次觸發(fā)只接收一個(gè)新的連接, 那么效率是比較低的,
            不如每次被觸發(fā)的時(shí)候"盡力"的去接收, 一直到接收了100個(gè)新的連接或者沒(méi)有可接收的連接之后才返回.接著來(lái)看看負(fù)責(zé)接收新連接的函數(shù)
            connection_accept做了什么:
            // 接收一個(gè)新的連接
            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
                 
            */

                
            // 如果正在使用的連接數(shù)大于最大連接數(shù) 就返回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;

                    
            // 當(dāng)前使用的fd數(shù)量+1
                    srv->cur_fds++;

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

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

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

                    
            // 狀態(tài)為可以接收請(qǐng)求
                    connection_set_state(srv, con, CON_STATE_REQUEST_START);

                    
            // 保存接收連接的時(shí)間
                    con->connection_start = srv->cur_ts;
                    
            // 保存目標(biāo)地址
                    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;

                    
            // 設(shè)置一下接收來(lái)的FD, 設(shè)置為非阻塞
                    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;
                }
            }
            拋開出錯(cuò)處理這部分不解釋, 一旦出錯(cuò), 就返回NULL指針, 這時(shí)可以終止上面那個(gè)循環(huán)接收新連接的過(guò)程,下面重點(diǎn)看看接收了一個(gè)新的連接之后需要
            做哪些事情, 在上面的代碼中我加了一些簡(jiǎn)單的注釋, 下面加一些更加詳細(xì)些的解釋:
            1)要將服務(wù)器已經(jīng)接收的fd數(shù)量(成員cur_fds)加一, 這個(gè)數(shù)量用于判斷是否可以接收新的連接的, 超過(guò)一定的數(shù)量時(shí), 服務(wù)器就暫停接收,
            等一些fd釋放之后才能繼續(xù)接收

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

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

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

            我們大體理一理上面的流程, 省略去對(duì)watcher-worker模型的描述:
            創(chuàng)建服務(wù)器監(jiān)聽(tīng)fd-->
            調(diào)用fdevent_register函數(shù)將監(jiān)聽(tīng)fd注冊(cè)到IO事件處理器中-->
            調(diào)用fdevent_event_add函數(shù)添加FDEVENT_IN到監(jiān)聽(tīng)fd所關(guān)注的事件中-->

            當(dāng)一個(gè)新的連接到來(lái)時(shí):
            IO事件處理器輪詢返回一個(gè)>0的值-->
            IO事件處理返回被觸發(fā)的fd, 回調(diào)函數(shù), ctx指針,在這里就是監(jiān)聽(tīng)fd,回調(diào)函數(shù)則是network_server_handle_fdevent->
            調(diào)用監(jiān)聽(tīng)fd注冊(cè)的回調(diào)函數(shù)network_server_handle_fdevent-->
            network_server_handle_fdevent函數(shù)盡力接收新的連接, 除非已經(jīng)接收了100個(gè)新連接, 或者沒(méi)有新連接到來(lái)-->
            對(duì)于新到來(lái)的連接, 同樣是調(diào)用fdevent_register函數(shù)將它注冊(cè)到IO事件處理器中, 同樣調(diào)用fdevent_event_add函數(shù)添加該fd所關(guān)注的事件是FDEVENT_IN

            以上, 就是lighttpd監(jiān)聽(tīng)fd處理新連接的大體流程.
            我們知道, fd分為兩種:一種是服務(wù)器自己創(chuàng)建的監(jiān)聽(tīng)fd, 負(fù)責(zé)監(jiān)聽(tīng)端口, 接收新到來(lái)的連接;
            另一種, 就是由監(jiān)聽(tīng)fd調(diào)用accept函數(shù)返回的連接fd, 這兩種fd在處理時(shí)都會(huì)注冊(cè)到IO事件處理器中(調(diào)用fdevent_register函數(shù)),
            同時(shí)添加它們所關(guān)注的IO事件(可讀/可寫等)(調(diào)用fdevent_event_add函數(shù)).

            也就是說(shuō),對(duì)IO事件處理器而言, 它并不關(guān)注所處理的fd是什么類型的, 你要使用它, 那么就把你的fd以及它的回調(diào)函數(shù)注冊(cè)到其中, 同時(shí)添加你所關(guān)注的IO事件是什么, 當(dāng)一個(gè)fd所關(guān)注的IO事件被觸發(fā)時(shí), IO事件處理器自動(dòng)會(huì)根據(jù)你所注冊(cè)的回調(diào)函數(shù)進(jìn)行回調(diào)處理, 這是關(guān)鍵點(diǎn), 如果你沒(méi)有明白, 請(qǐng)回頭看看前面提到的IO事件處理器.

            這些的基礎(chǔ)就是我們前面提到IO事件處理器, 前面我們提到過(guò), lighttpd對(duì)IO事件處理的封裝很漂亮, 每個(gè)具體實(shí)現(xiàn)都按照接口的規(guī)范進(jìn)行處理.
            我們?cè)谥v解時(shí), 也沒(méi)有涉及到任何一個(gè)具體實(shí)現(xiàn)的細(xì)節(jié), 這也是因?yàn)閘ighttpd的封裝很好, 以至于我們只需要了解它們對(duì)外的接口不需要深入細(xì)節(jié)就
            可以明白其運(yùn)行的原理.在本節(jié)中, 我們結(jié)合IO事件處理器, 對(duì)上面提到的第一種fd也就是監(jiān)聽(tīng)fd的處理流程做了介紹, 在后面的內(nèi)容中, 將重點(diǎn)講解對(duì)
            連接fd的處理.


            posted on 2008-09-03 11:17 那誰(shuí) 閱讀(3854) 評(píng)論(3)  編輯 收藏 引用 所屬分類: 網(wǎng)絡(luò)編程服務(wù)器設(shè)計(jì)Linux/Unixlighttpd

            評(píng)論

            # re: lighttpd1.4.18代碼分析(四)--處理監(jiān)聽(tīng)fd的流程  回復(fù)  更多評(píng)論   

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

            # re: lighttpd1.4.18代碼分析(四)--處理監(jiān)聽(tīng)fd的流程  回復(fù)  更多評(píng)論   

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

            # re: lighttpd1.4.18代碼分析(四)--處理監(jiān)聽(tīng)fd的流程  回復(fù)  更多評(píng)論   

            @踏雪赤兔
            兄臺(tái)為什么說(shuō)那個(gè)是拙劣的宏呢?
            2011-04-19 16:27 | zhanglistar
            无码精品久久久天天影视 | 久久久久久伊人高潮影院| 久久精品国产亚洲Aⅴ香蕉| 精品国产热久久久福利| 久久成人小视频| 精品久久久久久中文字幕| 亚洲欧洲精品成人久久曰影片| 欧美va久久久噜噜噜久久| 国产成人精品综合久久久| 99久久免费国产精品特黄| 99久久精品免费看国产| 久久久久se色偷偷亚洲精品av| 久久99精品综合国产首页| 狠狠色婷婷久久一区二区| 91精品国产91久久| 欧美黑人又粗又大久久久| 久久夜色精品国产www| 99精品国产在热久久 | 青青青青久久精品国产| 99久久99久久精品国产片果冻| 91久久国产视频| 精品永久久福利一区二区| 精品久久久无码人妻中文字幕| 国产99久久久国产精品~~牛| 久久无码人妻一区二区三区| 三级三级久久三级久久| 久久久久这里只有精品| 久久久中文字幕| 久久精品国产半推半就| 久久综合给久久狠狠97色| 亚洲狠狠婷婷综合久久蜜芽| 伊人色综合久久天天网 | 久久久精品国产sm调教网站 | 久久ZYZ资源站无码中文动漫 | 久久精品中文字幕有码| 人人狠狠综合久久亚洲88| 久久精品成人免费网站| 情人伊人久久综合亚洲| 国产亚洲美女精品久久久| 久久国产综合精品五月天| 久久激情五月丁香伊人|