I/O的阻塞、非阻塞、同步和异步

本文详细阐述了网络IO的两个阶段:数据准备和数据读写,分别介绍了阻塞与非阻塞模式,以及同步和异步I/O的工作原理。重点讲解了如何通过recv函数实例展示不同模式,并对比了它们的优缺点及在业务中的实际应用。
摘要由CSDN通过智能技术生成

典型的一次I/O的两个阶段分为 数据准备和数据读写

服务器接收客户端的请求,得先监听客户端有没有数据过来,这是数据准备状态,然后就是数据过来了该怎么去读写,这是数据读写状态

一、网络IO阶段一

数据准备阶段分为以下两种

  • 阻塞 : 让调用I/O的线程进入阻塞状态 ,数据准备好了就唤醒
  • 非阻塞: 不会改变线程的状态,通过返回值判断连接断开、无数据非阻塞IO返回、有数据返回等情况

在这里插入图片描述

sockfd相当于就是系统的文件描述符,代表1个I/O,创建的时候默认是阻塞,当我调用1个阻塞I/O的话,如果sockfd上没有数据可读,这个recv不会返回,造成当前线程阻塞,直到sockfd上有数据到来

如果我们在创建sockfd的时候设置是非阻塞,recv的体现是:如果sockfd上没有数据到来的话,recv直接返回回来,不会造成当前线程阻塞。sockfd上如果没有数据准备好,一般我们会编写一个循环,不断recv,不断让CPU空转

非阻塞情况下,我们一般这样判断

  • size==-1,表示错误,是系统的内部错误,需要查看错误码errno,可能要close(sockfd)
  • size==0 && errno==EAGAIN,表示正常的非阻塞返回,sockfd上没有数据,但是连接正常
  • size==0 && 无errno,表示网络对端关闭了连接,对端close(sockfd)
  • size>0,表示sockfd上有数据

二、网络IO阶段二

网络IO阶段2,数据读写阶段根据应用程序和内核的IO交互方式分为以下两种:同步、异步

1. I/O同步

在这里插入图片描述
IO同步方式下,我在应用程序上调用recv函数,这个sockfd我不管它工作在阻塞模式还是非阻塞模式,真的有数据准备好了之后(TCP的接收缓冲区有数据了),我们要读这个数据,这个buff是应用层自己定义的,recv会停在这里,然后从内核的TCP接收缓冲区搬数据到应用层上的buff,没搬完之前,recv不会返回。搬完了,recv才返回,size表示应用程序从TCP接收缓冲区搬了多少数据

I/O同步的意思就是:当我调用网络I/O的接口,数据到达内核TCP缓冲区后,应用程序读数据的时候,应用层自己去读写内核TCP缓冲区,耗时的过程都花在应用层上。数据没有从内核完全搬到应用层,recv就不会返回

同步接口要么是阻塞等待,要么是写一个循环,线程空转,等数据到达TCP缓冲区后,从内核的TCP缓冲区把数据搬到应用层

2. I/O异步

我们把sockfd告诉内核,我们对sockfd上的数据感兴趣
我们把buff告诉内核,我们需要用buff存放sockfd上的数据
我们把SIGIO告诉内核,buff中准备数据好了,用SIGIO通知我

内核把sockfd对应的TCP接收缓冲区的数据搬到buff里面,搬完以后,通过信号SIGIO给应用程序通知一下。应用程序收到SIGIO通知以前,应用程序就可以玩自己的,做任何事都可以
在这里插入图片描述
当我们应用程序调用异步I/O接口的时候,我们就把sockfd,buff,SIGIO(通知方式,也可以通过回调,我们在这里用的是sigio)通过异步I/O接口都塞给了操作系统。应用程序做自己的任何事情都可以,当操作系统通过SIGIO通知应用层的时候,buff中就已经有数据了,应用程序不用自己去TCP缓冲区搬数据,不用像I/O同步一样一直阻塞等待或者非阻塞空转

在同步I/O调用的时候,内核TCP缓冲区有数据准备好了,应用程序自己去内核搬数据,搬完以后recv才返回,耗的是应用程序的时间

