【计算机网络笔记九】I/O 多路复用

阻塞 IO 和 非阻塞 IO

阻塞 I/O 和 非阻塞 I/O 的主要区别:

  • 阻塞 I/O 执行用户程序操作是同步的,调用线程会被阻塞挂起,会一直等待内核的 I/O 操作完成才返回用户进程,唤醒挂起线程
  • 非阻塞 I/O 执行用户程序操作是异步的,读写操作调用后内核会立即返回给用户一个状态值,用户可以立即执行其他操作。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

多路复用的概念

先看一个例子

在这里插入图片描述

这里一旦使用 fgets() 方法等待标准输入,就没有办法在 Socket 有数据的时候读出数据:

在这里插入图片描述

I/O 多路复用:把标准输入Socket等都看做 I/O 的一路,多路复用的意思,就是在任何一路 I/O 有事件发生的情况下,通知应用程序去处理相应的 I/O 事件

在这里插入图片描述

多路中的每一路本质上就是一个 fd:

在这里插入图片描述

什么是 I/O 事件,例如:

  • I/O 事件一:fd 对应的内核缓冲区来了数据,可读;
  • I/O 事件二:fd 对应的内核缓冲区空闲,可写;
  • I/O 事件三:fd 出现异常

多路复用技术的实现主要有:

  • ① select
  • ② poll
  • ③ epoll

I/O 多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但 select,poll,epoll 本质上都是同步 I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步 I/O 则无需自己负责进行读写,异步 I/O 的实现会负责把数据从内核拷贝到用户空间。

select 多路复用

首先,应用进程需要告诉内核它感兴趣的 I/O 事件,然后,内核感知设备发生的 I/O 事件,然后通知应用进程:你感兴趣的 fd 发生了你感兴趣的 I/O 事件类型。

在这里插入图片描述

多路中的每一路本质上就是一个 fd。

select函数定义如下:

/* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */ 
#include <sys/time.h>
#include <sys/types.h> 
#include <unistd.h>int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 

fd_set

其中 fd_set 结构体定义如下:

#define __FD_SETSIZE 1024 typedef struct {unsigned long fds_bits[__FD_SETSIZE / (8 * sizeof(long))]; 
} __kernel_fd_set; 

这里一个 long8 个字节(64位系统),一个字节占 8 位,8 * sizeof(long) 总共占 64 位。

因此 __FD_SETSIZE / (8 * sizeof(long)) 的值是 1024/64 = 16,即数组大小16个(0-15),16long 数组总共有 64*16 = 1024 位 。

所以,fd_set 是长度为 1024 的比特位数组,数组索引表示文件描述符。

在这里插入图片描述

如何设置这些描述符集合
void FD_CLR(int fd, fd_set *set); 
int FD_ISSET(int fd, fd_set *set); 
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set); 
  • FD_ZERO:用来将这个 set 的所有元素都设置成0
  • FD_SETset[fd] = 1;
  • FD_CLRset[fd] = 0;
  • FD_ISSETset[fd] == 1 ? true : false

timeval

struct timeval {long tv_sec; /* seconds */ long tv_usec; /* microseconds */ 
};

最后一个参数是 timeval 时间结构体

  • ① 设置成空(NULL),表示如果没有 I/O 事件发生,则 select 一直等待下去。
  • ② 设置一个非零的值,这个表示等待固定的一段时间后从 select 阻塞调用中返回
  • ③ 将 tv_sectv_usec都设置成0,表示根本不等待,检测完毕立即返回。这种情况使用得比较少。

select 执行流程

select 底层调用流程图:https://www.processon.com/view/link/62d3fdfce401fd259605006d

下面是简要描述:

int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

  • select 函数监视的文件描述符分 3 类,分别是 writefdsreadfdsexceptfds
  • 调用后 select 函数会阻塞,直到有描述符就绪(可读/可写/有except),或者超时(timeout 指定等待时间,如果立即返回设为 null 即可),函数返回。
  • select 函数返回后,可以通过遍历 fdset,来找到就绪的描述符。
  • fd_set是一个只包含长度为1024的比特位数组的结构体,数组的索引表示文件描述符,数组的值用10表示是否对当前索引的fd的 I/O 事件感兴趣。
  • 将这三个fd_set文件描述符拷贝到内核态的三个数组,并创建三个对应的结果数组
  • 内核中for循环不断遍历 3 个fd_set数组中所有的fd,看其是否有可读/可写 I/O 事件发生,如果有,将结果数组的对应比特位设置为1,如果没有 I/O 事件发生,将该fd对应进程放入等待队列中(每个fd都有一个进程等待队列,当fd发生 I/O 事件时会唤醒这个进程)
  • for循环结束后,如果一个fd都没有 I/O 事件发生,则当前调用select的进程进入休眠,让出 CPU 使用权,如果有某个fd发生 I/O 事件,就将结果数组返回,拷贝到用户态空间的三个fd_set的数组中

