一、几个前置知识:
1、在应用层的这些read和write函数,本质就是把数据从用户层写给操作系统,这些函数本质也
是拷贝函数。
2、对IO的理解应该是:IO时间=等待时间+拷贝时间
3、要进行拷贝,就必须判断条件是否成立(比如读就要看接受缓冲区是否有数据,写就要看发送
缓冲区是否有数据),而这个条件就被称为读写事件。
4、高效IO就是之单位时间内拷贝的数据量大,也就是说,在单位时间内,IO过程中,等的比重越小,IO的效率越高。
5、几乎所有提高IO效率的办法,都是减小等的比重。
二、五种IО模型
2.1、阻塞IO
在内核将数据准备好之前,系统调用会一直等待,所有的套接字,默认都是阻塞方式。阻塞IO是最常见的IO模型:

2.2、非阻塞IO
如果内核还未将数据准备好,系统调用仍然会直接返回,并且返回EWOULDBLOCK错误码。非阻塞IO往往需要通过循环的方式反复尝试读写文件描述符,这个过程称为轮询。这对CPU来说是较大的浪费,一般只有特定场景下才使用。
2.3、信号驱动IO
内核将数据准备好的时候,使用SIGIO信号通知应用程序进行IO操作。

2.4、IO多路转接
虽然从流程图上看起来和阻塞IO类似,实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态。

2.5、异步IO
由内核在数据拷贝完成时,通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)。

总结:
1、阻塞式IO、非阻塞式IO、信号驱动式IO、多路转接这四种都是同步IO。
2、任何IO过程中,都包含两个步骤:第一是等待,第二是拷贝,而且在实际的应用场景中,等待消耗的时间往往都远远高于拷贝的时间,让1O更高效,最核心的办法就是让等待的时间尽量少。
3、阻塞式IO和非阻塞式IO在IO效率上是一样的,因为他们都在等,只不过等的方式不同而已。
4、同步IO和异步IO的区别是有没有直接参与IO的过程,直接参与IO就叫同步IO,不直接参与IO就叫异步IO ,异步IO知识发起IO,最后拿结果就行。
5、上述IO效率最高的是多路复用(多路转接)。
三、非阻塞IO
3.1fcntl
一个文件描述符,默认都是阻塞IO
函数原型如下:

传入的cmd的值不同,后面追加的参数也不相同。
fcntl函数有5种功能:
(1)复制一个现有的描述符(cmd=F_DUPFD)
(2)获得/设置文件描述符标记(Cmd=F_GETFD或F_SETFD)
(3)获得/设置文件状态标记(Cmd=F_GETFL或F_SETFL)
(4)获得/设置异步I/O所有权(Cmd=F_GETOWN或F_SETOWN)
(5)获得/设置记录锁(Cmd=F_GETLK,F_SETLK或F_SETLKW)
注意:用第三种功能,获取/设置文件状态标记,就可以将一个文件描述符设置为非阻塞。
3.2实现函数SetNoBlock
基于fcntl,实现一个SetNoBlock函数,将文件描述符设置为非阻塞。
cpp
void SetNoBlock(int fd)
{
int fl = fcntl(fd, F_GETFL);
if (fl < 0)
{
perror("fcntl error");
return;
}
fcntl(fd, F_SETFL, fl | O_NONBLOCK);
cout << "set " << fd << " done" << endl;
}
使用F_GETFL将当前的文件描述符的属性取出来(这个属性是一个位图),然后再使用F_SETFL将文件描述符设置回去,设置回去的同时,加上一个O_NONBLOCK参数。
四、轮询方式读取标准输入
cpp
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <cerrno>
#include <cstring>
using namespace std;
void SetNoBlock(int fd)
{
int fl = fcntl(fd, F_GETFL);
if (fl < 0)
{
perror("fcntl error");
return;
}
fcntl(fd, F_SETFL, fl | O_NONBLOCK);
cout << "set " << fd << " done" << endl;
}
int main()
{
char buffer[1024];
SetNoBlock(0);
while (true)
{
cout << "please enter@" << endl;
fflush(stdout);
ssize_t n = read(0, buffer, sizeof(buffer));
if (n > 0)
{
buffer[n] = 0;
cout << "echo:" << buffer << endl;
}
else if (n == 0)
{
cout << "read done" << endl;
}
else
{
// 1、设置为非阻塞轮询,如果底层fd的数据没有就绪,recv/read/write/send等就会以出错的形式给我们返回
// 2、有可能真的出错,有可能是底层资源未就绪,就通过errno错误码进行区分
if (errno == EWOULDBLOCK)
{
cout << "0 fd data not ready, try again!!!" << endl;
// 等待的时候可以做其他事情
sleep(1);
}
else
{
// 出错了
cerr << "read error, n =" << n << ", errno:" << errno << ", errno reason:" << strerror(errno) << endl;
}
}
}
return 0;
}
运行结果:
