C++网络编程——多路IO复用 重点epoll

前言补充

阻塞与非阻塞

  • 同步阻塞IO (Blocking IO) : 传统IO模型

  • 同步非阻塞IO (Non-blocking IO): 默认创建的socket都是阻塞的,若是要设置成非阻塞IO需要socket被设置成NONBLOCK。

  • IO多路复用(IO Multiplexing): 经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型

  • 异步IO (Asynchronous IO): 经典的Proactor设计模式,也称为异步非阻塞IO

阻塞与非阻塞IO

对于操作系统内核而言,发生在等待资源阶段,根据发起IO请求是否阻塞来判断

阻塞IO

这种模式下下一个用户进程在发起一个IO操作之后,CPU只有在收到响应或者超时后才可以处理其他事情,否则IO将会一直阻塞,例如读取磁盘上一段文件,系统内核在完成磁盘寻道、读取数据、复制数据到内存之中,这个调用才算完成,阻塞的这段时间对CPU资源来说是浪费掉了。

非阻塞IO

这种模式下一个用户进程发起一个IO操作后,如果数据没有就绪,则会立刻返回,同时标记数据资源不可用,此时CPU时间片可以做其他的事情

同步与异步IO

发生在使用资源阶段,根据实际IO操作来判断

同步IO

应用发送或者接收数据后,如果不返回,那么就阻塞等待,直到数据成功或者失败返回

异步IO

应用发送或者接收数据后立刻返回,数据写入OS缓存,由OS完成数据发送或者接收,等OS处理完成发送或接受后再通知应用,通常是通过回调的方式,Node.js就是典型的异步编程的例子

无论是BIO,NIO(同步非阻塞)还是多路复用,都是同步IO模型,其中BIO是同步阻塞模型,NIO和多路复用是同步非阻塞模型。

多路IO复用

多路IO复用是一种同步IO模型,实现一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序执行相应的读写操作,没有文件句柄就绪时监视线程会被阻塞,交出CPU,直到有文件/连接就绪。多路指的是网络连接,复用指的是同一个线程可以被复用来服务多个文件描述符

常见的IO多路复用模型有:select模型,poll模型,epoll模型

select模型

select函数的API

#include <sys/select.h>int select(int nfds, fd_set *readfds, fd_set *writefds,         fd_set *exceptfds, struct timeval *timeout);//功能: 监听多个文件描述符的属性变化(读,写,异常)
//参数:
//    nfds  : 最大文件描述符+1
//    readfds : 需要监听的读的文件描述符存放集合
//    writefds :需要监听的写的文件描述符存放集合   NULL
//    exceptfds : 需要监听的异常的文件描述符存放集合  NULL
//    timeout: 多长时间监听一次   固定的时间,限时等待   NULL 永久监听
//    struct timeval {
//               long    tv_sec;         /* seconds */ 秒
//               long    tv_usec;        /* microseconds */微妙
//           };
//返回值: 返回的是变化的文件描述符的个数
//注意: 变化的文件描述符会存在监听的集合中,未变化的文件描述符会从集合中删除
//监视的文件描述符有三类,readfds,writefds,exceptfds
//调用后函数会阻塞,直到有描述符就绪(有数据读、写、或者有except),或者超时(timeout指定时间,如果立即返回设置null),函数返回
//当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。

select模型是最古老的IO多路复用机制之一,使用fd_set数据结构来保存文件描述符集合,并提供了select()函数来等待文件描述符的就绪状态。它有一个限制,即所监视的文件描述符数量有一个上限,通常是1024。

每次调用select()时,都需要将整个文件描述符集合从用户空间复制到内核空间,同时只是返回变化的文件描述符的个数,具体哪个那个变化需要遍历,这都可能带来性能问题。

优点:良好的跨平台性

缺点:单个进程能够监视的文件描述符的数量存在最大限制 在Linux上为1024可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但这样会造成效率的降低 每次调用 select,都需要把 fd 集合从用户态拷贝到内核态,这个开销在 fd 很多时会很大 对 socket 扫描时是线性扫描,采用轮询的方法,效率较低(高并发)

线性扫描:select 函数会扫描所有被监视的 socket,依次检查每个 socket 的就绪状态;

轮询方式:select 函数会不断地轮询检查所有被监视的 socket,直到有 socket 就绪或者超时,这种轮询方式会占用大量 CPU 资源,因为即使没有 socket 就绪,CPU 也在不停地轮询检查。

