参考资料:
Manua pages
留下的只是回忆:epoll简介及触发模式(accept、read、send)
~青萍之末~:ET和LT模式详解
tom:EPOLL LT和ET区别
基本概念
二者均用于设置epoll_event的成员events
EPOLLET
用于设置epoll为边缘触发模式
-
边缘触发(edge-triggered) 和 条件触发(水平触发,epoll默认)(level-triggered)
- 条件触发中,只要输入缓冲有数据会一直通知该事件;效率低于ET,但编写简单,不需要担心事件丢失的情况
- 边缘触发中,输入缓冲收到数据只会注册一次该事件;效率高,比LT的epoll系统调用少,但需要细致处理每个请求,否则会出现丢失事件的情况
- 边缘触发 可以分离接收和处理数据的时间点
-
LT模式下,只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作,而在ET(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无论fd中是否还有数据可读
-
以man手册上的例子解释,对于如下事件
1.向epoll实例中注册读端的fd
2.写端写入2kB的数据
3.调用epoll_wait并返回就绪的文件描述符fd
4.读端从fd读取1kB数据
5.调用epoll_wait
若采用ET模式下的epoll,在上述 step5 会阻塞,因为只有 step3 当文件描述符发生变化时,才会触发事件
EPOLLONESHOT
ET模式降低了同一个epoll事件被重复触发的次数,但一个socket上的某个事件依旧可能被多次触发。
Linux高性能服务器编程:例如一个线程在读取完某个socket上的数据后并开始处理这些数据,而在数据的处理过程中该socket又有新的数据可读(EPOLLIN再次被触发),此时另外一个线程被唤醒来读取这些新的数据。于是出现了两个线程同时操作一个socket的局面。
对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读、可写、或异常事件 且只触发一次(保证同一时刻只有一个线程为其服务)
保证了同一时刻只有一个线程为其服务,避免竞争文件描述符
- 举例说明:
1. 对fd注册EPOLLIN | EPOLLET | EPOLLONESHOT事件
2. fd变为可读,调用 epoll_wait 得到 EPOLLIN事件通知
3. 由于设置了EPOLLONESHOT,将会禁用该文件描述符,即使fd还是可读的,也不会再次触发EPOLLIN事件通知
5. 如果想要再次接受通知,需要使用 EPOLL_CTL_MOD 重新配置文件描述符
应用
应用EPOLLET标志,建议以下面的方式调用ET模式下的epoll:
- 采用非阻塞文件描述符
- 只有当read、write返回,errno设置为EAGAIN时,才挂起等待。(并不是说每次read()时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read()返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成)
// 通过fcntl设置非阻塞套接字
int setSocketNonBlocking(int fd)
{
int flag = fcntl(fd, F_GETFL, 0);
if (flag == -1)
return -1;
flag |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flag) == -1)
return -1;
return 0;
}
解释
-
为什么ET模式下需要非阻塞套接字?(以EPOLLIN事件为例)
- 在LT模式下,每当缓冲区有数据,当调用epoll_wait时,都会触发EPOLLIN事件,由于事件触发,可以不断地调用read操作去读数据,直到缓冲区数据读完为止;
- 但在ET模式下,如上面所述,I/O事件发生时才会通知一次,调用epoll_wait时,只触发一次EPOLLIN事件,为了将缓冲区数据都读完,需要不断地循环调用read(当然,若数据较少,可能一次read就读完了全部的数据),但当数据读完后,若文件描述符所对应是阻塞套接字,那么会阻塞在下一次循环的read操作中,处于饥饿状态
-
非阻塞套接字调用read后,errno 为 EAGAIN含义
- 对非阻塞套接字进行read操作,会立即返回,若没有数据可读,程序不会阻塞等待数据就绪返回,而是返回-1,并设置errno为EAGAIN,提示当前没有数据可读请稍后再试。
- errno代码为11(EAGAIN),perror显示Resource temporarily unavailable