多路转接之epoll的两种触发方式(LT,ET的效率对比,原理,epoll读取数据的过程)
目录
EPOLL的触发方式
水平触发 LT(Level Triggered)
边沿触发 ET(Eage Triggered)
比喻
过程
总结
回到epoll
效率
为什么要一次读完?
如何确保读完了所有数据?
举例
回到epoll
效率对比总结
本质
epoll接口介绍 -- epoll接口介绍,epoll模型介绍+原理,接口和模型的关系,epoll优点(和select/poll进行对比)-CSDN博客
EPOLL的触发方式
水平触发 LT(Level Triggered)
epoll的默认方式
- 如果我们没有处理[已就绪的事件],会一直通知我们
可以把已就绪的事件看作高电平,通知看作有效
- 水平触发就是,只要处于高电平,就有效
边沿触发 ET(Eage Triggered)
数据/连接有变化的时候,才会通知,且只通知一次
- 变化 -- 从无到有,从有到多
比喻
过程
可以把这两个模式看做是两个快递员送货的方式
- a快递员 -- 当有快递送到,而你不去取/取了一部分,会一直打电话通知让你来取,直到取完
- b快递员 -- 也会在快递送到时通知,区别在于:如果你没有取/只取了一部分,他不会将剩下的部分留在那里,而是带走 ; 并且他这次通知了之后就不管了,直到有新快递到来,才又会通知我们,然后带上之前你剩下的快递
总结
乍眼一看,可能会觉得a方式更好
但因为b的特性,如果我们没有一次取完,可能导致货物丢失什么的,或是等待下一个货物等待了很久
- 所以,会倒逼我们在收到通知后,就一次取走所有货物
并且,对比a和b,我们会发现,b可以通知到更多的人
- 因为一个快递员在一定时间内打出的电话次数是有限的
- a可能一直在打重复电话,而b通知到的人数更多
回到epoll
效率
也就是说:
- ET的特性会倒逼程序员,在处理通知时,需要一次将收到的数据全部读取,否则会错过这一次发来的完整数据,直到对方向我们发来新的数据,才能接收到上一轮剩下的数据
- 所以,ET的io效率更高 -- 通知一次,就把数据全部取走
联系到tcp层:
- 如果一次就能把数据全部取走,接收缓冲区就会更大的空间,从而向对方发送一个更大的窗口,这样对方一次就能发送更多的数据(和延迟应答的原理类似)
因为ET下可以通知更多的进程
- 所以通知效率也更高
为什么要一次读完?
如果不强制要求,可能会导致通信双方无法完成正常通信
- 假设,a收到了b的10k数据,但a只读取了1k,剩下的还在内核缓冲区里
- 然后就会卡在这里: a在等待b的通知,想要继续读取数据 ; 而b在等待a的应答,收到应答后才会继续发送数据
- 这就尴尬了
- 所以,必须一次读完
如何确保读完了所有数据?
举例
假如a月初领了1000块,到了月中的时候,你找他借钱,此时你并不知道他现在有多少钱
如果你想把他手里的钱全部借走,怎么做?
- 可以先借100,如果要多少给多少,说明他手里还有钱,可以继续要
- 直到给的比要的少,有零有整的,说明此时已经要完了
- 如果此时再继续要,他就会说自己已经没有钱了
回到epoll
总结一下就是 -- 保证全部取走 = 循环读取,直到没有数据
注意:
- 如果是阻塞io下,recv/read函数在没有数据可以读取的情况下,会阻塞至有数据到达
- 但这不是我们希望的,因为我们写的是个单进程,一旦阻塞,整个服务器就寄了
- 所以,我们要在ET模式下设置为非阻塞模式
我们之前实验过,如果当前没有数据,会返回[错误码为11]的错误
效率对比总结
但并不是说ET的效率就一定比LT方式高
- 普遍情况下,确实是这样的,但还是取决于代码的具体实现
谁说采用了LT模式就不能将所有fd设置为非阻塞方式,然后循环读取,直到没有数据呢?
- 如果我们保证在LT模式下,在第一次通知时,就取走全部数据,也就达到了和ET一样的效果
- 只不过一个是主动,一个是被动这么做
本质
前面我们一直在说的通知,其本质是 -- 添加结点入就绪队列
- 只要上层调用epoll_wait,就能获取队列中的数据
当然,如果不调用也没办法
- 毕竟内核也不能强迫调用接口吧,通知就是它能做到的极限了
- 就像tcp中的psh标志,他也只是催促对方赶紧将收到的数据准备好交给上层,而无法硬塞给上层
epoll读取数据的流程:
- 就是等就绪队列有数据+调用epoll_wait接口 -> 将就绪队列中的数据读取到tcp层的接收缓冲区中 -> 上层从缓冲区中读出数据(读出的方式就是ET和LT的区别)
- LT -- 不保证一次读完,但每次都会将未被读取的结点放入队列
- ET -- 因为只会将结点放入队列中一次,所以必须读完数据