效率问题:

  • 在高并发场景下,被监视的 socket 数量会非常大,线性扫描和轮询的方式会导致 select 函数的性能下降。

  • 因为每次调用 select 函数时,都需要扫描所有的 socket,当 socket 数量增加时,扫描的时间也会随之增加。

  • 这种低效的扫描方式会导致 CPU 利用率偏高,无法支撑高并发的场景。

poll模型

poll API#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能: 监听多个文件描述符的属性变化
参数:fds : 监听的数组的首元素地址nfds: 数组有效元素的最大下标+1timeout : 超时时间 -1是永久监听 >=0 限时等待数组元素:
struct pollfdstruct pollfd {int   fd;         /* file descriptor */ 需要监听的文件描述符short events;     /* requested events */需要监听文件描述符什么事件  EPOLLIN 读事件   EPOLLOUT写事件short revents;    /* returned events */ 返回监听到的事件    EPOLLIN 读事件   EPOLLOUT写事};

相比于select的优点就是没有文件描述符1024的限制,不过缺点一样:每次都需要将需要监听的文件描述符从应用层拷贝到内核每次都需要将数组中的元素遍历一遍才知道那个变化了

epoll模型

Linux特有的IO多路复用机制,自从2.5.44内核版本引入后成为主流。它使用基于事件的方式来管理文件描述符,使用一个事件表(event table)来保存文件描述符和事件信息,并提供了epoll_create()、epoll_ctl()和epoll_wait()等函数来操作事件表。

epollAPI1. 创建红黑树#include <sys/epoll.h>int epoll_create(int size);参数:size :  监听的文件描述符的上限,  2.6版本之后写1即可,返回:  返回树的句柄2.  上树  下树 修改节点epoll_ctl
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数:epfd : 树的句柄op : EPOLL_CTL_ADD 上树   EPOLL_CTL_DEL 下树 EPOLL_CTL_MOD 修改fd : 上树,下树的文件描述符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 */ 需要监听的文件描述符};例如,将cfd上树int epfd =  epoll_create(1);
struct epoll_event ev;
ev. data.fd = cfd;
ev.events = EPOLLIN; //监听读事件,其他事件包括EPOLLOUT,EPOLLERR,EPOLLHUP,各自的含义是监听写事件、错误事件、挂起事件
epoll_ctl(epfd, EPOLL_CTL_ADD,cfd, &ev);3. 监听#include <sys/epoll.h>int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);功能: 监听树上文件描述符的变化epfd : 数的句柄events : 接收变化的节点的数组的首地址maxevents :  数组元素的个数timeout : -1 永久监听  大于等于0 限时等待
返回值: 返回的是变化的文件描述符个数

每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件。(读,写,错误,挂起),这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是logn,其中n为红黑树元素个数)。

而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当相应的事件发生时会调用这个回调方法。这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中,在epoll中,对于每一个事件,都会建立一个epitem结构体,如下所示:

struct epitem{struct rb_node  rbn;//红黑树节点struct list_head    rdllink;//双向链表节点struct epoll_filefd  ffd;  //事件句柄信息struct eventpoll *ep;    //指向其所属的eventpoll对象struct epoll_event event; //期待发生的事件类型
}

当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户。

优点:

  • 效率提升,不是轮询方式,不会随着文件描述符数量增加而效率下降

  • 没有最大并发连接1024的限制

  • 只有活跃的文件描述符才会使用回调函数,只管理活跃连接,而与连接总数无关

  • 同时使用了内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递,即epoll使用mmap减少复制开销

工作方式有两种:一种是水平触发:LT,一种是边沿触发:ET

从电路信号来讲,水平触发就是持续的高电平或者持续的低电平

而边沿触发则是电平从低到高的一个变化,或者电平从高到底的变化

  • 监听读缓冲区变化

    • 水平触发:只要读缓冲区有数据就会触发epoll_wait()

    • 边沿触发:数据来一次,epoll_wait()只触发一次

  • 监听写缓冲区的变化

    • 水平触发:只要可以写,那么就会触发

    • 边沿触发:数据从有到无,才会触发

因为如果设置为水平触发,只要缓存区有数据epoll_wait就会被触发,epoll_wait是一个系统调用,尽量少调用

