参考(原文简直超赞):https://zhidao.baidu.com/question/687563051895364284.html
下面是我结合原文写的,为了便于自己理解:
关于阻塞和非阻塞的理解可以看这个:http://www.cnblogs.com/xcywt/p/8146123.html

1.举例子说明
假设你在读大学,有个朋友F来找你,你住在A栋。但是不知道具体是哪个房间。于是你们约好在A栋门口见面。
如果用阻塞IO模型来处理这个问题,你就相当于一直在A栋门口等着,这个时候你不能做别的事情,效率比较低,如果F一直不来你就得一直在那等着。
接着来看用非阻塞模型来处理这个问题,主要有两种select/poll(这两个可以看成一种)和epoll:
select大妈做的事情是这样:当朋友F到了楼下时,她带着F一个个房间了轮询的去找你。
epoll大妈就比较高级了:大妈拿本子记录下你的房间号,当朋友F来的时候告诉F你的房间号。这样就不用整栋楼去跑了。
在大并发服务器中,轮询IO是一件比较费时的操作,就跟select大妈一样。
epoll大妈多用了一个本子,就有点用空间去换取时间的意思。

2.select/poll为什么慢:
1)select/poll 是遍历所有添加进fd_set的fd。并且需要将所有用户态的fd拷贝到内核态。数量巨大时这个效率比较慢
2)并且返回之后,还要轮询将所有集合查询一次
3)内核空间的数据需要拷贝到用户空间

3.epoll的实现原理:

具体使用方法可以参考:http://www.cnblogs.com/xcywt/p/8146094.html
先说几个函数的作用
       int epoll_create(int size); // 创建一个epoll对象,size是内核保证能够正确处理的最大句柄数。
       int epoll_create1(int flags);// 上面的加强版本,参数只能是EPOLL_CLOEXEC
       int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); // 操作epoll对象
       int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);// 在给定时间内,监控的所有句柄中有时间发生就返回
下面我们来看具体做了什么:
  epoll在内核初始化的时候向内核注册了一个文件系统,用于存储上述被监控的socket,同时还会开辟出epoll自己的内核高速cache区,用于安置需要监控的fd。这些fd以红黑树的形式保存在内核cache里,以支持快速的查找、插入、删除。这个内核高速cache区,就是建立连续的物理内存页,然后在之上建立slab层,简单的说就是物理上分配好你想要的大小的内存对象,每次使用时都是使用空闲的已分配好的对象。

  每次调用epoll_create时,会在这个虚拟的epoll文件系统里创建一个file节点,在内核cache中建立个红黑树来存储通过epoll_ctl添加进来的fd。这些fd其实已经在内核态了,当你再次调用epoll_wait时,不需要再拷贝进内核态(select需要再全部拷贝到内核态)。

  同时还会建立一个list链表,用来存储已经就绪的事件。被epoll_wait调用时,就去看这个list链表是不是为空,若不为空就返回,为空就等待指定的事件再返回。

  list链表是如何维护的呢:当我们执行epoll_ctl时,会把对应fd放到红黑树中,还会给内核终端处理程序注册一个回调函数。如果这个句柄的中断到了,就把它放在list链表中去。
  总结一下:一棵红黑树和一个list链表就解决大并发的问题。epoll_create时创建红黑树和就绪链表,epoll_ctl时添加到红黑树中(若存在则不添加)并向内核注册回调函数。epoll_wait时返回list就绪链表里面的数据就可以了。

4.epoll的两个工作模式:
LT:只要一个句柄上的事件一次没有处理完,接着调用epoll_wait时仍然会返回这个句柄。
ET:尽在空闲状态->就绪状态返回一次。
这件事是怎么做到的呢:当有fd'发生事件时,就放到list就绪链表中去了。然后epoll_wait返回,再然后清空准备list就绪链表。
最后如果是LT模式,并且仍有未处理的事件,就把这个fd重新放回到list就绪链表中。
如果是ET,就不管了,不管有没有事件未处理完都不再添加到list就绪链表中。

就有点像下面的流程:

wait返回 -> 清空list就绪链表
if(LT模式)
{
if(存在未处理完的事件)
{
重新添加进list就绪链表中
}
}
else // ET 模式
{ }

关于触发模式详解,这里面也讲的比较详细:
http://blog.csdn.net/weiyuefei/article/details/52242778
5.ET模式被唤醒的条件:
对于读取操作:
1)buffer由不可读,变为可读的时候。
2)buffer数据变多的时候,有新的数据到来
3)当buffer不为空(有数据可读),且用户对相应fd进行epoll_mod  IN 事件时。(待会用代码演示)
对于写操作:

1)由不可写,变成可写
2)buffer是数据变少的时候,也就是被读走了一部分3)buffer有可写空间,且用户对相应fd进行epoll_mod OUT 事件时。

