高级I/O知识分享【5种IO模型 || select || poll】

  博客主页:花果山~程序猿-CSDN博客

文章分栏:Linux_花果山~程序猿的博客-CSDN博客

关注我一起学习,一起进步,一起探索编程的无限可能吧!让我们一起努力,一起成长!

在这里插入图片描述

目录

一,前文

2,5种IO模型(钓鱼例子)

3. 5种IO模型图

4,进程同步&IO同步区分

那如何区分同步通信和异步通信?

5,阻塞与非阻塞区别

二,非阻塞IO

1.设置IO为非阻塞

2. I/O多路转接(select)

参数理解

用select优化listen_socket

select的优缺点

3.I/O多路转接(poll)


嗨!收到一张超美的图,愿你每天都能顺心!

一,前文

        我们曾经学习过类似IO的知识,例如接口:C语言的fwrite,fread,操作系统的read,write等对磁盘的IO,我们现在可以称他为——单机IO,本质上是内存到磁盘之间的读写操作,相比与网络IO,本地IO有着一些先天的优势(物理优势——近):

  1. 低延迟:由于数据交换不涉及网络传输,因此相对网络IO,单机IO的延迟较低。
  2. 简单直接:实现较为简单,通常使用操作系统提供的API(如C语言中的fopen, fread, fwrite等)直接进行操作。
  3. 可靠性高:相比网络环境,本地IO受外界干扰小,数据传输更可靠。

应用的场景多是对本地文件内容的读写。

        网络IO本质也是对外设进行读写(接口如:send,recv),但由于需要经过长延迟的网络环境与经历的繁琐步骤,网络IO的效率极其的低下,优化网络效率的问题被大家所关注。

网络IO的劣势:

  1. 高延迟:数据需要经过网络传输,可能受到网络延迟、丢包、拥塞等因素影响,因此延迟通常高于单机IO。
  2. 复杂性:需要处理网络连接建立、数据包封装/解封装、错误处理、流量控制等复杂问题。 

如何理解IO时间  =   等 +  数据拷贝

答:

本地IO:  当我们通过read打开某个文件,实质上是让操作系统帮我们向磁盘中获取数据,我们应用层只需要等待操作系统的缓冲区开始有数据,等到一定的时机,我们再拷贝到应用层准备好的缓冲区中。

网络IO:   网络版本其实也是类似外设从磁盘变成了网卡,依旧是操作系统为我们提取数据,应用程序等待操作系统从网络接收数据到内核的套接字缓冲区,然后再拷贝到应用层缓冲区中。

2,5种IO模型(钓鱼例子)

5种模型为:IO = 等 + 数据拷贝,这里用一个人在河边钓鱼为例子,“等”=等鱼上钩,“数据拷贝” =  将鱼钓上放在桶(应用层缓冲区)里。

  • 阻塞式 :这个人 ,就坐在哪儿,盯着鱼漂,啥事不干,就单纯在哪儿等鱼上钩,再调上。
  • 非阻塞轮询式:这个人,过一会来看看是否有上钩,上钩了再把鱼钓起来。
  • 信号驱动式:这个人将鱼钩上安装一个上钩提醒器,而他就做其他的事情,等到提醒了才过来把鱼钓起。
  • 多路复用,多路转接式:这个人安装100个鱼竿,坐在旁边,那个杆上钩了,把那个鱼钓起,因此这种方式效率最高
  • 异步IO式 :有两个人,老板自己嫌累,雇了个员工,员工在那里钓鱼,钓上来的鱼放在桶里,等到桶被装满,员工会提醒老板过来取鱼。

其中,除了异步IO,其余都是同步IO,那如何区分同步,异步IO只要(进程,线程)参与了等或者数据拷贝,都算同步IO,都没参加就是异步IO

通过了上面的IO模型下面我们来看看官方怎么解释的:(光看图理解是浅显的,这里就简单看看,后面会有代码实践

3. 5种IO模型图

阻塞 IO 是最常见的 IO 模型。
非阻塞IO: 如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回 EWOULDBLOCK错误码
非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为 轮询. 这对 CPU来说是较大的浪费, 一般只有特定场景下才使用。

信号驱动IO: 内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作。

IO多路转接: 虽然从流程图上看起来和阻塞IO类似. 实际上最核心在于IO多路转接能够 同时等待多个文件
描述符的就绪状态.
异步IO: 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据).
小结
任何IO过程中, 都包含两个步骤. 第一是 等待, 第二是 拷贝。而且在实际的应用场景中, 等待消耗的时间往往都远远高于拷贝的时间. 让IO更高效, 最核心的办法就是让等待的时间尽量少