select 缺点

  • ① 支持的文件描述符的个数是有限的。在 Linux 系统中,select 的默认最大值为 1024

  • ② 内核会修改用户态传递的 readfdswritefds 参数的值

最佳实践:多路复用 + 非阻塞 IO

在这里插入图片描述

在这里插入图片描述

poll 多路复用

#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout); 
  • fdspollfd 数组,存放应用进程所有感兴趣的fd及其相应的 IO 事件
  • nfdspollfd 数组的大小,可以大于1024,突破文件描述符个数限制
  • timeout:超时时间
    如果是一个 < 0 的数,表示在有事件发生之前永远等待;
    如果是 0,表示不阻塞进程,立即返回;
    如果是一个 > 0 的数,表示poll调用方等待指定的毫秒数后返回。

其中 pollfd 结构体定义如下:

struct pollfd { int fd;         /* file descriptor */ short events;   /* requested events */ short revents;  /* returned events */ 
};
  • fd:感兴趣的文件描述符
  • events:注册这个 fd 下感兴趣的 I/O 事件(可读事件、可写事件等)
  • revents:内核通知的这个fd下发生的 I/O 事件,称为 returned events

poll 中感兴趣的 IO 事件有哪些:

#define POLLIN Θx0001   /* any readable data available */
#define POLLPRI 0x0002  /* 00B/Urgent readable data */
#define POLLOUT 0x0004  /* file descriptor is writeable */#define POLLERR 0x0008   /* 一些错误发送 */
#define POLLHUP Θx0010   /* 描述符挂起 */
#define POLLNVAL Θx0020  /* 请求的事件无效 */

在这里插入图片描述
在这里插入图片描述

poll 执行流程

poll 底层调用流程图:https://www.processon.com/view/link/62d3fe350e3e74607274c241

其大致流程跟 select 相似

select vs poll

int poll (struct pollfd *fds, unsigned int nfds, int timeout);

  • 不同于 select 使用三个位图来表示三个 fdset 的方式,poll 使用一个 pollfd 数组实现。
  • pollfd 结构体包含了要监视的文件描述符 fd, 对该fd感兴趣的 IO 事件 events 和内核通知 fd 下发生的 IO 事件 revents
  • 使用nfds设置数组pollfd的大小,没有最大数量限制。
  • select 函数一样,poll 返回后,需要轮询 pollfd 来获取就绪的描述符。

最主要的区别是以下两点:

  1. select 支持最大的 fds1024poll 则没有这个限制
  2. select 还需要遍历不感兴趣的 fd, 但是 poll 只关心感兴趣的 fd (不感兴趣的fd不存在pollfd数组中)

select/poll 的缺点

  • 每次调用 select/poll 时,都需要在用户态和内核态之间拷贝数据
  • 在内核中,select/poll 在检测 IO 事件时,只要有一个 fd 有事件发生,就会线性扫描所有的 fds,时间复杂度 O(n)

epoll 多路复用

epoll 是在 Linux 2.6 内核中提出的,是之前的 selectpoll 的增强版本。相对于 selectpoll 来说,epoll 更加灵活,没有描述符限制

在这里插入图片描述

从图中可以明显地看到,epoll 的性能是最好的,即使在多达 10000 个文件描述的情况下,其性能的下降和有10个文件描述符的情况相比,差别也不是很大。而随着文件描述符的增大,常规的 selectpoll 方法性能逐渐变得很差。

epoll 的使用

epoll_create:创建 epoll 实例
#include <sys/epoll.h> int epoll_create(int size); 

创建一个 epoll 实例,从 Linux 2.6.8 开始,参数 size 被忽略,但是必须大于0

关于这个参数size,在一开始的 epoll_create 实现中,是用来告知内核期望监控fd的数量,然后内核使用这部分的信息来初始化内核数据结构,在新的实现中,这个参数不再被需要,因为内核可以动态分配需要的内核数据结构。

