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");     }
  }
}
编译并运行,结果如下:

  1. 当用户输入一组字符,这组字符被送入buffer,字符停留在buffer中,又因为buffer由空变为不空,所以ET返回读就绪,输出”welcome to epoll's world!”。
  2. 之后程序再次执行epoll_wait,此时虽然buffer中有内容可读,但是根据我们上节的分析,ET并不返回就绪,导致epoll_wait阻塞。(底层原因是ET下就绪fd的epitem只被放入rdlist一次)。
  3. 用户再次输入一组字符,导致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”。我们发现这将是一个死循环。下面具体分析一下这个程序的执行过程:

  1. 首先初始buffer为空,buffer中有空间可写,这时无论是ET还是LT都会将对应的epitem加入rdlist,导致epoll_wait就返回写就绪。
  2. 程序想标准输出输出”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模式下的读写问题,我们必须实现:

  1. 对于读,只要buffer中还有数据就一直读;
  2. 对于写,只要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好文章的更多相关文章

  1. 知乎上看到的一篇讲解Epoll的文章,较形象生动

    作者:蓝形参链接:https://www.zhihu.com/question/20122137/answer/14049112来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明 ...

  2. 【转】转载一篇优质的讲解epoll模型的文章

    从事服务端开发,少不了要接触网络编程.Epoll 作为 Linux 下高性能网络服务器的必备技术至关重要,Nginx.Redis.Skynet 和大部分游戏服务器都使用到这一多路复用技术. Epoll ...

  3. 如果这篇文章说不清epoll的本质,那就过来掐死我吧!

    转载自:https://www.toutiao.com/i6683264188661367309/ 目录 一.从网卡接收数据说起 二.如何知道接收了数据? 三.进程阻塞为什么不占用cpu资源? 四.内 ...

  4. 服务器 libevent中epoll使用实例demo

    名词解释:man epoll之后,得到如下结果: NAME       epoll - I/O event notification facility SYNOPSIS       #include ...

  5. epoll的本质

    目录 一.从网卡接收数据说起 二.如何知道接收了数据? 三.进程阻塞为什么不占用cpu资源? 四.内核接收网络数据全过程 五.同时监视多个socket的简单方法 六.epoll的设计思路 七.epol ...

  6. select,poll,epoll最简单的解释

    从事服务端开发,少不了要接触网络编程.epoll 作为 Linux 下高性能网络服务器的必备技术至关重要,nginx.Redis.Skynet 和大部分游戏服务器都使用到这一多路复用技术. epoll ...

  7. 知乎大佬图文并茂的epoll讲解,看不懂的去砍他

    select.poll.epoll的文章很多,自己也看过不少经典好文.不过第一次看到讲的如此通俗易懂.又图文并茂的.因此拿来分享下,供后续翻看学习. 原文链接:https://zhuanlan.zhi ...

  8. libevent源码深度剖析

    原文地址: http://blog.csdn.net/sparkliang/article/details/4957667 第一章 1,前言 Libevent是一个轻量级的开源高性能网络库,使用者众多 ...

  9. libevent源码深度剖析十

    libevent源码深度剖析十 ——支持I/O多路复用技术 张亮 Libevent的核心是事件驱动.同步非阻塞,为了达到这一目标,必须采用系统提供的I/O多路复用技术,而这些在Windows.Linu ...

随机推荐

  1. asp.net要验证的用户名和密码

    FormsAuthentication.Authenticate()方法要验证的用户名和密码必须存储在Web.config文件内.如果要验证存储在“ASP.NET成员资格数据库”中的密码,则需要调用M ...

  2. c# 之Web.config

    Web.config文件是一个XML文本文件,它用来储存 ASP.NET Web 应用程序的配置信息(如最常用的设置ASP.NET Web 应用程序的身份验证方式), 它可以出现在应用程序的每一个目录 ...

  3. JS 根据url 下载

    一. window.location="htpp://www.baidu.com/test.rar"; 二. var $form = $('<form method=&quo ...

  4. 南京大学发布无序列限制的DNA编辑新工具(转自生物通)

    编辑推荐: 内切酶经过改造可以成为强大的DNA编辑工具,比如ZFN.TALEN.风头正劲的CRISPR–Cas系统和充满争议的NgAgo技术.不过这些技术都是通过序列识别来实现靶向切割的,会受到序列偏 ...

  5. jdk版本问题

    今天遇到很郁闷的问题jdk 版本是1.6 如何设置1.8 记录一下 可以设置环境变量设置jdk版本问题再就是在 1.java工具设置jdk版本问题 2.grandle  设置要注意 3.生成环境设置j ...

  6. 基于Dcoker的ZooKeeper集群的搭建

    背景 原来学习 ZK 时, 我是在本地搭建的伪集群, 虽然说使用起来没有什么问题, 但是总感觉部署起来有点麻烦. 刚好我发现了 ZK 已经有了 Docker 的镜像了, 于是就尝试了一下, 发现真是爽 ...

  7. Visual Studio工具 vcpkg简介

    博客参考: https://blog.csdn.net/cjmqas/article/details/79282847#43-%E7%A7%BB%E9%99%A4%E5%85%A8%E5%B1%80% ...

  8. websocket客户端实现

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  9. python 多线程简介

    Thread类定义了以下常用方法与属性: Thread.getName() \Thread.setName():老方式用于获取和设置线程的名称,官方建议用Thread.name替代 Thread.id ...

  10. SSL握手通信详解及linux下c/c++ SSL Socket代码举例

    SSL握手通信详解及linux下c/c++ SSL Socket代码举例 摘自:http://www.169it.com/article/3215130236.html   分享到:8     发布时 ...