菜鳥學習nginx之事件模組epoll(2)
上一篇介紹的內容是ngx_epoll_module模組初始化、關閉以及核心內容事件迴圈。但是具體如何將事件註冊到事件驅動中呢?nginx對其進行了封裝,ngx_add_event/ngx_del_event。然而上層應用模組,例如HTTP模組並不直接使用這兩個介面,而是使用再次封裝函式ngx_handle_read_event/ngx_handle_write_event。
一、ngx_add_event註冊事件
Nginx封裝了新增/刪除事件介面:
#define ngx_add_event ngx_event_actions.add #define ngx_del_event ngx_event_actions.del
對epoll模型實際指向為ngx_epoll_add_event/ngx_epoll_del_event,參考程式碼如下:
/** * 新增事件到事件驅動epoll中 * @param ev 事件物件 * @param event 事件型別 * 取值為 NGX_READ_EVENT NGX_WRITE_EVENT * @param flags */ static ngx_int_t ngx_epoll_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags) { int op; uint32_t events, prev; ngx_event_t *e; ngx_connection_t *c; struct epoll_event ee; c = ev->data; events = (uint32_t)event; /* 對事件賦值 */ if (event == NGX_READ_EVENT) {//註冊讀事件 e = c->write; prev = EPOLLOUT; #if (NGX_READ_EVENT != EPOLLIN | EPOLLRDHUP) //epoll模型下不會進入此分支 events = EPOLLIN | EPOLLRDHUP; #endif } else {//註冊寫事件 e = c->read; prev = EPOLLIN | EPOLLRDHUP; #if (NGX_WRITE_EVENT != EPOLLOUT) //epoll模型下不會進入此分支 events = EPOLLOUT; #endif } if (e->active) {//表示活躍事件 則進行修改操作 op = EPOLL_CTL_MOD; events |= prev; } else {//非活躍事件 則進行新增操作 op = EPOLL_CTL_ADD; }
看到估計有很多人和我一樣很困惑,為什麼註冊的事件明明是讀(寫)事件卻獲取寫(讀)事件物件?
說明:
epoll模型有一個特點:
對於一個全新的socket要註冊到epoll中,操作型別為EPOLL_CTL_ADD。
對於已經新增到epoll中的socket,若要對其進行修改只能使用EPOLL_CTL_MOD,不能使用EPOLL_CTL_ADD,否則會報錯。
那麼如何判斷當前fd是否已經新增到epoll中呢?Nginx使用上面方式:
1、對於一個socket來說,可以向epoll同時註冊讀和寫兩種事件。所以Nginx在處理註冊事件時,這樣判斷的,如果新註冊的事件是讀事件,則取出對應的寫事件作為目標事件,反之亦然。
2、判斷目標事件是否為active,當目標事件active為1表示當前事件已經在epoll中,那麼我們就程序MOD操作否則進行ADD操作。這裡需要知曉,新註冊的事件active一定是0。
/**
* 1、設定event事件
* 2、設定私有資料欄位,當事件發生後epoll_wait會帶回該欄位。上層應用需要處理
* 3、指標最後1bit始終為0,此處體現出nginx設計巧妙之處
*/
ee.events = events | (uint32_t)flags;
ee.data.ptr = (void *)((uintptr_t)c | ev->instance);
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
"epoll add event: fd:%d op:%d ev:%08XD",
c->fd, op, ee.events);
if (epoll_ctl(ep, op, c->fd, &ee) == -1)
{
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
"epoll_ctl(%d, %d) failed", op, c->fd);
return NGX_ERROR;
}
ev->active = 1; //必須把當前事件active設定為1 代表該事件已經註冊到epoll中
#if 0
ev->oneshot = (flags & NGX_ONESHOT_EVENT) ? 1 : 0;
#endif
return NGX_OK;
}
這裡需要關注兩點:
1、私有資料欄位,ee.data.ptr設定為連線connection物件或上instance。關於instance說明在上一篇《菜鳥學習nginx之事件模組epoll(1)》已經介紹過。
2、當註冊完事件之後,必須把active設定為1。那麼什麼時候設定為0呢?把當前事件移除epoll時設定為0。
二、ngx_del_event刪除事件
/**
* 從事件驅動epoll中刪除事件
* @param ev 待刪除事件物件
* @param event 事件型別
* 取值為 NGX_READ_EVENT NGX_WRITE_EVENT
* @param flags
*/
static ngx_int_t
ngx_epoll_del_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t flags)
{
int op;
uint32_t prev;
ngx_event_t *e;
ngx_connection_t *c;
struct epoll_event ee;
/*
* when the file descriptor is closed, the epoll automatically deletes
* it from its queue, so we do not need to delete explicitly the event
* before the closing the file descriptor
* 表示當前socket是關閉事件 那麼直接將active設定為0即可
*/
if (flags & NGX_CLOSE_EVENT)
{
ev->active = 0;
return NGX_OK;
}
c = ev->data;
if (event == NGX_READ_EVENT)
{
e = c->write;
prev = EPOLLOUT;
}
else
{
e = c->read;
prev = EPOLLIN | EPOLLRDHUP;
}
if (e->active)
{
op = EPOLL_CTL_MOD;
ee.events = prev | (uint32_t)flags;
ee.data.ptr = (void *)((uintptr_t)c | ev->instance);
}
else
{
op = EPOLL_CTL_DEL;
ee.events = 0;
ee.data.ptr = NULL;
}
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0,
"epoll del event: fd:%d op:%d ev:%08XD",
c->fd, op, ee.events);
if (epoll_ctl(ep, op, c->fd, &ee) == -1)
{
ngx_log_error(NGX_LOG_ALERT, ev->log, ngx_errno,
"epoll_ctl(%d, %d) failed", op, c->fd);
return NGX_ERROR;
}
ev->active = 0; //事件被移除 需要把active設定為0
return NGX_OK;
}
事件從epoll中刪除,程式碼邏輯與新增流程打通小異,此處不再深入剖析。
三、總結
通過分析Nginx的epoll模型,對於epoll模型處理有了更加深入的瞭解,到現在為止,越來越發現Nginx真的是一款非常優秀的軟體,值得我們深入分析。下面一篇介紹Nginx驚群處理。