【Linux】高级IO——五种IO模型和基本概念 ,非阻塞IO,fcntl,实现非阻塞IO,同步通信和异步通信

文章目录

  • Linux高级IO
    • 1. 五种IO模型
      • 1.1 阻塞IO
      • 1.2 非阻塞IO
      • 1.3 信号驱动IO
      • 1.4 IO多路转接
      • 1.5 异步IO
    • 2. 同步通信和异步通信
    • 3. 阻塞和非阻塞

Linux高级IO

1. 五种IO模型

  IO是什么?

  IO是计算机领域中的缩写,指的是输入/输出(Input/Output)。在计算机系统中,IO通常指的是计算机与外部设备(如键盘、鼠标、显示器、硬盘、网络等)之间的数据交换过程。

  输入(Input) 输入指的是数据从外部设备传输到计算机系统内部的过程。例如:从键盘输入文字或命令到计算机中;从网络接收数据包;从磁盘读取文件内容。

  输出(Output) 输出指的是数据从计算机系统传输到外部设备的过程。例如:将计算机处理后的数据显示在屏幕上;将数据写入到打印机以打印文档;将文件保存到硬盘上。

  

  数据是怎么从输入到输出的呢,过程是什么样的?

  简单的看,调用read和write函数将数据传入OS,由OS传给不同的外设。

  输入和输出(IO)的过程发生在计算机系统与外部环境之间的数据交换过程中。在计算机系统内部,IO的过程涉及多个层次和组件:

  应用层:应用程序通过调用操作系统提供的IO接口(如read和write函数)来实现数据的输入和输出。

  操作系统操作系统负责管理和调度IO操作,包括与设备驱动程序的交互、数据缓存、IO调度等。

  设备驱动程序:设备驱动程序是操作系统和硬件设备之间的接口,负责控制和管理具体的硬件设备,处理来自设备的IO请求和响应。

  总结来说,IO过程涉及了从用户层到操作系统内核的数据传输,以及操作系统到物理设备或网络的数据传输过程。

  

  操作系统怎么判断IO发生,或者说是输入输出发生?

  通常在系统调用,硬件中断,状态改变,网络活动,资源占用这些情况下会产生IO事件。

  在计算机系统中,IO操作通常可以分解为两个主要阶段:等待(或者说请求)和拷贝(或传输)。在理解拷贝条件是否发生之前,需要考虑以下几个关键点:

  判断等待(或请求)条件发生:

  IO请求发起:应用程序发出IO请求,例如读取文件内容、发送网络数据等。

  系统调用:操作系统接收到应用程序的IO请求,然后开始进行IO操作的准备工作,这通常包括分配内核缓冲区、准备硬件接口等。

  判断拷贝(或传输)条件发生:

  系统调用返回:当应用程序发出读取或写入请求后,操作系统会开始处理这个请求,并最终完成数据的拷贝操作。可以通过系统调用的返回状态来判断拷贝操作是否已经完成。

  数据可用性:在读取操作中,可以通过检查读取的数据是否已经在应用程序的缓冲区中可用来判断拷贝操作是否完成。类似地,在写入操作中,可以通过检查数据是否成功地传输到目标设备或目标位置来确认拷贝操作的完成。

  事件通知:有些IO操作在完成后会通过事件通知机制来通知应用程序,例如异步IO模型中的完成事件或回调。应用程序可以通过这些事件或回调来确认拷贝操作已经完成。

  

  所以为了提高数据交互的效率,我们要提高IO操作的效率。

  

  总结来说:

  1. IO就是Input和Output,是计算机与外部进行交换的过程。

  2. 为了实现应用层的数据交换,我们通常使用read和write函数,本质上就是把数据从用户层写给操作系统,再本质上就是拷贝函数。

  3. IO = 等 + 拷贝 ,再进行拷贝之前,我们要判断拷贝条件是否发生。

  4. 所以我们想要实现高效的IO,在单位的时间里,等 或 拷贝 的时间越小效率越高,但是一般拷贝的时间差不多,所以怎么降低 等 的比重,就是我们提高IO效率的办法。

  

  所以说下面的可以提高IO效率的模型,都是降低了等待的时间。

  怎么降低等待的时间呢?

  

  我们先以简单的钓鱼例子来概括一下这五种IO模型:

  阻塞IO模型:你投放鱼饵后,就等待鱼儿咬钩。在等待鱼咬钩的过程中,不能做其他事情,只能一直盯着浮标,直到有鱼上钩。

  非阻塞IO模型:你在每次投放鱼饵后,你不会傻等鱼儿上钩,而是不断定期检查浮标的状态。如果没有鱼上钩,就继续做其他事情,直到鱼上钩就收杆。

  IO复用模型:你可能会使用多根钓竿,使用10个鱼竿,100个鱼竿,可以是无穷的鱼竿,当有任何一根钓竿的浮标动了,你就知道有鱼上钩,然后去收杆。

  信号驱动IO模型:你可能会使用自动报警装置。每次投放鱼饵后,设定了一个报警器,当有鱼上钩时触发报警器发出信号。在听到报警器响时,立即去收杆。

  异步IO模型:你会雇佣一个专业的钓鱼服务公司。他们会使用不同的钓鱼方式,但是你完全不用关心,你只要他们通知你来收杆就可以了。

  