4,进程同步&IO同步区分

另外, 我们回忆 多进程多线程的时候, 也提到 同步。这里的 同步通信进程之间的同步是完全不想干的概念。
多进程多线程的同步:  在操作系统或程序内部, 多进程或多线程之间的同步是指控制多个进程或线程 访问共享资源的方式,以避免竞态条件和其他并发问题。常见的同步机制有:互斥锁,条件便利,信号量,原子操作等
同步通信: 同步通信是指两个或多个网络实体之间进行有序的信息交换,其中一方在发送消息后 必须等待另一方的响应才能继续执行。

那如何区分同步通信和异步通信

以钓鱼为例,“在河边等待鱼上钩"=等待,”将鱼吊起“=拷贝,只要进程参与任一一步都理解为同步IO;都不参加,直接来提桶拿鱼为异步IO

以后在看到 "同步" 这个词, 一定要先搞清楚大背景是什么。这个同步, 是同步通信异步通信的同步, 还是进程,线程同步与互斥的同步。

5,阻塞与非阻塞区别

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。
  • 阻塞调用是指调用结果返回之前,当前线程会被挂起. 调用线程只有在得到结果之后才会返回。
  • 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

二,非阻塞IO

 首先我们先看看我们常见的阻塞式IO:

int main()
{while (1){char buff[1024];int ret = read(0, buff, sizeof buff - 1);if (ret){buff[ret] = 0;std::cout << "echo#:" << buff ;} else{std::cout << "....." << std::endl;}}return 0;
}

结论:I/O的文件描述符默认是阻塞式的。如果非阻塞式,需要我们通过fcntl设置。最常见的IO阻塞就是标准输入,会阻塞的等待我们输入内容。

1.设置I/O为非阻塞(fcntl)

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );

(注:这个接口适用于所有的本地IO,网络IO所打开的文件描述符设置是否阻塞形式。有了这个我们就可以设置非阻塞IO,不用单独学习特定接口参数来设置非阻塞) 

fcntl函数有5种功能(方法):
  • 复制一个现有的描述符(cmd=F_DUPFD).
  • 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
  • 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
  • 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
  • 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW).
废话少说,先上手看看,写一下用于 设置文件描述符方式为非阻塞式的函数,如下:
int SetNoBlock(int fd)
{int ret = fcntl(fd, F_GETFL);  // 获取原来fd的读写标志位,就比如:open时设置的O_RDONLY,O_RDWR等等if (ret == -1)return -1;fcntl(fd, F_SETFL, ret | O_NONBLOCK);// fcntl 更换为设置读写标准位,并在原来标准位的基础上添加 非阻塞模式return 0;
}
我们此处只是用第三种功能, 获取/设置文件状态标记, 就可以将一个文件描述符设置为非阻塞。设置好非阻塞后,在循环内文件描述符会被轮询,如果没有资源就绪,会 设置错误码,如:EAGAIN,EWOULDBLOCK——资源未就绪。

因此,修改后的非阻塞IO案例,如下:

#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <cerrno>
#include <string.h>int SetNoBlock(int fd)
{int ret = fcntl(fd, F_GETFL);  // 获取原来fd的读写标志位,就比如:open时设置的O_RDONLY,O_RDWR等等if (ret == -1)return -1;fcntl(fd, F_SETFL, ret | O_NONBLOCK);// fcntl 更换为设置读写标准位,并在原来标准位的基础上添加 非阻塞模式return 0;
}int main()
{SetNoBlock(0);  //文件描述符也只需要设置一次,如果还要设置标准位可以后面自己添加char buff[1024];while (1){sleep(1);errno = 0;int ret = read(0, buff, sizeof buff - 1);if ( ret > 0){buff[ret] = 0;std::cout << "echo# " << buff;continue;}else  if( ret == 0){std::cout << "echo# try again"  << std::endl;}else if (ret == -1){std::cout << "echo# "<< errno << ": "<< strerror(errno);// 由于文件描述符采用非阻塞式后,我们无法区分是错误导致,还是单纯是IO资源未就绪}// 针对无法区分非阻塞式IO错误,我们需要对错误码进行区分if (errno == EAGAIN || errno == EWOULDBLOCK) // RWOULDBLOCK 本质是 EAGAIN{std::cout << " IO资源未就绪, try again" << std::endl;continue; }else if (errno == EINTR) // read时被signal打断,返回时无法再调用read{continue;}else{break;}}return 0;
}

