select epoll poll

如何理解 Epoll select 和 poll 三种模型,可否用生活中的例子作比喻?

好比说你从某宝下单买了几个东西,这几个东西分别由N个快递员分别给你送过来。在某一时刻,你开始等快递。
对于select/poll,就是你在睡觉的时候,收到一条短信“你有快递到了,取一下”,但不知道发送方是谁(但必定是那N个快递员中的某人/某几我的给你发的),因此你必须挨个给那N个快递员分别打个电话,问他们,是否是个人快递已经到了。
至于select/poll的区别,相似于你和快递员都分别有两个手机号,一个移动,一个联通,其区别就在于你用哪一个手机号给他们打的问题。
对于epoll,是你收到那条短信的时候,看到了发送方的电话号码,你就能够直接给他打电话,问他在哪儿,你好去去快递。 html

做者:starsnow1982
连接:https://www.zhihu.com/question/21233763/answer/25314598
来源:知乎
著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。 linux

 

仍是收快递,select/poll/epoll分别对应快递公司A/B/C,映射关系以下: 编程

差异以下: windows

  1. A公司能够转运的快递比较少,BC公司能够运送的快递多(差别点:支持最大文件描述符区别)
  2. AB公司若是你一次快递没拿完,下次仍是会告诉你有快递; C公司告诉你有快递,无论你拿不拿走,下次都不会告诉你了(差别点: select/poll只支持LT工做模式,epoll支持ET以提升效率)
  3. AB公司快递到了以后,得本身去问全部的快递员快递在谁那呢,C公司直接告诉你(差别点: select/poll 采用轮询来检查就绪事件,后者采用回调方式,结果是前者索引事件复杂度是O(n), 后者是O(1))
  4. 在AB公司取完抵达的快递以后,须要把已经买过的货还再告诉他们一遍; 后者,只要是快递没有到,随意对快递进行增删改操做,且告诉他们一遍就够了(差别点: 前者在内核返回就绪事件的时候操做了共同的数据结构,后者则否则,注册/等待事件接口都是分离的)

固然,100%映射生活例子是不可能的,好比上面的映射关系实际上没有严格区分事件和文件描述符,这二者实际上在各个接口中的表现也不太同样,只是不太好映射了. 服务器

做者:Ender
连接:https://www.zhihu.com/question/21233763/answer/359650461
来源:知乎
著做权归做者全部。商业转载请联系做者得到受权,非商业转载请注明出处。 网络

 


下面引用知乎一书焚城的回答再次巩固一下IO模型数据结构

  1. 阻塞IO, 给女神发一条短信, 说我来找你了, 而后就默默的一直等着女神下楼, 这个期间除了等待你不会作其余事情, 属于备胎作法.
  1. 非阻塞IO, 给女神发短信, 若是不回, 接着再发, 一直发到女神下楼, 这个期间你除了发短信等待不会作其余事情, 属于专注作法.
  1. IO多路复用, 是找一个宿管大妈来帮你监视下楼的女生, 这个期间你能够些其余的事情. 例如能够顺便看看其余妹子,玩玩王者荣耀, 上个厕所等等. IO复用又包括 select, poll, epoll 模式. 那么它们的区别是什么?
    3.1 select大妈 每个女生下楼, select大妈都不知道这个是否是你的女神, 她须要一个一个询问, 而且select大妈能力还有限, 最多一次帮你监视1024个妹子
    3.2 poll大妈不限制盯着女生的数量, 只要是通过宿舍楼门口的女生, 都会帮你去问是否是你女神
    3.3 epoll大妈不限制盯着女生的数量, 而且也不须要一个一个去问. 那么如何作呢? epoll大妈会为每一个进宿舍楼的女生脸上贴上一个大字条,上面写上女生本身的名字, 只要女生下楼了, epoll大妈就知道这个是否是你女神了, 而后大妈再通知你.

上面这些同步IO有一个共同点就是, 当女神走出宿舍门口的时候, 你已经站在宿舍门口等着女神的, 此时你属于阻塞状态并发

接下来是异步IO的状况
你告诉女神我来了, 而后你就去王者荣耀了, 一直到女神下楼了, 发现找不见你了, 女神再给你打电话通知你, 说我下楼了, 你在哪呢? 这时候你才来到宿舍门口. 此时属于逆袭作法异步

 

首先引用levin的回答让咱们理清楚五种IO模型socket

