上一篇介绍的内容是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惊群处理。