【Linux】IO操作

news2024/11/24 12:31:47

IO

  • 典型 IO 模型
    • 阻塞 IO
    • 非阻塞 IO
    • 信号驱动 IO
    • 异步 IO
    • 常见问题
  • 多路转接模型
    • select 模型
    • poll 模型
    • epoll 模型

典型 IO 模型

IO 操作指的就是数据的输入输出操作;IO 过程可以分为两个步骤:等待 IO 就绪、数据拷贝

阻塞 IO

发起 IO 操作,若当前不具备 IO 条件,则等待直到条件满足完成 IO 后返回

在这里插入图片描述
优点:流程简单
缺点:资源利用率低,效率相对低下

非阻塞 IO

发起 IO 操作,若当前能够 IO ,则执行完 IO 操作后返回;若当前不具备 IO 条件则报错返回

在这里插入图片描述
优点:对资源利用率提高了
缺点:程序流程复杂–因为要进行循环操作;不是实时的

信号驱动 IO

定义 IO 信号处理方式,IO 就绪通过信号通知进程,然后发起 IO 调用

在这里插入图片描述
优点:对资源利用率更充分;IO 操作更实时
缺点:操作流程更加复杂–添加信号通知部分

异步 IO

发起异步 IO 操作,IO 的等待以及数据的拷贝都由系统完成,完成后通知进程

在这里插入图片描述
优点:对资源利用率提升;
确定:程序流程也更复杂了

常见问题

阻塞 vs 非阻塞
阻塞:发起一个操作,若当前操作条件不满足则一直等待
非阻塞:发起一个操作,若当前操作条件不满足则报错返回

阻塞与非阻塞关联:通常都是操作接口特性
阻塞与非阻塞区别:发起一个接口调用后,接口是否会立即返回

同步 vs 异步
同步:功能由进程自身来完成,且通常是串行化的
异步:功能并不由进程自身来完成,而是由系统完成的,完成不一定是串行的

同步与异步关联:通常用于讨论一个任务的完成流程
同步与异步区别:功能是否有当前执行流自身完成

异步阻塞:发起操作后, 功能由系统来完成,进程执行流自身等待系统完成
异步非阻塞:发起操作后,功能由系统来完成,操作会直接返回,并不会等待

多路转接模型

常用于高并发服务器中技术的使用
作用:针对大量描述符进行 IO 就绪事件监控

优点:
1:让进程能够仅针对就绪的描述符进行 IO 操作,提高了任务处理效率
2:避免进程因为未就绪描述符进行操作而导致阻塞

具体技术实现:select、poll、epoll

select 模型

IO 事件:可读事件、可写事件、异常事件

流程思想:
1:定义指定 IO 事件的描述符集合;

2:将需要对指定事件进行监控的描述符添加到指定集合中;

3:将事件的描述符集合拷贝到内核中,进行事件监控:
1)对集合中所有描述符进行遍历,若没有就绪则将描述符挂到内核的 IO 事件队列;
2)若监控过程中,有某个描述符就绪了所要监控的事件,则会唤醒进程的阻塞;
3)唤醒后,select 会再次遍历描述符集合,将集合中没有就绪的描述符移除

4:select 监控返回后,只需要判断哪个描述符还在集合中,哪个描述符就就绪了哪个事件;

5:进程可以根据就绪的不同事件对描述符进行不同的 IO 操作

接口介绍

1、定义集合:
fd_set set;

本质上这个集合是一个比特位图,默认拥有 1024 个比特位,取决于 __FD_SETSIZE;因此,select 对描述符进行 IO 事件监控是有最大描述符限制的

2、先初始化集合,然后将需要监控的描述符添加到集合中

初始化清空集合:
void FD_ZERO(fd_set *set);

将 fd 描述符添加到集合中:
void FD_SET(int fd, fd_set *set);

将 fd 描述符从 set 集合移除:
void FD_CLR(int fd, fd_set *set);

3、开始监控:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

