【Linux】多路转接epoll

news2024/9/23 7:33:24

一、I/O多路转接 poll

1.1 poll函数接口

函数原型

函数参数

  • fds:是一个poll函数监听的结构列表,每一个元素中包含了三部分内容:文件描述符,监听的事件集合,返回的事件集合。
  • nfds:表示的是fds数组的长度
  • timeout:表示poll函数的超时时间,单位是毫秒

函数功能

       poll函数用于检查或者等待事件的发生,并在事件发生时返回结果。他通常在系统编程和网络编程中使用,用于异步操作和事件通知。

函数返回值

  • 返回值小于0,表示出错
  • 返回值等于0,表示poll函数等待超时
  • 返回值大于0,表示poll由于监听的文件描述符就绪而返回

pollfd的结构

struct pollfd
{
    int fd;        // 文件描述符
    short events;  // 发起响应事件
    short revents; // 返回响应事件
}

1.2 poll的优点和缺点

1.2.1 poll的优点

       不同于select使用三个位图来表示三个fdset的方式,poll使用一个pollfd的指针实现。pollfd的结构包含了要监视的event和发生的event,不再使用select(参数-值)传递的方式,接口使用比select更方便,poll并没有最大数量的限制(但是数量过大后性能也是会下降的)

1.2.2 poll的缺点

       poll中监听的文件描述符数目增多时,和select函数一样,poll返回时,需要轮询pollfd来获取就绪的描述符,每次调用poll都需要把大量的pollfd结构从用户态拷贝到内核中。同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。

二、多路转接之epoll(重要)

2.1 初始epoll

       我们来根据man手册的说法:是为处理大批量句柄而做了改进的poll。他是在2.5.44内核中被引进的,其几乎具备了之前所说的一切优点,被公认为Linux2,6下性能最好的多路I/O就绪的通知方法。

2.2 epoll的相关系统调用

2.2.1 epoll_create

函数原型

函数参数

  • 自从Linux2.6.8之后,size参数是被忽略的,只要大于0就行
  • 在用完这个函数之后,必须调用close()函数进行关闭

函数功能

       epoll_create是Linux中用来创建的一个epoll实例的系统调用,epoll是一种高效的I/O事件通知机制,特别适用于需要处理大量文件描述符的场景。epoll_create函数创建一个新的epoll实例,并返回一个文件描述符,该文件描述符用于与epoll实例进行交互。创建的epoll实例允许你注册感兴趣的事件,并在这些事件发生时通知你。

函数返回值

  • 成功时,返回一个非负整数,表示新的epoll实例的文件描述符
  • 失败时,返回-1并设置errno以表示错误原因

2.2.2 epoll_ctl

函数原型

函数参数

  • epfd:这个参数是epoll_create()的返回值(epoll的句柄)
  • op:表示动作,在下面会介绍三个宏
  • fd:表示需要监听的文件描述符
  • event:告诉内核需要监听什么事件

函数功能

       epoll_ctl允许你在一个epoll实例上管理文件描述符的事件监听。你可以使用它来注册(添加)。修改或者注销(删除)文件描述符的事件。

函数返回值

  • 成功时,返回0
  • 失败时,返回-1并设置errno以指示错误原因

第二个参数op的取值:

  • EPOLL_CTL_ADD:注册新的fd到epfd中
  • EPOLL_CTL_MOD:修改已经注册的fd的监听事件
  • EPOLL_CTL_DEL:从epfd中删除一个fd

struct epoll_event的结构如下:

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

  • EPOLLIN:表示对应的文件描述符可以读(包括对端的socket正常关闭)
  • EPOLLOUT:表示对应的文件描述符可以写
  • EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
  • EPOLLERR:表示对应的文件描述符发生错误
  • EPOLLHUP:表示对应的文件描述符被挂断
  • EPOLLET:将EPOLL设为边缘触发(Wdge Triggered)模式,这是相当于水平触发来说的
  • EPOLLONSHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列中

2.2.3 epoll_wait

函数原型

函数参数

  • epfd:由epoll_create返回的epoll实例的文件描述符
  • events:指向epoll_event结构体数组的指针,用于存储发生的事件,数据的大小由maxevents参数指定的
  • maxevents:events数组的大小,即最多可以返回多少个事件
  • timeout:超时时间,单位是毫秒。如果设置为-1,epoll_wait会无限期的阻塞,直到由事件发生。如果设置为0,则epoll_wait会立即返回,不会阻塞。

