C++中的多路转接技术之epoll

      • `epoll` 是干什么的?
        • 举个简单的例子
      • epoll的相关系统调用
        • **epoll_create**和epoll_create1
          • 区别
        • epoll_ctl
          • 参数解释
        • **epoll_wait**
          • 参数说明
          • 返回值
        • epoll的使用
      • **epoll**工作原理
      • epoll的优点(和 **select** 的缺点对应)
      • epoll工作方式
          • **水平触发**Level Triggered 工作模式
          • 边缘触发Edge Triggered工作模式
        • **对比**LT和ET

epoll 是 Linux 内核提供的一种高效的 I/O 事件通知机制,常用于网络编程中以替代传统的 selectpoll 系统调用。相比于 selectpollepoll 在处理大量并发连接时具有更高的性能和更好的扩展性。

按照man手册的说法: 是为处理大批量句柄而作了改进的poll.

它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44)

它几乎具备了之前poll的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法

epoll 是干什么的?

  1. 监控多个连接epoll 能帮你同时监控很多网络连接,就像一个超级接线员能同时管理很多电话线路一样。它能告诉你哪些连接上有新消息需要处理,哪些连接被挂断了,等等。
  2. 高效通知:传统的 selectpoll 像是老式的接线员,每次都要检查所有的电话线路才能告诉你哪些线路有事。而 epoll 就聪明多了,它只会告诉你那些有变化的线路,大大提高了效率。
  3. 处理大量连接:如果你有上千个网络连接,epoll 能轻松应对。它的效率不会随着连接数量的增加而明显降低,因为它只关注那些真正有事情发生的连接。
举个简单的例子

假设你在经营一个餐厅,你需要管理很多外卖订单,epoll 就像是一个超级助理,帮你监控所有的订单系统,让你知道什么时候有新订单,什么时候订单完成,什么时候客户取消订单。它只会告诉你有变化的订单,而不是每次都汇报所有的订单状态,这样你就可以专注于处理重要的事情,而不用被不必要的信息干扰。

epoll的相关系统调用

epoll 有3个相关的系统调用.

epoll_create和epoll_create1
int epoll_create(int size);

创建一个epoll的句柄.

自从linux2.6.8之后,size参数是被忽略的.

用完之后, 必须调用close()关闭.

int epoll_create1(int flags);
  • flags:可以是以下值之一:
    • 0:不设置任何标志。
    • EPOLL_CLOEXEC:在返回的文件描述符上设置 FD_CLOEXEC 标志,这意味着当调用 exec 函数时,这个文件描述符会自动关闭。

返回值:成功时返回一个 epoll 实例的文件描述符,失败时返回 -1 并设置 errno。

区别
  1. 参数含义
  • epoll_create 需要一个整数参数 size,但这个参数在现代 Linux 内核中已经被忽略了。
  • epoll_create1 需要一个标志参数 flags,可以设置为 0EPOLL_CLOEXEC
  1. 功能
  • epoll_create 是老版本的接口,参数 size 已经没有实际意义。
  • epoll_create1 是新版本的接口,引入了 flags 参数,增加了对 EPOLL_CLOEXEC 标志的支持,使文件描述符更易于管理。

一般建议使用 epoll_create1,因为它是更现代的接口,并且提供了更好的功能和灵活性。

epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数解释
  • epfd:由 epoll_createepoll_create1 创建的 epoll 实例的文件描述符。

  • op:指定操作类型,可以是以下三个值之一:

    • EPOLL_CTL_ADD:注册新的文件描述符到 epoll 实例中。
    • EPOLL_CTL_MOD:修改已经注册的文件描述符的监听事件。
    • EPOLL_CTL_DEL:从 epoll 实例中删除文件描述符。
  • fd:需要管理的目标文件描述符。

  • event:指向一个 epoll_event 结构体的指针,包含需要监听的事件类型及用户数据。

epoll_event 结构体

