当前位置: 首页 > news >正文

IO 多路复用:select、poll、epoll

前提

我们创建若干 socketfd,绑定并监听,现在我们拥有若干 socketfd
对于 IO 事件,有如下2种触发方式:

  • 水平触发(在目标状态下持续触发)
  • 边缘触发(首次切换变化到目标状态时触发1次)

select

用法

流程

  • 对每个 socketfd 进行 accept() 得到 fd,保存到 fd 数组中,记录最大 fd 序号 max_fd
  • 循环
    • 设置监听的 fd_set 集合(bitmap)
    • fd_set 传入 select() 函数,并限制监听 fd 范围(0-max_fd)
      • 内核态会拷贝一份 fd_set,并进行监听,发生事件时,覆盖用户态的 fd_set
      • select 会阻塞当前进程,直到返回(发生事件或超时)
    • 等待select() 返回
      • 内核置为 fd_set
    • 遍历 fd,检测 fd_set 中是否置位,并进行处理

函数说明

// fd_set 全部置零
int FD_ZERO(fd_set *fdset);// 清零某一位
int FD_CLR(int fd, fd_set *fdset);// 置1某一位
int FD_SET(int fd, fd_set *fd_set);// 判断是否置1
int FD_ISSET(int fd, fd_set *fdset);int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout)
// 参数:       最大fd   监听读事件的fd集合 监听写事件的fd集合   监听异常的fd集合        超时时间    
// 超时时间:NULL-无限等待, 0-立刻返回,n-等待时间。等待可以被中断信号打断
// select 返回准备好的 fd 数量,超时返回0,出错返回-1(等待时被信号打断也会返回-1)

缺点

  • fd 上限:最大监听 1024 个 fd
    • 可调整但是受限于操作系统默认编译的值
  • 重新赋值:传入的 fd_set 会被内核修改,每次需要重新赋值
  • 拷贝开销:fd_set 用户态拷贝到内核态,有开销
  • 遍历 fd:select 返回后,需要遍历所有 fd 来判断真正可用的 fd

poll

用法

流程

  • 对每个 socketfd 进行 accept() 得到 fd,为每个 fd 创建 pollfd 结构体,存入数组
  • 循环
    • pollfd[] 数组传入 poll() 函数
      • 从用户态拷贝到内核态
      • poll() 阻塞当前进程,直到返回(发生事件或超时)
    • 等待 poll() 返回
      • 内核置位 revents
    • 遍历 fd,检测可用的 fd(清零 revents),并进行处理

函数说明

int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
// 参数           poll 数组            数组长度        超时时间struct pollfd {int fd;short events; // 监听的事件(事件掩码)short revents; // 实际发生的事件,只关注 events 设置的事件
}
// POLLIN      有数据可读
// POLLRDNORM    有普通数据可读
// POLLRDBAND    有优先数据可读
// POLLPRI      紧迫数据可读// POLLOUT      写数据,不会导致阻塞
// POLLWRNORM    写普通数据,不会导致阻塞
// POLLWRBAND    写优先数据,不会导致阻塞
// POLLMSGSIGPOLL   消息可用// POLLER    指定的 fd 发生错误
// POLLHUP   指定的 fd 挂起事件
// POLLNVAL  指定的 fd 非法

poll 对比 select

  • 没有 fd 上限
  • pollfd 可重用

缺点

  • 拷贝开销:pollfds[] 用户态拷贝到内核态,有开销
  • 遍历fd:poll() 返回后,需要遍历所有 fd 来判断真正可用的 fd

epoll

用法

流程

  • epoll_create()创建 epoll 对象(eventpoll),得到描述符(epfd)
  • 对每个 socketfd 进行 accept() 得到 fd,将 fd 保存到 epoll_event
  • epoll_ctl()添加 epoll_eventepoll 对象中
  • 循环
    • epoll_wait() 阻塞并等待事件发生
      • 实际调用 epoll_ctl 拷贝一次,后续复用,不需要每次都拷贝
    • 等待 epoll_wait() 返回可用的 fd
    • 遍历可用的 fd,并进行处理

函数说明