设置非阻塞后,socket的读事件未就绪,需要对错误值进行,判断。 

其他错误值可能为:
EBADF 文件描述词为无效的或该文件已关闭
EINTR 此调用被信号所中断
EINVAL 参数n 为负值。
ENOMEM 核心内存不足

2. I/O多路转接(select)

select接口 是一个用于监控多个文件描述符(file descriptors,简称fd)的系统调用,它可以检测多个文件描述符上是否有事件发生(如数据可读、可写或异常)。这对于需要同时处理多个客户端连接的服务器程序来说非常有用,比如Web服务器、聊天服务器等。(AI)

参数理解

1).  timeval结构体类型,里面存放着微秒数,如下:

输入意义:告诉此次select 阻塞 tv_sec + tv_usec 秒后select超时返回。

输出意义:如果在阻塞时间内,有fd事件就绪,则会设置剩余时间量

这里扩展一下,获取时间戳的方法,如:C语言的time_t  time(time_t *),系统接口gettimeofday

2). fd_set类型

select接口中有三个fd_set类型,意义:让系统分别注意,读事件,写事件,其他事件fd就绪。

fd_set本质是一个位图数据结构,其中位图下标值代表fd,一个位图最大能容纳1024个fd;

每个位的值为1,

  • 输入意义:告诉系统注意该fd的读事件(写事件,其他事件);
  • 输出意义:select会设置fd_set,输出时标记fd就绪的下标位为1;

设置fd_set类型,需要特定操作宏:

用select优化listen_socket

        首先,我们需要理解listen_socket在网络通信中的角色。listen_socket就像是餐馆门口的迎宾员,负责接收潜在顾客(客户端)的到来,并准备好迎接他们(监听连接请求)。accept操作则像是餐馆内的服务员,负责正式接待顾客并安排座位(接受连接请求并创建新的套接字来处理这个连接)。

listen_socket不仅要监听传入的连接请求,还要与客户进行TCP的三次握手,以确认连接的建立。只有当三次握手成功完成后,accept操作才能接收这个新连接,并为其提供服务(创建新的套接字来处理连接)。

当客户完成数据交换并准备断开连接时,需要进行TCP的四次挥手,以确保双方都知道连接将要关闭,并且释放占用的资源。

使用如下例子:

        MySocket tool;tool.listen_run(_server_socket);// 实现多路转接select,这里只有一个listen,不过演示一下即可fd_set fdset;while (1){    timeval time = {0, 0} //非阻塞FD_ZERO(&fdset);FD_SET(_server_socket, &fdset);int n = select(_server_socket + 1, &fdset, nullptr, nullptr, &time);switch (n){case 0:continue;break;case -1:Logmessage(ERROR, "select fail errno %d: %s", errno, std::to_string(errno).c_str());exit(-1);break;default:Logmessage(INFO, "select get a connect");// 获取成功}}

上面是select只监视listen_socket一个标识符的读事件就绪,那如何将已连接客户端(或者本地IO)描述符一起管理起来呢?

思路:

  1. 需要一个外部的int数组,自称它为socket数组集,用于存放select关注的所有描述符。
  2. 每次客户端连接成功后,将soket放入数组集中;当select监视到其中某socket读事件就绪并处理后,需要从数组集中消除。
  3. 3.有个该数组集,我们可以每次遍历数组集循环设置fd_set,同时方便FD_ISSET判断。

下面是用web服务器,对socket的select处理代码示例链接(仅看webserver.hpp):

IO_Model/webserver.hpp · 逆光/Linux - 码云 - 开源中国 (gitee.com)

select的优缺点

 优:

缺点:

1. 用户,OS层存在大量遍历:用户层,需要不断修改第三方数组;OS层,也需要不断遍历fd_set与修改

2. fd_set本身的1024上限。

3. 输入输出型参数,从用户到内核,从内核到用户存在频繁的数据拷贝。

4.编码复杂

3.I/O多路转接(poll)

        针对select的缺点,经过过优化后的poll能解决大部分问题。