异步,一定要记住通知这个特点(异步最大的标识,是异步就有通知)!!异步I/O优点是效率高,缺点是编程复杂

linux给我们提供的典型异步I/O接口:aio_read,aio_write

在这里插入图片描述
给异步IO接口传的数据,就是给内核传的数据

struct aiocb {
	int aio_fildes;                    // 文件描述符
	off_t aio_offset;
	volatile void *aio_buf;            // 用户空间的buff 
	size_t aio_nbytes;
	int aio_reqprio;
	struct sigevent aio_sigevent;      // 通知信号 
	int aio_lio_opcode;
}

在这里插入图片描述

陈硕:在处理 IO 的时候,阻塞和非阻塞都是同步 IO;只有使用了特殊的 API 才是异步IO

这里说的特殊的API就是平台提供的异步API,我们只管把参数给API,让内核把所有的IO相关事情都完成,应用程序等通知就行,收到内核通知时,应用层的缓冲区已经有数据了

epoll是同步的I/O

epoll_wait在调用的时候,我们传参数以后,最后一个参数的timeout,如果不自定义时间,相当于工作在阻塞状态,有事件发生,会返回发生事件的event,我们从event上的fd读,如果event对应的事件是它在sockfd上有可读数据,我们调用recv读,应用程序自己去内核TCP接收缓冲区搬数据,所以这还是同步的IO

我们如果有设置timeout超时时间后,我们也得检查有没有发生事件event,没有的话,我们继续循环epoll_wait监听

三、业务角度的同步和异步

同步就是A操作等待B操作做完事情,得到返回值,继续处理

异步就是A操作告诉B操作它感兴趣的事件以及通知方式,A操作继续执行自己的业务逻辑,等B监听到相应事件发生后,B会 通知 A,A开始相应的数据操作处理逻辑

四、总结

阻塞,非阻塞,同步,异步描述的都是I/O的一些状态,一个典型的网络I/O包含2个阶段:数据准备(数据就绪)和数据读写

数据准备阶段:指的是数据到达TCP缓冲区,分为阻塞和非阻塞
数据读写阶段:指的是应用层如何从TCP缓冲区获取数据,分为同步和异步方式

比如说recv,传一个sockfd,buff,buff的大小,数据就绪指的是远端有没有数据过来,就是内核相应的sockfd对应的TCP接收缓冲区是否有数据可读,若sockfd工作在阻塞模式,当我们调用recv的时候,如果数据没有就绪,当前线程会阻塞在recv

如果这个sockfd是工作在非阻塞模式下的话,当我们去调用系统I/O接口recv的时候,recv会直接返回的,我们一般根据返回值判断:

  • size==-1,表示错误,是系统的内部错误,需要查看错误码errno,可能要close(sockfd)
  • size==0 && errno==EAGAIN,表示正常的非阻塞返回,sockfd上没有数据,但是连接正常
  • size==0 && 无errno,表示网络对端关闭了连接,对端close(sockfd)
  • size>0,表示sockfd上有数据

如果是同步I/O接口

远端有数据过来存放在内核的TCP接收缓冲区,应用程序调用recv这个接口,应用程序自己花时间,把数据从内核的TCP接收缓冲区拷贝到给recv传的buff(应用程序的缓冲区),拷贝数据的过程中,应用程序是一直等待数据拷贝完成后,recv才会返回

如果是异步I/O接口

调用系统给我们提供异步I/O接口的时候,我们要传入

  • sockfd:对应一个TCP接收缓冲区,从远端接收数据的
  • buff:如果有数据,要把内核缓冲区的数据搬到应用程序的缓冲区中
  • 通知方式:通过信号或者回调,告诉操作系统,到时候内核负责监听sockfd上是否有数据可读,有的话把数据从内核的TCP缓冲区搬到buff上,数据搬完后,内核最后通过应用程序告知他的通知方式来通知应用程序
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

bugcoder-9905

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

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

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

打赏作者

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

抵扣说明:

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

余额充值