IO
什么是IO,以字面的意思来讲,I就是input,O就是output嘛,所以在c++中常见的IO流,cout就是将数据输出出去,cin就是将数据输入进来!
那么IO的本质是在干什么事情呢?
以cin来举例子,输入一个数据分几步呢?简单来说可以分两步,我们需要等待用户输入数据,再将数据拷贝到特定的内存上!
所以:IO=等+拷贝;
我们常见的IO模式就是阻塞与非阻塞的区别了!
5种IO模型
阻塞IO

阻塞IO是我们常见的一种IO模型,像我们的read,write就是很典型的I阻塞O模型!在阻塞 IO 模式下,当应用程序执行 IO 操作(如从网络读取数据或从文件读取内容)时,程序会一直等待,直到操作完成或出现错误。在等待过程中,线程会被阻塞,无法执行其他任务,这意味着线程在 IO 操作进行期间处于暂停状态,不能继续执行后续的代码逻辑。
阻塞IO优点非常明显,就是最容易实现,用起来非常流畅,缺点也非常明显,就是他会去阻塞进程,进程就不会往下进行了。
非阻塞IO
在非阻塞 IO 模式下,当应用程序发起一个 IO 操作时,无论该操作是否能够立即完成,程序都不会被阻塞,而是会立即返回一个结果。如果操作不能立即完成,通常会返回一个特定的错误码(如EWOULDBLOCK
或EAGAIN
),表示当前没有数据可读或写操作暂时无法进行。应用程序可以继续执行其他任务,并在之后的某个时间点再次尝试进行 IO 操作。
优点就是不会阻塞进程,缺点就是通过轮询的方式,就非常占用CPU资源。
信号驱动IO
信号驱动 I/O 允许进程在进行 I/O 操作时,通过信号机制来接收 I/O 完成的通知。进程可以预先向内核注册一个信号处理函数,当指定的 I/O 操作准备好时,内核会向进程发送一个信号,进程在接收到信号后,调用预先注册的信号处理函数来处理 I/O 操作。这样,进程在等待 I/O 操作完成的过程中可以继续执行其他任务,而不必像阻塞 I/O 那样一直等待,也无需像非阻塞 I/O 那样频繁地进行轮询。
相交于上面两种方案,优点就是不需要轮询,而且不用阻塞,缺点就是我们需要额外学习一些信号的知识!
IO多路转接
在传统的阻塞 I/O 模型中,一个线程一次只能处理一个 I/O 操作,如果要处理多个 I/O 流,就需要为每个流创建一个独立的线程,这会带来较大的线程创建和管理开销。而 IO 多路转接机制通过一个线程同时监视多个文件描述符,当某个文件描述符上有 I/O 事件发生时,才进行相应的处理,从而避免了创建大量线程,提高了系统的性能和资源利用率。
这种方法优点就是一次性可以监视多个文件描述符,在资源浪费上也比较少,主要是可以控制每次阻塞时间,唯一的缺点就是写起来可能比较麻烦!
异步IO
异步 I/O 允许程序在发起 I/O 操作后,无需等待该操作完成,就可以继续执行后续的代码。当 I/O 操作完成后,系统会通过某种方式(如回调函数、信号或事件通知)告知程序,程序再对操作结果进行处理。与阻塞 I/O 不同,异步 I/O 不会让线程在 I/O 操作期间处于阻塞状态;和非阻塞 I/O 相比,非阻塞 I/O 需要程序主动轮询操作状态,而异步 I/O 是由系统主动通知操作完成。
优点:可以对代码的模块进行解耦,缺点就是这样的代码一般都会联系到多线程或者多进程的代码!
非阻塞IO代码编写
在Linux系统里边有一些接口让我们用来将IO变为非阻塞
#include <unistd.h>
#include <fcntl.h>int fcntl(int fd, int cmd, ... /* arg */ );
fcntl 函数有5种功能:
• 复制一个现有的描述符(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
比如我们要将一个文件设置为非阻塞
voidSetNoBlock(intfd){intfl=fcntl(fd,F_GETFL);if(fl<0){perror("fcntl");return;}fcntl(fd,F_SETFL,fl|O_NONBLOCK);}
现在我们将键盘输入流设置为非阻塞
#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include<cerrno>
using namespace std;bool SetNonBlocking(int fd)
{int flags = fcntl(fd, F_GETFL, 0);if (flags == -1){return false;}flags |= O_NONBLOCK;if (fcntl(fd, F_SETFL, flags) == -1){return false;}return true;
}void test1()
{SetNonBlocking(0); // 设置将输入文件设置为非阻塞while (1){//SetNonBlocking(0);char str[1024] = "please input a string:\n";write(1, str, sizeof(str)); //像屏幕设备打印消息char get[1024];int n=read(0, get, sizeof(get)); //从键盘设备读取数据if (n==0){cout<<"文件结束"<<endl; //文件结束}else if(n>0){get[n]='\0';cout<<"input string is:"<<get<<endl; //打印输入的字符串}else{if(errno==EAGAIN||errno==EWOULDBLOCK)//errno==EAGAIN表示输入流没有数据,errno==EWOULDBLOCK表示输入流没有数据{cout<<"输入流没有数据"<<endl;}else if(errno==EINTR)//errno==EINTR表示信号中断,读取失败{cout<<"信号中断"<<endl;}else {perror("read error");//打印出错误信息}}sleep(1); //睡眠1秒}
}