五种IO模型和非阻塞IO

一、几个前置知识:

1、在应用层的这些read和write函数,本质就是把数据从用户层写给操作系统,这些函数本质也

是拷贝函数。

2、对IO的理解应该是:IO时间=等待时间+拷贝时间

3、要进行拷贝,就必须判断条件是否成立(比如读就要看接受缓冲区是否有数据,写就要看发送

缓冲区是否有数据),而这个条件就被称为读写事件。

4、高效IO就是之单位时间内拷贝的数据量大,也就是说,在单位时间内,IO过程中,等的比重越小,IO的效率越高。

5、几乎所有提高IO效率的办法,都是减小等的比重。

二、五种IО模型

2.1、阻塞IO

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

2.2、非阻塞IO

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

2.3、信号驱动IO

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

2.4、IO多路转接

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

2.5、异步IO

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

总结:

1、阻塞式IO、非阻塞式IO、信号驱动式IO、多路转接这四种都是同步IO。

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

3、阻塞式IO和非阻塞式IO在IO效率上是一样的,因为他们都在等,只不过等的方式不同而已。

4、同步IO和异步IO的区别是有没有直接参与IO的过程,直接参与IO就叫同步IO,不直接参与IO就叫异步IO ,异步IO知识发起IO,最后拿结果就行。

5、上述IO效率最高的是多路复用(多路转接)。

三、非阻塞IO

3.1fcntl

一个文件描述符,默认都是阻塞IO

函数原型如下:

传入的cmd的值不同,后面追加的参数也不相同。

fcntl函数有5种功能:

(1)复制一个现有的描述符(cmd=F_DUPFD)

(2)获得/设置文件描述符标记(Cmd=F_GETFD或F_SETFD)

(3)获得/设置文件状态标记(Cmd=F_GETFL或F_SETFL)

(4)获得/设置异步I/O所有权(Cmd=F_GETOWN或F_SETOWN)

(5)获得/设置记录锁(Cmd=F_GETLK,F_SETLK或F_SETLKW)

注意:用第三种功能,获取/设置文件状态标记,就可以将一个文件描述符设置为非阻塞。

3.2实现函数SetNoBlock

基于fcntl,实现一个SetNoBlock函数,将文件描述符设置为非阻塞。

cpp 复制代码
void SetNoBlock(int fd)
{
    int fl = fcntl(fd, F_GETFL);
    if (fl < 0)
    {
        perror("fcntl error");
        return;
    }
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);
    cout << "set " << fd << " done" << endl;
}

使用F_GETFL将当前的文件描述符的属性取出来(这个属性是一个位图),然后再使用F_SETFL将文件描述符设置回去,设置回去的同时,加上一个O_NONBLOCK参数。

四、轮询方式读取标准输入

cpp 复制代码
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <cerrno>
#include <cstring>

using namespace std;

void SetNoBlock(int fd)
{
    int fl = fcntl(fd, F_GETFL);
    if (fl < 0)
    {
        perror("fcntl error");
        return;
    }
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);
    cout << "set " << fd << " done" << endl;
}

int main()
{
    char buffer[1024];
    SetNoBlock(0);
    while (true)
    {
        cout << "please enter@" << endl;
        fflush(stdout);
        ssize_t n = read(0, buffer, sizeof(buffer));
        if (n > 0)
        {
            buffer[n] = 0;
            cout << "echo:" << buffer << endl;
        }
        else if (n == 0)
        {
            cout << "read done" << endl;
        }
        else
        {
            // 1、设置为非阻塞轮询,如果底层fd的数据没有就绪,recv/read/write/send等就会以出错的形式给我们返回
            // 2、有可能真的出错,有可能是底层资源未就绪,就通过errno错误码进行区分
            if (errno == EWOULDBLOCK)
            {
                cout << "0 fd data not ready, try again!!!" << endl;
                // 等待的时候可以做其他事情
                sleep(1);
            }
            else
            {
                // 出错了
                cerr << "read error, n =" << n << ", errno:" << errno << ", errno reason:" << strerror(errno) << endl;
            }
        }
    }
    return 0;
}

运行结果:

相关推荐
luweis18 小时前
企智孪生 ETA (6.5 人机协同:定义“协作界面 (Collaboration UI)”)【杭州联保致新科技有限公司 卢伟舜】
网络·人工智能·科技·程序人生·创业创新·学习方法
土星云SaturnCloud18 小时前
边缘计算赋能烟草行业数字化转型
服务器·人工智能·ai·边缘计算
兰令水18 小时前
【agent第3篇】agent上下文+面经
java·大数据·数据库
*neverGiveUp*18 小时前
PostgreSql常用SQL大全
数据库·sql·postgresql
跳动的世界线18 小时前
WSL 2 + Docker 本地全栈开发环境配置指南
运维·docker·容器
算力视野18 小时前
GPU、NPU、TPU有什么区别?
服务器·人工智能·gpu算力
专注VB编程开发20年18 小时前
上位机监控接收数据(从站)-Modbus TCP 从机(Slave)模式多站点设计
网络·网络协议·tcp/ip
真实的菜18 小时前
Redis 从入门到精通(三):持久化机制 —— RDB 与 AOF 深度解析
数据库·redis·缓存
ALINX技术博客18 小时前
【黑金云课堂】FPGA技术教程Linux开发:NVMe/Qt/OpenCV人脸检测
linux·qt·fpga开发
We Just Keep growing18 小时前
【MySQL运维篇】——日志、主从复制、分库分表、读写分离
java·运维·数据库·windows·学习·mysql