LT自动挡,ET手动挡(epoll)
epoll有 ET和LT两种模式, 默认是LT模式。
LT模式的时候,epoll_wait 会把有事件的 file 再次加到 rdllist 列表中,以便下次epoll_wait可以再检查一遍。
if (epi->event.events & EPOLLONESHOT)
epi->event.events &= EP_PRIVATE_BITS;
else if (!(epi->event.events & EPOLLET)) { //LT模式
/*
* If this file has been added with Level
* Trigger mode, we need to insert back inside
* the ready list, so that the next call to
* epoll_wait() will check again the events
* availability. At this point, noone can insert
* into ep->rdllist besides us. The epoll_ctl()
* callers are locked out by
* ep_scan_ready_list() holding "mtx" and the
* poll callback will queue them in ep->ovflist.
*/
list_add_tail(&epi->rdllink, &ep->rdllist);
}
LT 模式下,状态不会丢失,程序完全可以于 epoll 来驱动。
ET模式下,程序首先要自己驱动逻辑,如果遇到 EAGAIN错误的时候,就要依赖epoll_wait来驱动,这时epoll帮助程序从阻碍中脱离。
所以LT是自动挡, ET是手动挡。
ET 边沿触发的 边沿是 AGAIN错误,属于下降沿触发。
ET 的驱动事件依靠 socket的 sk_sleep 等待队列唤醒,这只有在有新包到来才能发生,数据包导致POLLIN, ACK确认导致 sk_buffer destroy从而导致POLLOUT, 但这不是一对一得关系,是多对一(多个网络包产生一个POLLIN, POLLOUT事件)。
ET常见错误:
recv到了合适的长度, 程序处理完毕后就epoll_wait
这时程序可能长期阻塞,因为这时socket的 rev_buffer里还有数据,或对端close了连接,但这些信息都在上次通知了你,你没有处理完,就epoll_wait了
正确做法是:
recv到了合适的长度, 程序处理; 再次recv, 若果是EAGAIN则epoll_wait。
使用ET模式时, 程序自己驱动下,会发生socket被关闭的情况,这时要处理EPIPE信号和返回值。(如果不处理EPIPE那么会导致程序core掉)
总结:ET 使用准则,只有出现EAGAIN错误才调用epoll_wait。
场景:短连接,短数据包(一个recv和send就可以完成所有的网络操作),这种情形下测试的结果是ET和LT一样的效率。(所以不要以为用了ET效率就有提高)
strace -c -fF ./your_prog 后得到信息是 epoll_ctl 消耗的时间比 recv+send 还多。
总结原因:整个程序是以网络事件来驱动的,所以每个连接都要epoll_ctl 3次; 如果程序自己主动recv+send, 不行的时候再网络驱动的话,可以节省这些epoll_ctl开销。
对于长连接,大数据包应用,因为 LT模式只能设置当时感兴趣的事件(如果不写数据也设置POLLOUT的话,会导致cpu 100%) ,所以要频繁调用epoll_ctl,内核也要多次操作链表,所以效率会比ET模式低。