[Linux][网络][高级IO][三][IO多路转接][epoll]详细讲解

目录

  • 1.IO多路转接之epoll
    • 1.epoll初识
    • 2.epoll_create()
    • 3.epoll_ctl()
    • 4.epoll_wait()
    • 5.epoll工作原理
    • 6.epoll使用过程三部曲
    • 7.epoll的优点(和select缺点对应)
    • 8.思考 && 问题
  • 2.epoll工作方式
    • 0.感性理解 && 铺垫
    • 1.水平触发(Level Triggered)工作模式
    • 2.边缘触发(Edge Triggered)工作模式
    • 3.对比LT和ET
    • 4.理解ET模式和非阻塞文件描述符
    • 5.epoll的使用场景
    • 6.思考 && 问题


1.IO多路转接之epoll

1.epoll初识

  • 按照man手册的说法:是为处理大批量句柄而作了改进的poll
    • 但个人认为是epoll是poll pro max版本:P
  • 它是在2.5.44内核中被引进的
    • 它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法

2.epoll_create()

  • 功能:创建一个epoll模型,通过句柄相勾连
    • 用完之后,必须调用close()关闭
  • 原型:int epoll_create(int size);
  • 参数size:现在已经被废弃,忽略即可 – 随便写,128/256即可
  • 返回值
    • 成功返回一个文件描述符
    • 失败返回-1,同时errno被设置

3.epoll_ctl()

  • 参数
    • epfd:epoll_create()的返回值 – epoll的句柄
    • op:表示要进行的操作,用三个宏表示
      • EPOLL_CTL_ADD:注册新的fd到epfd中
      • EPOLL_CTL_MOD:修改已经注册的fd的监听事件
      • EPOLL_CTL_DEL:从epfd中删除一个fd
    • fd:需要监听的fd
    • event:是一个epoll()监听的结构,告诉内核需要监听什么事,其中event成员可以是以下几个宏的集合
      • **EPOLLIN:表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
        • 让epoll监控,接收函冲去有没有数据,如果有,告诉用户调用recv去接收
      • EPOLLOUT:**表示对应的文件描述符可以写
        • 让epoll监控,发送缓冲区还有没有空闲的空间,如果有,才可以调用send等拷贝函数将数据拷贝到底层的发送缓冲区
      • EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
      • EPOLLERR:表示对应的文件描述符发生错误
      • EPOLLHUP:表示对应的文件描述符被挂断
      • EPOLLET:将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的
      • EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
  • 返回值
    • 成功返回0
    • 失败返回-1,同时errno被设置
  • struct epoll_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 */
} __EPOLL_PACKED;

4.epoll_wait()

  • 功能:收集在epoll监控的事件中已经发生的事件,内核告知用户,哪些文件描述符上的什么时间已经就绪
  • 原型:int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  • 参数
    • epfd:epoll_create()的返回值 – epoll的句柄
    • event:分配好的epoll_event结构体数组
    • maxevents:告诉内核这个events有多大
    • timeout
      • 表示epoll()的超时时间,单位是毫秒
      • 设置为0,表示非阻塞等待
      • 设置为-1,表示阻塞等待
  • 返回值
    • 执行成功则返回文件描述词状态已改变的个数
      • 有几个fd上的事件就绪,就返回几
      • epoll返回的时候,会将所有的event按照顺序放入到events数组中,一共有**[返回值]**个
      • 如果底层就绪的sock非常多,revs承装不下,怎么办?
        • 不影响,一次拿不完,那就下一次拿(LT模式)
    • 如果返回0代表在描述词状态改变前已超过timeout时间,没有返回
    • 当有错误发生时则返回-1,错误原因存于errno

5.epoll工作原理

  • 当某一进程调用epoll_create()时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关
struct eventpoll
{// ..../*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/struct rb_root rbr;/*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/struct list_head rdlist;// ....
};
  • 每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl()向epoll对象中添加进来的事件
    • 这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来
    • 而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系
      • 也就是说,当响应的事件发生时,会调用这个回调方法
        • 不需要OS进行频繁遍历了
    • 这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中(本质是队列)
void ep_poll_callback(int fd)
{// 1.根据红黑树上节点要关心的事件,结合已经发生的事件// 2.自动根据fd和已经发生的事件,构建就绪节点// 3.自动将构建好的节点,插入到就绪队列中
}
  • 在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)

      请添加图片描述