函数功能

       epoll_wait函数是Linux中用于等待和获取epoll实例中发生的事件的系统调用,他会阻塞到有事件发生或者超时。这个系统调用通常与epoll_create.epoll_ctl一起使用,形成一个完整的I/O多路复用机制。

函数返回值

  • 成功时,返回实际发生的事件数目
  • 失败时,返回-1并设置errno以指示错误原因

收集在epoll监控的事件中已经发生的事件

  1. 参数events是分配好的epoll_event结构体数组
  2. epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)
  3. macevents告诉这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size
  4. 参数timeout是超时时间(毫秒,0会立即返回,-1是永久阻塞)
  5. 如果函数调用成功,返回对应的I/O上已经准备好的文件描述符数目,如果返回0表示已经超时,返回小于0表示函数失败

2.3 epoll的工作原理

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

struct eventpoll{
    ....
    /*红黑树的根节点,这颗树中存储着所有添加到 epoll 中的需要监控的事件
    */
    struct rb_root rbr;
    /*双链表中则存放着将要通过 epoll_wait 返回给用户的满足条件的事件*/
    struct list_head rdlist;
    ....
};

       每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件

       这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间是lgn,其中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不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户,这个操作时间复杂度为O(1)

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

  1. 调用epoll_create创建一个epoll句柄
  2. 调用epoll_ctl,将要监控的文件描述符进行注册
  3. 调用epoll_wait,等待文件描述符就绪

2.4 epoll的优点(与select的缺点对应)

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

一定要注意:

网上有些博客中说,epoll中使用了内存映射机制

       内存映射机制:内核直接将就绪队列通过mmap 方式映射到用户态,避免了拷贝内存这样的额外性能开销

       这种说法是不准确的,我们定义的struct epoll_event是我们在用户空间中分配好的内存,势必还是需要将内核中的数据拷贝到这个用户空间的内存中的。

三、epoll的工作方式

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

3.1 水平触发(LT)

       我们来举一个例子:我们已经把一个tcp socket添加到epoll描述符中,这时候socket的另一端被写入了2KB的数据,调用epoll_wait,并且他会返回,说明他已经准备好了读取操作,然后调用read,只读取了1KB的数据,然后继续调用epoll_wait函数。

  • epoll默认状态下就是LT工作模式,这也解释了为什么我们在编写完epoll服务器后,没有编写其处理函数,当我们连接完毕后,服务器会一直循环,就是LT模式。
  • 当epoll检测到socket上的事件就绪的时候,可以不立刻进行处理,或者只处理一部分
  • 比如上面的例子中,由于只读了1K数据缓冲区中还剩下1K数据,在第二次调用epoll_wait时,epoll_wait仍然会立刻返回并通知socket读事件就绪
  • 直到缓冲区中所有的数据都被处理完毕,epoll_wait才不会立刻返回
  • 支持阻塞读写和非阻塞读写

3.2 边缘触发(ET)

       如果我们在第一步将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

3.3 LT和ET进行对比

       LT是epoll的默认行为,使用ET能够减少epoll触发的次数,但是代价就是强逼着程序员一次响应就将数据全部处理完毕

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

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

3.4 理解ET模式和非阻塞文件描述符

        使用ET模式下的epoll,需要将文件描述设置为非阻塞,这个不是接口上的要求,而是“工程实践”上的要求。假设这样的场景,服务器接收到一个10K的请求,会向客户端返回一个应答数据,如果客户端收不到应答,不会发送第二个10K请求。

       如果服务端写的代码是阻塞式的read,并且一次只read 1K数据的话(read不能保证一次就将所有的数据都读出来,参考man手册的说明,可能被信号打断)剩下的9K数据就会待在缓冲区中。

       此时由于epoll是ET模式,并不会认为文件描述符读就绪,epoll_wait就不会再次返回,剩下的9K数据会一直在缓冲区中,直到下一次客户端再次给服务器写数据,epoll_wait才能返回。

