select,poll和epoll IO复用函数的比较

三种IO复用函数的区别

  事件/fd集合:

    select:用户分别传入注册的可读,可写和异常事件对应的fd集合(内置数组,封装成一个结构体),内核对fd集合进行在线修改后返回给用户,通知用户fd上实际发生了哪种事件;fd集合没有将fd和事件绑定,需要提供三个fd集合分别传入和输出可读,可写和异常事件

    poll:用户传入一个可变长数组,数组元素为结构体(含文件描述符fd,注册的事件events,实际发生的事件revents);内核对revents进行修改反馈给用户,通知用户fd上实际发生了哪些事件;将fd和事件绑定到一起,任何事件都被统一处理;

    epoll:内核维护一个事件表,用户传入一棵红黑树(其元素为一个结构体,内含事件类型和fd),通过红黑树向事件表中添加,删除和修改事件,内核对事件表进行修改,然后将从事件表中取出实际发生的事件复制到链表中(其元素为一个和结构体,内含事件类型和fd);

 

  监听的fd的数量:

    select:使用内置数组标识fd集合,由于内置数组的限制,有最大值限制;

    poll:使用可变长数组(vector?有对这里有深入研究的欢迎评论留言指点迷津)表示fd和事件组成的结构体的集合,监听数量可达到65535;

    epoll:使用红黑树表示fd和事件组成的结构体的集合,监听数量可达到65535;

 

  是否需要重复初始化fd集合:

    select:内核对fd集合进行在线修改,应用程序下次调用都要重置fd集合;

    poll:events 字段设置待检测事件, revents 字段去检测就绪的事件,内核修改结构体的revents成员,而events成员保持不变,应用程序下次调用时不需要重置events成员

    epoll:内核修改事件表进而修改链表(events),而红黑树保持不变,应用程序下次调用时不许重置红黑树(event);

 

  对实际发生的事件对应的fd集合的处理方式:

    select和poll采用轮询的机制,每次调用都要扫描整个注册的fd集合,检测就绪的fd,检测就绪事件的时间复杂度为O(n);epoll采用的是回调的方式,内核检测到就绪的fd,将触发回调函数,回调函数将fd上对应的事件插入双向链表,最后再将其内容拷贝到用户空间,其检测就绪事件的时间复杂度为O(1)。

    select:返回整个用户注册的事件的集合,用户程序需要要通过遍历内置数组来检测就绪事件(轮询方式);

    poll:返回整个用户注册的事件的集合,用户程序需要通过遍历revevts来检测就绪事件(轮询方式);

    epoll:返回实际发生的事件(含事件类型和fd),用户程序通过回调方式检测就绪事件;

 

  工作模式:

    select:fd为水平触发模式;

    poll:fd为水平触发模式;

    epoll:fd为水平触发模式或者边缘触发模式。

 

  使用场景:

     当活动连接比较多时,epoll的效率不一定比poll和select高,因为epoll需要多次触发回调函数;epoll适用于连接数多而活动连接少的情况。

 

三种IO复用函数的相同之处

  均可同时监听多个fd,并且等待指定的超时时间,直到一个或多个fd上有事件发生,返回就绪的fd的数量。                                                         

 

总结

  select: fd受限,内置数组的形式使得select的最大fd数量受限于FD_SIZE;

        重复初始化,调用select之前需重新初始化fd集合,将fd集合从用户态拷贝到内核态;调用select之后,将fd集合从内核态拷贝到用户态;

      轮询排查,将内核修改过的fd集合返回给用户;

  poll:解决的fd受限(可变长数组)和重复初始化(数组元素为结构体,结构体元素为fd,注册的事件,实际发生的事件,每增加一个fd,就向数组中加入一个结构体,结构体只需要拷贝一次到内核态)的问题;未解决轮询排查的问题;

  epoll:解决了上述三个问题(红黑树)(内核检测到实际发生的事件,将其添加到链表中,不改变红黑树的结构)(将实际发生的事件返回给用户)。
                       

posted @ 2021-01-31 19:42  封狼居胥!  阅读(74)  评论(0编辑  收藏  举报