struct epoll_event {uint32_t events;  // 监听的事件类型epoll_data_t data;  // 用户数据
};typedef union epoll_data {void *ptr;int fd;uint32_t u32;uint64_t u64;
} epoll_data_t;

events可以是以下几个宏的集合:

EPOLLIN:表示对应的文件描述符可以进行读取操作,或者对端正常关闭了(例如,对于一个 socket,这意味着有数据可以读取,或者对端关闭了连接)。

EPOLLOUT:表示对应的文件描述符可以进行写操作(例如,对于一个 socket,这意味着可以写入数据)。

EPOLLPRI:表示对应的文件描述符有紧急的数据可读,这通常指带外数据(Out-Of-Band Data)。带外数据通常用于 TCP 的紧急数据机制。

EPOLLERR:表示对应的文件描述符发生了错误,例如,读写操作失败或者遇到了网络错误。

EPOLLHUP:表示对应的文件描述符被挂断。对于一个 socket,这通常意味着对端关闭了连接,并且不会再有数据到来。

EPOLLET:将 epoll 设置为边缘触发模式(Edge Triggered)。边缘触发模式只会在状态变化时通知一次,即只有在状态发生变化时才会报告事件。与水平触发模式(Level Triggered)相比,边缘触发模式可能需要更频繁地检查文件描述符的状态,因为它不会在状态保持不变的情况下重复报告事件。(下方会详细说)

EPOLLONESHOT:表示只监听一次事件。设置了这个标志的文件描述符在事件触发后会从 epoll 实例中移除,直到你手动将它重新添加到 epoll 实例中。这是为了处理事件后重新注册,以防止在事件处理过程中丢失事件。适用于需要在每次事件发生后都重新注册的场景,确保事件处理的健壮性。

epoll_data是一个联合体,在某些情况下,你可能只需要其中一种东西,当然我们可以看到其中有一个指针参数,这更是加大了灵活性,比如

struct connection_info {int fd;// 其他与连接相关的数据
};
struct connection_info *conn_info = new connection_info();
conn_info->fd = conn_fd;
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLRDHUP;
ev.data.ptr = conn_info;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn_fd, &ev) == -1) {perror("epoll_ctl: conn_fd");close(conn_fd);delete conn_info;
}
//在处理事件时,你可以通过 events[n].data.ptr 访问该指针,并获取结构体中的信息:
struct connection_info *conn_info = static_cast<connection_info*>(events[n].data.ptr);
int fd = conn_info->fd;
// 处理连接的读写等事件

​ 这样的处理可以增强灵活性、空间节省、便于传递数据。

epoll_wait
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
参数说明
  1. epfdepoll 实例的文件描述符,是之前调用 epoll_createepoll_create1 函数时获得的。
  2. events:一个指向 epoll_event 结构体数组的指针,用于存储返回的事件列表。epoll_wait 将填充这个数组,数组的大小由 maxevents 参数指定。
  3. maxeventsevents 数组的大小,即最大可以返回的事件数量。epoll_wait 可能返回的事件数量最多为 maxevents,实际返回的数量由事件发生的数量决定。
  4. timeout:等待事件的超时时间,以毫秒为单位。如果设置为 -1epoll_wait 将会阻塞,直到至少一个事件发生。如果设置为 0epoll_wait 会立即返回,适用于非阻塞检查。如果设置为正数,epoll_wait 会等待指定的时间后返回,适用于有超时要求的场景。
返回值
  • 成功时,返回发生的事件数量。这个数量可能小于或等于 maxevents
  • 如果没有事件发生并且 timeout0,返回 0
  • 失败时,返回 -1 并设置 errno

其中events是一个输出型参数,epoll_wait 会在返回时填充 events 数组,数组的前 nfds 个元素会包含发生的事件。nfdsepoll_wait 返回的事件数量,表明数组中有多少个有效的事件。

处理事件:遍历 events 数组,根据 events[n].events 的值来识别事件类型,并执行相应的处理逻辑。events[n].data 包含与事件相关的文件描述符或其他数据。

epoll的使用

总结一下, epoll的使用过程就是三部曲:

