读
水平模式
只要socket缓冲区有数据,且执行了EPOLL_CTL_ADD
监听socket,内核就一直通知进程;即,只要内核通知了进程,但是进程没有read或者没有read完数据,就会触发唤醒进程。
边缘模式
socket有数据,会通知进程,不管进程有没有read,后续不再通知。
但是一种情况例外,就是唤醒进程不read或者read部分数据之后,如果有再次调用了 EPOLL_CTL_MOD
添加EPOLLIN
事件的情况,则该进程还是会被再次唤醒。
EPOLL_CTL_MOD
的时候,如果还需要 EPOLLET
边缘模式,则需要显示设置为边缘模式,否则,即使EPOLL_CTL_ADD
时设置了边缘模式,socket会因为在EPOLL_CTL_MOD
没有设置EPOLLET
而边缘模式失效。
写
水平模式
只要socket可写,那么内核会一直通知进程去写。然而,一个socket只要写窗口一直非0(绝大多数情况下是这样的),那么会一直通知进程,这是非常恐怖的。
边缘触发
socket可写,内核只会唤醒一次进程,进程执行写;但是也有一种情况导致在边缘模式下,进程疯狂的被唤醒:那就是write之后,进程调用了 EPOLL_CTL_MOD
添加 EPOLLOUT
事件。
Nginx
Nginx是边缘模式还是水平模式:
Listen的listen fd是水平模式,accept 的新 client fd 是 ET,后续如果对 client fd 执行了 add_event,则client fd变成了 LT。
但是绝大多数情况下 client fd 都是ET模式的。所以在整个常规的业务流程中,调用ngx_handle_write_event
、ngx_handle_read_event
其实没啥作用。
因为Nginx对于accept的连接时ET模式,所以当把read->handler
设置为block或者empty时,要小心,如果数据来了,但是block了没去read,那么必须在业务中,主动去read,因为置为read信号触发一次。
Nginx 的 ngx_event_t 中 active 和 ready 的作用:
active:表示这个event是否被加进过epoll监听事件中,一旦加进去过,active一直未1,除非del过,active未0;所以 想nginx 的 ngx_handle_read_event
和ngx_handle_write_event
基本直接return掉,不起什么作用。
ready:什么时候为1?以read为例,当read到数据时,ready 被置为1;当你业务中主动调用read,然后read的数据小于你期望的或者read返回了EAGAIN,时,底层函数会将ready为0;作用是什么,我个人理解是减少read调用次数罢了,例如一般大家read代码这么写:
for (;;) {
if (buf->end - buf->last && c->read->ready) {
n = c->read(c, buf->last, buf->end - buf->last);
if (n <= 0) {
break;
}
//process
}
break;
}
c->read->ready
会在 c->read 的中,当实际读到的数据长度小于入参 buf->end - buf->last
,被设置为0,表示socket队列中没有数据了。
上面这个代码其实不判断c->read->ready
也没有关系,反正也会靠第二次read 的返回 返回值n为 EAGAIN 而 break 掉循环,多一次read系统调用而已。
反正,说白了ready
字段,是用来判断socket队列中有没有数据,让业务流程少一次系统调用而已。