Linux网络 | 初识高级IO | 认识五种IO模型、代码实现非阻塞IO

        前言:本节内容进入高级IO, 我们将要讲解高级IO的铺垫性质的知识点, 比如理解高级IO,IO分为什么等等。 下面废话不多说,开始我们的学习吧!!!

        ps:本节内容刚刚进入高级IO, 友友们放心观看哦!

目录

重新理解IO

五种IO模型

阻塞式等待

非阻塞式等待

多路转接

异步IO

非阻塞


重新理解IO

        以read为例,我们今天调用read,接收缓冲区没有数据,那么read就阻塞等待了。

        以write为例,我们今天调用write, 发送缓冲区被打满了,那么write阻塞等待了。

        我们就知道一个基本事实就是,在应用层读写的时候,本质不是从网络当中读数据,或者把数据写到网络里,我们其实是把数据写给操作系统,至于操作系统什么时候发,发多少,由操作系统自己决定。所以read和write其实本质就是拷贝函数,我们平时应用层用的这些读写函数,其实就是拷贝函数。

        更重要的是,我们把读写就叫做IO,即Input和Output。

        当我们进行IO的时候,其实应用层大部分时间,都是等待的操作。只有当有数据才读,只有当有空位,才写。所以,IO本质 = 等 + 拷贝。那么我们IO的过程中在等什么呢?对于read,在等缓冲区有数据;对于write,在等缓冲区有空位。所以,我们真正IO的时候,要进行拷贝,就要先判断条件成立,这里的条件就叫做读写事件。

        所以,我们以后就一句话: IO = 等 + 贝,等什么? 等读写事件就绪。

        什么叫做高效的IO呢?单位时间内,IO过程中等的比重越小,IO的效率就越高。几乎所有的提高IO效率的策略,本质就是让等的比重减小。

  

五种IO模型

五种IO模型分为以下五种:

  • 1、阻塞式。我们学过的大部分接口,比如read, write接口。都是阻塞式。
  • 2、非阻塞式。就是非阻塞轮询查看是否读写事件就绪。
  • 3、信号驱动。就是当读写事件就绪的时候通知我们。
  • 4、多路复用,多路转接。
  • 5、异步IO

        阻塞IOVS非阻塞IO。两者在IO层面没有区别。因为IO = 等 + 拷贝。只不过等的方式不同,所以在非阻塞时候可以做其他事,在其他事上效率更高。

        同步IO和异步IO的区别?区别就是有没有参与IO。 参与了,就是同步。没参与,只是发起IO,就是异步。

        这里讲的同步IO和线程同步之间,没有关系!!!        

        上面的这些方法,谁的效率最高?就是多路转接。所以我们接下来几节内容都是要学习多路转接。 但是由于非阻塞也有一些重要, 所以也要了解一下非阻塞,本节内容的任务就是将非阻塞进行讲解完毕。

阻塞式等待

 

        当我们recvfrom向操作系统要数据的时候,操作系统说数据还没有准备好,则等待数据。等待数据,其实就是阻塞在了recvfrom这个系统调用里面了。怎么阻塞?

        本质就是把PCB从运行队列里面拿下来,当数据好了之后,再把我们的进程唤醒运行。再把数据从内核当中拷贝到应用层返回。这个时候revfrom才能够返回。所以recvfrom这个函数就是先等,然后再拷贝。

非阻塞式等待

        
        然后recvfrom也可以设置成为非阻塞。设置成非阻塞如果再没有数据,那么不会等待,而是直接返回一个EWOULDBLOCK。如果检测到数据报准备好了,那么我们就可以把数据从内核拷贝到用户层,然后就完成了。

多路转接

        上面的recvfrom一个函数,承担了两种任务。第一种是等,第二种是数据拷贝。然后多路转接有自己的函数,其中一个就叫select。select只负责IO当中的等,事件就绪后他会通知上层。然后上层再来读取,这样recvfrom或者read等接口直接调用的话就不需要等了。所以,这里的select,以及我们以后讲解的其他的多路转接接口,最大的特点就是他们会一次性等待多个文件描述符。

异步IO

        

        其实异步IO接口在操作系统层面和应用层面都有。我们调用异步IO接口。接口就直接返回。应用层就能继续做自己的事情。在做自己的事情的时候,操作系统帮我等,等好之后操作系统帮我把数据从内核拷贝到用户。之后再进行数据处理。

非阻塞

        我们这里要使用代码来实现一下网络套接字版本的非阻塞IO。首先下面是我们要用到的核心函数fcntl。 其中第一个参数就是文件描述符(可以是网络版本的,也可以是非网络版本的)。

         对于上面的cmd,对指定的描述符,进行设置状态。 传入的cmd不同,后面的可变参数就不同。

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

        下面是我们测试非阻塞的代码:

#include<iostream>
#include<unistd.h>
#include<fcntl.h>
#include<cstring>
using namespace std;


//这里之所以要先获得标志位, 是因为担心这个文件描述符已经设置过标记位了。
void SetNoBlock(int fd) //
{
    int fl = fcntl(fd, F_GETFL);
    if (fl < 0)
    {
        perror("fcntl");
        exit(0);
    }
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}


int main()
{
    SetNoBlock(0);
    char buffer[1024];
    while (true)
    {
        cout << "Please Enter# ";
        fflush(stdout); 

        ssize_t n = read(0, buffer, sizeof(buffer) - 1);
        if (n > 0)
        {
            buffer[n - 1] = 0;
            cout << "echo: " << buffer << endl;
        }
        else if (n == 0)
        {
            cout << "read done" << endl;
        }
        else
        {
            if (errno == EWOULDBLOCK)
            {
                cout << "not ready, try again" << endl;
                sleep(1);
        
            }
            else
            {

                cout << "read error n = "  << n << endl;
                cout << "error code: " << errno << " " << "strerror: " << strerror(errno) << endl;
            
            }
        }
    }
    return 0;
}

        上面的代码, 为什么当n == -1的时候里面还要进行判断一下是否errno是EWOULDBLOCK。 因为如果一旦我们把fd设置成非阻塞, 一旦底层没有数据,那么返回值就会以出错的形式返回。所以非阻塞轮询的时候出错有两种情况:1、真的出错。2、底层没有就绪。

        我们如何区分, 就是使用错误码区分, 如果错误码是11,那么就是没有就绪。宏定义名称叫做:EWOULDBLOCK。

 ——————以上就是本节全部内容哦, 如果对友友们有帮助的话可以关注博主, 方便学习更多知识哦!!!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值