nfds:将所有需要监控的集合中,最大描述符+1,提高监控遍历效率
readfds:可读事件的描述符集合
writefds:可写事件的描述符集合
exceptfds:异常事件的描述符集合
timeout:设置本次监控的阻塞时长; NULL-一直阻塞,直到描述符就绪或被信号打断; 0-非阻塞
返回值:返回实际就绪的描述符事件个数;出错返回 -1;0-监控超时

select 接口一旦返回,就意味着三个集合中,就只保留了就绪了指定事件的描述符

4、判断哪个描述符还在集合中,哪个描述符就绪了哪个事件
int FD_ISSET(int fd, fd_set *set);
返回值:非0-描述符还在集合中;0-描述符不在集合中

Demo:

  1 #include<stdio.h>
  2 #include<unistd.h>
  3 #include<stdlib.h>
  4 #include<time.h>
  5 #include<sys/select.h>
  6 
  7 /*对标准输入进行可读事件监控,有数据则读取,没有数据则阻塞*/
  8 
  9 int main()
 10 {
 11   fd_set rfds;    //定义可读事件集合
 12   while(1){
 13      int maxfd=0;    //集合中最大描述符个数 --- 因为只对标准输入进行可读事件监控,因此最大描述符 1 个
 14      struct timeval tv;    //定义时间结构体
 15      tv.tv_sec=3;        //阻塞 3s;因为 select每次监控都会重置阻塞时间为0,所有每次循环都需要重新设置
 16      tv.tv_usec=0;                                                                                                      
 17      
 18      FD_ZERO(&rfds);    //初始化集合        因为select每次监控都会重置描述符集合,因此每次循环都需要重新添加描述符到集合中
 19      FD_SET(0,&rfds); //将标准输入-0,添加到可读集合
 20      
 21      int nfds=select(maxfd+1,&rfds,NULL,NULL,&tv);   //开始监控,只有可读集合 rfds 
 22      if(nfds<0){        //监控失败
 23         perror("select error~!\n");
 24         return -1;
 25      }else if(nfds==0){        //返回值为0,表示没有描述符存在
 26         perror("select timeout!\n");        //等待超时
 27         continue;
 28      }
 29         
 30      //for 循环遍历集合
 31      for(int i=0;i<=maxfd;++i){
 32          if(FD_ISSET(i,&rfds)!=0){
 33              //判断 i 号描述符在可读集合中,说明就绪了可读事件
 34              char buf[1024]={0};
 35              read(i,buf,1024);          //从 i 号描述符中读取数据放入 buf
 36              printf("buf:[%s]\n",buf);
 37          }   
 38          //else if(FD_ISSET(i,&efds))  {
 39                //判断 i 号描述符在可写事件中,说明就绪了可写事件
 40          //} 
 41      }   
 42   }            
 43   return 0;
 44 }    

运行结果:
在这里插入图片描述

将 select 应用在 TCP 服务器搭建上

搭建一个 TCP 服务器,会涉及到服务器为每一个客户端都建立一个新的套接字进行通信,需要对大量的描述符进行 IO 操作;之前介绍 TCP 服务器时候的解决方案是:使用多执行流来处理 – 为每一个客户端的通讯都创建执行流

这里,我们可以使用 多路转接模型+线程池 进行应用

思想:
封装一个 Select 类,每一个实例化的对象,都是一个能够针对大量描述符进行 IO 事件监控的对象 添加链接描述

在这里插入图片描述

封装 TCP:添加链接描述

客户端代码:添加链接描述

服务端代码:添加链接描述

在这里插入图片描述

select 总结:
优点:遵循 posix 标准,跨平台移植性好; 监控超时可以细微到微妙
缺点:
1.能够监控的描述符数量是有上线限制的 — 取决于 _FD_SETSIZE,默认1024;
2.监控过程中需要多次遍历描述符集合,因此监控的描述符越多,性能就越低;
3.因为每次监控都会修改描述符集合,因此每次监控都需要重新条件描述符到集合中;
4.监控返回的是就绪的描述符集合(位图),因此监控调用返回后,无法直接针对就绪的描述符进行操作,需要遍历一遍描述符看哪个还在集合中才能确定是否就绪了事件

高并发服务器中,有一种并发模型 :reactor 模型

