什么是過期事件
對於不需要加入到 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
在獲取連接的代碼
// 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》