但是问题来了:

  • 服务器只读到了1K个数据,要10K读完才会给客户端返回响应数据
  • 客户端要读到服务器的响应,才会发送下一个请求
  • 客户端发送了下一个请求,epoll_wait才会返回,才能去读取缓冲区中剩余的数据

       所以,为了解决上述问题(阻塞read不一定能一下把完整的请求读完),于是就可以使用非阻塞轮询的方式来读缓冲区,保证一定能把完整的请求都读出来。而如果是LT没有这个问题,只要缓冲区中的数据没有读完,就能够让epoll_wait返回文件描述符读就绪。

四、epoll的使用场景

       epoll的高性能,是有一定的特定场景的,如果场景选择的不适宜,epoll的性能可能适得其反。对于多连接,并且多连接中只有一部分连接比较活跃时,比较适合使用epoll。

       例如, 典型的一个需要处理上万个客户端的服务器, 例如各种互联网 APP 的入口服务器, 这样的服务器就很适合 epoll. 如果只是系统内部, 服务器和服务器之间进行通信, 只有少数的几个连接, 这种情况下用 epoll 就并不合适. 具体要根据需求和场景特点来决定使用哪种 IO 模型。

五、epoll中的惊群问题

epoll的惊群效应_epoll惊群-CSDN博客

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

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

相关文章

VUE + NODE 历史版本安装

以node 12.20.0为例子,想下载哪个版本,后面写哪个版本 https://registry.npmmirror.com/binary.html?pathnode/v12.20.0/ 安装国内镜像7.1.0 cnpm npm install -g cnpm7.1.0 -g --registryhttps://registry.npmmirror.com 安装vue脚手架4.5.15 cnpm …

【有啥问啥】深入浅出马尔可夫链蒙特卡罗(Markov Chain Monte Carlo, MCMC)算法

深入浅出马尔可夫链蒙特卡罗(Markov Chain Monte Carlo, MCMC)算法 0. 引言 Markov Chain Monte Carlo(MCMC)是一类用于从复杂分布中采样的强大算法,特别是在难以直接计算分布的情况下。它广泛应用于统计学、机器学习…

【linux基础】linux中的开发工具(4)--调试器gdb的使用

目录 前言一,背景二,gdb的使用1. 启动 gdb 调试器:2. 罗列代码信息3. 运行程序4. 有关断点的操作(1) 打断点(2) 查看断点(3) 删除断点(4) 在一次调试中,断点是递增的(5) 关闭断点(6) 开启断点(7) 逐过程调试,相当于 F1…

我与Linux的爱恋:进程|进程的查看与管理|创建进程

​ ​ 🔥个人主页:guoguoqiang. 🔥专栏:Linux的学习 ​ 文章目录 一、进程的概念1.什么是进程2.在这里插入代码片多进程管理3.描述进程-PCB 2.查看进程与管理进程1.使用指令查看进程2.通过系统调用函数查看pid3.杀进程4.ppid&…

如何在 Visual Studio Code 中反编译具有正确行号的 Java 类?

优质博文:IT-BLOG-CN 问题 我在 macOS 中使用 vscode 版本 1.92.2,并安装了Java 扩展包v0.29.0。当我打开command click或right click->Go to definition一个没有源代码的类时,vscode 会使用 FernFlower 反编译器打开 .class 文件。但…

一步一步自制py脚本并且并且修改为exe可执行文件教学外附带SHA-1解密exe文件资源

第一步:安装 Python 下载 Python:访问 Python 官网 下载并安装最新版本的 Python。安装时选择添加到环境变量 PATH:在安装过程中,确保勾选“Add Python to PATH”选项。 第二步:编写 Python 脚本 创建一个新的 Pyth…

HTB-Base(strcmp函数绕过、sudo -l提权)

前言 各位师傅大家好,我是qmx_07,今天给大家讲解Base靶场,起点内容到此完结 渗透过程 信息搜集 服务器开放了22SSH服务 和 80HTTP服务 目录爆破 通过目录扫描出/login 和/asserts文件夹 发现/login 拥有目录遍历漏洞login.php.swp 是使用…

Mysql_使用简介

课 程 推 荐我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈虚 拟 环 境 搭 建 :&#x1…

循环练习 案例

