0
点赞
收藏
分享

微信扫一扫

五种IO模型基本概念(九千字长文详解)

五种IO模型基本概念

什么是高效的IO?

我们常说的IO,站在体系结构的角度上来看!把数据从内存拷贝到外设就是output,把数据从外设搬到内存就是input!——确实没有错!但是理解还不够!

当我们在网络中,我们通过write进行发送,read进行读取!但是当我们write的时候我们实际不是发送数据!而是将数据拷贝到发送缓冲区里面,所以write的本质就是拷贝!而read进行读取的本质是从TCP的接收缓冲区里面把数据从内核中拷贝到应用层!

可是你想拷贝就要能拷贝呢?

例如:你想write,有没有可能发送缓冲区就已经流量控制的问题,发送缓冲区已经被写满了!没有空间让我们write了!那么此时write操作就会阻塞!直到发送缓冲区空出位置!

如果我们想要read,万一接收缓冲区里面压根就没有数据呢?那么read不就会阻塞住吗?就像在进程间通信,使用管道对方不写入数据!我们读取就会被阻塞! 写网络套接字的时候,如果对方不发送数据!那么服务端就会阻塞卡住!——==read,write的本质是拷贝!而拷贝是有条件的!读取的是要求接收缓冲区有数据!写入的时候要求发送缓冲区有空间!==

==阻塞的本质就是等待某种资源就绪,当操作系统发现接收队列没有数据/发送队列没有空间的时候,就会把进程的PCB放入等待队列里面进行等待!——等待到某种资源就绪,等到有数据之后,就会将数据拷贝,拷贝完毕后就进行返回==

所以我们对于read/recv/write等接口我们不仅要知道本质是拷贝!还要明白是要等待的!

==IO = 等待 + 数据拷贝!==

在系统中,我们对于等待的情况感知并不明确!像是进行文件操作,我们是没有资格知道文件的写入的!只是调用了系统接口!然后系统给我们将数据刷新到文件里面,对于网络,也是一样!也是操作系统来替我们发送的!所以系统和网络在IO的处理上是一致的!

但是在系统上,我们对于等待的情况是不直观的!我们访问一个本地文件,很快就访问完成,写入完成了!看起来就是一瞬间!没有等待的样子!但是其实还是有等待的!

==但对于网络通信,距离长了之后,还有拥塞控制,流量控制的策略之后,等待时间的比重就变得十分的明显了!==

五种IO模型概念

我们先用一个例子来引入

五种IO模型

阻塞IO是最常见的IO模型

Quicker_20231113_102557_result

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

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

高级IO重要概念

同步通信 vs 异步通信

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

阻塞 vs 非阻塞

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

其他高级IO

非阻塞IO,纪录锁,系统V流机制,I/O多路转接(也叫I/O多路复用),readv和writev函数以及存储映射 IO(mmap,这个可以去了解),这些统称为高级IO

非阻塞IO

fcntl函数

一个文件描述符, 默认都是阻塞IO——所以想要非阻塞IO,我们首先就要将文件描述符设置为非阻塞的!

在我们使用read/send之类接口的时候,都一个一个flag参数选项!

void SetNoBlock(int fd) 
{ 
    int fl = fcntl(fd, F_GETFL);//首先获取该该文件的文件标志位
    //其实就是原来该文件的设置选项!
    if (fl < 0)
    {
        perror("fcntl"); 
        return;
    } 
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);//新的标志位和旧的标志位一起设置进文件描述符里面!这样子这个文件描述符就变成了非阻塞
}

使用轮询的方式进行读取

#include"Util.hpp"
#include<cstdio>

int main()
{
  while (true)
  {
        printf(">>>>> ");
        fflush(stdout);
        // 从0号文件描述符(键盘)里面进行读取!
        char buffer[1024];
        ssize_t s = read(0, buffer,sizeof(buffer)-1);
        if(s > 0)
        {
            buffer[s] = 0;
            std::cout <<"echo: " << buffer;
        }
        else if(s == 0)//读取到文件尾
        {
            std::cout <<"read end" <<std::endl;
            break;
            //命令行输入的文件可以用ctrl+ d 来表示文件尾
        }
        else //为负数
        {

        }
  }
  return 0;
}

image-20231113202200856

