IO多路复用之select、poll、epoll

select()和poll()、epoll都是IO多路复用的机制。所谓多路复用就是使用一个进程监视多个文件描述符,一旦有文件描述符就绪就通知用户程序进行读写操作。

select()函数

先来看看select函数的语法格式:

通过上图可以看到,主要关注三个参数:

三个参数:readfds、writefds 和 exceptfds 分别表示了不同类型的文件描述符集合,select()在监视文件描述符的时候,如果发现某个文件描述符准备就绪,就将其放入对应的集合中;然后返回准备好的数量;最后用户程序可以从对应的文件描述符集合中遍历就绪的文件描述符。

select()函数的优点:

  • 良好的跨平台性,在大部分平台被支持;

select()函数的缺点:

  • 监视的文件描述符数量由限制(1024)。
  • 监视使用轮询的方式,效率较低。
  • 需要三个set存放不同类型就绪文件描述符,空间消耗。

poll()函数

poll与select类似,先来看函数的语法要点:

通过上图可以看到,poll()函数一共只有三个参数。在poll()函数中不再将读、写、异常的文件描述符分别放在不同的集合中,而是使用一个pollfd的结构体表示每一个被监控的文件描述符。

在上图中可以看到struct pollfd的结构体中有:被监视的文件描述符、该描述符的监视事件(读、写等)、是否已经发生(就绪)。所以,一个结构体对应了一个被监视文件

在poll()返回之后,用户程序遍历该结构体数组,找到就绪的文件描述符进行处理即可。

poll()函数的优点:

  • 没有最大监视fd限制;

poll()函数的缺点:

  • 如果某文件描述符被监视到就绪后没有被处理,则会再次被监视。
  • 随着监视的fd的数量增大,效率降低。

对比可以看到:select和poll都需要在返回之后,遍历所有文件描述符来找到就绪的文件描述符,所以在监视的文件描述符的数量很大的时候,效率会很低。

epool()函数

epoll()函数时对select和poll的加强。epoll使用一个文件描述符管理多个文件描述符。

epoll主要使用到三个函数:

  1. int epoll_create():表示告知内核监听的文件描述符的数目(只是一个初始值,可扩),返回一个epfd;
  2. int epoll_ctl():将一个被监视的文件描述符及被监听事件注册到上一步的epfd中;当该文件描述符就绪的时候就会使用回调机制激活epfd;
  3. int epoll_wait():等到被监视描述符就绪的通知,返回的时就绪的fd个数。

所以总体流程可以概括为:首先使用epoll_create创建一个dpfd,初始化其监视的fd个数;然后使用epoll_ctl将需要监视的fd及其事件注册到epfd中,当该fd就绪时就会激活epfd,epfd便会通知用户程序该fd准备就绪;用户程序调用epoll_wait等待通知,收到通知即可进行io操作。

LT与ET模式

在epoll中支持两种模式:LT(level trigger)水平触发、ET(edge trigger)边缘触发。

  • LT:模式模式。当内核通知用户程序一个文件描述符就绪,如果用户程序不做处理,下次内会在通知;
  • ET:当内核通知用户程序一个文件描述符就绪,如果用户程序不做处理,下次就不会通知了;减少了重复触发的次数。必须使用非阻塞套接口。

epoll()函数的优点:

  • 监视的文件描述符数量不受限制。
  • IO效率不会随着监视的描述符数量增加而降低。因为epoll不采用轮询的方式获取就绪fd,而是使用fd就绪时调用回调函数实现。

总结

select() poll() epoll()
最大监视(连接)数 32位默认1024,可修改 没有限制(因为使用链表存储) 10w+
监视的fd增加对效率的影响 轮询遍历,效率降低 轮询遍历,效率降低 无影响(使用回调机制)
消息传递方式 从内核拷贝到用户地址空间 从内核拷贝到用户地址空间 内核与用户共享内存空间
posted on 2021-09-25 15:31  wuraoo  阅读(124)  评论(0编辑  收藏  举报