我们只需要注意,每次将 size 设置成一个大于0的整数就可以了。

epoll_create() 返回一个文件描述符,这个文件描述符对应着这个epoll实例。Linux 中一切皆文件,epoll 也被看成是一个文件,在内核中也有 file 实例与之对应。

epoll_ctl:操作 epoll 实例中的 IO 事件
#include <sys/epoll.h>int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 
  • epfdepoll_create 创建的 epoll 实例对应的文件描述符
  • op:对 IO 事件的操作类型
    EPOLL_CTL_ADD: 向 epoll 实例添加 fd 对应的事件;
    EPOLL_CTL_DEL: 向 epoll 实例删除 fd 对应的事件;
    EPOLL_CTL_MOD:修改 fd 对应的事件。
  • fd:注册的事件的文件描述符,比如一个监听套接字(socket)
  • event:表示注册的事件类型

其中 epoll_event 结构体定义如下:

struct epoll_event { uint32_t events;   /* Epoll events */epoll_data_t data; /* User data variable*/ 
};

events 就表示事件类型,Epoll 中的 IO 事件类型主要有以下几种:

  • EPOLLIN:表示对应的文件描述字可以读
  • EPOLLOUT:表示对应的文件描述字可以写
  • EPOLLRDHUP:表示套接字的一端已经关闭,或者半关闭
  • EPOLLHUP:表示对应的文件描述字被挂起
  • EPOLLET:设置为edge-triggered,默认为 level-triggered
epoll_wait:等待内核 I/O 事件的分发
#include <sys/epoll.h>int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  • epfdepoll_create 创建的 epoll 实例对应的文件描述符

  • events: 接口的返回参数,内核返回给用户态应用进程所有需要处理的 I/O 事件,这是一个数组,数组中的每个元素都是一个需要待处理的 I/O 事件。epoll 会把发生的事件的集合从内核复制到 events 数组中。

    events数组是一个用户分配好大小的数组,数组长度大于等于maxevents。(events不可以是空指针,内核只负责把数据复制到这个 events 数组中,不会去帮助我们在用户态中分配内存)

    其中 events 表示具体的事件类型,事件类型取值和 epoll_ctl 可设置的值一样

  • maxevents:一个大于0的整数,表示本次 epoll_wait 可以返回的最大事件值。通常maxevents参数与预分配的events数组的大小是相等的。

  • timeout:超时事件,如果这个值设置为-1,表示不超时;如果设置为0则立即返回,即使没有任何 I/O 事件发生。设置 > 0 的数值,则表示等待一段时间内没有事件发生,则超时。

epoll 执行流程

epoll 原理图:https://www.processon.com/view/link/62d3fe5a7d9c08119ce3bbbc

  • epoll_cteate() : 内核会创建一个 eventpoll 结构体实例,返回一个文件描述符与 eventpoll 实例相对应

  • epoll_ctl():注册感兴趣的 fd,以及对该 fd 感兴趣的事件类型

  • epoll_wait():等待内核 IO 事件分发,返回值表示要处理 IO 事件的数量,最大不超过 maxevents,需要返回给用户态的所有需要处理的 IO 事件存放在events数组中,events数组的大小由epoll_wait()返回值决定。

  • 用户注册的fd有 IO 事件发生时,就会将其对应的 epitem 挂到一个双向链表rdllist中(位于eventpoll 结构体中)

  • epoll_wait 就是在一个循环中不断查看这个rdllist链表中是否有就绪事件,如果有,就将就绪事件返回,拷贝到用户空间中,如果没有,当前进程就进入休眠,CPU 被调度给其他进程使用

  • 进程被唤醒的条件:
    1)进程超时
    2)进程收到一个signal信号
    3)某个fd上有事件发生
    4)当前进程被CPU重新调度

epoll vs select/poll

select/poll 的缺点:

  • ① 每次调用 select/poll 时都需要在用户态/内核态之间进行拷贝数据

  • ② 在内核中,select/poll 在检测 IO 事件时,只要有一个 fd 有事件发生,就会线性扫描所有的 fds,时间复杂度 O(n)

