【Linux高级IO】多路转接(poll epoll)
目录
1. poll
2. epoll
2.1 epoll_ctl
2.2 epoll_wait
2.3 epoll原理
2.4 epoll的工作模式
2.5 epoll的惊群效应
使用建议
总结

1. poll
poll也是实现 I/O 多路复用的系统调用,可以解决select等待fd上限的问题,将输入输出参数分离,不需要每次对参数重置;
原型:
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);struct pollfd {int fd; /* file descriptor */short events; /* requested events */short revents; /* returned events */
};
参数:
- fds是一个poll函数监听的结构列表, 每一个元素中,包含了三部分内容:文件描述符,监听的事件集合, 返回的事件集合;
- nfds表示fds数组的长度.(由此可以看出,fds其实就是一个数组)
- timeout:指定 poll 函数的超时时间,单位为毫秒。其取值有以下几种情况:
timeout > 0:poll 函数会阻塞,直到指定的文件描述符上有事件发生或者超时时间到达。
timeout == 0:poll 函数会立即返回,不会阻塞,用于非阻塞检查文件描述符的状态。
timeout == -1:poll 函数会一直阻塞,直到指定的文件描述符上有事件发生。
调用时:调用poll时,用户->内核,OS帮用户关心fd,上面的events事件;
返回时:poll返回时,内核->用户,用户关心的fd上面,有哪些的revents事件准备就绪;
返回结果:
- 大于 0:表示发生事件的文件描述符的数量。
- 等于 0:表示超时,即在指定的时间内没有文件描述符发生事件。
- 等于 -1:表示发生错误,同时会设置 errno 来指示具体的错误类型。
events和revents的取值:

示例:
#include <stdio.h>
#include <poll.h>
#include <unistd.h>int main() {struct pollfd fds[1];fds[0].fd = 0; // 监控标准输入fds[0].events = POLLIN; // 监控可读事件int ret = poll(fds, 1, -1); // 一直阻塞,直到有事件发生if (ret == -1) {perror("poll");return 1;}if (fds[0].revents & POLLIN) {char buf[1024];ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));if (n > 0) {buf[n] = '\0';printf("Read from stdin: %s", buf);}}return 0;
}
pollserver示例:poll_server
优点:
- 可以等待多个fd,效率高
- 输入,输出参数分离(events和revents),不用频繁对poll参数进行重置
- poll关心的fd没上限(内部动态申请空间)
缺点:
- 用户到内核要进行数据拷贝(struct pollfd 结构体数组传递给内核空间,这涉及到用户空间和内核空间之间的数据拷贝操作)
- 应用层使用时,仍然需要遍历,在内核层面,要遍历检测,关心的fd是否有对应的事件就绪;在应用层,当 poll 函数返回后,需要遍历 struct pollfd 结构体数组,检查每个 fd 的 revents 成员,以确定哪些文件描述符上发生了事件。在内核层监控依然是线性遍历;
2. epoll
epoll在man 手册中是这样描述的:是为处理大批量句柄而作了改进的poll;并且解决了poll遗留下来的问题;
解决 poll 遍历开销大的问题:epoll 的改进:epoll 采用红黑树来管理需要监控的文件描述符。
epoll相对于select、poll、提供了更多的操作接口:
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);
2.1 epoll_ctl
用于控制 epoll 实例,可以添加、修改或删除要监控的文件描述符及其事件;
接口原型:
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);typedef union epoll_data {void *ptr;int fd;uint32_t u32;uint64_t u64;
} epoll_data_t;struct epoll_event {uint32_t events; /* Epoll events */epoll_data_t data; /* User data variable */
};
- events:表示要监听的事件类型,如 EPOLLIN(可读)、EPOLLOUT(可写)等。
- data:是一个联合体,通常使用 fd 成员来存储关联的文件描述符,也可以用 ptr 指向自定义的数据。
参数:
- epfd:epoll_create 返回的 epoll 实例的文件描述符。
- op:操作类型,有 EPOLL_CTL_ADD(添加)、EPOLL_CTL_MOD(修改)、EPOLL_CTL_DEL(删除)。
- fd:要监控的文件描述符。
- event:指向 epoll_event 结构体的指针,指定要监控的事件类型和关联的数据。
返回值:成功返回 0,失败返回 -1
2.2 epoll_wait
等待 epoll 实例中注册的文件描述符上的事件发生;
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
参数:
- epfd:epoll 实例的文件描述符。
- events:用于存储发生事件的 epoll_event 结构体数组。
- maxevents:events 数组的最大元素个数。
- timeout:超时时间,单位为毫秒。-1 表示阻塞等待,0 表示立即返回。
返回值:成功返回发生事件的文件描述符数量,超时返回 0,失败返回 -1
events:
- EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭);
- EPOLLOUT : 表示对应的文件描述符可以写;
- EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来);
- EPOLLERR : 表示对应的文件描述符发生错误;
- EPOLLHUP : 表示对应的文件描述符被挂断;
- EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.
- EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要 再次把这个socket加入到EPOLL队列里
简单使用示例:
#include <stdio.h>
#include <sys/epoll.h>
#include <unistd.h>#define MAX_EVENTS 10int main() {// 创建 epoll 实例int epoll_fd = epoll_create1(0);if (epoll_fd == -1) {perror("epoll_create1");return 1;}// 定义要监控的事件struct epoll_event ev, events[MAX_EVENTS];ev.events = EPOLLIN;ev.data.fd = STDIN_FILENO;// 添加标准输入到 epoll 实例中进行监控if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) == -1) {perror("epoll_ctl: STDIN_FILENO");close(epoll_fd);return 1;}while (1) {// 等待事件发生int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);if (nfds == -1) {perror("epoll_wait");close(epoll_fd);return 1;}// 处理事件for (int i = 0; i < nfds; i++) {if (events[i].data.fd == STDIN_FILENO) {char buf[1024];ssize_t n = read(STDIN_FILENO, buf, sizeof(buf));if (n > 0) {buf[n] = '\0';printf("Read from stdin: %s", buf);}}}}// 关闭 epoll 实例close(epoll_fd);return 0;
}
epollserver的示例:epoll_server
2.3 epoll原理
前边提到epoll是采用红黑树来解决遍历效率低的问题, 而epoll_create的功能其实就是构建红黑树等一系列操作;具体流程:申请struct file,创建epoll模型:构建红黑树,构建就绪队列,设置回调机制;把struct file设置到文件描述符表,返回fd;
epoll_ctl的选项操作,其实就是对epoll模型中的红黑树进行增删改;设置进去的fd和event就会添加到红黑树中(要关心的fd上的哪些事件)
epoll_wait获取准备就绪的fd ,可以直接到就绪队列获取;(内核告诉用户哪些fd的哪些事件就绪);结合epoll_wait参数,"数组的首元素地址""数组大小",epoll会从就绪队列获取节点,会按照顺序严格的放在 events *中(select和poll中的数组中间会出现空缺的情况),并用返回值表明就绪事件个数;
- epoll_wait检测是否有事件就绪,时间复杂度O(1);检查就绪链表是否为空即可判断;
- 获取所有就绪事件,事件复杂度O(N)——这个是必然,无法优化;遍历就绪链表,将N个节点添加到 event* 数组中;
如果 events * 数组满了怎么办?满了就返回;