swich新特性 jdk12 穿透 逢七过 //含有七和被七整除舍去 public class test1 {public static void main(String[] args){for (int i 1; i <100 ; i) {if(i%70||i%107||i/107){continue;}System.out.println(i);}} } 求平方根 //输入大于2的整数&#xff0c;求平方根&…

AI基础 L22 Uncertainty over Time I 时间的不确定性

Time and Uncertainty 1 Time and Uncertainty States and Observations • discrete-time models: we view the world as a series of snapshots or time slices • the time interval ∆ between slices, we assume to be the same for every interval • Xt: denotes the se…

C++编译环境(IDE)推荐及安装

IDE是什么 嗨嗨嗨&#xff0c;我又来水博文了 今天来给大家推荐几款好用的IDE IDE是集成开发环境&#xff08;Integrated Development Environment&#xff09;的缩写&#xff0c;是一种软件应用程序&#xff0c;提供了用于软件开发的各种工具和功能&#xff0c;包括代码编辑…

windows C++ 并行编程-PPL 中的取消操作(一)

并行模式库 (PPL) 中取消操作的角色、如何取消并行工作以及如何确定取消并行工作的时间。 运行时使用异常处理实现取消操作。 请勿在代码中捕捉或处理这些异常。 此外&#xff0c;还建议你在任务的函数体中编写异常安全的代码。 例如&#xff0c;可以使用获取资源即初始化 (RA…

LidarView之定制版本号

介绍 LidarView软件需要关注2个版本号&#xff1a;1.Application版本号&#xff1b;2.安装包版本号 Application版本号 改变LV_VERSION_FULL可达到改变软件版本号的目的 SET(LV_VERSION_FULL "V1.3.0")标题栏版本号 关于对话框 安装包版本号 在Inno Setup Compi…

【退役之再次线上部署】Spring Boot + VUE + Nginx + MySQL

这篇博客写在凌晨 4 点 20 分&#xff0c;这个时候我刚线上部署完成 web 项目&#xff0c;自己写的全栈项目 这个点儿&#xff0c;也睡不着了&#xff0c;索性就写篇博客记录一下 一、踩坑实录 这个是 最重要的&#xff0c;所以写在前面 Nginx 配置文件 location location /a…

如何做系统架构?从动态系统思考的角度

在动态系统思考的背景下&#xff0c;系统架构不再只是一个静态的、结构化的设计&#xff0c;而是一个随着时间推移、基于不同要素互动产生涌现行为的动态过程。系统架构师的任务不仅仅是定义系统的形态和结构&#xff0c;更是通过剖析系统的互动网络、功能涌现和使用场景&#…

文章解读与仿真程序复现思路——电力系统自动化EI\CSCD\北大核心《计及抢修人员调度的配电网信息-物理协同恢复策略》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

个人随想-向量数据库,你到底应该选择谁?

随着大模型的新起&#xff0c;vectorstore这1、2年也非常的火。从以前只能用chroma到现在几十种向量数据库&#xff0c;选都选不过来。 以我接触过的很多公司来说&#xff0c;他们去选择向量数据库的时候&#xff0c;很多都和迷茫&#xff0c;不知道应该选择哪个向量数据库&am…

MySQl篇(数据类型)(持续更新迭代)

目录 常见类型一&#xff1a;数值类型 常见类型二&#xff1a;字符串类型 一、文本字符串类型 1. char & varchar 1.1. CHAR(M)类型 1.2. VARCHAR(M)类型 1.3. 两者应用 2. enum & set 二、二进制字符串类型 1. BINARY & VARBINARY类型 2. 二进制字符串和…

C++ IO框架

文章目录 I/O 复用概述I/O 模型一个输入操作的两个阶段 select 函数概述详细解析函数内容详解select总结 poll 函数概述详细解析函数内容详解 epoll 函数概述基础API注意事项总结一下select, poll, epoll的区别 Reactor 和 Proactor概述概念服务器连接多个客户端的业务场景解决…

【DVWA】——File Upload(文件上传)

&#x1f4d6; 前言&#xff1a;文件上传漏洞是由于对上传文件未作过滤或过滤机制不严&#xff08;文件后缀或类型&#xff09;&#xff0c;导致恶意用户可以上传脚本文件&#xff0c;通过上传文件可达到控制网站权限的目的。 目录 &#x1f552; 1. Low&#x1f552; 2. Mediu…