1.1 阻塞IO

  阻塞IO: 在内核将数据准备好之前, 系统调用会一直等待. 所有的套接字, 默认都是阻塞方式。

  阻塞IO是最常见的IO模型。
在这里插入图片描述

  我们写一个简单的套接字阻塞IO:

  Socket.hpp


#include <iostream>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

class Sock
{
public:
    Sock()
    {}

    ~Sock()
    {}

    //创建socket
    void Socket()
    {
        //表示创建一个IPV4,协议为tcp的套接字,0表示使用默认协议,
        _sockfd=socket(AF_INET,SOCK_STREAM,0);
        if(_sockfd){
            perror("Socket creation failed");
            return ;
        }
    }

    //绑定端口
    void Bind(uint16_t port)
    {
        //sockaddr_in结构体用于指定IPv4地址和端口号
        //INADDR_ANY 表示服务器将接受来自本机任意IP地址的连接
        //htons() 用于将端口号从主机字节顺序转换为网络字节顺序
        struct sockaddr_in addr;
        addr.sin_family=AF_INET;
        addr.sin_addr.s_addr=INADDR_ANY;
        addr.sin_port=htons(port);
        if(bind(_sockfd,(struct sockaddr *)&addr,sizeof(addr))==-1)
        {
            perror("Listen failed!");
            return ;
        }
    }

    //监听连接
    void Listen()
    {
        if(listen(_sockfd,5)==-1)
        {
            perror("Listen failed!");
            return ;
        }
    }

    //接受连接并且阻塞
    int Accept(std::string *clientip, uint16_t *clientport)
    {
        int client_fd=accept(_sockfd,nullptr,nullptr);
        if (client_fd == -1)
        {
            perror("Accept failed");
            return -1;
        }
        return client_fd;
    }

    //释放连接
    void Close()
    {
        close(_sockfd);
    }

    //返回sock文件描述符
    int Sockfd()
    {
        return _sockfd;
    }

private:
    int _sockfd;
};

  

  Block.cpp

  在进程接收到数据之前,进行会阻塞,直到有数据输入。

#include "Socket.hpp"

int main()
{
    Sock server_sock;
    
    server_sock.Socket();
    server_sock.Bind(8080);
    server_sock.Listen();

    std::cout << "Waiting for connections...\n";

    while (true)
    {
        int client_fd = server_sock.Accept(nullptr, nullptr);
        std::cout << "Connection accepted. Client connected.\n";

        // 循环接收数据
        while (true)
        {
            char buffer[1024];
            //recv 函数用于从指定的 client_fd 描述符(客户端连接的Socket)接收数据
            //buffer 是用来存储接收数据的缓冲区的指针
            //sizeof(buffer) - 1 表示缓冲区的大小
            //0 是可选的标志参数,这里表示没有特殊的接收行为标志
            //bytes_read 是 recv 函数的返回值,表示实际接收到的字节数
            ssize_t bytes_read = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
            if (bytes_read == -1)
            {
                perror("Receive failed");
                break;
            }
            else if (bytes_read == 0)
            {
                std::cout << "Client disconnected.\n";
                break;
            }

            buffer[bytes_read] = '\0';
            std::cout << "Received message from client: " << buffer;
        }

        // 关闭连接
        close(client_fd);
    }
    server_sock.Close();

    return 0;
}

1.2 非阻塞IO

  非阻塞IO: 如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码。

  非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符,这个过程称为轮询,这对CPU来说是较大的浪费,一般只有特定场景下才使用。

在这里插入图片描述

  NoBlock.cpp

  不管进程是否接收到数据,函数都会有返回值。

#include "Socket.hpp"
#include <unistd.h>
#include <fcntl.h>
#include <cstdio>
#include <string.h>
#include <errno.h>

int main()
{
    Sock server_sock;
    
    server_sock.Socket();

    //设置为非阻塞
    int flags=fcntl(server_sock.Sockfd(),F_GETFL,0);
    if(flags<0)
    {
        perror("fcntl");
    }
    fcntl(server_sock.Sockfd(),F_SETFL,flags|O_NONBLOCK);

    std::cout<<"set "<<server_sock.Sockfd()<<" nonblock done"<<std::endl;

    server_sock.Bind(8080);
    server_sock.Listen();

    std::cout << "Waiting for connections...\n";

    while (true)
    {
        int client_fd = server_sock.Accept(nullptr, nullptr);
        sleep(1);

        //循环接收数据
        while (true)
        {
            char buffer[1024];
            //recv 函数用于从指定的 client_fd 描述符(客户端连接的Socket)接收数据
            //buffer 是用来存储接收数据的缓冲区的指针
            //sizeof(buffer) - 1 表示缓冲区的大小
            //0 是可选的标志参数,这里表示没有特殊的接收行为标志
            //bytes_read 是 recv 函数的返回值,表示实际接收到的字节数
            ssize_t bytes_read = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
            if (bytes_read > 0)
            {
                std::cout << "Connection accepted. Client connected.\n";
                buffer[bytes_read] = '\0';
                std::cout << "Received message from client: " << buffer;
                sleep(1);
            }
            else if (bytes_read == 0)
            {
                std::cout << "Client disconnected.\n";
                break;
            }
            else 
            {
                //1.设置为非阻塞,如果底层的fd数据没有就绪,recv/read/write/send
                //返回值就会以出错的形式返回
                //2.1.真的出错了
                //2.2.底层没有就绪,我们通过errno区分
                //如果错误码是11就表示底层没有就绪
                if(errno==EAGAIN)
                {
                    std::cout<<"0 fd data not ready,try again"<<std::endl;
                    break;
                }
                else
                {
                    std::cout<<"read error"<<" errno code: "<<errno<<" strerror: "<<strerror(errno)<<std::endl;
                    break;
                }
                sleep(1);
            }
        }
        // 关闭连接
        close(client_fd);
    }
    server_sock.Close();

    return 0;
}

  

1.3 信号驱动IO

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

在这里插入图片描述

  

1.4 IO多路转接

  IO多路转接: 虽然从流程图上看起来和阻塞IO类似. 实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态。

在这里插入图片描述

  

1.5 异步IO

  异步IO: 由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)。

在这里插入图片描述

  总结:任何IO过程中,都包含两个步骤:第一是等待,第二是拷贝。而且在实际的应用场景中,等待消耗的时间往往都远远高于拷贝的时间,让IO更高效,最核心的办法就是让等待的时间尽量少。

  

2. 同步通信和异步通信

  同步和异步关注的是消息通信机制

  所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回. 但是一旦调用返回,就得到返回值了; 换句话说,就是由调用者主动等待这个调用的结果

  异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果; 换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果; 而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用

  同步通信:类比钓鱼例子,同步通信就像你投放鱼饵后,自己必须一直盯着浮标,直到鱼上钩才能继续下一步操作。这种方式下,发送方和接收方都必须严格按照某种约定的时间或步骤进行操作,以确保信息的同步传输和处理。

  异步通信:类比钓鱼例子,异步通信就像你投放鱼饵后不再一直盯着浮标,而是定期检查浮标的状态。你可以在等待鱼上钩的同时做其他事情,当浮标动了或者有鱼上钩时,你会得到通知,然后再去收杆。

  

