epoll的水平触发和边缘触发
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了epoll的水平触发和边缘触发相关的知识,希望对你有一定的参考价值。
参考技术A 参考文章:java nio使用的是水平触发还是边缘触发?参考文章:netty中的水平触发和边缘触发
我自己总结下吧~
TCP负责运输数据到本地内核缓存区,用户程序可以自由的选择select,poll还是epoll。它们并不影响TCP传输数据的性能,只影响用户程序去拿已经到来的数据性能。假设我们应用程序选择了epoll模式,然后选择了水平触发,那么用户程序可以一点一点拿数据。关键是它为啥要一点一点拿,它咋知道啥时候结束呢?
这就靠两个东西:1.内核缓存数据拿完了,还没找到结束标识(应用层协议约定) 2. 拿到应用层协议约定的结束标识符
如果是边沿触发,那么用户程序如果任性的只拿一部分数据给程序,程序解析出来没发现结束符,继续系统调用epoll 去拿。可惜就算该socket的缓存区还有数据,但是它不会唤醒epoll,除非等到下次就绪。很遗憾,前面一次请求已经全部发来了,结束符就在内核缓存区里面,但是用户程序读不到。读不到就没法处理,也就没法响应请求方。请求方如果是http协议的话,是必须等到response的才能发下一个请求。这下好了,请求方不会再发任何数据来填充这个socket的缓存区了,也就不可能触发可读事件给epoll了,这个socket算是废了。所以边缘触发下,用户程序一定要保证一次读完,所以它的效率比较高。
但是它的缺点就是要把数据读完,比较占用用户空间,netty的AbstractNioMessageChannel.read方法,其实可以看出来为了readBuf的大小不至于太大,用了LT触发,用户程序直接多次读取,没读完epoll也会继续唤醒。
Linux网络编程之selectpollepoll的比较,以及epoll的水平触发(LT)和边缘触发(ET)
Linux的网络通信先后推出了select、poll、epoll三种模式。
select有以下三个问题:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。
(3)select支持的文件描述符数量太小了,默认是1024。
poll解决了第三个问题,select保存描述符fd的数据结构是数组,poll改成了链表,突破了fd的个数限制。
但是第1和第2个问题依然存在。
epoll在poll的基础上,又解决了前两个问题:
(1)对第一个问题,epoll每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。这样epoll保证了每个fd在整个过程中只会拷贝一次。
(2)对第二个问题,epoll单独设置了一个就绪链表,当fd就绪(可读/可写)之后,放入就绪链表。epoll_wait只需要遍历就绪链表,而不需要遍历所有的fd,从而节省大量的CPU时间。
epoll有LT和ET两种工作模式,默认工作模式是LT(水平触发),高速工作模式是ET(边缘触发)。
LT是fd只要处于可读或可写状态,就会通知用户;ET只有不可读变为可读,或不可写变为可写之时,才会通知用户。
ET对系统的调用,比LT要少得多,所以ET是高速工作模式,效率高很多。
用户使用ET模式时,读/写fd的时候,必须连续读/写完(直到返回EAGAIN错误)。否则如果未读/写完,系统会认为状态没有变化,就不会再重复通知,这样这个fd就死掉了。
以上是关于epoll的水平触发和边缘触发的主要内容,如果未能解决你的问题,请参考以下文章
epoll的水平触发和边缘触发,以及边缘触发为什么要使用非阻塞IO