对于LT模式:

读操作:只要缓冲区中有数据,且读完一部分之后还不空的时候,就会返回

写操作:当发送缓冲区没满,写了一下还不满的时候,epoll_wait返回读事件。

补充一个例子1:验证ET模式的读取返回的前2个:

#include<unistd.h>
#include<iostream>
#include<sys/epoll.h>
using namespace std;
int main()
{
int epfd, ret;
struct epoll_event ev, events[];
epfd = epoll_create();
ev.data.fd = STDIN_FILENO;
ev.events = EPOLLIN|EPOLLET; // 标记A,这里是ET模式
//ev.events = EPOLLIN; // 标记B。表示默认是LT模式
char buf[] = {};
epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev); //添加标准输入
while()
{
ret = epoll_wait(epfd, events, , -);
for(int i=; i < ret; i++)
{
if(events[i].data.fd == STDIN_FILENO)
{
//read(STDIN_FILENO, buf, sizeof(buf)); // 标记C
cout << "hello world, recv:" << buf << endl;
}
}
}
return ;
}

分三种情况讨论:
1)打开标记A,注释B和C:这种情况运行,虽然输入缓冲区里面还有数据,但是“hello world”也不会一直打印。
因为边沿触发,一定要等到下一次事件到来 wait才会返回。
2)打开B,注释A和C:切换成了LT模式,只要缓冲区里面还有数据吗,wait会一直返回。所以helloworld会一直打印
3)打开B和C,注释A:LT模式,但是每次wait之后把缓冲区里面的数据读完了,相当于处理完了这个事件。wait就不会返回了。除非标准输入中再输入数据。

例子2:验证ET模式的读取返回的第3个:

#include<unistd.h>
#include<iostream>
#include<sys/epoll.h>
using namespace std;
int main()
{
int epfd, ret;
struct epoll_event ev, events[];
epfd = epoll_create();
ev.data.fd = STDIN_FILENO;
ev.events = EPOLLIN|EPOLLET;
char buf[] = {};
epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev);
while()
{
ret = epoll_wait(epfd, events, , -);
for(int i=; i < ret; i++)
{
if(events[i].data.fd == STDIN_FILENO)
{
cout << "hello world << endl;
ev.data.fd = STDIN_FILENO;
ev.events = EPOLLIN|EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_MOD, STDIN_FILENO, &ev); // 这里对fd进行epoll_mod IN 事件
}
}
}
return ;
}

可以看到当输入一次之后,依然会有死循环打印helloworld。

例子3:验证ET模式的写返回,前2个

#include<unistd.h>
#include<iostream>
#include<sys/epoll.h>
using namespace std;
int main()
{
int epfd, ret;
struct epoll_event ev, events[];
epfd = epoll_create();
ev.data.fd = STDIN_FILENO;
ev.events = EPOLLOUT|EPOLLET;
char buf[] = {};
epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev);
while()
{
ret = epoll_wait(epfd, events, , -);
for(int i=; i < ret; i++)
{
if(events[i].data.fd == STDIN_FILENO)
{
//cout << "hello world" << endl; // 标记A
cout << "hello world"; // 标记B
}
}
}
return ;
}

对于ET模式。
1)打开标记A,注释标记B:可以看到会死循环,因为这里有 endl 。标准输出为控制台的时候缓冲的“行缓冲”,所以换行符号导致buffer中的内容被清空。就相当于上面条件中的第二个,有数据发送走了。所以会一直循环
2)打开B,注释A:不发送endl,就相当于buffer中一直有数据存在,所以wait不会一直返回。

例子4,ET模式的写返回第三个条件。

#include<unistd.h>
#include<iostream>
#include<sys/epoll.h>
using namespace std; int main()
{
int epfd, ret;
struct epoll_event ev, events[];
epfd = epoll_create();
ev.data.fd = STDIN_FILENO;
ev.events = EPOLLOUT|EPOLLET; char buf[] = {};
epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev);
while()
{
ret = epoll_wait(epfd, events, , -);
for(int i=; i < ret; i++)
{
if(events[i].data.fd == STDIN_FILENO)
{
cout << "hello world";
ev.data.fd = STDIN_FILENO;
ev.events = EPOLLOUT;
epoll_ctl(epfd, EPOLL_CTL_MOD, STDIN_FILENO, &ev); // 这里对fd进行epoll_mod OUT 事件
}
}
} return ;
}

每次输出helloworld后重新MOD OUT 事件。也会一直循环打印。
注意:LT模式没有验证