6.epoll使用过程三部曲

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

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

  • 接口使用方便:虽然拆分成了三个函数,但是反而使用起来更方便高效,不需要每次循环都设置关注的文件描述符,也做到了输入输出参数分离开
  • 数据拷贝轻量:只在合适的时候调用EPOLL_CTL_ADD将文件描述符结构拷贝到内核中,这个操作并不频繁
    • 而select/poll都是每次循环都要进行拷贝
  • 事件回调机制:避免使用遍历,而是使用回调函数的方式,将就绪的文件描述符结构加入到就绪队列中,epoll_wait()返回直接访问就绪队列就知道哪些文件描述符就绪,这个操作时间复杂度O(1),即使文件描述符数目很多,效率也不会受到影响
  • 没有数量限制:文件描述符数目无上限
  • 注意网上有些博客说,epoll中使用了内存映射机制
    • 内存映射机制:内核直接将就绪队列通过mmap的方式映射到用户态,避免了拷贝内存这样的额外性能开销
    • 这种说法是不准确的,用户定义的struct epoll_event是在用户空间中分配好的内存,势必还是需要将内核的数据拷贝到这个用户空间的内存中的

8.思考 && 问题

  • 细节1:红黑树中,是要有key值的?
    • 这里由文件描述符充当
  • **细节2:**用户只需要设置关系,获取结果即可,不用再关心任何对fd与event的管理细节
  • 细节3:epoll为什么高效呢?
    • epoll底层用红黑树管理事件,以前都是用数组
    • 以前事件就绪需要OS去遍历数组,而现在只需要通过回调函数回调即可,不需要OS花更多精力在事件检测上了
    • 以前事件就绪,还是需要OS去遍历数组,现在有就绪队列,获取就绪节点的时间复杂度为O(1)
  • **细节4:**底层只要有fd就绪了,OS自己会构建节点,连入到就绪队列中,上层只需要通过epoll_wait()不断地从就绪队列中将数据拿走,就完成了获取就绪事件的任务
    • 即:生产者消费者模型,存在共享资源
    • epoll已经保证所有的epoll接口都是线程安全的
  • 细节5:如果底层没有就绪事件呢?上层应该怎么办?
    • 阻塞等待

2.epoll工作方式

0.感性理解 && 铺垫

  • 你正在吃鸡,眼看进入了决赛圈,你妈饭做好了,喊你吃饭的时候有两种方式:
    • 如果你妈喊你一次,你没动,那么你妈会继续喊你第二次,第三次…(亲妈,水平触发)
    • 如果你妈喊你一次,你没动,你妈就不管你了(后妈,边缘触发)
  • epoll有两种工作方式
    • 水平触发(LT)
    • 边缘触发(ET)
  • 假如有这样一个例子:
    • 已经把一个tcp socket添加到epoll描述符
    • 这个时候socket的另一端被写入了2KB的数据
    • 调用epoll_wait,并且它会返回,说明它已经准备好读取操作
    • 然后调用read,只读取了1KB的数据
    • 继续调用epoll_wait…

1.水平触发(Level Triggered)工作模式

  • epoll默认状态下就是LT工作模式
    • 当epoll检测到socket上事件就绪的时候,可以不立刻进行处理,或者只处理一部分
    • 如上面的例子,由于只读了1K数据,缓冲区中还剩1K数据,在第二次调用epoll_wait()时,epoll_wait()仍然会立刻返回并通知socket读事件就绪
    • 直到缓冲区上所有的数据都被处理完,epoll_wait()才不会立刻返回
    • 支持阻塞读写和非阻塞读写

2.边缘触发(Edge Triggered)工作模式

  • 如果在第1步将socket添加到epoll描述符的时候使用了EPOLLET标志,epoll进入ET工作模式
    • 当epoll检测到socket上事件就绪时,必须立刻处理
    • 如上面的例子,虽然只读了1K的数据,缓冲区还剩1K的数据,在第二次调用epoll_wait()的时候,epoll_wait()不会再返回了
      • 也就是说,ET模式下,文件描述符上的事件就绪后,只有一次处理机会
    • ET的性能比LT性能更高(epoll_wait()返回的次数少了很多)
      • Nginx默认采用ET模式使用epoll
    • 只支持非阻塞的读写
  • 数据从无到有、从有到多(变化)的时候,epoll才会通知
  • select和poll其实也是工作在LT模式下,epoll既可以支持LT,也可以支持ET

3.对比LT和ET

  • LT是epoll的默认行为,使用ET能够减少epoll触发的次数,但是代价就是强逼着程序猿一次响应就绪过程中就把所有的数据都处理完
  • 相当于一个文件描述符就绪之后,不会反复被提示就绪,看起来就比LT更高效一些
    • 但是在LT情况下如果也能做到每次就绪的文件描述符都立刻处理,不让这个就绪被重复提示的话,其实性能也是一样的
  • 另一方面,ET的代码复杂程度更高了

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()返回文件描述符读就绪

