前言
学习自用,有错麻烦提出,感谢
基本知识
ET和LT是IO复用的两种模式,ET早于LT出现(依据TODO)
对于几种IO复用,select和poll只支持LT(?TODO)
level triggered 和edge trigger,起源于电频的激发,TODO
ET是一次事件只会触发一次,如一次客户吨发来消息,fd可读,epoll_wait返回.等下次再调用epoll_wait则不会返回了
LT是一次事件会触发多次,如一次客户端发消息,fd可读,epoll_wait返回,不处理这个fd,再次调用epoll_wait,立刻返回,
LT和ET的内部实现
对于epoll,每次返回会有个ready list,以下参考自[1],epoll对于事件到来,是采用回调的机制来把fd加进ready_list中的,(后面有提到)
对于ET模式,事件到来后,回调函数把触发的事件的fd加入ready list中,
对于LT模式,还有个额外的操作,即对所有监听的(即红黑树里的)socket再poll以判断是否事件未被处理(来自参考资料[9]),如果事件未被处理,则重新再把事件对应的fd加入ready list中
以上可看出,ET比LT的高效点在于这里,论单纯的ET和LT调用的开销很容易看出ET较为高效,而若要算上应用层开销复杂度,则另当别论,详情往下读
LT和ET的应用场景和使用方法
使用方法
ET要与非阻塞fd一起使用,因为ET一次事件只触发一次,所以epoll_wait返回后一定要处理完毕,对于可读事件,要一直read fd到此fd被read完为止,而如果设置成blocking以后,fd上的数据read完后会阻塞,即while{epoll_wait(); read(fd)}这段代码会一直阻塞而影响重新调用epoll_wait来监听其他事件,正确做法是设置fd成non_blocking,且epoll_wait返回后吧事件read到EAGAIN为止
LT可搭配非阻塞也可搭配阻塞使用(证据TODO)
应用场景
LT的编程会比ET的编程更简洁的场景
对于可读事件,ET模式下的编程需要read到EAGAIN位置,发来的数据量多且并发量大的时候,还可能造成其他事件的饥饿,需要在应用层再额外代码以保证及时响应,而LT可直接每个消息事件read固定大小以保证每个连接公平,数据量大的且没读完的下次还会继续触发,(ET用应用层维护的例子TODO)
对于写事件,ET会实现更简单高效,例子:
要write1M数据,而缓冲区只有2kb,则需要epoll_wait() 可写事件EPOLLOUT,对于ET模式,等写完后则直接就可以了
而如果是LT模式对付这样场景,在写完后,需要再调用一次epoll_ctl来删去EPOLLOUT事件,否则下次调用epoll_wait还是会继续触发返回可写事件,具体代码可看参考资料[4]中文末位置的链接
可以看出,对于EPOLLOUT可写事件,用ET更高效
上面讲的可读事件和可写事件分别用LT和ET的代码实验,可见参考资料[5]
服务器场景
对于服务器编程,要处理三个半事件,这里先讲可读和连接到来事件.并讲下缓冲区满的可写事件
muduo所使用的连接到来事件(acceptor)是LT,对于消息到来(poller)也是LT模式..先不讨论为什么这么设计,因为陈硕还有其他要考虑的方面TODO
而Nginx,listen fd用的是LT来监听,connection fd用的是ET来监听,做实验这样组合并发度最高
listen fd用LT的原因:来自[7],若使用ET(边缘触发)模式,则非常可能有两个连接请求因为太靠近,而只accept()了其中一个(why,这是用户bug还是系统API的bug??)
此博客原话:这种情况要不就修改为LT,要不就继续ET模式,但listen socket为NOBLOCK模式。 accept()不断接收,直到返回 EAGAIN or EWOULDBLOCK。
connection socket用LT并发量高的原因:
TODO
另外ET和LT哪个更高效的讨论见参考资料[8]中评论的连接,评论中都是讨论,以后有时间再总结吧..TODO
epoll的实现
主要来源于参考资料[6]和[1]
直接说两个重要结构,红黑树和双向链表readylist
红黑树
功能:维护监听的fd和事件,方便添加删除时间复杂度都是logn水平,用来检查的fd添加是否重复
结构:key是fd,value是epitem结构
双向链表readylist,用于记录已发生的事件,并传输给应用层,
readylist中每个节点也是epitem对象吧?TODO
epoll_ctl流程:设置某个事件如网卡有数据的事件处理回调函数为添加fd到readylist
return_type epoll_ctl(epoll_event,fd) //省略类型
{
res = rbtree.find(fd);
if(res != rbtree.end() ) return;
rbtree[fd]=对应结构;
设置事件回调函数(如连接到来事件,消息到来事件)为添加事件fd到readylist
}
epitem的封装:TODO
select和epoll
这里和标题不符,但是还是放进来了,
为什么连接少又活跃的时候应该选择select而不是epoll,是什么实现原理导致的呢? TODO
参考资料
[1](https://www.cnblogs.com/charlesblc/p/6242479.html)
[2](https://www.zhihu.com/question/20502870)
[3](https://www.zhihu.com/question/47002053)
[4](https://www.zhihu.com/question/20502870/answer/89738959)
[5]https://www.zhihu.com/question/47002053/answer/794254562
[6](https://zhuanlan.zhihu.com/p/63179839)
[7](http://blog.sina.com.cn/s/blog_602f87700102y2ob.html)
[8](http://www.cppblog.com/Leaf/archive/2013/02/25/198061.html)
[9](https://www.cnblogs.com/bbqzsl/p/7060819.html)