epoll 的水平触发(LT)和边缘触发(ET)描述就绪事件如何通知。LT 类似电平:只要文件描述符仍满足就绪条件,等待调用就可能再次报告它。ET 更接近状态变化边沿:当状态从未就绪变为就绪时通知,应用不能指望未处理完的数据每次都会重复提醒。
例如 socket 收到 10KB 数据,应用只读 2KB。LT 下只要剩余数据仍可读,后续等待通常还能报告可读;ET 下若此时没有新的状态转换,程序可能收不到第二次边沿,因此通常将描述符设为非阻塞,并在收到事件后循环读取,直到 read 返回 EAGAIN/EWOULDBLOCK 或连接结束。写事件也类似,但通常不应无条件一直监听可写,因为大多数连接常态下可写,会造成事件繁忙;应在发送缓冲区有待写数据时关注可写。
ET 并不是“每个数据包恰好通知一次”,内核事件通知和 TCP 数据边界不是一一对应关系。一次可读事件可能包含多个数据段;应用层协议还需自行按长度或分隔符组帧。边缘触发可以减少重复通知,但要求正确处理非阻塞读写、部分完成和错误状态,复杂度更高。
使用 LT 可先把事件循环写得较容易理解;只有在测量发现事件处理成本值得优化时,再考虑 ET。无论模式如何,都要处理 EINTR、EAGAIN、EOF、错误事件以及连接关闭,并确保事件循环不会因一个繁忙连接长期饿死其他连接。
常见追问
Q:ET 下为什么必须使用非阻塞套接字?
A:若一次读写阻塞,线程可能卡在单个连接上,无法处理其他已就绪连接。非阻塞让循环在数据暂时耗尽时以 EAGAIN 结束,回到事件循环。
Q:收到一次 ET 可读事件后读多少?
A:通常循环读取,直到 EAGAIN/EWOULDBLOCK、EOF 或不可恢复错误。若使用单次读取,剩余数据可能没有新的边沿提醒。
Q:LT 一定低效吗?
A:不是。LT 更容易实现且适用于许多负载;效率取决于连接数、活跃度和处理成本,不应仅凭触发模式判断。