struct eventpoll{ .... /*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/ struct rb_root rbr; /*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/ struct list_head rdlist; ....
};
网卡从网络中读取到数据后,通过硬件中断将数据读到网卡驱动,网卡驱动允许注册相应的回调方法,红黑树中每添加一个节点,就会为该节点注册一个相应的回调方法;这个回调方法主要完成两个工作:
- 在内核中确认发生的事件(是否关心)
- 向ready_queue中形成新节点,表明哪个fd上的哪些事件已经发生
这整个过程都是依靠单线程/进程完成的,那如果是多线程/进程呢?
epoll模型创建返回值是一个文件描述符,只需对文件描述符表的管理,就可以做到对epoll模型进行管理;
epoll的优点:
- 接口使用方便:虽然拆分成了三个函数, 但是反而使用起来更方便高效.
- 不需要每次循环都设置关注的文件描述符,也做到了输入输出参数分离开
- 数据拷贝轻量:只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中,这个操作并不频繁(而select/poll都是每次循环都要进行拷贝)
- 事件回调机制:避免使用遍历,而是使用回调函数的方式,将就绪的文件描述符结构加入到就绪队列中,epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度O(1). 即使文件描述 符数目很多, 效率也不会受到影响.
- 没有数量限制: 文件描述符数目无上限.
2.4 epoll的工作模式
epoll有两种工作模式:水平触发、边缘触发;它是epoll提供给用户的一种通知事件就绪的策略;
epoll默认是LT模式;如果数据就绪,但上层并不做处理,epoll就会一直通知——LT模式;
ET策略:底层有数据,只通知一次,就不再通知,直到下次数据发生变化的时候(底层收到新的数据),才通知;
LT模式和ET模式相比,ET模式更高效;因为在通知策略中,没有无效的通知,全部都是有效的;
ET模式只会通知一次,倒逼上层(程序员),要取数据就要把本轮的数据取完;
在TCP服务中,这样的话,底层也会给发送方告知窗口大小,会通告一个更大的窗口;发送方的滑动窗口大小就会变得更大,从概率上,也可以提高双方的通信效率;
ET模式下要求数据一次读完,那如何判断数据是否一次读完了?循环读取,直到缓冲区没有数据,就会阻塞;
但是,epoll一般是单线程/进程,一旦阻塞就没法继续后续的操作了,所以ET模式下必须以非阻塞状态进行IO操作;
epoll的高性能,是有一定的特定场景的。如果场景选择的不适宜,epoll的性能可能适得其反;
对于多连接,且多连接中只有一部分连接比较活跃时,比较适合使用epoll;
2.5 epoll的惊群效应
在多线程或者多进程环境下,有些人为了提高程序的稳定性,往往会让多个线程或者多个进程同时在epoll_wait监听的socket描述符。当一个新的链接请求进来时,操作系统不知道选派那个线程或者进程处理此事件,则干脆将其中几个线程或者进程都给唤醒;而实际上只有其中一个进程或者线程能够成功处理accept事件,其他线程都将失败,这种现象称为惊群效应,结果是肯定的,惊群效应肯定会带来资源的消耗和性能的影响;
解决办法:
- 不建议让多个线程同时在epoll_wait监听的socket,而是让其中一个线程epoll_wait监听的socet,当有新的链接请求进来之后,由epoll_wait的线程调用accept,建立新的连接,然后交给其他工作线程处理后续的数据读写请求;
- 每个子进程仍然管自己在监听的socket上调用epoll_wait,当有新的链接请求发生时,操作系统仍然唤醒其中部分的子进程来处理该事件,仍然只有一个子进程能够成功处理此事件,那么其他被惊醒的子进程捕获EAGAIN错误,并无视;
- 创建一个全局的pthread_mutext,在子进程进行epoll_wait前,先获取锁,在同一时刻,永远都只有一个子讲程在监听的sacket 上epoll_wait;
使用建议
发数据问题:
什么时候不能发?—— 发送缓冲区被写满的时候;
什么时候能发?—— 缓冲区不满,刚开始的时候可以直接发(大部分情况下发送缓冲区不会被写满);不能发的时候,把fd交给epoll,让epoll关心什么时候可以发(写事件就绪);
怎么知道发送缓冲区写满?
一直循环的去写;直到发送条件不具备,再交给epoll处理;
对于多路转接而言:
- 一般对于任何fd,EPOLLIN事件,常设关心;
- 对于写事件,按需关心即可
总结
以上便是本文的全部内容,希望对你有所帮助,感谢阅读!
相关文章:
【Linux高级IO】多路转接(poll epoll)
目录 1. poll 2. epoll 2.1 epoll_ctl 2.2 epoll_wait 2.3 epoll原理 2.4 epoll的工作模式 2.5 epoll的惊群效应 使用建议 总结 1. poll poll也是实现 I/O 多路复用的系统调用,可以解决select等待fd上限的问题,将输入输出参数分离,不需要…...
Linux上用C++和GCC开发程序实现两个不同PostgreSQL实例下单个数据库中多个Schema稳定高效的数据迁移到其它PostgreSQL实例
设计一个在Linux上运行的GCC C程序,同时连接三个不同的PostgreSQL实例,其中两个实例中分别有两个数据库中多个Schema的表结构分别与第三实例中两个数据库中多个Schema个结构完全相同,同时复制两个实例中两个数据库中多个Schema里的所有表的数…...
Linux下的网络通信编程
在不同主机之间,进行进程间的通信。 1解决主机之间硬件的互通 2.解决主机之间软件的互通. 3.IP地址:来区分不同的主机(软件地址) 4.MAC地址:硬件地址 5.端口号:区分同一主机上的不同应用进程 网络协议…...
Windows在多网络下指定上网接口
Windows在多网络下指定上网接口 一、说明 设备情况:win11,同时连接了有线网和WLAN,有线网连接着NAS必须保持连接。需求:有些情况时,有线网无网络而WLAN有网,但系统仍走着有线导致无法上网。 二、方法 过…...
网络安全员证书
软考网络安全员证书:信息安全领域的黄金标准 随着信息技术的飞速发展,网络安全问题日益凸显,网络安全员的需求也日益增加。软考网络安全员证书作为信息安全领域的黄金标准,对于网络安全从业者来说具有重要意义。本文将详细介绍…...
CMU15445(2023fall) Project #4 - Concurrency Control踩坑历程
把树木磨成月亮最亮时的样子, 就能让它更快地滚下山坡, 有时会比骑马还快。 完整代码见: SnowLegend-star/CMU15445-2023fall: Having Conquered the Loftiest Peak, We Stand But a Step Away from Victory in This Stage. With unwavering…...
医疗AR眼镜:FPC如何赋能科技医疗的未来之眼?【新立电子】
随着科技的飞速发展,增强现实(AR)技术在医疗领域的应用逐渐成为焦点。医疗AR眼镜作为一种前沿的智能设备,正在为医疗行业带来深刻的变革。它不仅能够提升医生的工作效率,还能改善患者的就医体验,成为医疗科…...
Python从0到100(八十九):Resnet、LSTM、Shufflenet、CNN四种网络分析及对比
前言: 零基础学Python:Python从0到100最新最全教程。 想做这件事情很久了,这次我更新了自己所写过的所有博客,汇集成了Python从0到100,共一百节课,帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…...
服务器迁移记录【腾讯云-->阿里云】
准备工作 压缩/root /usr/local/nginx /data三个目录到zip,并下载到本地。 zip root.zip /root zip nginx.zip /usr/local/nginx zip data.zip /datasz root.zip sz nginx.zip sz data.zip连接mysql数据库,导出数据库结构与数据到dzs_mysql.sql 安装l…...
序列化选型:字节流抑或字符串
序列化既可以将对象转换为字节流,也可以转换为字符串,具体取决于使用的序列化方式和场景。 转换为字节流 常见工具及原理:在许多编程语言中,都有将对象序列化为字节流的机制。例如 Python 中的 pickle 模块、Java 中的对象序列化…...
面向实时性的超轻量级动态感知视觉SLAM系统
一、重构后的技术架构设计(基于ROS1 ORB-SLAM2增强) #mermaid-svg-JEJte8kZd7qlnq3E {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-JEJte8kZd7qlnq3E .error-icon{fill:#552222;}#mermaid-svg-JEJte8kZd7qlnq3E .…...
4-3自定义加载器,并添加功能
一、自定义类加载器的实现步骤 继承ClassLoader类 自定义类加载器需继承java.lang.ClassLoader,并选择性地重写以下方法: findClass(String name):核心方法,用于根据类名查找并加载类的字节码。需从自定义路径(…...
Python Scrapy爬虫面试题及参考答案
目录 简述 Scrapy 框架的基本工作流程,并说明各组件的作用 Scrapy 中的 Spider、CrawlSpider 和 Rule 的作用及区别? 如何通过 Scrapy Shell 快速调试页面解析逻辑? Scrapy 的 start_requests 方法与 start_urls 的关系是什么? 解释 Scrapy 的 Request 和 Response 对象…...
Swan 表达式 - 选择表达式
ANSYS Swan 表达式支持选择(selection)表达式 case, if/then/else。选择表达式根据特定的条件选择不同的分支流。 if/then/else 表达式 if/then/else 表达式的文法如下 if expr then expr else expr 其中,首个expr 的布尔表达式,若其为 true, 则返回 …...
微信小程序:完善购物车功能,购物车主页面展示,详细页面展示效果
一、效果图 1、主页面 根据物品信息进行菜单分类,点击单项购物车图标添加至购物车,记录总购物车数量 2、购物车详情页 根据主页面选择的项,根据后台查询展示到页面,可进行多选,数量加减等 二、代码 1、主页面 页…...
javaweb将上传的图片保存在项目文件webapp下的upload文件夹下
前端HTML表单 (upload.html) 首先,创建一个HTML页面,允许用户选择并上传图片。 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><title>图片上传</title> </head> <…...
LabVIEW 无法播放 AVI 视频的编解码器解决方案
用户在 LabVIEW 中使用示例程序 Read AVI File.vi(路径: 📌 C:\Program Files (x86)\National Instruments\LabVIEW 2019\examples\Vision\Files\Read AVI File.vi)时发现: ✅ LabVIEW 自带的 AVI 视频可正常播放 这是…...
composer 错误汇总
文章目录 1: 安装EasyWeChat 报错2: composer install 报错, laravel/framework[v11.9.0, ..., v11.44.0] require fruitcake/php-cors ^1.33: 卸载Pulse 报错, Class "Laravel\Pulse\Pulse" not found4: 卸载Telescope报错 1: 安装EasyWeChat 报错 解决: composer …...
MySQL锁分类
一、按锁的粒度划分 全局锁 定义:锁定整个数据库实例,阻止所有写操作,确保数据备份一致性。加锁方式:通过FLUSH TABLES WITH READ LOCK实现,释放需执行UNLOCK TABLES。应用场景:适用于全库逻辑备份…...
DeepSeek 助力 Vue3 开发:打造丝滑的悬浮按钮(Floating Action Button)
前言:哈喽,大家好,今天给大家分享一篇文章!并提供具体代码帮助大家深入理解,彻底掌握!创作不易,如果能帮助到大家或者给大家一些灵感和启发,欢迎收藏关注哦 💕 目录 Deep…...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...
19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
网络编程(UDP编程)
思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
0x-3-Oracle 23 ai-sqlcl 25.1 集成安装-配置和优化
是不是受够了安装了oracle database之后sqlplus的简陋,无法删除无法上下翻页的苦恼。 可以安装readline和rlwrap插件的话,配置.bahs_profile后也能解决上下翻页这些,但是很多生产环境无法安装rpm包。 oracle提供了sqlcl免费许可,…...
VisualXML全新升级 | 新增数据库编辑功能
VisualXML是一个功能强大的网络总线设计工具,专注于简化汽车电子系统中复杂的网络数据设计操作。它支持多种主流总线网络格式的数据编辑(如DBC、LDF、ARXML、HEX等),并能够基于Excel表格的方式生成和转换多种数据库文件。由此&…...
Vue3 PC端 UI组件库我更推荐Naive UI
一、Vue3生态现状与UI库选择的重要性 随着Vue3的稳定发布和Composition API的广泛采用,前端开发者面临着UI组件库的重新选择。一个好的UI库不仅能提升开发效率,还能确保项目的长期可维护性。本文将对比三大主流Vue3 UI库(Naive UI、Element …...