所以尽量使用边沿触发,边沿出触发数据来一次只触发一次,这个时候要求一次性将数据读完,所以while循环读,读到最后read默认带阻塞,不能让read阻塞,因为不能再去监听,设置cfd为非阻塞,read读到最后一次返回值为-1.判断errno的值为EAGAIN,代表数据读干净

工作中,边沿触发+非阻塞 = 高速模式

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/24852.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微软 Windows 10 22H2 发布可选更新 19045.4474,修复窗口显示问题等

微软今天面向 Windows 10 22H2 版本&#xff0c;发布了 KB5037849 非安全可选更新&#xff0c;用户安装后版本号升至 Build 19045.4474。 IT之家 5 月 30 日消息&#xff0c;微软今天面向 Windows 10 22H2 版本&#xff0c;发布了 KB5037849 非安全可选更新&#xff0c;用户安…

内部资料13000+变现资源,写作副业项目,找灵感就用它!

很多刚学在公众号&#xff0c;或是在其他自媒体平台写副业项目的朋友&#xff0c;写一段时间后会发现找不到写的内容。于是&#xff0c;每天都会为写什么而苦恼&#xff0c;找不到素材&#xff0c;没有灵感。 关注富哥资源站的朋友都知道&#xff0c;咱们的资源站已经更新了内…

2024 Jiangsu Collegiate Programming Contest E. Divide 题解 主席树

Divide 题目描述 Given an integer sequence a 1 , a 2 , … , a n a_1,a_2,\ldots,a_n a1​,a2​,…,an​ of length n n n. For an interval a l , … , a r a_l,\ldots,a_r al​,…,ar​ in this sequence, a Reduce operation divides the maximum value of the inter…

【NI国产替代】产线测试:数字万用表(DMM),功率分析仪,支持定制

数字万用表&#xff08;DMM&#xff09; • 6 位数字表显示 • 24 位分辨率 • 5S/s-250KS/s 采样率 • 电源和数字 I/O 均采用隔离抗噪技术 • 电压、电流、电阻、电感、电容的高精度测量 • 二极管/三极管测试 功率分析仪 0.8V-14V 的可调输出电压&#xff0c;最大连…

[Cloud Networking] Layer Protocol (continue)

文章目录 1. STP / RSTP / MSTP Protocol1.1 STP的作用1.2 STP 生成树算法的三个步骤1.3 STP缺点 2. ARP Protocol3. DHCP Protocol3.1 DHCP 三种分配方式3.2 DHCP 攻击 4. IPSEC / MACSEC 1. STP / RSTP / MSTP Protocol 1.1 STP的作用 消除二层环路&#xff1a;通过阻断冗余…

shell脚本基础教程

