ET(Edge Triggered,边缘触发)
和 LT(Level Triggered,水平触发)
是 epoll 中两种不同的事件触发模式。
它们的主要区别在于通知事件的时机与处理事件的方式不同:
一、含义与区别:
模式 | 含义(直观理解) | 事件通知条件 |
---|---|---|
LT (默认) | 只要有数据未处理完,内核会一直通知(重复触发) | 状态触发,多次通知 |
ET | 数据到达时仅通知一次,必须全部读完才有下一次通知 | 边缘触发,只通知一次 |
一、详细解释与区别
(1)LT模式(默认)
- 含义:
内核只要发现描述符处于就绪状态,就会不断通知应用程序。
例如,有数据未读完时,epoll会一直告诉你“可读”。
例子说明:
- 假如有10字节的数据到来,而你只读取了其中的5字节。
- 下一次调用
epoll_wait
时,它仍然会提醒你有数据可读,直到所有数据都读取完为止。
代码示例(LT模式):
epoll_event ev;
ev.events = EPOLLIN; // 默认是LT模式
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
// 每次调用epoll_wait都会返回fd,直到缓冲区完全为空
int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
二、边缘触发(ET)的特点:
ET模式下:
- 当被监控的文件描述符状态发生变化时,内核仅通知一次。
- 必须一次性完整处理完数据,直到
EAGAIN
或EWOULDBLOCK
返回为止。 - 如果未读完数据,epoll不会再次通知,这可能导致遗漏数据(或事件)!
因此,在ET模式下,必须采用非阻塞方式,一次读完数据:
代码示例(ET模式下正确做法):
epoll_event ev;
ev.events = EPOLLIN | EPOLLET; // 使用ET模式
ev.data.fd = fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
// ET模式下的正确读取方式:
while (true) {
int n = read(fd, buf, sizeof(buf));
if (n == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// 已经没有数据可读了,退出循环
break;
}
// 其他错误处理
} else if (n == 0) {
// 对端关闭了连接
break;
} else {
// 成功读取数据,继续循环直到读完
}
}
三、LT与ET对比:
对比项 | LT(默认) | ET |
---|---|---|
触发次数 | 只要有数据就一直通知(重复) | 数据到达时仅通知一次 |
读取方式 | 可一次读完或多次读取 | 必须一次性全部读取完毕 |
漏读风险 | 基本没有 | 如果没全部读完,可能漏读 |
使用复杂度 | 简单 | 较复杂,需额外处理逻辑 |
性能 | 低一些 | 更高效,更适合高性能场景 |
四、ET 和 LT 适用场景:
- LT模式适用于简单场景,比如小型服务、对性能要求不高的应用,尤其是不确定数据何时读完的场景。
- ET模式特别适合高并发服务器:
- 减少事件重复通知的开销。
- 强制程序一次性处理完所有数据,效率更高。
四、实际应用建议:
- 一般建议使用
ET
模式以提高性能,但要特别注意:- 必须使用非阻塞IO。
- 循环读写直到返回EAGAIN为止,以避免漏读数据。
如果对性能要求不高,且为了方便起见,可以选择使用LT
模式。
五、小结(直观对比):
- LT模式:
- 内核告诉你:『fd可以读了』。
- 如果你没读完,下次还继续提醒你。
- ET模式:
- 内核只告诉你一次:『fd状态变了,有数据可读』。
- 如果你不一次读完,下次不会再提醒你(直到下次新数据到来)。
因此:
场景 | 推荐模式 |
---|---|
高性能服务器 | ET |
普通场景、简便程序 | LT(默认) |
希望以上能彻底解答你的疑惑!