epoll的水平触发和边缘触发
参考文章:java nio使用的是水平触发还是边缘触发?
参考文章:netty中的水平触发和边缘触发
我自己总结下吧~
比如发送方发送了一个字符串"123456&",一次性发给服务器,服务器也都一次性读到内核缓存区了。这个时候epoll唤醒用户线程过来读,用户线程可以自己决定读走多少数据。假设现在用户程序读走了5个字节"12345",留了一个“6”还在内核里面。用户线程读完“12345”,它就必须要根据自己的协议来判断是不是读取完了,比如协议约定要遇到“&”才算读完。那么用户线程会继续向内核读取该socket的数据,这个时候执行select的时候立刻就给用户程序返回了(LT水平触发下),因为你上次压根没读完,用户继续读走数据"6&",用户读完解析出来"&"。根据协议约定&是结束符,用户程序理解请求数据已经结束了,自己可以愉快的处理了。
TCP负责运输数据到本地内核缓存区,用户程序可以自由的选择select,poll还是epoll。它们并不影响TCP传输数据的性能,只影响用户程序去拿已经到来的数据性能。假设我们应用程序选择了epoll模式,然后选择了水平触发,那么用户程序可以一点一点拿数据。关键是它为啥要一点一点拿,它咋知道啥时候结束呢?
这就靠两个东西:1.内核缓存数据拿完了,还没找到结束标识(应用层协议约定) 2. 拿到应用层协议约定的结束标识符
如果是边沿触发,那么用户程序如果任性的只拿一部分数据给程序,程序解析出来没发现结束符,继续系统调用epoll 去拿。可惜就算该socket的缓存区还有数据,但是它不会唤醒epoll,除非等到下次就绪。很遗憾,前面一次请求已经全部发来了,结束符就在内核缓存区里面,但是用户程序读不到。读不到就没法处理,也就没法响应请求方。请求方如果是http协议的话,是必须等到response的才能发下一个请求。这下好了,请求方不会再发任何数据来填充这个socket的缓存区了,也就不可能触发可读事件给epoll了,这个socket算是废了。所以边缘触发下,用户程序一定要保证一次读完,所以它的效率比较高。
但是它的缺点就是要把数据读完,比较占用用户空间,netty的AbstractNioMessageChannel.read方法,其实可以看出来为了readBuf的大小不至于太大,用了LT触发,用户程序直接多次读取,没读完epoll也会继续唤醒。