1.阻塞I/O模型
老李去火车站买票,排队三天买到一张退票。
耗费:在车站吃喝拉撒睡 3天,其余事一件没干。

2.非阻塞I/O模型
老李去火车站买票,隔12小时去火车站问有没有退票,三天后买到一张票。耗费:往返车站6次,路上6小时,其余时间作了好多事。

3.I/O复用模型
1.select/poll
老李去火车站买票,委托黄牛,而后每隔6小时电话黄牛询问,黄牛三天内买到票,而后老李去火车站交钱领票。
耗费:往返车站2次,路上2小时,黄牛手续费100元,打电话17次
2.epoll
老李去火车站买票,委托黄牛,黄牛买到后即通知老李去领,而后老李去火车站交钱领票。
耗费:往返车站2次,路上2小时,黄牛手续费100元,无需打电话

4.信号驱动I/O模型
老李去火车站买票,给售票员留下电话,有票后,售票员电话通知老李,而后老李去火车

5.异步I/O模型
老李去火车站买票,给售票员留下电话,有票后,售票员电话通知老李并快递送票上门。
耗费:往返车站1次,路上1小时,免黄牛费100元,无需打电话

站交钱领票。
耗费:往返车站2次,路上2小时,免黄牛费100元,无需打电话

 

1同2的区别是:本身轮询

2同3的区别是:委托黄牛

3同4的区别是:电话代替黄牛

4同5的区别是:电话通知是自取仍是送票上门

 

 

理解 I/O-- 阻塞、非阻塞,同步、异步的概念及其区别

概念介绍

阻塞(blocking)、非阻塞(non-blocking):能够简单理解为须要作一件事能不能当即获得返回应答,若是不能当即得到返回,须要等待,

那就阻塞了(进程或线程就阻塞在那了,不能作其它事情),不然就能够理解为非阻塞(在等待的过程当中能够作其它事情)。

同步(synchronous)、异步(asynchronous): 你老是作完一件再去作另外一件,无论是否须要时间等待,这就是同步(就是在发出一个功能

调用时,在没有获得结果以前,该调用就不返回,即此时不能作下一件事情);异步则反之,你能够同时作几件事,并不是必定须要一件事作

完再作另外一件事(当一个异步过程调用发出后,调用者不能马上获得结果,此时能够接着作其它事情)。同步简单理解成一问一答同步进行,

异步能够简单理解为没必要等一个问题有了答案再去问另外一个问题,尽管问,有答了再通知你。

阻塞和同步:

有人会把阻塞调用和同步调用等同起来,实际上他是不一样的。对于同步调用来讲,不少时候当前线程仍是激活的,只是从逻辑上当前函数

没有返回而已。 例如,咱们在socket中调用recv函数,若是缓冲区中没有数据,这个函数就会一直等待,直到有数据才返回。而此时,当

前线程还会继续处理各类各样的消息。

IO模型

针对网络IO的操做,能够分红两个阶段,准备阶段和操做阶段。

1,准备阶段是判断是否可以操做(即等待数据是否可用),在内核进程完成的;

2,操做阶段则执行实际的IO调用,数据从内核缓冲区拷贝到用户进程缓冲区。

好比对于一个read操做发生时,它会经历下面两个阶段:

A, 等待数据准备,数据是否拷贝到内核缓冲区;

B, 将数据从内核拷贝到用户进程空间

上面两点比较重要,注意理解。

《Unix网络编程卷1:套接字联网API》(即UNP)中第六章对unix 系统将IO模型分为五类:阻塞IO,非阻塞IO,IO复用,信号驱动,异步IO。

一、阻塞IO:在准备阶段即同步阻塞,应用进程调用I/O操做时阻塞,只有等待要操做的数据准备好,并复制到应用进程的缓冲区中才返回;

二、非阻塞IO:当应用进程要调用的I/O操做会致使该进程进入阻塞状态时,该I/O调用返回一个错误,通常状况下,应用进程须要利用轮询的方式

      来检测某个操做是否就绪。数据就绪后,实际的I/O操做会等待数据复制到应用进程的缓冲区中之后才返回;

三、IO复用:多路IO共用一个同步阻塞接口,任意IO可操做均可激活IO操做,这是对阻塞IO的改进(主要是select和poll、epoll,关键是能实现同时对

     多个IO端口进行监听)。此时阻塞发生在select/poll的系统调用上,而不是阻塞在实际的I/O系统调用上。IO多路复用的高级之处在于:它能同时等

     待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select等函数就能够返回。

