nginx 是如何處理過期事件的?


什么是過期事件

對於不需要加入到 post 隊列 延后處理的事件,nginx 的事件都是通過 ngx_epoll_process_events 函數進行處理的

舉例:假如 epoll_wait 一次性返回 3 個事件,在第一個事件關閉了一個連接對應的正好是第三個事件的連接,第二個事件 accept 了一個連接,正好使用的是第二個事件的文件描述符

如圖所示:

那么如果僅僅判斷是否使用的同一個描述符或者描述符是否被置為 -1,就不能判斷是否是同一個連接

上面的這個問題,稱之為事件過期問題

nginx 是如何處理過期事件的?

nginx 中的指針的最后一位一定是 0 ,於是,nginx 就使用這最后一位用來表示是否過期

深入理解nginx中,第9章中有一句:利用指針的最后一位一定是0的特性。能解釋一下這個特性?

看下面代碼中取出與判斷 instance 位的操作

nginx 會在每次 accept 一個連接的時候,將 instance 位取反,那么只需要判斷 instance 位是否一直就能判斷事件是否過期了

// nginx-1.9.2/src/core/ngx_connection.c
// ngx_get_connection
    instance = rev->instance;

    ngx_memzero(rev, sizeof(ngx_event_t));
    ngx_memzero(wev, sizeof(ngx_event_t));

    rev->instance = !instance;
    wev->instance = !instance
// nginx-1.9.2/src/event/modules/ngx_epoll_module.c
// ngx_epoll_process_events
    //遍歷本次epoll_wait返回的所有事件
    for (i = 0; i < events; i++) { //和ngx_epoll_add_event配合使用
        /*
        對照着上面提到的ngx_epoll_add_event方法,可以看到ptr成員就是ngx_connection_t連接的地址,但最后1位有特殊含義,需要把它屏蔽掉
          */
        c = event_list[i].data.ptr; //通過這個確定是那個連接

        instance = (uintptr_t) c & 1; //將地址的最后一位取出來,用instance變量標識, 見ngx_epoll_add_event

        /*
          無論是32位還是64位機器,其地址的最后1位肯定是0,可以用下面這行語句把ngx_connection_t的地址還原到真正的地址值
          */ //注意這里的c有可能是accept前的c,用於檢測是否客戶端發起tcp連接事件,accept返回成功后會重新創建一個ngx_connection_t,用來讀寫客戶端的數據
        c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);

        rev = c->read; //取出讀事件 //注意這里的c有可能是accept前的c,用於檢測是否客戶端發起tcp連接事件,accept返回成功后會重新創建一個ngx_connection_t,用來讀寫客戶端的數據

        if (c->fd == -1 || rev->instance != instance) { //判斷這個讀事件是否為過期事件
          //當fd套接字描述符為-l或者instance標志位不相等時,表示這個事件已經過期了,不用處理
            /*
             * the stale event from a file descriptor
             * that was just closed in this iteration
             */

            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                           "epoll: stale event %p", c);
            continue;
        }

        // 還有代碼,但是不貼這么多了
        ......
    }

但是,問題來了,思考一下下面兩種情況:


這樣 instance 位又成了 1,那 e 事件處理的豈不是 3 連接的事件了,這樣過期事件並沒有解決啊

首先,第二張圖片中的情況不可能存在,因為 epoll 中的事件是有順序的,c 事件必然是再 e 事件之后

那么第一張圖片中的情況還是沒有解決過期事件啊

於是我就翻閱很多資料(主要靠百度)

看到有人遇到過這個疑問:

nginx中事件模型中instance變量的處理細節

如果覺得上面文章太長可以看我的講解:

意思呢就是

nginx accept 連接之后,會立刻將連接放到 post 延遲處理隊列中,不會出現 accept 之后立刻 close 的情況

於是呢,nginx 就完美的解決了事件過期的情況

看一下 nginx 在獲取連接的代碼

// nginx-1.9.2/src/http/ngx_http_request.c
// ngx_http_init_connection
    /*
     如果新連接的讀事件ngx_event_t結構體中的標志位ready為1,實際上表示這個連接對應的套接字緩存上已經有用戶發來的數據,
     這時就可調用上面說過的ngx_http_init_request方法處理請求。
     */
    //這里只可能是當listen的時候添加了defered參數並且內核支持,在ngx_event_accept的時候才會置1,才可能執行下面的if里面的內容,否則不會只需if里面的內容
    if (rev->ready) {
        /* the deferred accept(), iocp */
        if (ngx_use_accept_mutex) { //如果是配置了accept_mutex,則把該rev->handler延后處理,
        //實際上執行的地方為ngx_process_events_and_timers中的ngx_event_process_posted
            ngx_post_event(rev, &ngx_posted_events);
            return;
        }

        rev->handler(rev); //ngx_http_wait_request_handler
        return;
    }

參考資料

《深入理解 Nginx》

深入理解nginx中,第9章中有一句:利用指針的最后一位一定是0的特性。能解釋一下這個特性?

nginx中事件模型中instance變量的處理細節

github 項目:reading-code-of-nginx-1.9.2


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM