什麼是過期事件
對於不需要加入到 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
accept
連線之後,會立刻將連線放到 post
延遲處理佇列中,不會出現 accept
之後立刻 close
的情況
於是呢,nginx
就完美的解決了事件過期的情況
參考資料
《深入理解 Nginx》