与 select/poll 的缺点相比,epoll 的高效之处是:

  • 将应用进程关心的 fd 直接维护在内核中,不再进行用户态和内核态间的拷贝(使用红黑树维护,高效增删改)
  • epoll 只处理有 IO 事件发生的 fd,不会扫描所有的 fds,时间复杂度 O(1)

在这里插入图片描述

条件触发 和 边缘触发

条件触发(Level_triggered):又叫水平触发,当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait() 时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率!!!

边缘触发(Edge_triggered):当被监控的文件描述符上有可读写事件发生时,epoll_wait() 会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait() 时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!

总结:

  • 条件触发:只要满足事件的条件,比如有数据需要读,就一直不断的把这个事件传递给用户
  • 边缘触发:只有第一次满足条件的时候才触发,之后就不会再传递同样的事件了
  • 边缘触发的效率比条件触发的效率高,epoll 支持边缘触发和条件触发,默认是条件触发,select 和 poll 都是条件触发

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

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

相关文章

909. 蛇梯棋

909. 蛇梯棋 题目-中等难度示例1. bfs 题目-中等难度 给你一个大小为 n x n 的整数矩阵 board &#xff0c;方格按从 1 到 n2 编号&#xff0c;编号遵循 转行交替方式 &#xff0c;从左下角开始 &#xff08;即&#xff0c;从 board[n - 1][0] 开始&#xff09;每一行交替方向…

基于Android+OpenCV+CNN+Keras的智能手语数字实时翻译——深度学习算法应用(含Python、ipynb工程源码)+数据集(四)

目录 前言总体设计系统整体结构图系统流程图 运行环境模块实现1. 数据预处理2. 数据增强3. 模型构建4. 模型训练及保存5. 模型评估6. 模型测试1&#xff09;权限注册2&#xff09;模型导入3&#xff09;总体模型构建4&#xff09;处理视频中的预览帧数据5&#xff09;处理图片数…

github搜索技巧

指定语言 language:java 比如我要找用java写的含有blog的内容 搜索项目名称包含关键词的内容 vue in:name 其他如项目描述跟项目文档&#xff0c;如下 组合使用 vue in:name,description,readme 根据Star 或者fork的数量来查找 总结 springboot vue stars:>1000 p…

程序启动-大数据平台搭建

1、启动zookeeper集群 /home/cluster/zookeeper.sh start /home/cluster/zookeeper.sh stop 2、启动hadoop和yarn集群 /home/cluster/hadoop-3.3.6/sbin/start-dfs.sh /home/cluster/hadoop-3.3.6/sbin/start-yarn.sh /home/cluster/hadoop-3.3.6/sbin/stop-dfs.sh /home/clust…

Java括号匹配

目录 一、题目描述 二、题解 一、题目描述 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭…

AG35学习笔记(二):安装编译SDK、CMakeLists编译app、Scons编译server

目录 一、概述二、安装SDK2.1 网盘SDK - 权限不够2.2 bj41 - 需要交叉source2.3 mullen - relocate_sdk.py路径有误 三、编译SDK3.1 /bin/sh: 1: gcc: not found3.2 curses.h: No such file or directory 四、CMakeLists - 编译app4.1 cmake - 项目构建4.2 make - 项目编译4.3 …

计算机图像处理:图像轮廓

图像轮廓 图像阈值分割主要是针对图片的背景和前景进行分离&#xff0c;而图像轮廓也是图像中非常重要的一个特征信息&#xff0c;通过对图像轮廓的操作&#xff0c;就能获取目标图像的大小、位置、方向等信息。画出图像轮廓的基本思路是&#xff1a;先用阈值分割划分为两类图…

如何修复wmvcore.dll缺失问题,wmvcore.dll下载修复方法分享

近年来&#xff0c;电脑使用的普及率越来越高&#xff0c;人们在日常生活中离不开电脑。然而&#xff0c;有时候我们可能会遇到一些问题&#xff0c;其中之一就是wmvcore.dll缺失的问题。wmvcore.dll是Windows平台上用于支持Windows Media Player的动态链接库文件&#xff0c;如…

命运2中文wiki搭建记录——MediaWiki安装与初设置

命运2中文wiki搭建记录 本文转自我的博客&#xff0c;原文地址——>命运2中文wiki搭建记录——MediaWiki安装与初设置 可能是出于闲的发霉&#xff0c;想自己搭建一个命运2wiki。 因为bilibili上的命运2Bwiki也全是自己搭的。指路——>命运2Bwiki 但是当自己实际上手Me…