fds 是一个 poll 函数监听的结构列表 . 每一个元素中 , 包含了三部分内容 : 文件描述符 , 监听的事件集合 , 返回的事件集合。
nfds 表示: fds 数组的长度。
timeout 表示: poll 函数的超时时间 , 单位是毫秒 (ms)。
events 如何设置事件集合
使用示例如下:
#include <stdio.h>
#include <sys/time.h>
#include <sys/poll.h>int main() {struct pollfd fds[2];int timeout;// 初始化第一个文件描述符为标准输入fds[0].fd = 0; // 标准输入fds[0].events = POLLIN; // 只关心可读事件// 假设这里有一个套接字,其文件描述符为 sockfdint sockfd = 3;fds[1].fd = sockfd;fds[1].events = POLLRDNORM; // 监听套接字是否有数据可读timeout = 5000; // 超时时间为5秒// 进行 poll 调用int ret = poll(fds, 2, timeout);if (ret > 0) {for (int i = 0; i < 2; i++) {if (fds[i].revents & POLLIN) {printf("文件描述符 %d 可读\n", fds[i].fd);fds[i].revents = 0; //poll不在关注该描述符}}} else if (ret == 0) {printf("超时\n");} else {perror("poll error");}return 0;
}

poll优点:

缺点:

1. 用户层,OS层任存在不少的遍历。用户层需要不断检测数组就绪;OS层不断检测fd就绪,本质上与select一样需要维护第三方数组。

2. 当有大量链接接入,但就绪事件较少,这样遍历fd数组量加大,效率线性下降。

3. 编码较select容易些。

没关系,epoll会解决,按照 man 手册的说法 : 是为处理大批量句柄而作了改进的 poll . epoll 几乎具备了之前所说的一切优点,被公认为Linux2.6 下性能最好的多路 I/O 就绪通知方法。

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获,请动动你发财的小手点个免费的赞,你的点赞和关注永远是博主创作的动力源泉。

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

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

相关文章

在Unity UI中实现UILineRenderer组件绘制线条

背景介绍 在Unity的UI系统中&#xff0c;绘制线条并不像在3D世界中那样直观(使用Unity自带的LineRender组件在UI中连线并不方便,它在三维中更合适)。没有内置的工具来处理这种需求。如果你希望在UI元素之间绘制连接线&#xff08;例如在UI上连接不同的图标或控件&#xff09;&a…

20240918 每日AI必读资讯

o1突发内幕曝光&#xff1f;谷歌8月论文已揭示原理&#xff0c;大模型光有软件不存在护城河 - 谷歌DeepMind一篇发表在8月的论文&#xff0c;揭示原理和o1的工作方式几乎一致 - 谷歌DeepMind这篇论文的题目是&#xff1a;优化LLM测试时计算比扩大模型参数规模更高效。 - Op…

828华为云征文 | 云服务器Flexus X实例:one-api 部署,支持众多大模型

目录 一、one-api 介绍 二、部署 one-api 2.1 拉取镜像 2.2 部署 one-api 三、运行 one-api 3.1 添加规则 3.2 运行 one-api 四、添加大模型 API 4.1 添加大模型 API 五、总结 本文通过 Flexus云服务器X实例 部署 one-api。Flexus云服务器X实例是新一代面向中小企业…

拥控算法BBR入门1

拥塞控制算法只与本地有关 一个TCP会话使用的拥塞控制算法只与本地有关。 两个TCP系统可以在TCP会话的两端使用不同的拥塞控制算法 Bottleneck Bandwidth and Round-trip time Bottleneck 瓶颈 BBR models the network to send as fast as the available bandwidth and is 2…

Java | Leetcode Java题解之第414题第三大的数

题目&#xff1a; 题解&#xff1a; class Solution {public int thirdMax(int[] nums) {Integer a null, b null, c null;for (int num : nums) {if (a null || num > a) {c b;b a;a num;} else if (a > num && (b null || num > b)) {c b;b num;…

驱动器磁盘未格式化危机:专业数据恢复实战指南

认识危机&#xff1a;驱动器中的磁盘未被格式化 在日常的数字生活中&#xff0c;我们时常依赖于各种存储设备来保存重要的文件、照片、视频等数据。然而&#xff0c;当某一天你尝试访问某个驱动器或外接硬盘时&#xff0c;突然弹出的“驱动器中的磁盘未被格式化。您想现在格式…

floodfill+DFS(2)