5.epoll的使用场景

  • epoll的高性能,是有一定的特定场景的,如果场景选择的不适宜,epoll的性能可能适得其反
    • 对于多连接,且多连接中只有一部分连接比较活跃时,比较适合使用epoll
    • **例如:**典型的一个需要处理上万个客户端的服务器,例如各种互联网APP的入口服务器,这样的服务器就很适合epoll
  • 如果只是系统内部,服务器和服务器之间进行通信,只有少数的几个连接,这种情况下用epoll就并不合适
  • 具体要根据需求和场景特点来决定使用哪种IO模型

6.思考 && 问题

  • 为什么一定要听ET的?
    • 如果不取,底层再也不通知了,上层调用时,无法再获取该fd的就绪事件了,无法再调用recv了
    • 即:变相的数据丢失了
    • 强逼着程序猿一次响应就绪过程中就把所有的数据都处理完
  • 怎么保证把本轮数据全部读完了呢?
    • 必须一直循环读取,在最后一次正常读取完毕,势必还要进行下一次读取(无法确认是否读取完成)
      • 必然会阻塞,导致进程挂起
    • 为了避免这个问题,在ET模式下工作,sock必须被设置为非阻塞
    • 只要循环读取一直读取直到读取出错EAGAIN
  • 可以暂时不处理LT的通知事件吗?
    • 可以,因为如果不取,或者取了一部分,用户并不担心,因为底层还是会让fd就绪,还有读取的机会
  • 在LT情况下如果也能做到每次就绪的文件描述符都立刻处理,不让这个就绪被重复提示的话,其实性能也是一样的
  • ET模式为何更高效?
    • 更少的返回次数
    • ET****模式强逼着程序猿一次响应就绪过程中就把所有的数据都处理完,应用层尽快的取走了缓冲区中的数据,那么在单位时间下,该模式下工作的服务器,就可以在一定程度上,给发送方回送一个更大的接收窗口,所以对方就可以有更大的滑动窗口,一次向服务器发送更多的数据,提高IO吞吐

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

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

相关文章

webpack优化构建速度示例-externals:

externals 配置项主要用于防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再从外部获取这些扩展依赖(external dependencies)。这样做的主要目的是为了解决打包文件过大…

cypress的安装使用

cypress npm install -g cnpm --registryhttps://registry.npm.taobao.org cypress的启动打开 npx cypress open js函数的回调 function print(string,callback){console.log(string)callback() } print("a",function(){print("b",function(){console.l…

spark知识点

目录 第二章Scala基础 一.Scala常用数据类型 二.定义与使用数组 三.定义与使用函数 四.定义与使用列表 五.定义与使用集合 六.定义与使用映射 七.定义与使用元组 第三章Spark编程基础 一.从内存中读取创建RDD 二.从外部存储系统中读取数据创建RDD 三.RDD方法归纳 1.…

Qt+C++串口调试工具

程序示例精选 QtC串口调试工具 如需安装运行环境或远程调试,见文章底部个人QQ名片,由专业技术人员远程协助! 前言 这篇博客针对《QtC串口调试工具》编写代码,代码整洁,规则,易读。 学习与应用推荐首选。 …

异步编程CompletableFuture总结

文章目录 1. 简介:2. 比较1、传统方式2、使用CompletableFuture:异步执行返回值 3、组合处理:anyOfallof : 4. 异步回调:thenAcceptthenApplywhenComplete等同于 thenAccepthandel()等同于thenApply 5. 常用方法:1、su…

Vue的学习 —— <路由与网络请求>

目录 前言 正文 一、初识路由 二、初识Vue Router 1、安装Vue Router 2、Vue Router基本使用 三、路由重定向 四、嵌套路由 前言 在之前的学习中了解到单页Web应用通常只有一个HTML页面,所有的组件展示和切换都在这个页面上完成。虽然我们可以通过动态组件…

基于CentOS-7搭建hadoop3.3.6大数据集群(保姆级教程)

目录 安装虚拟机 为hadoop用户添加权限 关闭防火墙 修改主机名以及ip地址映射 配置ip 连接xshell ,以hadoop用户登录 创建目录并将该文件夹权限赋予hadoop用户 安装配置jdk 关闭虚拟机,克隆其他两个节点 修改主机名和ip地址 配置免密登录 安装…

基于Vue和uni-app的增强型多选框Checkbox组件开发

基于Vue和uni-app的增强型多选框(Checkbox)组件开发 摘要 随着前端技术的不断发展和用户体验要求的提升,传统的Checkbox组件已不能满足所有需求。本文介绍了一种基于Vue和uni-app的增强型多选框(Checkbox)组件的开发…

C语言 | Leetcode C语言题解之第91题解码方法