为什么epoll会那么高效的更多相关文章

  1. linux下epoll如何实现高效处理百万句柄的

    linux下epoll如何实现高效处理百万句柄的 分类: linux 技术分享 2012-01-06 10:29 4447人阅读 评论(5) 收藏 举报 linuxsocketcachestructl ...

  2. 从I/O复用谈epoll为什么高效

    上一篇文章中,谈了一些网络编程的基本概念.在现实使用中,用的最多的就是I/O复用了,无非就是select,poll,epoll 很多人提到网络就说epoll,认为epoll效率是最高的.单纯的这么认为 ...

  3. epoll的内部实现 & 百万级别句柄监听 & lt和et模式非常好的解释

    epoll是Linux高效网络的基础,比如event poll(例如nodejs),是使用libev,而libev的底层就是epoll(只不过不同的平台可能用epoll,可能用kqueue). epo ...

  4. 基本套接字编程(5) -- epoll篇

    1. epoll技术 epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃 ...

  5. epoll的原理和使用方法

    设想一个场景:有100万用户同一时候与一个进程保持着TCP连接,而每个时刻仅仅有几十个或几百个TCP连接时活跃的(接收到TCP包),也就是说,在每一时刻,进程值须要处理这100万连接中的一小部分连接. ...

  6. 什么是epoll

    什么是epoll epoll是什么?按照man手册的说法:是为处理大批量句柄而作了改进的poll.当然,这不是2.6内核才有的,它是在2.5.44内核中被引进的(epoll(4) is a new A ...

  7. epoll详解

    转自:http://blog.chinaunix.net/uid-24517549-id-4051156.html 什么是epoll epoll是什么?按照man手册的说法:是为处理大批量句柄而作了改 ...

  8. [转载] epoll详解

    转载自http://blog.csdn.net/xiajun07061225/article/details/9250579 什么是epoll epoll是什么?按照man手册的说法:是为处理大批量句 ...

  9. [转]epoll详解

    什么是epollepoll是什么?按照man手册的说法:是为处理大批量句柄而作了改进的poll.当然,这不是2.6内核才有的,它是在2.5.44内核中被引进的(epoll(4) is a new AP ...

随机推荐

  1. Fedora 23建立wifi热点(Android手机可用)

    在ubuntu14.04下使用ap-hotspot,速度还不错.但是在15.04下就用不了了,不知为啥.现在使用fedora23,在学校还是挺需要给手机连wifi的,于是google看看ap-hots ...

  2. HTML页面加载异常,按F12调试后居然又好了的解决办法!

    原因: 你的代码中获取数据那一段应该是有console控制台调用的代码,一般应该是console.log之类的,就是因为这句话在没开F12的时候,console是个undefined的东西就卡在那啦. ...

  3. 基于 HTML5 Canvas 的 3D 模型贴图问题

    之前注意到的一个例子,但是一直没有沉下心来看这个例子到底有什么优点,总觉得就是一个 list 列表,也不知道右边的 3d 场景放两个节点是要干嘛,今天突然想起来就仔细地看了一下这个例子的代码,实际操作 ...

  4. 一步步搭建Retrofit+RxJava+MVP网络请求框架(一)

    首先,展示一下封装好之后的项目的层级结构. 1.先创建一个RetrofitApiService.java package com.xdw.retrofitrxmvpdemo.http; import ...

  5. JavaScript内置的预定义函数

    javascript引擎中有一组可供随时调用的内建函数.这些内建函数包括 parseInt()  将收到的任何输入值转换成整数类型输出,如果转换失败,返回NaN parseFloat() 功能基本与p ...

  6. JAVA8新特性(一)

    default拓展方法 java8为接口声明添加非抽象方法的实现,也成为拓展方法. public interface Formula { void doSomething(); default voi ...

  7. cookie解决跨域问题

    v一.前言 随着项目模块越来越多,很多模块现在都是独立部署.模块之间的交流有时可能会通过cookie来完成.比如说门户和应用,分别部署在不同的机器或者web容器中,假如用户登陆之后会在浏览器客户端写入 ...

  8. jquery写的树状列表插件-alvintree

    在做项目的时候遇到选择部门下人员的功能,可多选可单选,所以就想着使用树状列表来进行选择,但在网上找了很多,发现要么就是挺复杂,要么就是需要各种前端框架的支持,试了一个感觉难用,所以就想着自己写一个简便 ...

  9. 初识AOP与动态代理

    AOP AOP是指在jvm运行时, 动态将代码切入到指定位置. OOP是一个维度上写代码, AOP是把他切开来, 变成立体的. 这样的好处是: 业务逻辑跟辅助逻辑分离, 例如日志打印, 性能监控, 安 ...

  10. Activit各个网关使用简单介绍

    一.排他网关 Exclusive Gateway 排他网关又叫互斥网关,条件计算为true的顺序流会被选择继续流程,有且只有一条出口,如果出现多个条件为true,则会默认选择第一条true来执行,如果 ...