四、信号驱动IO:注册一个IO信号事件,在数据可操做时经过SIGIO信号通知线程,这应该算是一种异步机制;

以上四种模型在第一阶段即判断是否可操做阶段各不相同,但一旦数据可操做,则切换到同步阻塞模式下执行IO操做,因此都算是同步IO。

五、异步IO: 应用进程通知内核开始一个异步I/O操做,并让内核在整个操做(包含将数据从内核复制到应该进程的缓冲区)完成后通知应用进程。

根据上面所说的IO操做的两个阶段,能够把上面的I/O模型进行以下归类:

a,阻塞IO:在两个阶段上面都是阻塞的;

b,非阻塞IO:在第1阶段,程序不断的轮询直到数据准备好,第2阶段仍是阻塞的;

c,IO复用:在第1阶段,当一个或者多个IO准备就绪时,通知程序,第2阶段仍是阻塞的,在第1阶段仍是轮询实现的,只是全部的IO都集中在一个地方,这个地方进行轮询;

d,信号IO:当数据准备完毕的时候,信号通知程序数据准备完毕,第2阶段阻塞;

e,异步IO:1,2都不阻塞,异步IO模型 好比 windows之上的iocp,linux AIO,详情点这里;

结果以下图(图来自UNP)。

阻塞式I/O模型、非阻塞式I/O模型、I/O复用模型,这三种模型的区别在于第一阶段(阻塞式I/O阻塞在I/O操做上,非阻塞式I/O轮询,

I/O复用阻塞在select/poll/epoll上),第二阶段都是同样的,即这里的阻塞不阻塞体如今第一阶段,从这方面来讲I/O复用类型也

能够归类到阻塞式I/O,它与阻塞式I/O的区别在于阻塞的系统调用不一样。而异步I/O的两个阶段都不会阻塞进程。

其中POSIX将IO只分红了同步IO、异步IO两种模型。

同步I/O操做:实际的I/O操做将致使请求进程阻塞,直到I/O操做完成。

异步I/O操做:实际的I/O操做不致使请求进程阻塞。

由此,前面分类中:阻塞式I/O,非阻塞式I/O,I/O复用,信号驱动I/O模型都属于同步I/O,由于第二阶段的数据复制都是阻塞的。

而只有前面定义的异步I/O模型与这里的异步I/O操做

总结

同步或者异步I/O主要是指访问数据的机制(即实际I/O操做的完成方式),同步通常指主动请求并等待I/O操做完毕的方式,I/O操做

未完成前,会致使应用进程挂起;而异步是指用户进程触发IO操做之后便开始作本身的事情,而当IO操做已经完成的时候会获得IO

完成的通知(异步的特色就是通知),这可使进程在数据读写时也不阻塞。

阻塞或者非阻塞I/O主要是指I/O操做第一阶段的完成方式(进程访问的数据若是还没有就绪),即数据还未准备好的时候,应用进程

的表现,若是这里进程挂起,则为阻塞I/O,不然为非阻塞I/O。说白了就是阻塞和非阻塞是针对于进程在访问数据的时候,根据IO

操做的就绪状态来采起的不一样方式,说白了是一种读取或者写入操做函数的实现方式,阻塞方式下读取或者写入函数将一直等待,

而非阻塞方式下,读取或者写入函数会当即返回一个状态值。

 

 

 

漫谈五种IO模型(主讲IO多路复用)

1. I/O多路复用
1.1 它的造成缘由

若是一个I/O流进来,咱们就开启一个进程处理这个I/O流。那么假设如今有一百万个I/O流进来,那咱们就须要开启一百万个进程一一对应处理这些I/O流(——这就是传统意义下的多进程并发处理)。思考一下,一百万个进程,你的CPU占有率会多高,这个实现方式及其的不合理。因此人们提出了I/O多路复用这个模型,一个线程,经过记录I/O流的状态来同时管理多个I/O,能够提升服务器的吞吐能力

1.2 经过它的英文单词来理解一下I/O多路复用

I/O multiplexing 也就是咱们所说的I/O多路复用,可是这个翻译真的很不生动,因此我更喜欢将它拆开,变成 I/O multi plexing
multi意味着多,而plex意味着丛(丛:汇集,许多事物凑在一块儿。),那么字面上来看I/O multiplexing 就是将多个I/O凑在一块儿。就像下面这张图的前半部分同样,中间的那条线就是咱们的单个线程,它经过记录传入的每个I/O流的状态来同时管理多个IO。

