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

            那誰的技術博客

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

            lighttpd1.4.18代碼分析(六)--處理連接fd的流程

            現在開始講解lighttpd如何處理連接fd.

            第四節,已經講解了如何處理服務器負責監聽連接的fd,處理連接fd的流程在前半部分與監聽fd大體相同:在通過監聽fd接收一個新的連接之后,服務器將這個fd加入到事件處理器中, 設置它所感興趣的IO事件類型, 當IO狀態發生變化時, 事件處理器獲取被觸發的fd,回調函數,事件等,然后通過已經注冊的回調函數進行處理.

            這里的區別就在于回調函數的不同上.對于監聽fd而言, 該回調函數是network.c文件中的network_server_handle_fdevent函數,這個在第四節中已經做了分析;與之對應的,連接fd的回調函數是connections.c文件中的connection_handle_fdevent函數.

            // 這個函數是處理接受鏈接的函數, 與network_server_handle_fdevent對應
            handler_t connection_handle_fdevent(void *s, void *context, int revents) {
                server     
            *srv = (server *)s;
                connection 
            *con = context;

                
            // 添加到server的joblist中
                joblist_append(srv, con);

                
            // 可讀
                if (revents & FDEVENT_IN) {
                    con
            ->is_readable = 1;
                }
                
                
            // 可寫
                if (revents & FDEVENT_OUT) {
                    con
            ->is_writable = 1;
                    
            /* we don't need the event twice */
                }

                
            // 既不可讀也不可寫, 可能是出錯了
                if (revents & ~(FDEVENT_IN | FDEVENT_OUT)) {
                    
            /* looks like an error */

                    
            /* FIXME: revents = 0x19 still means that we should read from the queue */
                    
            if (revents & FDEVENT_HUP) {
                        
            if (con->state == CON_STATE_CLOSE) {
                            con
            ->close_timeout_ts = 0;
                        } 
            else {
                            
            /* sigio reports the wrong event here
                             *
                             * there was no HUP at all
                             
            */
            #ifdef USE_LINUX_SIGIO
                            
            if (srv->ev->in_sigio == 1) {
                                log_error_write(srv, __FILE__, __LINE__, 
            "sd",
                                    
            "connection closed: poll() -> HUP", con->fd);
                            } 
            else {
                                connection_set_state(srv, con, CON_STATE_ERROR);
                            }
            #else
                            connection_set_state(srv, con, CON_STATE_ERROR);
            #endif

                        }
                    } 
            else if (revents & FDEVENT_ERR) {
            #ifndef USE_LINUX_SIGIO
                        log_error_write(srv, __FILE__, __LINE__, 
            "sd",
                                
            "connection closed: poll() -> ERR", con->fd);
            #endif
                        connection_set_state(srv, con, CON_STATE_ERROR);
                    } 
            else {
                        log_error_write(srv, __FILE__, __LINE__, 
            "sd",
                                
            "connection closed: poll() -> ???", revents);
                    }
                }

                
            // 如果連接的狀態是READ, 那么處理read狀態
                if (con->state == CON_STATE_READ ||
                    con
            ->state == CON_STATE_READ_POST) {
                    connection_handle_read_state(srv, con);
                }

                
            // 如果連接狀態是WRITE并卻寫緩沖區隊列中不為空 并且該連接是可寫的, 就去處理寫狀態
                if (con->state == CON_STATE_WRITE &&
                    
            !chunkqueue_is_empty(con->write_queue) &&
                    con
            ->is_writable) {

                    
            if (-1 == connection_handle_write(srv, con)) {
                        connection_set_state(srv, con, CON_STATE_ERROR);

                        log_error_write(srv, __FILE__, __LINE__, 
            "ds",
                                con
            ->fd,
                                
            "handle write failed.");
                    } 
            else if (con->state == CON_STATE_WRITE) {
                        con
            ->write_request_ts = srv->cur_ts;
                    }
                }

                
            // 如果連接的狀態是關閉
                if (con->state == CON_STATE_CLOSE) {
                    
            /* flush the read buffers */
                    
            int b;

                    
            // 檢查讀緩沖區中是否還有數據
                    if (ioctl(con->fd, FIONREAD, &b)) {
                        log_error_write(srv, __FILE__, __LINE__, 
            "ss",
                                
            "ioctl() failed", strerror(errno));
                    }

                    
            // 如果還有數據, 打印錯誤log, 并且讀入數據
                    if (b > 0) {
                        
            char buf[1024];
                        log_error_write(srv, __FILE__, __LINE__, 
            "sdd",
                                
            "CLOSE-read()", con->fd, b);

                        
            /* */
                        read(con
            ->fd, buf, sizeof(buf));
                    } 
            else {
                        
            /* nothing to read */

                        con
            ->close_timeout_ts = 0;
                    }
                }

                
                
            return HANDLER_FINISHED;
            }
            在結構體connection,也就是保存連接相關數據的結構體中, 有一個叫state的成員, 顧名思義, 這個成員保存的是一個連接的狀態,這里所說的狀態與前面提到的IO狀態是不同, IO狀態是用于表示一個fd可讀/可寫/出錯等, 而這個狀態更多的是與協議相關的部分.它是一個枚舉類型:
            /* the order of the items should be the same as they are processed
             * read before write as we use this later 
            */
            typedef 
            enum {
                CON_STATE_CONNECT,            
            // 連接
                CON_STATE_REQUEST_START,    // 開始獲取請求
                CON_STATE_READ,                // 處理讀
                CON_STATE_REQUEST_END,        // 請求結束
                CON_STATE_READ_POST,        // 處理讀,但是是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中采用了所謂"狀態機"去處理連接,而這些狀態就是狀態機中的各種不同狀態.
            在lighttpd的官方文檔中, 對其使用的狀態機有一篇文檔,在這里:
            http://redmine.lighttpd.net/wiki/lighttpd/Docs:InternalHTTPStates

            我覺得里面的這幅圖非常的直觀,學習過編譯原理的人一看就可以知道這是狀態機的轉換圖:


            現在回到本章的主題中, lighttpd如何處理連接fd.
            前面給出的處理連接fd的回調函數,最開始地方有一段代碼:
                joblist_append(srv, con);
            這個函數將一個連接connection結構體放入到joblist中, 后面的部分根據不同的情況設置connection中的state字段,調用的是connection_set_state函數.

            現在回到server.c函數中, 第四節中已經結合處理監聽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);
                    } 
            else if (n < 0 && errno != EINTR) {
                        log_error_write(srv, __FILE__, __LINE__, 
            "ss",
                                
            "fdevent_poll failed:",
                                strerror(errno));
                    }

            在server.c文件中, 緊跟著這段代碼的是:
                    // 處理joblist中的連接
                    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指針, 再調用connection_state_machine函數進行處理.

            connection_state_machine函數是一個非常重要的函數, 它就是處理連接fd的狀態機, 在后面將詳細分析這個函數.

            現在回顧一下lighttpd處理連接fd的大體框架:前面的流程與處理監聽fd大體相同,在與處理連接fd相關的回調函數中, 首先將需要處理的fd相關的connection加入到joblist中, 設置它的state, 后面再輪詢joblist, 進入狀態機進行處理.

            這個過程可能值得商榷, 比如為什么在回調函數中需要將一個connection指針加入到joblist中, 后面再一個循環輪詢joblist中的connection,這樣不是顯得效率低下嗎?我們需要注意的是,前面提到的IO事件狀態和connection中的成員state是不同的!第一個輪詢過程(IO事件處理器的輪詢)是根據哪些fd的IO發生了變化被觸發而去調用回調函數, 而后面的循環(joblist的輪詢)中的connection則不一定都是IO發生變化的!

            打一個比方, 一個連接到來, 此時我們把它放入到事件處理器中, 當它可讀時被觸發, 也就是在第一個輪詢中被觸發;如果在處理的時候出了問題, 不能繼續, 此時我們只需要保存它當前的state字段, 在下一次操作中, 由于它的IO沒有發生變化, 那么將不會在第一個輪詢也就是IO事件處理器中被處理, 而只會在輪詢joblist時被處理, 只需要它的state字段是正確的, 放到狀態機處理函數中就可以繼續下去.


            posted on 2008-09-19 10:46 那誰 閱讀(3721) 評論(1)  編輯 收藏 引用 所屬分類: 網絡編程服務器設計Linux/Unixlighttpd

            評論

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

            “在下一次操作中, 由于它的IO沒有發生變化, 那么將不會在第一個輪詢也就是IO事件處理器中被處理, 而只會在輪詢joblist時被處理”

            這句容易誤導讀者,應改為"輪詢完畢(io操作完成,可能設置status)后的joblist循環里繼續處理status

            2010-03-26 15:55 | sunceenjoy
            99热热久久这里只有精品68| 久久久久99精品成人片三人毛片| 欧美伊人久久大香线蕉综合| 国产精品成人久久久久久久| 国产精品久久久久久搜索| 大伊人青草狠狠久久| 久久青青草原亚洲av无码| 久久久久久久久久久| 久久久青草久久久青草| 香蕉久久永久视频| 久久免费高清视频| 无码人妻久久一区二区三区免费| 久久777国产线看观看精品| 久久久久久亚洲精品无码| 久久夜色精品国产噜噜麻豆| 久久精品国产一区二区三区不卡| 久久亚洲精品成人av无码网站| 精品久久久久中文字幕一区| 浪潮AV色综合久久天堂| 亚洲国产精品狼友中文久久久| 国产精品久久久久9999高清| 亚洲va久久久噜噜噜久久天堂| 久久www免费人成看国产片| 国产69精品久久久久777| 欧美日韩精品久久免费| 久久精品中文字幕有码| 久久免费精品一区二区| 国产精品久久久久久久久鸭| 久久久精品国产sm调教网站| 亚洲av伊人久久综合密臀性色| 久久精品国产99久久久香蕉| 91精品国产91热久久久久福利| 精品久久人妻av中文字幕| 久久AV高清无码| 国产精品禁18久久久夂久| 久久99国产精品尤物| 亚洲精品乱码久久久久久按摩| 久久99国产精品久久99小说| 久久这里的只有是精品23| 久久亚洲AV成人无码| 99精品久久久久久久婷婷|