【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…...
挑战杯推荐项目
“人工智能”创意赛 - 智能艺术创作助手:借助大模型技术,开发能根据用户输入的主题、风格等要求,生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用,帮助艺术家和创意爱好者激发创意、提高创作效率。 - 个性化梦境…...

eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...

苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...

AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...

C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...