multiplexing

1.3 I/O多路复用的实现

I/O多路复用模型

咱们来分析一下上面这张图

  1. 当进程调用select,进程就会被阻塞
  2. 此时内核会监视全部select负责的的socket,当socket的数据准备好后,就当即返回。
  3. 进程再调用read操做,数据就会从内核拷贝到进程。

其实多路复用的实现有多种方式:select、poll、epoll

1.3.1 select实现方式

先理解一下select这个函数的形参都是什么

int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);

  • nfds:指定待测试的描述子个数
  • readfds,writefds,exceptfds:指定了咱们让内核测试读、写和异常条件的描述字
  • fd_set:为一个存放文件描述符的信息的结构体,能够经过下面的宏进行设置。

void FD_ZERO(fd_set *fdset);
//清空集合
void FD_SET(int fd, fd_set *fdset);
//将一个给定的文件描述符加入集合之中
void FD_CLR(int fd, fd_set *fdset);
//将一个给定的文件描述符从集合中删除
int FD_ISSET(int fd, fd_set *fdset);
// 检查集合中指定的文件描述符是否能够读写

  • timeout:内核等待指定的描述字中就绪的时间长度
  • 返回值:失败-1 超时0 成功>0
#define FILE "/dev/input/mouse0"
int main(void)
{
 int fd = -1;
 int sele_ret = -1;
 fd_set Fd_set;
 struct timeval time = {0};
 char buf[10] = {0};

 //打开设备文件
 fd = open(FILE, O_RDONLY);
 if (-1 == fd)
{
      perror("open error");
      exit(-1);
}

//构建多路复用IO
FD_ZERO(&Fd_set); //清除所有fd
FD_SET(0, &Fd_set); //添加标准输入
FD_SET(fd, &Fd_set); //添加鼠标
time.tv_sec = 10; //设置阻塞超时时间为10秒钟
time.tv_usec = 0; 

sele_ret = select(fd+1, &Fd_set, NULL, NULL, &time);
if (0 > sele_ret)
{
    perror("select error");
    exit(-1);
}
else if (0 == sele_ret)
{
    printf("无数据输入,等待超时.\n");
}
else
{
    if (FD_ISSET(0, &Fd_set)) //监听获得获得的结果如果键盘,则让去读取键盘的数据
{
    memset(buf, 0, sizeof(buf));
    read(0, buf, sizeof(buf)/2);
    printf("读取键盘的内容是: %s.\n", buf);
}

if (FD_ISSET(fd, &Fd_set)) //监听获得获得的结果如果鼠标,则去读取鼠标的数据
{
    memset(buf, 0, sizeof(buf));
    read(fd, buf, sizeof(buf)/2);
    printf("读取鼠标的内容是: %s.\n", buf);
}
}

//关闭鼠标设备文件
    close(fd);
    return 0;
}
1.3.2 poll实现方式

先理解一下poll这个函数的形参是什么

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

  • pollfd:又是一个结构体
struct pollfd {
int fd; //文件描述符
short events; //请求的事件(请求哪一种操做)
short revents; //返回的事件
};

后两个参数都与select的第一和最后一个参数概念同样,就不细讲了

  • 返回值:失败-1 超时0 成功>0
#define FILE "/dev/input/mouse0"

int main(void)
{
    int fd = -1;
    int poll_ret = 0;
    struct pollfd poll_fd[2] = {0};
    char buf[100] = {0};

    //打开设备文件
    fd = open(FILE, O_RDONLY);
    if (-1 == fd)
    {
        perror("open error");
        exit(-1);
    }

    //构建多路复用IO
    poll_fd[0].fd = 0; //键盘
    poll_fd[0].events = POLLIN; //定义请求的事件为读数据
    poll_fd[1].fd = fd; //鼠标
    poll_fd[1].events = POLLIN; //定义请求的事件为读数据
    int time = 10000; //定义超时时间为10秒钟

    poll_ret = poll(poll_fd, fd+1, time);
    if (0 > poll_ret)
    {
        perror("poll error");
        exit(-1);
    }
     else if (0 == poll_ret)
    {
        printf("阻塞超时.\n");
    }
    else
    {
        if (poll_fd[0].revents == poll_fd[0].events)
 //监听获得获得的结果如果键盘,则让去读取键盘的数据
        {
            memset(buf, 0, sizeof(buf));
            read(0, buf, sizeof(buf)/2);
            printf("读取键盘的内容是: %s.\n", buf);
        }

        if (poll_fd[1].revents == poll_fd[1].events) 
//监听获得获得的结果如果鼠标,则去读取鼠标的数据
        {
              memset(buf, 0, sizeof(buf));
              read(fd, buf, sizeof(buf)/2);
              printf("读取鼠标的内容是: %s.\n", buf);
        }
  }
//关闭文件
close(fd);
return 0;
}
1.3.3 epoll实现方式(太过复杂,为了避免增长篇幅不放进来了)

