epoll好文章
https://www.cnblogs.com/apprentice89/p/3234677.html
https://www.jianshu.com/p/aa486512e989
https://cloud.tencent.com/developer/article/1005481
最后看看epoll独有的两种模式LT和ET。无论是LT和ET模式,都适用于以上所说的流程。区别是,LT模式下,只要一个句柄上的事件一次没有处理完,会在以后调用epoll_wait时次次返回这个句柄,而ET模式仅在第一次返回。 这件事怎么做到的呢? 当一个socket句柄上有事件时,内核会把该句柄插入上面所说的准备就绪list链表,这时我们调用epoll_wait,会把准备就绪的socket拷贝到用户态内存,然后清空准备就绪list链表, 最后,epoll_wait干了件事,就是检查这些socket,如果不是ET模式(就是LT模式的句柄了),并且这些socket上确实有未处理的事件时,又把该句柄放回到刚刚清空的准备就绪链表了。 所以,非ET的句柄,只要它上面还有事件,epoll_wait每次都会返回。而ET模式的句柄,除非有新中断到,即使socket上的事件没有处理完,也是不会次次从epoll_wait返回的。
epoll为什么使用红黑树
因为epoll要求快速找到某个句柄,因此首先是一个Map接口,候选实现: 哈希表 O(1)
红黑树 O(lgn)
跳表 近似O(lgn)
据说老版本的内核和FreeBSD的Kqueue使用的是哈希表.
个人理解现在内核使用红黑树的原因: 哈希表. 空间因素,可伸缩性.
(1)频繁增删. 哈希表需要预估空间大小, 这个场景下无法做到.
间接影响响应时间,假如要resize,原来的数据还得移动.即使用了一致性哈希算法,
也难以满足非阻塞的timeout时间限制.(时间不稳定)
(2) 百万级连接,哈希表有镂空的空间,太浪费内存.
跳表. 慢于红黑树. 空间也高.
红黑树. 经验判断,内核的其他地方如防火墙也使用红黑树,实践上看性能最优.
fd数量受限于内核内存大小,理论上无限
epoll 跟mmap没关系 没用到mmap
https://www.cnblogs.com/lojunren/p/3856290.html
后面的ETLT区别写得好
LT与ET模式
在这里,笔者强烈推荐《彻底学会使用epoll》系列博文,这是笔者看过的,对epoll的ET和LT模式讲解最为详尽和易懂的博文。下面的实例均来自该系列博文。限于篇幅原因,很多关键的细节,不能完全摘录。
话不多说,直接上代码。
程序一:
#include <stdio.h>
#include <unistd.h>
#include <sys/epoll.h> int main(void)
{
int epfd,nfds;
struct epoll_event ev,events[5]; //ev用于注册事件,数组用于返回要处理的事件
epfd = epoll_create(1); //只需要监听一个描述符——标准输入
ev.data.fd = STDIN_FILENO;
ev.events = EPOLLIN|EPOLLET; //监听读状态同时设置ET模式
epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev); //注册epoll事件
for(;;)
{
nfds = epoll_wait(epfd, events, 5, -1);
for(int i = 0; i < nfds; i++)
{
if(events[i].data.fd==STDIN_FILENO)
printf("welcome to epoll's word!\n"); }
}
}
- 当用户输入一组字符,这组字符被送入buffer,字符停留在buffer中,又因为buffer由空变为不空,所以ET返回读就绪,输出”welcome to epoll's world!”。
- 之后程序再次执行epoll_wait,此时虽然buffer中有内容可读,但是根据我们上节的分析,ET并不返回就绪,导致epoll_wait阻塞。(底层原因是ET下就绪fd的epitem只被放入rdlist一次)。
- 用户再次输入一组字符,导致buffer中的内容增多,根据我们上节的分析这将导致fd状态的改变,是对应的epitem再次加入rdlist,从而使epoll_wait返回读就绪,再次输出“Welcome to epoll's world!”。
接下来我们将上面程序的第11行做如下修改:
1 ev.events=EPOLLIN; //默认使用LT模式
编译并运行,结果如下:
程序陷入死循环,因为用户输入任意数据后,数据被送入buffer且没有被读出,所以LT模式下每次epoll_wait都认为buffer可读返回读就绪。导致每次都会输出”welcome to epoll's world!”。
程序二:
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/epoll.h>
4
5 int main(void)
6 {
7 int epfd,nfds;
8 struct epoll_event ev,events[5]; //ev用于注册事件,数组用于返回要处理的事件
9 epfd = epoll_create(1); //只需要监听一个描述符——标准输入
10 ev.data.fd = STDIN_FILENO;
11 ev.events = EPOLLIN; //监听读状态同时设置LT模式
12 epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev); //注册epoll事件
13 for(;;)
14 {
15 nfds = epoll_wait(epfd, events, 5, -1);
16 for(int i = 0; i < nfds; i++)
17 {
18 if(events[i].data.fd==STDIN_FILENO)
19 {
20 char buf[1024] = {0};
21 read(STDIN_FILENO, buf, sizeof(buf));
22 printf("welcome to epoll's word!\n");
23 }
24 }
25 }
26 }
编译并运行,结果如下:
本程序依然使用LT模式,但是每次epoll_wait返回读就绪的时候我们都将buffer(缓冲)中的内容read出来,所以导致buffer再次清空,下次调用epoll_wait就会阻塞。所以能够实现我们所想要的功能——当用户从控制台有任何输入操作时,输出”welcome to epoll's world!”
程序三:
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/epoll.h>
4
5 int main(void)
6 {
7 int epfd,nfds;
8 struct epoll_event ev,events[5]; //ev用于注册事件,数组用于返回要处理的事件
9 epfd = epoll_create(1); //只需要监听一个描述符——标准输入
10 ev.data.fd = STDIN_FILENO;
11 ev.events = EPOLLIN|EPOLLET; //监听读状态同时设置ET模式
12 epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev); //注册epoll事件
13 for(;;)
14 {
15 nfds = epoll_wait(epfd, events, 5, -1);
16 for(int i = 0; i < nfds; i++)
17 {
18 if(events[i].data.fd==STDIN_FILENO)
19 {
20 printf("welcome to epoll's word!\n");
21 ev.data.fd = STDIN_FILENO;
22 ev.events = EPOLLIN|EPOLLET; //设置ET模式
23 epoll_ctl(epfd, EPOLL_CTL_MOD, STDIN_FILENO, &ev); //重置epoll事件(ADD无效)
24 }
25 }
26 }
27 }
编译并运行,结果如下:
程序依然使用ET,但是每次读就绪后都主动的再次MOD IN事件,我们发现程序再次出现死循环,也就是每次返回读就绪。但是注意,如果我们将MOD改为ADD,将不会产生任何影响。别忘了每次ADD一个描述符都会在epitem组成的红黑树中添加一个项,我们之前已经ADD过一次,再次ADD将阻止添加,所以在次调用ADD IN事件不会有任何影响。
程序四:
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/epoll.h>
4
5 int main(void)
6 {
7 int epfd,nfds;
8 struct epoll_event ev,events[5]; //ev用于注册事件,数组用于返回要处理的事件
9 epfd = epoll_create(1); //只需要监听一个描述符——标准输入
10 ev.data.fd = STDOUT_FILENO;
11 ev.events = EPOLLOUT|EPOLLET; //监听读状态同时设置ET模式
12 epoll_ctl(epfd, EPOLL_CTL_ADD, STDOUT_FILENO, &ev); //注册epoll事件
13 for(;;)
14 {
15 nfds = epoll_wait(epfd, events, 5, -1);
16 for(int i = 0; i < nfds; i++)
17 {
18 if(events[i].data.fd==STDOUT_FILENO)
19 {
20 printf("welcome to epoll's word!\n");
21 }
22 }
23 }
24 }
编译并运行,结果如下:
这个程序的功能是只要标准输出写就绪,就输出“welcome to epoll's world”。我们发现这将是一个死循环。下面具体分析一下这个程序的执行过程:
- 首先初始buffer为空,buffer中有空间可写,这时无论是ET还是LT都会将对应的epitem加入rdlist,导致epoll_wait就返回写就绪。
- 程序想标准输出输出”welcome to epoll's world”和换行符,因为标准输出为控制台的时候缓冲是“行缓冲”,所以换行符导致buffer中的内容清空,这就对应第二节中ET模式下写就绪的第二种情况——当有旧数据被发送走时,即buffer中待写的内容变少得时候会触发fd状态的改变。所以下次epoll_wait会返回写就绪。如此循环往复。
程序五:
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/epoll.h>
4
5 int main(void)
6 {
7 int epfd,nfds;
8 struct epoll_event ev,events[5]; //ev用于注册事件,数组用于返回要处理的事件
9 epfd = epoll_create(1); //只需要监听一个描述符——标准输入
10 ev.data.fd = STDOUT_FILENO;
11 ev.events = EPOLLOUT|EPOLLET; //监听读状态同时设置ET模式
12 epoll_ctl(epfd, EPOLL_CTL_ADD, STDOUT_FILENO, &ev); //注册epoll事件
13 for(;;)
14 {
15 nfds = epoll_wait(epfd, events, 5, -1);
16 for(int i = 0; i < nfds; i++)
17 {
18 if(events[i].data.fd==STDOUT_FILENO)
19 {
20 printf("welcome to epoll's word!");
21 }
22 }
23 }
24 }
编译并运行,结果如下:
与程序四相比,程序五只是将输出语句的printf的换行符移除。我们看到程序成挂起状态。因为第一次epoll_wait返回写就绪后,程序向标准输出的buffer中写入“welcome to epoll's world!”,但是因为没有输出换行,所以buffer中的内容一直存在,下次epoll_wait的时候,虽然有写空间但是ET模式下不再返回写就绪。回忆第一节关于ET的实现,这种情况原因就是第一次buffer为空,导致epitem加入rdlist,返回一次就绪后移除此epitem,之后虽然buffer仍然可写,但是由于对应epitem已经不再rdlist中,就不会对其就绪fd的events的在检测了。
程序六:
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/epoll.h>
4
5 int main(void)
6 {
7 int epfd,nfds;
8 struct epoll_event ev,events[5]; //ev用于注册事件,数组用于返回要处理的事件
9 epfd = epoll_create(1); //只需要监听一个描述符——标准输入
10 ev.data.fd = STDOUT_FILENO;
11 ev.events = EPOLLOUT; //监听读状态同时设置LT模式
12 epoll_ctl(epfd, EPOLL_CTL_ADD, STDOUT_FILENO, &ev); //注册epoll事件
13 for(;;)
14 {
15 nfds = epoll_wait(epfd, events, 5, -1);
16 for(int i = 0; i < nfds; i++)
17 {
18 if(events[i].data.fd==STDOUT_FILENO)
19 {
20 printf("welcome to epoll's word!");
21 }
22 }
23 }
24 }
编译并运行,结果如下:
程序六相对程序五仅仅是修改ET模式为默认的LT模式,我们发现程序再次死循环。这时候原因已经很清楚了,因为当向buffer写入”welcome to epoll's world!”后,虽然buffer没有输出清空,但是LT模式下只有buffer有写空间就返回写就绪,所以会一直输出”welcome to epoll's world!”,当buffer满的时候,buffer会自动刷清输出,同样会造成epoll_wait返回写就绪。
程序七:
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/epoll.h>
4
5 int main(void)
6 {
7 int epfd,nfds;
8 struct epoll_event ev,events[5]; //ev用于注册事件,数组用于返回要处理的事件
9 epfd = epoll_create(1); //只需要监听一个描述符——标准输入
10 ev.data.fd = STDOUT_FILENO;
11 ev.events = EPOLLOUT|EPOLLET; //监听读状态同时设置LT模式
12 epoll_ctl(epfd, EPOLL_CTL_ADD, STDOUT_FILENO, &ev); //注册epoll事件
13 for(;;)
14 {
15 nfds = epoll_wait(epfd, events, 5, -1);
16 for(int i = 0; i < nfds; i++)
17 {
18 if(events[i].data.fd==STDOUT_FILENO)
19 {
20 printf("welcome to epoll's word!");
21 ev.data.fd = STDOUT_FILENO;
22 ev.events = EPOLLOUT|EPOLLET; //设置ET模式
23 epoll_ctl(epfd, EPOLL_CTL_MOD, STDOUT_FILENO, &ev); //重置epoll事件(ADD无效)
24 }
25 }
26 }
27 }
编译并运行,结果如下:
程序七相对于程序五在每次向标准输出的buffer输出”welcome to epoll's world!”后,重新MOD OUT事件。所以相当于每次都会返回就绪,导致程序循环输出。
经过前面的案例分析,我们已经了解到,当epoll工作在ET模式下时,对于读操作,如果read一次没有读尽buffer中的数据,那么下次将得不到读就绪的通知,造成buffer中已有的数据无机会读出,除非有新的数据再次到达。对于写操作,主要是因为ET模式下fd通常为非阻塞造成的一个问题——如何保证将用户要求写的数据写完。
要解决上述两个ET模式下的读写问题,我们必须实现:
- 对于读,只要buffer中还有数据就一直读;
- 对于写,只要buffer还有空间且用户请求写的数据还未写完,就一直写。
ET模式下的accept问题
请思考以下一种场景:在某一时刻,有多个连接同时到达,服务器的 TCP 就绪队列瞬间积累多个就绪连接,由于是边缘触发模式,epoll 只会通知一次,accept 只处理一个连接,导致 TCP 就绪队列中剩下的连接都得不到处理。在这种情形下,我们应该如何有效的处理呢?
解决的方法是:解决办法是用 while 循环抱住 accept 调用,处理完 TCP 就绪队列中的所有连接后再退出循环。如何知道是否处理完就绪队列中的所有连接呢? accept 返回 -1 并且 errno 设置为 EAGAIN 就表示所有连接都处理完。
关于ET的accept问题,这篇博文的参考价值很高,如果有兴趣,可以链接过去围观一下。
ET模式为什么要设置在非阻塞模式下工作
因为ET模式下的读写需要一直读或写直到出错(对于读,当读到的实际字节数小于请求字节数时就可以停止),而如果你的文件描述符如果不是非阻塞的,那这个一直读或一直写势必会在最后一次阻塞。这样就不能在阻塞在epoll_wait上了,造成其他文件描述符的任务饥饿。
epoll的使用实例
这样的实例,网上已经有很多了(包括参考链接),笔者这里就略过了。
小结
LT:水平触发,效率会低于ET触发,尤其在大并发,大流量的情况下。但是LT对代码编写要求比较低,不容易出现问题。LT模式服务编写上的表现是:只要有数据没有被获取,内核就不断通知你,因此不用担心事件丢失的情况。
ET:边缘触发,效率非常高,在并发,大流量的情况下,会比LT少很多epoll的系统调用,因此效率高。但是对编程要求高,需要细致的处理每个请求,否则容易发生丢失事件的情况。
从本质上讲:与LT相比,ET模型是通过减少系统调用来达到提高并行效率的。
epoll好文章的更多相关文章
- 知乎上看到的一篇讲解Epoll的文章,较形象生动
作者:蓝形参链接:https://www.zhihu.com/question/20122137/answer/14049112来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明 ...
- 【转】转载一篇优质的讲解epoll模型的文章
从事服务端开发,少不了要接触网络编程.Epoll 作为 Linux 下高性能网络服务器的必备技术至关重要,Nginx.Redis.Skynet 和大部分游戏服务器都使用到这一多路复用技术. Epoll ...
- 如果这篇文章说不清epoll的本质,那就过来掐死我吧!
转载自:https://www.toutiao.com/i6683264188661367309/ 目录 一.从网卡接收数据说起 二.如何知道接收了数据? 三.进程阻塞为什么不占用cpu资源? 四.内 ...
- 服务器 libevent中epoll使用实例demo
名词解释:man epoll之后,得到如下结果: NAME epoll - I/O event notification facility SYNOPSIS #include ...
- epoll的本质
目录 一.从网卡接收数据说起 二.如何知道接收了数据? 三.进程阻塞为什么不占用cpu资源? 四.内核接收网络数据全过程 五.同时监视多个socket的简单方法 六.epoll的设计思路 七.epol ...
- select,poll,epoll最简单的解释
从事服务端开发,少不了要接触网络编程.epoll 作为 Linux 下高性能网络服务器的必备技术至关重要,nginx.Redis.Skynet 和大部分游戏服务器都使用到这一多路复用技术. epoll ...
- 知乎大佬图文并茂的epoll讲解,看不懂的去砍他
select.poll.epoll的文章很多,自己也看过不少经典好文.不过第一次看到讲的如此通俗易懂.又图文并茂的.因此拿来分享下,供后续翻看学习. 原文链接:https://zhuanlan.zhi ...
- libevent源码深度剖析
原文地址: http://blog.csdn.net/sparkliang/article/details/4957667 第一章 1,前言 Libevent是一个轻量级的开源高性能网络库,使用者众多 ...
- libevent源码深度剖析十
libevent源码深度剖析十 ——支持I/O多路复用技术 张亮 Libevent的核心是事件驱动.同步非阻塞,为了达到这一目标,必须采用系统提供的I/O多路复用技术,而这些在Windows.Linu ...
随机推荐
- Git 软件开发过程
一.关于Git与Subversion的区别 二.目前我们用Subversion是怎么执行软件过程的 三.优势与缺点 架构 * Git:分布式,所有的teammates本地可以clone一份独立完整的仓 ...
- sceneManager.loadscene加载场景时不会主动去加载场景的依赖包,要手动加载或添加场景到build setting列表中
假设有一场景1001.unity,,manifest文件如下: ManifestFileVersion: 0CRC: 425184873Hashes: AssetFileHash: serialize ...
- Cardboard Talk02 Accelerometer
操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Android studio 3.0.0 | Cardboard 1.0 在深入讨论具体实现之前,有必要了解一下Andro ...
- 7.25 10figting!
TEXT 88 European utilities欧洲公用事业 Power struggles 能源之争(陈继龙编译) Nov 30th 2006 From The Economist print ...
- 如何在ArcMap中监听键盘鼠标事件(转)
如何在ArcMap中监听键盘鼠标事件(转) Link: http://www.cnblogs.com/dyllove98/p/3155551.html 昨天有个朋友想要实现一个功能,就是在ArcMap ...
- EL的基本使用
总结:EL操作的是作用域 <body> <% Users users = new Users("lisi","lisi123","l ...
- binary tree
一.中序线索化 二叉树节点定义: class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; int isle ...
- S导入部门数据 更新父部门、责任人
导入部门数据分两步骤,EXCEL模板可以一样 一.导入部门主数据,导入时选择INSERT (注意以下还有问题,父区域会自动带出一个值) [Public] ConnectString=host=&quo ...
- 【bzoj3437】小P的牧场
3437: 小P的牧场 Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 947 Solved: 542[Submit][Status][Discuss ...
- 深入理解那该死的BOM
BOM(Byte Order Mark),是UTF编码方案里用于标识编码的标准标记,在UTF-16里本来是FF FE,变成UTF-8就成了EF BB BF.这个标记是可选的,因为UTF8字节没有顺序, ...