3. 阻塞和非阻塞

  阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态

  阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。

  非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。

  类比钓鱼例子,阻塞就像你投放鱼饵后,不得不一直盯着浮标,不能离开或者做其他事情,直到鱼上钩。

  类比钓鱼例子,非阻塞就像你投放鱼饵后,定期检查浮标的状态。如果浮标没有动,你可以继续做其他事情,不需要一直等待鱼上钩。

          

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【vue3|第15期】Vue3模板语法入门指南

日期:2024年7月2日 作者:Commas 签名:(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释:如果您觉得有所帮助,帮忙点个赞,也可以关注我,我们一起成长;如果有不对的地方,还望各位大佬不吝赐教,谢谢^ - ^ 1.01365 = 37.7834;0.99365 = 0.0255 1.02365 = 1377.4083…

上海网站建设如何做

上海是中国最繁华的城市之一&#xff0c;作为全国的经济、文化和科技中心&#xff0c;网站建设在上海变得越来越重要。如何做好上海网站建设&#xff0c;让网站更加吸引人&#xff0c;成为企业和个人宣传自身的重要平台呢&#xff1f; 首先&#xff0c;要有清晰的定位和目标。在…

IT之旅启航:高考后IT专业预习全攻略

✨作者主页&#xff1a; Mr.Zwq✔️个人简介&#xff1a;一个正在努力学技术的Python领域创作者&#xff0c;擅长爬虫&#xff0c;逆向&#xff0c;全栈方向&#xff0c;专注基础和实战分享&#xff0c;欢迎咨询&#xff01; 您的点赞、关注、收藏、评论&#xff0c;是对我最大…

鸿蒙开发:Universal Keystore Kit(密钥管理服务)【密钥生成介绍及算法规格】

密钥生成介绍及算法规格 当业务需要使用HUKS生成随机密钥&#xff0c;并由HUKS进行安全保存时&#xff0c;可以调用HUKS的接口生成密钥。 注意&#xff1a; 密钥别名中禁止包含个人数据等敏感信息。 开发前请熟悉鸿蒙开发指导文档&#xff1a;gitee.com/li-shizhen-skin/harm…

Java实现电子围栏的小例子

主要需求是实现一个电子围栏判断的小例子其中包括前端和后端的demo代码 引入对应的依赖库 <!--jts库通常用于几何计算和表示地理空间数据--> <dependency><groupId>org.locationtech.jts</groupId><artifactId>jts-core</artifactId><…

web学习笔记(七十五)

目录 1.小程序修改响应式数据 1.1修改基本数据类型的值 1.2修改复合数据类型的值 2. 发送请求 3.小程序解决跨域问题 1.小程序修改响应式数据 1.1修改基本数据类型的值 在小程序中需要先将data中的数据拿过来并结构&#xff0c;才可以在this.setdata中修改数据&#xf…

2024攻防演练:亚信安全推出MSS/SaaS短期定制服务

随着2024年攻防演练周期延长的消息不断传出&#xff0c;各参与方将面临前所未有的挑战。面对强大的攻击队伍和日益严格的监管压力&#xff0c;防守单位必须提前进行全面而周密的准备和部署。为应对这一形势&#xff0c;亚信安全特别推出了为期三个月的MSS/SaaS短期订阅方案。该…

SpringBoot Task 定时任务

springboot中使用Task定时任务非常简单 springboot 中自带的都有注解不需要引入依赖 第一步&#xff1a;在启动类上添加启用定时任务注解 EnableScheduling //开启任务调度 第二步&#xff1a;创建一个springboot组件用于定时任务管理 package cn.lsy.api.Task;import cn.ls…

【LeetCode】十一、滑动窗口:长度最小的子数组 + 定长子串的元音最大数目

文章目录 1、滑动窗口2、leetcode209&#xff1a;长度最小的子数组3、leetcode1456&#xff1a;定长子串中元音的最大数目 1、滑动窗口 如下&#xff0c;有一个数组&#xff0c;现三个元素为一组&#xff0c;求最大的和&#xff0c;自然可以while循环实现&#xff1a;i 、i1、…

着色器预热?为什么 Flutter 需要?为什么原生 App 不需要?那 Compose 呢?Impeller 呢?

依旧是来自网友的问题&#xff0c;这个问题在一定程度上还是很意思的&#xff0c;因为大家可能会想&#xff0c;Flutter 使用 skia&#xff0c;原生 App 是用 skia &#xff0c;那为什么在 Flutter 上会有着色器预热&#xff08;Shader Warmup&#xff09;这样的说法&#xff1…

使用getline()从文件中读取一行字符串

我们知道&#xff0c;getline() 方法定义在 istream 类中&#xff0c;而 fstream 和 ifstream 类继承自 istream 类&#xff0c;因此 fstream 和 ifstream 的类对象可以调用 getline() 成员方法。 当文件流对象调用 getline() 方法时&#xff0c;该方法的功能就变成了从指定文件…

lnternet 发展史

一&#xff0c;lnternet 发展史 ARPA net &#xff08;上世纪50年代二战结束&#xff09; 无线 战场指挥通信协议落后 TCP/IP 包交换 WEB (70年代 ) 80年代 90年代 二&#xff0c;互联网的典型应用&#xff1a; 96年到2008年 第一代技术…

8.ApplicationContext常见实现

ClassPathXmlApplicationContext 基于classpath下xml格式的配置文件来创建 <?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XMLSchema-i…

Linux-页表如何对物理内存进行映射

1.1 页框和页帧 我们知道通过页表可以将虚拟内存映射到对应的物理内存&#xff0c;而操作系统对于物理内存的管理并不是以字节为单位的&#xff0c;而是将物理内存分为许多大小为4KB的块&#xff0c;称为页框或页帧&#xff0c;这就是为什么我们在创建共享内存是建议将大小设定…

【server】3、注册中心与配置中心

1、服务注册与发现 1.1、consul 1.1.1 是什么 官网&#xff1a; Consul by HashiCorp spring-cloud-consul: Spring Cloud Consul :: Spring Cloud Consul gitHub 官网 &#xff1a;GitHub - hashicorp/consul: Consul is a distributed, highly available, and data cent…

上海-灵曼科技(面经)

上海-灵曼科技 hr电话面 个人简介 个人信息的询问 是否知道芋道框架 技术面 算法题 14. 最长公共前缀&#xff08;写出来即可&#xff09; 聊一下Docker Docker核心概念总结Docker实战 聊一下AOP Spring AOP详解 聊一下JWT JWT 基础概念详解JWT 身份认证优缺点分析 Spri…

2024华为OD机试真题- 电脑病毒感染-(C++/Python)-C卷D卷-200分

2024华为OD机试题库-(C卷+D卷)-(JAVA、Python、C++) 题目描述 一个局域网内有很多台电脑,分别标注为 0 ~ N-1 的数字。相连接的电脑距离不一样,所以感染时间不一样,感染时间用 t 表示。 其中网络内一台电脑被病毒感染,求其感染网络内所有的电脑最少需要多长时间。如果…

Spzhi知识付费社区主题免费下载

主题介绍 用typecho打造一款知识付费社区主题&#xff0c;带会员功能&#xff0c;为内容创业者提供知识变现一站式解决方案&#xff0c;让用户沉淀到自己的平台&#xff0c;形成自己的私域流量池&#xff0c;打造流量闭环&#xff0c;零门槛搭建你的移动网络课堂 主题功能 支…

RpcChannel的调用过程

目录 1. RPC调用方&#xff08;caller&#xff09;的调用(消费)过程 2.在caller下创建文件&#xff1a;calluserservice.cc 3.在src的include下创建文件&#xff1a;mprpcchannel.h 4.在src下创建mprpcchannel.cc 1. RPC调用方&#xff08;caller&#xff09;的调用(消费)过…

Python:Python基础知识(注释、命名、数据类型、运算符)

四、Python基础知识&#xff08;注释、命名、数据类型、运算符&#xff09; 1.注释 Python有两种注释方法&#xff1a;单行注释和多行注释。单行注释以#开头&#xff0c;多行注释以‘’‘开头和结尾。 2.命名规则 命名规则: 大小写字母、数字、下划线和汉字等字符及组合&am…