思想:使用多路转接模型对大量描述符进行事件监控,谁触发了事件就处理谁

分类:
1.单 reactor 单线程 :在一个线程中,进行事件监控以及事件处理;
2.单 reactor 多线程 :在一个线程中进行 reactor 事件监控,触发事件后交给其他线程进行事件处理;
3.多 reactor 多线程 :在一个线程中进行新连接到来事件监控,有事件触发则获取新建连接,将新建连接分发给其他 reactor 线程,其他的 reactor 进行描述符的事件监控以及 IO 操作

在这里插入图片描述

poll 模型

操作流程:

1.定义一个事件结构体数组

struct poollfd{
	int fd;     //要监控的文件描述符
	short events;    //想要监控的事件,POLLIN-可读,POLLOUT-可写
	short revents;   //监控返回后,存储实际就绪的事件
}

//定义事件结构体数组
struct pollfd fds[MAX];

2.若哪个描述符需要监控什么事件,就在数组中进行设置

fds[0].fd=0;
fds[0].events=POLLIN;      //对标准输入描述符进行可读事件监控
fds[1].fd=1;
fds[1].events=POLLOUT;  //对标准输出描述符进行可写事件监控

3.开始监控
原理:将数组中有效数据拷贝到内核中,进行多次轮询遍历

第一次遍历:判断有没有就绪的事件,没有则挂起到监控队列中;
第二次遍历:进程的阻塞被唤醒后进行遍历,对每个元素的 revents 设置实际就绪的事件

int poll(struct pollfd*fds,nfds_t maxevents,int timeout);

fds:定义的时间结构体数组首地址
maxevents:数组中有效元素个数
timeout:监控阻塞的超时时间,以毫秒为单位

返回值:>0 表示实际就绪的事件个数; ==0 表示超时; <0 表示出错了

4.调用返回后,遍历事件结构体数组,根据 revents 成员确定描述符是否就绪了某个事件,进而对描述符进行操作

Demon:


#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<poll.h>

#define MAX_POLL_SIZE 10

int main()
{
  //定义事件结构体数组
  struct pollfd fds[MAX_POLL_SIZE];

  //添加要监控的描述符事件信息
  fds[0].fd=0;      //标准输入描述符
  fds[0].events=POLLIN;  //监控可读事件

  while(1){
    int ret=poll(fds,1,3000);     //超时时间为 3000 毫秒 --- 3s
    if(ret<0){
      perror("poll error!\n");
      continue;
    }
    else if(ret==0){
      printf("poll timeout!\n");
      continue;
    }
    int i=0,valid_count=1;       //只监控可读事件,因此有效监控个数 valid_count=1
    for(i=0;i<valid_count;++i){
      if(fds[i].revents & POLLIN){    //就绪可读事件
         char buf[1024]={0};
         read(fds[i].fd,buf,1023);     //从 fds[i].fd 描述符中读取数据到 buf 中
         printf("buf:[%s]\n",buf);
      }else if(fds[i].revents & POLLOUT)  {
       //就绪可写事件
         printf("POLLOUT EVENTS!\n");  
      }
    }
  }
  return 0;
}

运行结果:

在这里插入图片描述

poll 总结:

优点:
1.使用事件结构体替代了事件集合,相较于 select 操作,简便性提高了很多;
2.所能监控的描述符数量不在上限限制;

缺点:
1.每次监控需要将信息拷贝到内核;
2.监控原理涉及到多次对事件数组的遍历,因此性能会随着描述符的增多而下降;
3.每次监控完毕后,依然需要遍历整个事件数组才能确定哪个描述符就绪了哪个事件

events = POLLIN | POLLOUT; 对两个事件同时监控采用 |

epoll 模型

操作流程:
1.在内核中创建 epoll 句柄 eventpoll 结构

int epoll_create(int size);

size: 所能监控的描述符上限 ,在Linux 2.6.8 后被忽略,但必须大于 0
返回值:返回 epoll 描述符;出错返回 -1

struct eventpoll{
	...
	list_head rdllist;   //双向链表
	rbtree rbr;     //红黑树
	...
}

