五种IO模型
阻塞IO
- 阻塞IO:在内核将数据准备好之前,系统调用会一直等待,所有的套接字,默认都是阻塞方式
非阻塞IO
- 非阻塞IO:如果内核的还未将数据准备好,系统调用仍然会直接返回,并且返回EWOULDBLOCK错误码
- 非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符,这个过程称为轮询,这对CPU来说是较大的浪费,只有特定的场景下才使用。
信号驱动
- 信号驱动IO:内核将数据准备好的时候,使用SIGIO信号通知应用程序进行IO操作
IO多路转接
- IO多路转接:虽然从流程图上看起来和阻塞IO类似,实际上最核心在于IO多路转接能够同时等待多个文件描述符的就绪状态
异步IO
- 异步IO:由内核在数据拷贝完成时,通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据),不需要自己主动获取
- 任何IO过程,都包含两个步骤,第一是等待,第二是拷贝,而且在实际的应用场景中,等待消耗的时间玩往往高于拷贝的时间,让IO更高效,最核心的办法就是让等待的时间尽量少
钓鱼
- 阻塞
- 非阻塞:钓鱼的时候玩玩手机,再看看鱼
- 信号:鱼缸上绑铃铛,铃铛响了了
- IO多路转接:阻塞的,扎了一排鱼杆,一眼不眨的等鱼
- 异步IO:鱼竿是自动的,可以自己钓鱼,鱼钓上来就通知
高级IO的重要概念
同步通信和异步通信
同步和异步关注的是消息通信机制
- 所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不反悔,但是一旦返回,就得到了返回值了,换句话说,就是由调用者主动等待这个调用过程
- 异步则相反,调用在出发之后,这个调用就直接返回了,所以没有返回结果,换句话说,当一个异步过程调用出发后,调用者不会立刻得到结果,而是在调用出发后,被调用者通过状态,通知调用者,或通过回调函数处理这个调用
- 这里的同步和多线程的同步和互斥中的同步不是一个东西,不要混淆
- 例如epoll的回调函数机制,通过回调函数来直接修改数组中的标记,调用epoll不需要返回结果,这个场景就是异步。
例如
假设你去吃饭,点了一个炒菜
(1)同步就是你点饭后,一直在那里等着厨师给你做,直到厨师做完,你自己把饭端走,即就是说结果由你自己主动获取
(2)异步,就是点饭之后,去找一个位置作者,当服务员告诉你的饭做好了,你去把饭端过来,即就是说,结果由服务员通知你,你自己使被动的得知调用结果,如果服务员不通知,你是不知道结果的
- 在于这个结果你是怎么知道的。
- 是主动的还是被动的知道的
阻塞和非阻塞
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态
- 阻塞调用是指调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回
- 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程
- 关注调用函数的时候是否阻塞
同步阻塞
- 调用结果由调用者主动获取,且是阻塞式的等待,即调用者会主动的关注调用结果,一直在那看着
同步非阻塞
- 调用结果是由调用者主动获取,但是是非阻塞的等着,轮询的方式,时不时回来看一下执行的结果。
异步阻塞
- 调用结果是被调用者通知给调用者的,而且是阻塞式的在哪里等着,并且自己不光等着,也不会看执行得怎么样了,而是等待被调用者的通知
异步非阻塞I
- 调用结果是由被调用者通知给调用者就行了,调用者自己可以去干其他事,不需要主动的获取结果,同时自己也不会被阻塞。
等女朋友的例子:
同步阻塞
- 等女朋友,单纯的等,啥都不干,就一直看着她有没有做完事
同步非阻塞 - 等女朋友的时候还一般等一边打游戏,打会游戏然后看看她做完事没
异步阻塞 - 等女朋友,然后什么也不做,就一直等,但是也不会关注她什么时候收拾好,等她收拾好了会主动通知你,然后你就知道她收拾好了
异步非阻塞 - 等女朋友,然后你还边敲代码,你也不用去关注她收拾好没,她会主动通知你,然后你就知道她完事了
fcntl
- 一个文件描述符,默认都是阻塞IO,fcntl有能力让阻塞变成阻塞
函数原型
#include <unistd>
#include <fcntl.h>
int fcntl(int fd,int cmd,.../*arg*/);
根据传入的cmd值不同,后面追加的参数也不相同
,fcntl函数有五种功能
- 复制一个现有的文件描述符 (cmd = F_DUPFD)
- 获得/设置文件描述符标记 (cmd = F_GETFD 或 F_SETFD)
- 获得/设置文件状态标记 (cmd = F_GETFL或 F_SETEL)
- 获得/设置异步I/O所有权 (cmd = F_GETOWN或F_SETOWN)
- 获得/设置记录锁 (cmd = F_GETLK,F_SETLK 或 F_SETLKW)
我们此处只是用三种功能,获取/设置文件状态标记,就可以将一个文件描述符设置为非阻塞
基于fcntl实现一个SetNoBlock函数
//传入文件描述符
void SetNoBlock(int fd)
{
//保存修改前的状态信息(一个位图)
int f1 = fcntl(fd,F_GETFL);//get file
if(f1<0)
{
cerr<<"fcntl"<<endl;
return;
}
//将文件描述符设置为非阻塞
fcntl(fd,F_SETFL,f1|O_NONBLOCK);//set file
}
select
- select系统调用是让我们的程序监控多个文件描述符的状态变化的
- 程