==这就是一个阻塞的过程!——如果这是一个套接字的话,对方不把数据通过网络发送也会卡主!==

//Util.hpp
#pragma once

#include<iostream>
#include<unistd.h>
#include<cstring>
#include<fcntl.h>
#include<cerrno>


void setNoBlock(int fd)
{
  int fl = fcntl(fd,F_GETFL);
  if(fl == -1)
  {
        std::cerr << "fcntl: " << strerror(errno)<<std::endl;
        return;
  }
  fcntl(fd,F_SETFL,fl|O_NONBLOCK);
}
//main.cc
#include<cstdio>

int main()
{
  setNoBlock(0);//设置为非阻塞
  while (true)
  {
        printf(">>>>> ");
        fflush(stdout);
        sleep(1);
        // 从0号文件描述符(键盘)里面进行读取!
        char buffer[1024];
        ssize_t s = read(0, buffer,sizeof(buffer)-1);
        if(s > 0)
        {
            buffer[s] = 0;
            std::cout <<"echo: " << buffer ;
        }
        else if(s == 0)//读取到文件尾
        {
            std::cout <<"read end" <<std::endl;
            break;
        } 
        else //为负数
        {
        }
  }
  return 0;
}

image-20231113202517127

如果不加上sleep

image-20231113202558509

==在非阻塞等下,我们可以看到代码会一直不断的进行死循环!而不会因为我们没有输入数据从而导致阻塞等待!read函数会立马返回!==

那么这有什么用呢?——我们可以在我们代码空闲的时候做一些其他的工作!

//Util.hpp
#pragma once

#include<iostream>
#include<unistd.h>
#include<cstring>
#include<fcntl.h>
#include<functional>


void setNoBlock(int fd)
{
    int fl = fcntl(fd,F_GETFL);
    if(fl == -1)
    {
        std::cerr << "fcntl: " << strerror(errno)<<std::endl;
        return;
    }
    fcntl(fd,F_SETFL,fl|O_NONBLOCK);
}

using func_t = std::function<void()>;

void printlog()
{
    std::cout << "this is a log" <<std::endl;
}

void download()
{
    std::cout << "this is a download" <<std::endl;
}

void executeSql()
{
    std::cout << "this is a executeSql" <<std::endl;
}
//main.cc
#include"Util.hpp"
#include<cstdio>
#include<vector>
#define INIT(v) do{ \
      v.push_back(printlog);\
      v.push_back(download);\
      v.push_back(executeSql);\
  }while(0)

#define EXEX_OTHER(v) do{ \
  for(auto const &  cb:v) cb();\ 
}while(0)


int main()
{
    std::vector<func_t> callback;
    INIT(callback);
    setNoBlock(0);//设置为非阻塞
    while (true)
    {
        printf(">>>>> ");
        fflush(stdout);
        sleep(1);
        // 从0号文件描述符(键盘)里面进行读取!
        char buffer[1024];
        ssize_t s = read(0, buffer,sizeof(buffer)-1);
        if(s > 0)
        {
            buffer[s] = 0;
            std::cout <<"echo: " << buffer ;
        }
        else if(s == 0)//读取到文件尾
        {
            std::cout <<"read end" <<std::endl;
            break;
        } 
        else //为负数
        {
        }
        EXEX_OTHER(callback);

    }
    return 0;
}

image-20231113204953352

==非阻塞的时候对应的返回值是什么呢?==

#include"Util.hpp"
#include<cstdio>
#include<vector>

int main()
{
  INIT(callback);
  setNoBlock(0);//设置为非阻塞
  while (true)
  {
        printf(">>>>> ");
        fflush(stdout);
        sleep(1);
        // 从0号文件描述符(键盘)里面进行读取!
        char buffer[1024];
        ssize_t s = read(0, buffer,sizeof(buffer)-1);
        if(s > 0)
        {
            buffer[s] = 0;
            std::cout <<"echo: " << buffer ;
        }
        else if(s == 0)//读取到文件尾
        {
            std::cout <<"read end" <<std::endl;
            break;
        } 
        else //为负数
        {
            std::cout << "result: " << s <<std::endl;
        }
  }
  return 0;
}

image-20231113210937775

==我们可以看到返回值就是-1==

举报

相关推荐

0 条评论