2.向内核的句柄中,添加/移除/修改所要监控的描述符及其对应的事件结构

int epoll_ctl(int epfd,int op,int fd,struct epoll_event* ev);

epfd: epoll_create 返回的 epoll 描述符
op: 对 epoll 要进行的操作:EPOLL_CTL_ADD / EPOLL_CTL_DEL / EPOLL_CTL_MOD
fd: 要操作的描述符,对 fd 描述符进行 op 操作

struck epoll_event* ev;   //对描述符要进行操作的详细信息


struct epoll_event{
	uint32_t events;  //想要监控的事件以及监控后存放实际就绪的事件
	union{        //可以监控的事件 : EPOLLIN-可读,EPOLLOUT-可写
      void* ptr;
      int fd; 
    }data;        //额外信息
};

3.开始监控

epoll 的监控是一个异步阻塞操作

发起监控调用是为了告诉系统,可以开始监控了,监控由系统完成 (而系统内部为 epoll 的每个描述的就绪事件挂了一个回调函数)
回调函数功能:描述符一旦就绪了指定事件,将事件信息拷贝一份到 rdllist 中,其实 rdllist 双向链表的作用:存放就绪的描述符对应的事件结构

一旦系统监控有描述符就绪了,则唤醒进程的阻塞,进程一旦被唤醒,查看 rellist 双向链表中是否有数据就可以确定是否有描述符就绪

监控调用返回的数据就是一个事件结构体数组 – 就绪的描述符对应的事件

int epoll_wait(int epfd,struct epoll_event* evs,int maxevents,int timeout);

epfd: epoll 描述符
evs: epoll_event 结构体数组的空间首地址,接收就绪事件
maxevents: 数组的最大元素个数,也表示了当前想要获取的最大事件个数
timeout: 要设置的监控超时时间 -- 以毫秒为单位
返回值: >0 实际就绪的事件个数;==0 超时 ; <0 出错了

在这里插入图片描述

封装一个 Epoll 类

在这里插入图片描述
将封装的 epoll 应用于 TCP 通信的操作与 select 应用相同,感兴趣可以自己琢磨琢磨,这里就不放代码咯~

epoll 事件触发方式

水平触发:select 与 poll 只有水平触发,epoll 默认水平触发
可读:缓冲区中数据大小小于高水平标记 (默认1字节)就会触发可读事件
可写:缓冲区中剩余空间大小小于高水平标记,就会触发可写事件
思想:只要满足触发条件就会触发对应事件

边缘触发: EPOLLET
可读:每当套接字有新数据到来时,则会触发一次事件
可写:缓冲区剩余空间从无到有的时候,才会触发一次事件
思想:尽量让用户在一次事件触发中,将能处理的数据都处理完毕,尽量减少事件触发次数,减少运行态切换次数

因为有新数据到来才触发一次事件,因此若一次事件触发后的处理中没有将所有数据进行处理,则在下一次新数据到来前,这些剩余数据都得不到处理

场景:http请求接收 – 在一次请求的接收处理中,发现缓冲区中数据不足以进行一次处理,取出来则需要额外存储,不取出来则水平触发就会一直触发可读事件,这种情况下希望能够在有新数据到来时再去进行数据处理,则使用边缘触发。

Q:如何将数据在一次处理中全部取出进行处理?

若想要将缓冲区中数据全部取出就只能循环取出
但循环读取数据,在套接字 recv 时候,有可能因为 socket 没有数据而阻塞

解决:将套接字的阻塞属性设置为非阻塞
将属性进行设置之后,则套接字的所有操作将变为非阻塞操作

int fcntl(int fd,int op,int arg);

op:F_GETFL 获取文件访问属性以及状态标志
    F_SETFL 设置文件的访问属性或状态标志 -- O_NONBLOCK--非阻塞


int flag=fcntl(fd,F_GETFL,0);  //F_GETFL 获取文件原有属性--第三个参数被忽略
fcntl(fd,F_SETFL,flag|O_NONBLOCK); //在原有属性基础上添加非阻塞属性

epoll 总结:

