五种IO模型的介绍

前言

本文将介绍五种常见的IO模型:阻塞、非阻塞、信号驱动IO、多路复用、异步IO

文章的重点在于非阻塞与多路复用。而多路复用IO又有三种常见的方式。后续将会详细介绍。

这里就先熟悉一下IO模型、以及认识一下非阻塞IO的编写。

建立一个认识

IO分为:等+拷贝。比如往磁盘中写数据,会先等待,等待底层就绪(打开文件或者文件可写),然后才将用户的数据拷贝到内核缓冲区再由内核刷新到磁盘上。

所以什么是IO?IO就是等待条件就绪,然后进行拷贝。

而在实际的IO操作中,最耗时间的其实是等待,拷贝其实不花什么时间。提供IO的效率问题,就是围绕着如何减少等待的时间。

基于等待的方式不同,衍生出五种IO模型,下面就来认识一下。


阻塞IO

阻塞IO最常见,比如建立连接的俩台主机,主机A调用read ,而B主机没有发送数据,就会导致主机A阻塞等待消息的到来。

阻塞IO:在内核将数据准备好之前,系统调用会进入等待状态。所有的套接字,默认都是阻塞

阻塞IO是平常用的最多的模型,并且编写是最简单的。

以一个 例子总结阻塞IO

int main(){
    
    char buffer [1024];
    while(true){
        int n=read(0,&buffer,sizeof(buffer)-1);
        if(n>0){
            buffer[n]=0;
            std::cout<<"echo#"<<buffer;
        }
        else if(n==0){
            std::cout<<"read over"<<std::endl;
        }
        else{
            std::cout<<"出错!"<<std::endl;
        }
    }

    return 0;
}

如果没有输入数据,就会阻塞等待


非阻塞IO

如果数据没有就绪,就以出错的方式返回

非阻塞IO:内核没有将数据准备好,就以出错的方式返回,EAGAIN错误码被设置。

缺点:非阻塞会一直去轮询文件描述符,检测数据是否就绪, 对CPU来说是一直资源浪费

详解非阻塞IO

如果要将文件描述符设置为非阻塞状态,只需要fcntl系统调用即可。

设为非阻塞方式的步骤

SYNOPSIS
       #include <fcntl.h>

       int fcntl(int fd, int op, ... /* arg */ );

1.fcntl获取文件标记状态

2.对fd设置为非阻塞NON_BLOCK

fcntl的五种功能

  • 复制一个现有的描述符(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)
     

常用到的只有获取和设置

关于OP NONBLOCK表示为非阻塞

下面依旧以读取为例子,介绍一下非阻塞

//设置文件描述符为非阻塞
void SetNonBlock(int fd){
    //获取状态标志位
    int fl=fcntl(fd,F_GETFL);
    if(fl<0){
        std::cout<<"文件描述符获取失败!"<<std::endl;
        exit(1);
    }
    fcntl(fd,F_SETFL,O_NONBLOCK|fl);
}
int main(){
    //设置文件为非阻塞
    SetNonBlock(0);

    char buffer [1024];
    while(true){
        int n=read(0,&buffer,sizeof(buffer)-1);
        if(n>0){
            buffer[n]=0;
            std::cout<<"echo#"<<buffer;
        }
        else if(n==0){
            std::cout<<"read over"<<std::endl;
        }
        else{
            if(errno==EAGAIN){
                std::cout<<"EAGAIN中...."<<std::endl;
            }
            else if(errno==EINTR) //硬件中断
            {
                std::cout<<"EINTR中...."<<std::endl;
            }
            else{
                std::cout<<"读取出错!"<<std::endl;
            }
            sleep(1);
        }
    }
    return 0;
}

先将文件描述符设置为非阻塞,然后往外设中读数据,为了便于观察,每次轮询就休眠一秒

观察到每隔一秒就出错返回一次。

为了将NONBLOCK返回与实际出错分开

以errno错误码为标记

  • EAGAIN:再试一次
  • EINTR:信号中断

设置文件描述符为非阻塞的操作


信号驱动IO

内核将数据准备好,通过信号的方式来通知程序IO

信号驱动也是同步吗?

这里介绍一下同步和异步的概念:
如果一个IO模型,同时进行了等待+拷贝的任意一种就是同步

反之任何都不进行的就是异步

信号驱动虽然没有实际的等,但是拷贝是由自己(本线程/进程)执行的,也是同步。

同时阻塞IO和异步IO:不仅进行等待,还进行拷贝,属于同步型IO。


 IO多路转接

多路转接:允许一个进程同时监听多个IO事件,并且在任意一个IO事件就绪时进行处理。

多路转接看上去与阻塞IO差不多,只不过通过系统调用,同时等待多个文件描述符。

常见的多路转接方式:

  • select
  • poll
  • epoll

 也是后续介绍的重点


异步IO

内核完成数据拷贝,并告知应用程序拷贝完成,可以操作数据


IO的实质就是等待+拷贝,实际上最影响性能的就是等待过程,为了提高效率。总是围绕着如何减少等待的时间。

 同步通信 vs 异步通信

同步:发起一个调用,在这个调用没有处理完毕时候,就会进入等待状态,直到将结果拿到。

异步:异步则是在发起调用时,不需要等待获取结果,而是直接返回。什么时候获取结果?在被调用者处理完毕后,通过回调函数来告知结果。

后续将详细介绍多路转接的模型,并且编写服务器echo,以及基于epoll的reator模型.

  • 21
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深度搜索

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值