epoll原理
n = epoll_wait(epfd,events,20,500);
for(i=0;i<n;++i)
{
c = event_list[i].data.ptr;
c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~3);
/* 连接有可读事件,且该读事件是active活跃的 */
if ((revents & EPOLLIN) && rev->active) {
rev = c->read;
/*
* 这里要区分active与ready:
* active是指事件被添加到epoll对象的监控中,
* 而ready表示被监控的事件已经准备就绪,即可以对其进程IO处理;
* 读事件的ready代表有数据可以接受处理,写事件的ready代表buffer有空间可写
*/
rev->ready = 1;
/*
* NGX_POST_EVENTS表示已准备就绪的事件需要延迟处理,
* 根据accept标志位将事件加入到相应的队列中;
*/
if (flags & NGX_POST_EVENTS) {
queue = rev->accept ? &ngx_posted_accept_events: &ngx_posted_events;
ngx_post_event(rev, queue);
} else {
/* 若不延迟处理,则直接调用事件的处理函数 */
rev->handler(rev);
}
}
}
// write事件同理
}
每个event在rev→ready设置为1之后,需要进行io处理。
ssize_t
ngx_unix_send(ngx_connection_t *c, u_char *buf, size_t size)
{
...
for ( ;; ) {
n = send(c->fd, buf, size, 0);
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"send: fd:%d %z of %uz", c->fd, n, size);
if (n > 0) {
if (n < (ssize_t) size) {
// 发送的数据大小大于0,小于n,说明未发送完成,wev->ready置为0,此次发送结束,下次继续调用write事件进行发送。
wev->ready = 0;
}
c->sent += n;
return n;
}
}
...
}
ssize_t
ngx_unix_recv(ngx_connection_t *c, u_char *buf, size_t size)
{
...
do {
n = recv(c->fd, buf, size, 0);
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"recv: fd:%d %z of %uz", c->fd, n, size);
if (n == 0) {
// 无可接受数据,此次读事件结束,ready置0
rev->ready = 0;
rev->eof = 1;
}
...
}
每次的读事件,是该fd的读buffer上有数据被写入,引发epollin,如果是水平触发,只要有数据就会一直触发这个读事件,如果是边缘触发,就会等到新的数据被写入才会触发该事件,因此在面对高性能的边缘触发中,一定要注意数据的接收,每次用while true读完,不然一旦有残留,需要等下个包到达才能取出。
每次的写事件很难被触发,因为正常都会被直接发送完成,只有发送一部分数据,写buffer就满了的情况下,epoll会监听该事件,一旦写队列缓冲区有空间,可写,才会触发新的写事件,将数据发送完成。