菜鸟学习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惊群处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值