文章目录 太平洋大西洋流水问题扫雷游戏迷路的机器人 太平洋大西洋流水问题 class Solution { public:vector<vector<int>> res;int m 0, n 0;vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {m heights.size…

iOS 18 正式上線,但 Apple Intelligence 還要再等一下

在 iPhone 16 即將正式開賣之際&#xff0c;Apple 如約上線了 iOS 18。雖然今年的重頭戲 Apple Intelligence 還要等下月的 iOS 18.1 才會有&#xff0c;但自訂主畫面和全新的鎖定頁面、控制中心等特性已可在最新的版本中體驗。除此之外&#xff0c;相簿、訊息、地圖、Safari 等…

React学习day07-ReactRouter-抽象路由模块、路由导航、路由导航传参、嵌套路由、默认二级路由的设置、两种路由模式

14、ReactRouter续 &#xff08;2&#xff09;抽象路由模块 1&#xff09;新建page文件夹&#xff0c;存放组件 组件内容&#xff1a; 2&#xff09;新建router文件夹&#xff0c;在其下创建实例 3&#xff09;实例导入&#xff0c;使用 4&#xff09;效果 &#xff08;3&…

佛山网站制作与设计

佛山网站制作与设计 在当今数字化时代&#xff0c;网站已成为企业展示形象、推广产品和服务的重要窗口。佛山作为一个经济迅速发展的城市&#xff0c;其网站制作与设计也日益受到重视。优质的网站不仅能提升企业的品牌形象&#xff0c;更是实现商业价值的重要工具。 一、网站制…

cout无法正常显示中文

cout无法正常显示中文 虽然你使用了buf.length()来指定写入的字节数&#xff0c;但是在包含中文字符&#xff08;UTF-8编码下每个中文字符占用3个字节&#xff09;的情况下&#xff0c;直接使用length()可能不会正确反映实际的字节数&#xff0c;因为它给出的是字符数而非字节…

RK3568平台(文件系统篇)VFS虚拟文件系统

一.VFS虚拟文件系统简介 为什么 Linux 内核的文件系统类型那么多,都能挂载上呢?为什么系统里可以直接 mount 其他文件系统呢?为什么 Linux 的虚拟文件系统这么强大?这得益于它的数据结构设计得十分精妙。 为支持各种本机文件系统,且在同时允许访问其他操作系统的文件,L…

gitee远程仓库OPEN GIT BASH HERE从错误中学习

推荐一个ai软件&#xff08;搜索器搜索kimi&#xff09;&#xff0c;是一个ai&#xff0c;有什么错误跟着一步步解决就可以了 当你创建一个仓库 会出现这些 打开这个窗口跟着敲就行了 到这里为止我还没出现错误&#xff0c;后面我把remote add添加远程仓库的地址输错地址了 所…

C++第七节课 运算符重载

一、运算符重载 并不是所有情况下都需要运算符重载&#xff0c;要看这个运算符对这个类是否有意义&#xff01; 例如&#xff1a;日期减日期可以求得两个日期之间的天数&#xff1b;但是日期 日期没有意义&#xff01; #include<iostream> using namespace std; clas…

文档内容识别系统源码分享

文档内容识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

鸿蒙开发之ArkUI 界面篇 五

Image 图片组件&#xff0c;用专门用于显示图片 语法&#xff1a;Image(图片源)&#xff0c;这里可以是网络、也可以是本地的图片 例如&#xff1a;Image(https://wxls-cms.oss-cn-hangzhou.aliyuncs.com/online/2024-04-18/218da022-f4bf-456a-99af-5cb8e157f7b8.jpg)效果如下…

VTD激光雷达(5)——05_OptiX_GPU

文章目录 前言一、总结 前言 一、 1 2 3 总结

如何在自动化测试中应用装饰器、多线程优化自动化架构?

1、装饰器概念 装饰器是Python中用于修改函数或类的语法结构的工具。它以函数作为输入参数&#xff0c;并返回一个函数作为一个输出函数&#xff0c;在不改变原有函数的代码情况下&#xff0c;给函数增加功能或改变函数行为。 装饰器的使用方式是在函数定义的上方使用 decorato…

re题(30)BUUCTF-[HDCTF2019]Maze

BUUCTF在线评测 (buuoj.cn) 查一下壳&#xff0c;32位upx壳 脱完壳放到ida&#xff0c;shiftF12看一下字符串&#xff0c;是个迷宫&#xff0c;maze&#xff08;迷宫&#xff09; 这里有一个经典的花指令 (导致找不到main函数) 下方有个奇怪的jnz指令&#xff0c;它跳转到了下…

[数据结构]算法复杂度详解

文章目录 一、引言1、想象数据结构与算法的奇妙世界2、算法复杂度的轻松解读3、数据结构与算法的温馨寄语 二、轻松掌握复杂度基础1、时间复杂度&#xff1a;算法速度的衡量尺2、空间复杂度&#xff1a;算法占地的衡量尺3、常见的复杂度 三、复杂度的计算1、时间复杂度计算2、空…