epoll的ET(边缘触发)模式
ET 模式触发条件:ET 模式仅在缓冲区状态变化时触发事件(如从“有数据”变为“无数据”),未读完的数据会导致后续事件失效,因为缓冲区一直有数据,导致事件不在触发
所以如果fd处于阻塞模式,一定要一次性读完缓冲区数据。但还是建议使用非阻塞模式,避免监听主进程阻塞在对一个IO的处理上
在epoll的边缘触发(ET)模式下,必须使用非阻塞套接字,否则可能导致事件处理“饿死”(即某个文件描述符的阻塞操作阻塞了其他就绪文件描述符的处理)。以下是具体原因和机制:
1. ET模式的工作原理
ET模式仅在文件描述符状态发生改变时触发一次事件。例如:
- 当数据从内核缓冲区到达(不可读→可读)时,触发
EPOLLIN事件; - 当数据被完全读取后,状态变为不可读,若后续又有新数据到达,ET模式不会再次触发事件。
2. 阻塞I/O在ET模式下的致命问题
若使用阻塞读/写,可能出现以下场景:
- 场景1:客户端发送2048字节数据,服务端读取1024字节后阻塞在第二次
read()调用。此时剩余1024字节滞留内核缓冲区,但ET模式不会再次触发EPOLLIN事件,导致数据丢失。 - 场景2:服务端循环读取数据(如
while (read() > 0)),若客户端发送600字节后暂停,服务端在读取到600字节后继续循环read(),此时缓冲区为空,read()阻塞,导致线程无法处理其他就绪文件描述符。
3. 非阻塞I/O的必要性
将套接字设为非阻塞模式后:
- 读操作:若缓冲区有数据,
read()立即返回实际字节数;若无数据,返回EAGAIN/EWOULDBLOCK。 - 写操作:若缓冲区有空间,
write()立即返回;若无空间,返回EAGAIN。
通过循环读写直到EAGAIN,可确保一次性处理完所有可用数据,避免因未读完数据导致事件丢失。例如:
while (1) {
ssize_t n = read(fd, buf, sizeof(buf));
if (n > 0) {
process_data(buf, n);
} else if (n == 0) {
close(fd);
break;
} else if (errno == EAGAIN) {
break; // 数据已全部读取
} else {
// 错误处理
}
}
4. 饥饿现象的避免
阻塞I/O会导致线程长时间卡在某个文件描述符上,无法响应其他就绪文件描述符的事件。例如:
- 单线程模型中,若一个连接因阻塞写操作占用CPU,其他连接的事件将无法被处理。
- 多线程模型中,若未正确使用
EPOLLONESHOT,同一连接可能被多个线程同时处理,导致资源竞争。
总结
ET模式依赖非阻塞I/O实现高效事件驱动:
- 非阻塞确保读写操作不会因缓冲区暂时无数据而阻塞;
- 循环读写保证一次性处理完所有可用数据,避免事件丢失;
- ET模式 + 非阻塞是Linux高并发服务器(如Nginx、Redis)的标准实践。

浙公网安备 33010602011771号