// 创建一个 epoll 对象,返回该对象的描述符
// 注意要使用 close 关闭该描述符
int epoll_create(int size);// 操作控制 epoll 对象
// 主要涉及 epoll 红黑树上节点的一些操作,比如添加节点,删除节点,修改节点事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// op:EPOLL_CTL_ADD,EPOLL_CTL_DEL,EPOLL_CTL_MOD// 阻塞一段时间并等待事件发生,返回事件集合
// 也就是获取内核的事件通知。即遍历双向链表,把双向链表里的节点数据拷贝出来,拷贝完毕后就从双向链表移除
int epoll_wait(int epid, struct epoll_event *events, int maxevents, int timeout);
// epid:epoll_create 返回的 epoll 对象描述符
// events:存放就绪的事件集合,这个是传出参数
// maxevents:代表可以存放的事件个数,也就是 events 数组的大小
// timeout:阻塞等待的时间长短,以毫秒为单位,如果传入 -1 代表阻塞等待typedef union epoll_data
{void        *ptr;int          fd;uint32_t     u32;uint64_t     u64;
} epoll_data_t;struct epoll_event
{uint32_t     events;      // Epoll 事件epoll_data_t data;        // 用户数据
};
// EPOLLIN:	    监听 fd 的读事件。举例:如果客户端发送消息过来,代表服务器收到了可读事件
// EPOLLOUT:	  监听 fd 的写事件。如果 fd 对应的发数据内核缓冲区不为满,只要监听了写事件,就会触发可写事件
// EPOLLRDHUP:	监听套接字关闭或半关闭事件,Linux 内核 2.6.17 后可用
// EPOLLPRI:	  监听紧急数据可读事件

原理

说明

  • epoll 没有“置位”操作,通过“重排”来标识可用的 fd
  • fd 可用时,被移到 epoll 对象的最前面,

实现

  • rbr 是一棵红黑树,支持快速查找键值对,所有需要加入监听事件的描述符都需要添加到这棵红黑树来,rbcnt 记录红黑树节点个数
  • rdlist 是一个双向链表,当红黑树上监听的描述符发生对应的监听事件时,内核会将这个节点插入到该双向链表来,rdnum 是双向链表节点的个数,也就是发生了事件的节点个数

epoll 对比 poll

  • 几乎无拷贝开销:epoll 仅拷贝一次到内核态,后续复用
    • 没有共享内存:https://www.zhihu.com/question/39792257,epoll_wait() 会将当前的触发的事件和用户传入的数据放在双向链表中,都 copy 给用户态
  • 不需要遍历 fd

参考

  • io多路复用:https://www.bilibili.com/video/BV1qJ411w7du
  • select: https://www.cnblogs.com/alantu2018/p/8612722.html
  • poll: https://www.cnblogs.com/fah936861121/articles/6656885.html
  • epoll: https://zhuanlan.zhihu.com/p/406175793
http://www.wuyegushi.com/news/502.html

相关文章:

  • 7.27总结
  • manacher 马拉车算法 寻找最长回文子串
  • 笛卡尔树
  • 如何获取集合控件中子项元素的容器
  • 火山引擎-大模型应用防火墙
  • chorme如何设置在新标签页打开页面?
  • Gentoo解决clocksource未使用tsc问题
  • UIUCTF 2024 syscalls
  • POLIR-Laws-民法典: 第三编 合同 : 第二分编 典型合同: 17.承揽
  • 2025-07-27 模拟赛总结
  • widedeep在adult数据集上的应用
  • POLIR-Laws-民法典: 第三编 合同 : 第二分编 典型合同
  • 协议版iM蓝号检测,批量筛选iMessages数据,无痕检测是否开启iMessage服务
  • 2025年7月27日
  • 连续动作强化学习中的反事实探索:揭示AI决策背后的可能性
  • ADC模数转换器
  • 启明星辰-大模型应用防火墙
  • VulnHub 靶场--broken(十六进制转图片)
  • TIM输入捕获
  • 文件权限标记机制在知识安全共享中的应用实践
  • PID
  • POLIR-Laws-民法典: 民法典 包括 并 废止 《合同法》
  • 18
  • 字节-大模型联邦精调方案
  • 分块
  • 并查集
  • 7-27
  • CVE-2021-21311 服务器端请求伪造(SSRF)漏洞 (复现)
  • 【Rag实用分享】小白也能看懂的文档解析和分割教程
  • 【纯干货】三张图深入分析京东开源Genie的8大亮点