优点:
1.所能监控的描述符没有数量上限
2.监控性能并不会随着描述符的增多而下降
3.直接返回就绪描述符对应的事件结构,减少外界空遍历
4.描述符监控的事件信息,只需要向内核中添加一次,不需要每次监控都添加

缺点:
跨平台移植性不好,只能在类unix平台下使用

Q:select 、poll、epoll 哪个好?

不管是哪种模型,多路转接模型针对的都是对大量描述符进行 IO 事件监控,但是同一时间少量活跃的场景
若活跃连接也较多,则一定要搭配多执行流进行处理,充分利用系统资源

相较之下,select & poll 比较适用于单个描述符的事件监控以及超时管理,而 epoll 适用于大量描述符的事件监控场景。

在这里插入图片描述

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

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

相关文章

【面试高高手】 —— Java基础(36题)

文章目录 1. 八大基本数据类型分类2. 重写和重载的区别3. int和integer区别4. Java的关键字5. 什么是自动装箱和拆箱&#xff1f;6. 什么是Java的多态性&#xff1f;7. 接口和抽象类的区别&#xff1f;8. Java中如何处理异常&#xff1f;9. Java中的final关键字有什么作用&…

iview 的table表格组件使单元格可编辑和输入

表格的列定义中&#xff0c;在需要编辑的字段下使用render函数 template表格组件 <Table border :data"data" :columns"tableColumns" :loading"loading"></Table>data中定义table对象 table: {tableColumns: [{title: 商品序号,k…

服务断路器_Resilience4j的断路器

断路器&#xff08;CircuitBreaker&#xff09;相对于前面几个熔断机制更复杂&#xff0c;CircuitBreaker通常存在三种状态&#xff08;CLOSE、OPEN、HALF_OPEN&#xff09;&#xff0c;并通过一个时间或数量窗口来记录当前的请求成功率或慢速率&#xff0c;从而根据这些指标来…

【JVM】第三篇 JVM对象创建与内存分配机制深度剖析

目录 一. JVM对象创建过程详解1. 类加载检查2. 分配内存2.1 如何划分内存?2.2 并发问题 3. 初始化4. 设置对象头5. 执行<init>方法 二. 对象头和指针压缩详解三. JVM对象内存分配详解四.逃逸分析 & 栈上分配 & 标量替换详解1. 逃逸分析 & 栈上分配2. 标量替…

用纹理图集优化3D场景性能【Texture Atlas】

推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景 在 Unity 中开发移动应用程序时&#xff0c;确保一切都得到优化始终至关重要。 最大化帧速率使我们能够专注于优化脚本、烘焙灯光、修改对象等。 当我们将移动应用程序带入虚拟现实时&#xff0c;这一点变得更加重要。 虽…

嵌入式Linux应用开发-文件 IO

嵌入式Linux应用开发-文件 IO 第四章 文件 IO4.1 文件从哪来&#xff1f;4.2 怎么访问文件&#xff1f;4.2.1 通用的 IO 模型&#xff1a;open/read/write/lseek/close4.2.2 不是通用的函数&#xff1a;ioctl/mmap 4.3 怎么知道这些函数的用法&#xff1f;4.4 系统调用函数怎么…

基于微信小程序的健身小助手打卡预约教学系统(源码+lw+部署文档+讲解等)

文章目录 前言系统主要功能&#xff1a;用户的功能设计为&#xff1a;管理员的功能设计为&#xff1a;健身房的功能设计为&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获…

QFrame类学习笔记

1、QFrame的作用 QFrame类继承于QWidget类&#xff0c;被QAbstractScrollArea, QLabel, QLCDNumber, QSplitter, QStackedWidget, and QToolBox等类继承。 QFrame作为许多基础控件的基类&#xff0c;提供许多成员方法给子类&#xff0c;实现子类的框架样式的设计。框架样式主要…

Android 13 定制化开发--开启相机或麦克风时,去掉状态栏上的绿色图标