题目&#xff1a; 题解&#xff1a; int numDecodings(char* s) {int n strlen(s);// a f[i-2], b f[i-1], c f[i]int a 0, b 1, c;for (int i 1; i < n; i) {c 0;if (s[i - 1] ! 0) {c b;}if (i > 1 && s[i - 2] ! 0 && ((s[i - 2] - 0) * 10…

程序员的实用神器之——通义灵码

通义灵码介绍 “通义灵码”是一款基于阿里云通义代码大模型打造的智能编码助手&#xff0c;产品于2023年10月31日云栖大会上&#xff0c;正式对外发布。新手亦能驾轻&#xff0c;老手恒常运&#xff0c;唯手熟尔。 通义灵码产品介绍_智能编码助手_AI编程_云效(Apsara Devops)…

括号生成[中等]

优质博文&#xff1a;IT-BLOG-CN 一、题目 数字n代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;["((()))","(()())","(())(…

mysql主从热备+keepalived 部署mysql高可用主备模式

目录 1、环境准备 2、分别在主服务器和备用服务器上安装keepalived 3、修改keepalived服务的配置文件 3.1 修改主服务器上的keepalive服务的配置文件 3.2 修改备用服务器上的keepalive服务配置文件 4、编写mysql监控脚本放到主服务器上 5、在主服务器和备用服务器上查看…

太极图形学——高级数据结构——稀疏

我们在之前学习的稠密数据结构中主要可以分为root&#xff0c;dense&#xff0c;和field三个&#xff0c;而实际上我们还可以定义一个bitmasked和pointer这两个就是用来帮助我们维护空间稀疏性用的 举一个例子&#xff0c;首先是一个稠密结构&#xff0c;它的数据利用率很低 那…

万物皆可监控(shell脚本监控TIDB-DM和DSG同步状态)

监控的方式有很多&#xff0c;常用的有zabbix和prometheus平台&#xff0c;理论上都可以做到对有状态服务的监控&#xff0c;因为我个人对这两个监控平台不是很熟悉&#xff0c;所以一般喜欢使用shell脚本来做监控&#xff1b; 纯oracle 数据库的监控推荐使用EMCC&#xff0c;…

Unity之Image图片挖洞

最近开发一款打地鼠小游戏&#xff0c;地鼠是3D的&#xff0c;身体比较庞大&#xff0c;管道比较窄小&#xff0c;所以管道其实没办法将地鼠完全遮盖&#xff0c;于是想了一下几个方法。 1&#xff0c;让美术把3D物体裁剪掉&#xff0c;让地鼠的形态体积不至于露出UI&#xff…

短视频矩阵系统/源码----可视化剪辑技术独家开发

现阶段市面上大多矩阵软件都非常程序化且需要使用者具有较强的逻辑思维能力或剪辑经验&#xff0c;这使得一些个人、团队、企业在使用时无形中增加了学习成本&#xff0c;剪辑出来的效果大多不尽如人意&#xff0c;发出来的视频没有流量&#xff0c;根本达不到预期效果。 如何提…

GIT基础02 多机器协作等命令

前言 首先我们知道git给我们提供了分支管理的功能 我们一般使用master分支作为线上环境,master分支一般是一个稳定的分支 我们通常是会创建一个其他分支进行开发,这样不会影响线上的机器运行 如果没有git提供这样的分支功能,就无法做到这一套了 指令学习 假设软件出现问题咋办…

Redis教程(二):Redis在Linux环境下的安装

Linux环境下安装&#xff1a; 下载地址&#xff1a;Downloads - Redis 安装步骤&#xff1a; 下载得到一个 tar.gz 压缩文件 上传到Linux的/opt/soft目录&#xff0c;使用以下命令解压 tar -zxvf redis-6.2.14.tar.gz Linux安装基本环境gcc&#xff0c;安装命令 yum insta…

基于Vue和uni-app的增强型单选ccRadioView组件开发

标题&#xff1a;基于Vue和uni-app的增强单选组件ccRadioView的设计与实现 摘要&#xff1a;本文将详细介绍如何使用Vue和uni-app构建一个简单、好用且通用的单选框组件ccRadioView。该组件提供了单选列表的功能&#xff0c;并支持反向传值&#xff0c;方便开发者快速实现单选…

强化学习的优化策略PPO和DPO

DPO DPO(直接偏好优化)简化了RLHF流程。它的工作原理是创建人类偏好对的数据集&#xff0c;每个偏好对都包含一个提示和两种可能的完成方式——一种是首选&#xff0c;一种是不受欢迎。然后对LLM进行微调&#xff0c;以最大限度地提高生成首选完成的可能性&#xff0c;并最大限…