Epoll 的水平模式、边缘模式 与 Nginx

水平模式

只要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_eventngx_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_eventngx_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队列中有没有数据,让业务流程少一次系统调用而已。

猜你喜欢

转载自blog.csdn.net/mrpre/article/details/80595540