1. 程式人生 > >菜鳥學習nginx之事件模組epoll(2)

菜鳥學習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驚群處理