windows系统删除网络适配器

此电脑&#xff0c;右键&#xff0c;管理 打开本机设备管理器 其中找到网络适配器&#xff1a; 选中要删除的&#xff0c;右键点击“卸载设备”&#xff0c;点击卸载即可完成。

控价与数据分析的关系

品牌在做线上控价时&#xff0c;会面对许多的数据&#xff0c;如店铺数据、行业数据&#xff0c;当这些数据仅仅只是拿来做监测低价输出低价报表使用&#xff0c;是没有发挥其最大作用的&#xff0c;因为商品链接的字段较丰富&#xff0c;涉及的内容会包含销量、评价量、促销信…

Linux:理解进程的多种状态

文章目录 理解状态运行状态阻塞状态挂起状态Linux系统下的进程状态的解析状态的查看 本篇总结的是进程的多种状态 对于进程的状态理解&#xff0c;在教材上通常是有下面的思维模式图 那么如何理解上面图片中的内容&#xff1f; 理解状态 如何理解状态&#xff1f;其实理解状…

PyCharm中使用pyqt5的方法2-2

1.2 是否下载成功 按照以上步骤安装了“pyqt5”、“pyqt5-tools”模块和“pyqt5designer”模块后&#xff0c;可以打开保存这三个模块的路径&#xff0c;找到其对应的文件夹&#xff0c;即可验证是否下载成功。 获取PyCharm保存下载模块路径的方法是&#xff0c;在PyCharm界面…

解决java.io.FileNotFoundException: HADOOP_HOME and hadoop.home.dir are unset.的错误

文章目录 1. 复现错误2. 分析错误3. 解决问题3.1 下载Hadoop3.2 配置Hadoop3.3 下载winutils3.4 配置winutils 1. 复现错误 今天在运行同事给我的项目&#xff0c;但在项目启动时&#xff0c;报出如下错误&#xff1a; java.io.FileNotFoundException: java.io.FileNotFoundEx…

【AI视野·今日NLP 自然语言处理论文速览 第四十二期】Wed, 27 Sep 2023

AI视野今日CS.NLP 自然语言处理论文速览 Wed, 27 Sep 2023 Totally 50 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computation and Language Papers Attention Satisfies: A Constraint-Satisfaction Lens on Factual Errors of Language Models Authors Mert …

苹果 CMS 原生 Java 白菜影视 App 源码【带打包教程】

苹果 CMS 原生 Java 白菜影视 App 源码是一款功能强大的影视应用程序&#xff0c;支持画中画、投屏、点播、播放前广告和支持普通解析等多种功能。与萝卜 App 源码相比&#xff0c;该套源码更加稳定&#xff0c;且拥有画中画投屏和自定义广告等功能&#xff0c;提高了安全性。 …

河北吉力宝以步力宝健康鞋引发的全新生活生态商

在当今瞬息万变的商业世界中&#xff0c;成功企业通常都是那些不拘泥于传统、勇于创新的先锋之选。河北吉力宝正是这样一家企业&#xff0c;通过打造一双步力宝健康鞋&#xff0c;他们以功能性智能科技穿戴品为核心&#xff0c;成功创造了一种结合智能康养与时尚潮流的独特产品…

Leetcode算法入门与数组丨5. 数组二分查找

文章目录 1 二分查找算法2 二分查找细节3 二分查找两种思路3.1 直接法3.2 排除法 task09task10 1 二分查找算法 二分查找算法是一种常用的查找算法&#xff0c;也被称为折半查找算法。它适用于有序数组的查找&#xff0c;并通过将待查找区间不断缩小一半的方式来快速定位目标值…

Redis 线程模式

Redis 是单线程吗&#xff1f; Redis 单线程指的是 [接收客户端请求 -> 解析请求 -> 进行数据读写操作 -> 发送数据给客户端] 这个过程是由一个线程 (主线程) 来完成的&#xff0c;这也是常说的 Redis 是单线程的原因。 但是 &#xff0c;Redis 程序不是单线程的&am…

VB从资源文件中播放wav音乐文件

Private Const SND_SYNC &H0 Private Const SND_MEMORY &H4 API函数 Private Declare Function sndPlaySoundFromMemory Lib "winmm.dll" Alias "sndPlaySoundA" (lpszSoundName As Any, ByVal uFlags As Long) As Long 音乐效果请“单击” Pr…