epoll操做过程当中会用到的重要函数

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
  • int epoll_create(int size):建立一个epoll的句柄,size表示监听数目的大小。建立完句柄它会自动占用一个fd值,使用完epoll必定要记得close,否则fd会被消耗完。
  • int epoll_ctl:这是epoll的事件注册函数,和select不一样的是select在监听的时候会告诉内核监听什么样的事件,而epoll必须在epoll_ctl先注册要监听的事件类型。
    它的第一个参数返回epoll_creat的执行结果
    第二个参数表示动做,用下面几个宏表示

EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;

第三参数为监听的fd,第四个参数是告诉内核要监听什么事

  • int epoll_wait:等待事件的发生,相似于select的调用

2. select
2.1 select函数的调用过程

a. 从用户空间将fd_set拷贝到内核空间
b. 注册回调函数
c. 调用其对应的poll方法
d. poll方法会返回一个描述读写是否就绪的mask掩码,根据这个mask掩码给fd_set赋值。
e. 若是遍历完全部的fd都没有返回一个可读写的mask掩码,就会让select的进程进入休眠模式,直到发现可读写的资源后,从新唤醒等待队列上休眠的进程。若是在规定时间内都没有唤醒休眠进程,那么进程会被唤醒从新得到CPU,再去遍历一次fd。
f. 将fd_set从内核空间拷贝到用户空间

2.2 select函数优缺点

缺点:两次拷贝耗时、轮询全部fd耗时,支持的文件描述符过小
优势:跨平台支持


3. poll
3.1 poll函数的调用过程(与select彻底一致)
3.2 poll函数优缺点

优势:链接数(也就是文件描述符)没有限制(链表存储)
缺点:大量拷贝,水平触发(当报告了fd没有被处理,会重复报告,很耗性能)


4. epoll
4.1 epoll的ET与LT模式

LT延迟处理,当检测到描述符事件通知应用程序,应用程序不当即处理该事件。那么下次会再次通知应用程序此事件。
ET当即处理,当检测到描述符事件通知应用程序,应用程序会当即处理。

ET模式减小了epoll被重复触发的次数,效率比LT高。咱们在使用ET的时候,必须采用非阻塞套接口,避免某文件句柄在阻塞读或阻塞写的时候将其余文件描述符的任务饿死

4.2 epoll的函数调用流程

a. 当调用epoll_wait函数的时候,系统会建立一个epoll对象,每一个对象有一个evenpoll类型的结构体与之对应,结构体成员结构以下。

rbn,表明将要经过epoll_ctl向epll对象中添加的事件。这些事情都是挂载在红黑树中。
rdlist,里面存放的是将要发生的事件

b. 文件的fd状态发生改变,就会触发fd上的回调函数
c. 回调函数将相应的fd加入到rdlist,致使rdlist不空,进程被唤醒,epoll_wait继续执行。
d. 有一个事件转移函数——ep_events_transfer,它会将rdlist的数据拷贝到txlist上,并将rdlist的数据清空。
e. ep_send_events函数,它扫描txlist的每一个数据,调用关联fd对应的poll方法去取fd中较新的事件,将取得的事件和对应的fd发送到用户空间。若是fd是LT模式的话,会被txlist的该数据从新放回rdlist,等待下一次继续触发调用。

4.3 epoll的优势
  1. 没有最大并发链接的限制
  2. 只有活跃可用的fd才会调用callback函数
  3. 内存拷贝是利用mmap()文件映射内存的方式加速与内核空间的消息传递,减小复制开销。(内核与用户空间共享一块内存)

只有存在大量的空闲链接和不活跃的链接的时候,使用epoll的效率才会比select/poll高

相关文章
相关标签/搜索