Android 12 或更高版本的设备上&#xff0c;当应用使用麦克风或相机时&#xff0c;图标会出现在状态栏中。如果应用处于沉浸模式&#xff0c;图标会出现在屏幕的右上角。用户可以打开“快捷设置”&#xff0c;并选择图标以查看哪些应用当前正在使用麦克风或摄像头。图 1 显示了…

Ubuntu 安装Kafka

在本指南中&#xff0c;我们将逐步演示如何在 Ubuntu 22.04 上安装 Apache Kafka。 在大数据中&#xff0c;数以百万计的数据源生成了大量的数据记录流&#xff0c;这些数据源包括社交媒体平台、企业系统、移动应用程序和物联网设备等。如此庞大的数据带来的主要挑战有两个方面…

【数据结构】插入排序:直接插入排序、折半插入排序、希尔排序的学习知识总结

目录 1、排序的基本概念 2、直接插入排序 2.1 算法思想 2.2 代码实现 3、折半插入排序 3.1 算法思想 3.2 代码实现 4、希尔排序 4.1 算法思想 4..2 代码实现 1、排序的基本概念 排序是将一组数据按照预定的顺序排列的过程&#xff0c;排序的基本概念包括以下内容…

自学WEB后端01-安装Express+Node.js框架完成Hello World!

一、前言&#xff0c;网站开发扫盲知识 1.网站搭建开发包括什么&#xff1f; 前端 前端开发主要涉及用户界面&#xff08;UI&#xff09;和用户体验&#xff08;UX&#xff09;&#xff0c;负责实现网站的外观和交互逻辑。前端开发使用HTML、CSS和JavaScript等技术来构建网页…

数据结构--快速排序

文章目录 快速排序的概念Hoare版本挖坑法前后指针法快速排序的优化三数取中法小区间用插入排序 非递归的快速排序 快速排序的概念 快速排序是通过二叉树的思想&#xff0c;先设定一个值&#xff0c;通过比较&#xff0c;比它大的放在它的右边&#xff0c;比它小的放在它的左边…

Python中的数据常见问题

数据可视化在Python中是一个非常重要的主题&#xff0c;它可以帮助我们更好地理解和分析数据。无论是探索数据的特征&#xff0c;还是向其他人展示数据的结果&#xff0c;数据可视化都起到了关键作用。然而&#xff0c;在进行数据可视化时可能会遇到一些常见问题。本文将为您分…

基于微信小程序的同城家政服务预约系统(源码+lw+部署文档+讲解等)

文章目录 前言系统主要功能&#xff1a;具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计…

DC电源模块关于宽电压输入和输出的范围

BOSHIDA DC电源模块关于宽电压输入和输出的范围 DC电源模块是一种电子设备&#xff0c;能够将输入的直流电源转换成所需的输出电源&#xff0c;用于供电各种电子设备。其中&#xff0c;关于宽电压输入和输出的范围&#xff0c;是DC电源模块常见的设计要求之一。本文将详细介绍…

嵌入式Linux应用开发-基础知识及GCC 编译器的使用

嵌入式Linux应用开发-基础知识及GCC 编译器的使用 第一章 HelloWorld 背后没那么简单1.1 交叉编译 hello.c1.2 请回答这几个问题1.3 演示 (...) 第二章 GCC 编译器的使用2.1 配套视频内容大纲2.1.1 GCC 编译过程(精简版)2.1.2 常用编译选项2.1.3 怎么编译多个文件2.1.4 制作、使…

深度学习:模型训练过程中Trying to backward through the graph a second time解决方案

1 问题描述 在训练lstm网络过程中出现如下错误&#xff1a; Traceback (most recent call last):File "D:\code\lstm_emotion_analyse\text_analyse.py", line 82, in <module>loss.backward()File "C:\Users\lishu\anaconda3\envs\pt2\lib\site-packag…

【Unity】LODGroup 计算公式

Unity 在配置 LodGroup 时&#xff0c;其分级切换的计算方法是按照物体在相机视野中占据的比例计算的。在运行时&#xff0c;如果相机视野范围&#xff08;Field of View&#xff09;没有改变&#xff0c;那么这个值可以直接换算成物体距离相机的距离。这里就讨论下如何计算得到…