快捷目录 1、解释器2、shell脚本调用方式3、变量自定义变量环境变量特殊变量变量的默认值declare 命令readonly命令let命令 4、数据类型字符串 5、字符串操作6、数组定义数组读取数组修改数组删除数组获取数组长度 7、运算符算术运算符关系运算符布尔运算符逻辑运算符 要在[[ ]…

求极差(range)

题目描述 小红拿到了一个数组&#xff0c;其中每个元素都是素数。小红准备进行若干次以下操作&#xff1a; 选择两个素数元素&#xff0c;将他们合并&#xff0c;生成的新元素为原来两个素数的乘积。 现在小红希望操作到不能再操作为止&#xff0c;然后使得最终的极差&#…

王道408数据结构CH4_串

概述 4 串 4.1 串的实现 4.1.1 存储结构 定长顺序存储 #define Maxsize 255typedef struct{char *ch[Maxsize];int length; }SString;堆分配存储 typedef struct{char *ch;int length; }HString;块链存储 4.1.2 基本操作 4.2 模式匹配&#xff08;子串定位&#xff09; 4.2.…

Docker面试整理-Docker Swarm是什么?

Docker Swarm 是 Docker 的原生集群管理和编排工具,用于将多个 Docker 主机集合成一个虚拟的 Docker 主机。它允许你在多台机器上部署、管理和扩展容器化应用,是构建和管理容器化应用的高可用集群的一种方式。 Docker Swarm 的核心特性包括: 1. 集群管理: ● Docker Swarm…

RaspAP:轻松实现树莓派无线 AP

RaspAP 是一个可以将树莓派轻松部署成无线 AP&#xff08;Access Point&#xff09;的软件方案&#xff0c;具有一套响应式的 WebUI 来控制 WiFi&#xff0c;用起来和家用路由器一样方便。RaspAP 可以运行在 Raspbian 上&#xff0c;只需要先给树莓派安装好 Raspbian 系统&…

12寸晶圆厂建设概述

1、关键步骤 半导体12英寸&#xff08;12吋&#xff09;晶圆厂的建厂过程是一个复杂且耗时的工程&#xff0c;涉及到多个阶段&#xff0c;包括但不限于以下几个关键步骤&#xff1a; 1. **项目规划与设计**&#xff1a;首先需要进行项目的可行性研究&#xff0c;确定产品定位…

docker和docker-compose的安装

docker的安装 1.安装 curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun2.设置开机自启动 systemctl start docker #启动docker systemctl enable docker3.配置阿里云镜像 不配置镜像的话&#xff0c;进行 docker pull 等操作会比较慢。进入阿里云&…

Hudi Spark Sql Procedures 回滚 Hudi 表数据

前言 因为有 Hudi Rollback 的需求,所以单独总结 Hudi Spark Sql Procedures Rollback。 版本 Hudi 0.13.0(发现有bug)、(然后升级)0.14.1Spark 3.2.3Procedures 官方文档:https://hudi.apache.org/docs/procedures 相关阅读:Hudi Spark SQL Call Procedures学习总结…

【NI国产替代】电池模拟器,快速模拟 3C 产品电池的充放电功能

电池模拟器 快速模拟 3C 产品电池的充放电功能输出灵活可调节的电压/电流内置双向 DC-DC 降压变换器为 3C 产品提供漏电检测 电池模拟器系列包含单节双通道&#xff08;1S&#xff09;、双节双通道&#xff08;2S&#xff09;、三节单通道&#xff08;3S&#xff09;三种规格&…

C++基础与深度解析 | 异常处理 | 枚举与联合 | 嵌套类与局部类 | 嵌套名字空间与匿名名字空间 | 位域与volatile关键字

文章目录 一、异常处理二、枚举与联合三、嵌套类与局部类四、嵌套名字空间与匿名名字空间五、位域与volatile关键字 一、异常处理 异常处理用于处理程序在调用过程中的非正常行为。 传统的处理方法&#xff1a;传返回值表示函数调用是否正常结束。 例如&#xff0c;返回 0 表示…

中介子方程七

X$XFX$XEXyXαXiX$XαX$XiXαXyXEX$XFX$XEXyXαXiX$XαX$XiXαXyXEX$XαXηXtXαX$XWXyX$XyXWX$XpXαXqXηX$XeXαXhX$XdX$XpX$XdX$XyXeXαX$XEXyXαXiX$XαX$XiXαXyXEX$XαXeXyX$XdX$XpX$XdX$XhXαXeX$XηXqXαXpX$XWXyX$XyXWX$XαXtXηXαXpX$XEX$XZX$XpXαXηXtXαX$XWXyX$…

SpringBoot+Vue教师工作量管理系统(前后端分离)

技术栈 JavaSpringBootMavenMySQLMyBatisVueShiroElement-UI 角色对应功能 教师管理员 功能截图

声量2024 | 脱离『生活监狱』——对部分主流价值的质疑与冒犯

点击文末“阅读原文”即可参与节目互动 剪辑、音频 / 卷圈 运营 / SandLiu 卷圈 监制 / 姝琦 封面 / 姝琦Midjourney 产品统筹 / bobo 场地支持 / 阿那亚 联合制作 / 声量The Power of Voice 特别鸣谢 / 深夜谈谈播客网络 本期节目录制于第二届「声量The Power of Voic…

自定义类型:结构体+结构体内存对齐+结构体实现位段

结构体内存对齐实现位段 一.结构体1.结构体的声明2.结构体变量成员访问操作符3.结构体传参4.匿名结构体5.结构的自引用 二.结构体内存对齐1.对齐规则2.为什么存在内存对齐&#xff1f;3.修改默认对齐数 三.结构体实现位段1.什么是位段2.位段的内存分配3.位段的跨平台问题4.位段…

安卓打造安装包(应用打包、规范处理安装包、安全加固)

本章介绍应用安装包的基本制作规范&#xff0c;主要包括&#xff1a;如何导出既美观又精简的APK文件、如何按照上线规范调整App的相关设置、如何对APK文件进行安全加固以防止安装包被破解。 应用打包 本节介绍APK安装包的打包过程&#xff0c;包括&#xff1a;如何利用Androi…