调用epoll_create创建一个epoll句柄;

调用epoll_ctl, 将要监控的文件描述符进行注册;

调用epoll_wait, 等待文件描述符就绪;

epoll工作原理

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关.

struct eventpoll{ .... /*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/ struct rb_root rbr; /*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/ struct list_head rdlist; .... 
};
  1. 每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件.
  2. 这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度).
  3. 而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的事件发生时会调用这个回调方法.
  4. 这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中.
  5. 在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不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户. 这个操作的时间复杂度是O(1).

epoll的优点(和 select 的缺点对应)

  • 接口使用方便: 虽然拆分成了三个函数, 但是反而使用起来更方便高效. 不需要每次循环都设置关注的文件描述符, 也做到了输入输出参数分离开
  • 数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不频繁(而select/poll都是每次循环都要进行拷贝)
  • 事件回调机制: 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中, epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度O(1). 即使文件描述符数目很多, 效率也不会受到影响.
  • 没有数量限制: 文件描述符数目无上限

epoll工作方式

你正在吃鸡, 眼看进入了决赛圈, 你妈饭做好了, 喊你吃饭的时候有两种方式:

  1. 如果你妈喊你一次, 你没动, 那么你妈会继续喊你第二次, 第三次…(亲妈, 水平触发)
  2. 如果你妈喊你一次, 你没动, 你妈就不管你了(后妈, 边缘触发)

epoll有2种工作方式-水平触发(LT)和边缘触发(ET)

假如有这样一个例子:

我们已经把一个tcp socket添加到epoll描述符

这个时候socket的另一端被写入了2KB的数据

调用epoll_wait,并且它会返回. 说明它已经准备好读取操作

然后调用read, 只读取了1KB的数据

继续调用epoll_wait…

水平触发Level Triggered 工作模式

epoll默认状态下就是LT工作模式.

当epoll检测到socket上事件就绪的时候, 可以不立刻进行处理. 或者只处理一部分.

如上面的例子, 由于只读了1K数据, 缓冲区中还剩1K数据, 在第二次调用 epoll_wait 时, epoll_wait

仍然会立刻返回并通知socket读事件就绪.

直到缓冲区上所有的数据都被处理完, epoll_wait 才不会立刻返回.

支持阻塞读写和非阻塞读写

边缘触发Edge Triggered工作模式

如果我们在第1步将socket添加到epoll描述符的时候使用了EPOLLET标志, epoll进入ET工作模式.

  • 当epoll检测到socket上事件就绪时, 必须立刻处理.
  • 如上面的例子, 虽然只读了1K的数据, 缓冲区还剩1K的数据, 在第二次调用 epoll_wait 的时候, epoll_wait 不会再返回了.
  • 也就是说, ET模式下, 文件描述符上的事件就绪后, 只有一次处理机会.
  • ET的性能比LT性能更高( epoll_wait 返回的次数少了很多). Nginx默认采用ET模式使用epoll.
  • 只支持非阻塞的读写

select和poll其实也是工作在LT模式下. epoll既可以支持LT, 也可以支持ET.

对比LT和ET

LT是 epoll 的默认行为. 使用 ET 能够减少 epoll 触发的次数. 但是代价就是强逼着程序猿一次响应就绪过程中就把所有的数据都处理完.

相当于一个文件描述符就绪之后, 不会反复被提示就绪, 看起来就比 LT 更高效一些. 但是在 LT 情况下如果也能做到每次就绪的文件描述符都立刻处理, 不让这个就绪被重复提示的话, 其实性能也是一样的.

另一方面, ET 的代码复杂程度更高了.

select和poll其实也是工作在LT模式下. epoll既可以支持LT, 也可以支持ET

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

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

相关文章

Springboot 启动时Bean的创建与注入(一)-面试热点-springboot源码解读-xunznux

Springboot 启动时Bean的创建与注入&#xff0c;以及对应的源码解读 文章目录 Springboot 启动时Bean的创建与注入&#xff0c;以及对应的源码解读构建Web项目流程图&#xff1a;堆栈信息&#xff1a;堆栈信息简介堆栈信息源码详解1、main:10, DemoApplication (com.xun.demo)2…

HashMap与ConcurrentHashMap

文章目录 HashMap1.1 HashMap 的数据结构&#xff1f;1.2 HashMap 的动态扩容1.3 Hash实现方法1.4 如何解决Hash冲突 ConcurrentHashMap HashMap 1.1 HashMap 的数据结构&#xff1f; 哈希表结构&#xff08;链表散列&#xff1a;数组链表&#xff09;实现&#xff0c;结合数…

详细分析Springboot自定义启动界面(附Demo)

目录 前言1. banner.text1.1 配置文件关闭1.2 启动类关闭1.3 命令行关闭 2. 自定义Banner类3. 自动配置类4. 总结 前言 实现自定义启动动画是一项有趣的任务&#xff0c;虽然Spring Boot本身不提供内置的动画功能&#xff0c;但可以通过一些技巧来实现 以下主要以Demo的形式展…

三字棋游戏(C语言详细解释)

hello&#xff0c;小伙伴们大家好&#xff0c;算是失踪人口回归了哈&#xff0c;主要原因是期末考试完学校组织实训&#xff0c;做了俄罗斯方块&#xff0c;后续也会更新&#xff0c;不过今天先从简单的三字棋说起 话不多说&#xff0c;开始今天的内容 一、大体思路 我们都知…

MongoDB教程(十三):MongoDB覆盖索引

&#x1f49d;&#x1f49d;&#x1f49d;首先&#xff0c;欢迎各位来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里不仅可以有所收获&#xff0c;同时也能感受到一份轻松欢乐的氛围&#xff0c;祝你生活愉快&#xff01; 文章目录 引言什么是覆盖…

数据结构(栈及其实现)

栈 概念与结构 栈&#xff1a;⼀种特殊的线性表&#xff0c;其只允许在固定的⼀端进⾏插⼊和删除元素操作。 进⾏数据插⼊和删除操作的⼀端称为栈顶&#xff0c;另⼀端称为栈底。栈中的数据元素遵守后进先出 LIFO&#xff08;Last In First Out&#xff09;的原则。 压栈&…

PyCharm创建一个空的python项目

1.设置项目路径 2.配置python解释器 右下角可以选择always

【Linux】线程——生产者消费者模型、基于阻塞队列的生产消费者模型、基于环形队列的生产消费者模型、POSIX信号量的概念和使用

文章目录 Linux线程6. 生产消费者模型6.1 基于阻塞队列的生产消费者模型6.1.1 阻塞队列模型实现 6.2 基于环形队列的生产消费者模型6.2.1 POSIX信号量的概念6.2.2 POSIX信号量的使用6.2.3 环形队列模型实现 Linux线程 6. 生产消费者模型 生产消费者模型的概念 生产者消费者模…

Jackson详解

文章目录 一、Jackson介绍二、基础序列化和反序列化1、快速入门2、序列化API3、反序列化API4、常用配置 三、常用注解1、JsonProperty2、JsonAlias3、JsonIgnore4、JsonIgnoreProperties5、JsonFormat6、JsonPropertyOrder 四、高级特性1、处理泛型1.1、反序列化List泛型1.2、反…

Java 写一个可以持续发送消息的socket服务端

前言 最近在学习flink, 为了模仿一个持续的无界的数据源, 所以需要一个可以持续发送消息的socket服务端. 先上效果图 效果图 socket服务端可以持续的发送消息, flink端是一个统计单词出现总数的消费端,效果图如下 源代码 flink的消费端就不展示了, 需要引入一些依赖和版本…

Linux系统编程基础

Linux操作系统 Linux不是一个具体的操作系统&#xff0c;而是一类操作系统的总称&#xff0c;具体版本成为发行版。 Red Hat&#xff1a;目前被IBM收购&#xff0c;收费版&#xff0c;目前最大的Linux供应商CentOS&#xff1a; Red Hat退出的免费版Ubuntu&#xff1a;界面比较友…

二十一、【机器学习】【非监督学习】- 谱聚类 (Spectral Clustering)​​

系列文章目录 第一章 【机器学习】初识机器学习 第二章 【机器学习】【监督学习】- 逻辑回归算法 (Logistic Regression) 第三章 【机器学习】【监督学习】- 支持向量机 (SVM) 第四章【机器学习】【监督学习】- K-近邻算法 (K-NN) 第五章【机器学习】【监督学习】- 决策树…

hung 之 Android llkd

目录 1. llkd 简介 2. 原理 2.1 内核活锁 2.2 检测机制 2.3 为什么 persistent stack signature 检测机制不执行 ABA 检查&#xff1f; 2.4 为什么 kill 进程后&#xff0c;进程还存在就能判定发生了内核 live-lock&#xff1f; 3. 代码 3.1 内核 live-lock 检查 3.2 …

摸鱼大数据——用户画像——如何给用户“画像”

2、如何给用户“画像” 2.1 什么是标签体系 标签: 是某一种用户特征的符号表示 标签体系: 把用户分到多少类别里面去, 这些类是什么, 彼此之间有什么关系, 就构成了标签体系 标签解决的问题: 解决描述(或命名)问题以及解决数据之间的关联 2.2.1 标签的分类 用户画像标签一…

【附源码】IMX6U嵌入式Linux开发板连接阿里云--MQTT协议

演示 IMX6U嵌入式Linux开发板连接阿里云 阿里云创建设备&&获取LinkSDK 如果还不知道怎么在阿里云创建设备和获取连接阿里云的LinkSDK的话&#xff0c;先看这篇文章&#xff0c;再到这里。看这篇文章的时候&#xff0c;麻烦将下方文章打开对照着看&#xff0c;因为一些…

重测序数据处理得到vcf文件

重测序数据处理得到vcf文件 文章目录 重测序数据处理前言1. 数据是rawdata&#xff0c;需用fastp对数据进行质控和过滤2. 利用getorganelle软件组装叶绿体基因组3. 检查基因组大小&#xff0c;确认是否完整&#xff0c;然后和已知的红毛菜科叶绿体基因组一起构树4. 根据树形结果…

微积分-微分应用2(平均值定理)

要得出平均值定理&#xff0c;我们首先需要以下结果。 罗尔定理 设函数 f f f 满足以下三个假设&#xff1a; f f f 在闭区间 [ a , b ] [a, b] [a,b] 上连续。 f f f 在开区间 ( a , b ) (a, b) (a,b) 上可导。 f ( a ) f ( b ) f(a) f(b) f(a)f(b) 则在开区间 ( a , b …

CTFHUB-SQL注入-UA注入

目录 判断是否存在注入 判断字段数量 判断回显位置 查询数据库名 查询数据库下的表名 查询表中的字段名 查询字段名下的数据 由于本关是UA注入&#xff0c;就不浪费时间判断是什么注入了&#xff0c;在该页面使用 burp工具 抓包&#xff0c;修改User-Agent&#xff0c;加…

JavaScript之Web APIs-DOM

目录 DOM获取元素一、Web API 基本认知1.1 变量声明1.2 作用和分类1.3 DOM树1.4 DOM对象 二、获取DOM对象2.1 通过CSS选择器来获取DOM元素2.2 通过其他方式来获取DOM元素 三、操作元素内容3.1 元素.innerTest属性3.2 元素.innerHTML属性 四、操作元素属性4.1 操作元素常用属性4…

图形编辑器基于Paper.js教程09:鼠标拖动画布,以鼠标点为缩放中心进行视图的缩放

如何使用Paper.js实现画布的缩放与拖动功能 在Web开发中&#xff0c;利用Paper.js库进行图形的绘制和交互操作是一种常见的实践。Paper.js是一个强大的矢量图形库&#xff0c;可以让开发者通过简洁的API完成复杂的图形操作。在本文中&#xff0c;我们将详细探讨如何使用Paper.…