目录
一般系统提供的IO接口,实际上都在完成两件事,一件是等,等待数据就绪,如recvfrom会阻塞等待数据就绪,还有一件就是拷贝,把数据从内核缓冲区拷贝到用户缓冲区或者把数据从用户缓冲区拷贝到内核缓冲区。而可以想象,等待所消耗的时间肯定是远大于拷贝的时间,例如网络通信中大部分时间都消耗在等待上了,等待客户端的消息到达服务端,等待服务端的消息到达客户端。为了优化IO的效率,人们提出了五种不同的IO等待方式,这就是五种IO模型。
阻塞等待
首先是最常见的的等待方式,以recvfrom为例,在内核将数据准备好之前,系统调用会一直等待,进程不会执行后面的代码,所有套接字默认都是这种等待方式。还有一种典型的阻塞等待场景,当你调用scanf或者cin时,如果你不输入,内核数据就一直无法就绪,就会卡着不动。
举个钓鱼的例子,阻塞IO相当于在钓鱼时一直盯着鱼竿,直到看到有鱼咬钩才去动鱼竿
非阻塞IO
这种与阻塞最大的不同在于,内核即使没有准备好数据,系统调用也不会等待,而是直接返回,并且设置错误码EWOULDBLOCK表示是由于非阻塞等待导致的返回。
非阻塞 IO 往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这
对 CPU 来说是较大的浪费, 一般只有特定场景下才使用.
非阻塞就相当于你在钓鱼时并不盯着鱼竿,而是在做自己的事情,但是会隔一会就看向鱼竿确认是否有鱼上钩
信号驱动IO
在内核将数据准备好之后,使用SIGIO信号通知应用程序进行IO操作。
信号驱动相当于你在鱼竿上挂了个铃铛,你一直在干自己的事,完全去看鱼竿,只有铃铛响了你才去关注
IO多路转接
多路转接类似于阻塞IO,会一次阻塞在多个文件描述符,如果有哪个文件描述符的数据准备好了,就会将数据进行拷贝。
相当于你用很多个鱼竿钓鱼,同时你还盯着这些鱼竿,哪个动了你就去哪个鱼竿把鱼钓上来。
异步IO
异步,也就是事情的发生没有先后顺序,也就是IO这件事与进程所干的事之间没有先后顺序。
进程将用户级缓冲区交给操作系统,内核独自完成等待+拷贝两个动作,在内核拷贝完之后,才通知进程
相当于你请了个人帮你钓鱼,你完全不关心他等了多久又是怎么钓的鱼,你只关心他最后将鱼交到你手上,你完全不受钓鱼的任何影响
异步IO与同步IO区别
前四种IO模型都是同步IO。IO就是等+拷贝,因为前四种IO和进程本身都是有关的,也就是说,无论如何,进程至少都是要自己去拷贝的,哪怕像非阻塞IO和信号驱动IO可以省掉等待,拷贝都是要自己去做的,参与了拷贝也就相当于参与了IO,因为IO是等+拷贝
而异步IO,等和拷贝这两个动作进程都不会参与,操作系统拿到用户级缓冲区,完成等待和拷贝
实现非阻塞IO
这里以一种十分常见的方式实现一下非阻塞IO——向标准输入中写入
linux很多IO接口有选项可以传入非阻塞,这里用一种通用的方式——直接将文件属性设置为非阻塞,这样无论哪个IO接口访问这个文件进行IO都是非阻塞的。
要改变这种文件属性,先来介绍一个系统调用fcntl
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
获得/设置文件状态标记(cmd=F_GETFL 或 F_SETFL).
cmd为F_GETFL时,返回值是文件的状态标志位,cmd为F_SETFL时,返回值是0
失败时,返回值是-1
#include <unistd.h>
#include <fcntl.h>
#include <iostream>
void set_no_block(int fd)
{
int flags = fcntl(fd, F_GETFL);
if (flags == -1)
{
std::cout << "Error getting flags" << std::endl;
return;
}
//fcntl是覆盖式的修改,flags或上O_NONBLOCK就是这两个属性都需要被设置
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
if (flags == -1)
{
std::cout << "Error setting flags" << std::endl;
return;
}
}
通过这个函数来实现下面的非阻塞IO
int main()
{
while(true)
{
write(1, "Enter# ", 6);
char buf[100];
set_no_block(0);
ssize_t read_size = read(0, buf, 100);
if(read_size > 0)
{
write(1, buf, read_size);
write(1, "\n", 1);
}
//读到文件结尾返回值为0
//可同时按下键盘ctrl和D两个键发送信号来查看效果
else if(read_size == 0)
{
write(1, "EOF\n", 4);
}
else if(read_size == -1)
{
//EAGAIN是宏,表示是由于非阻塞IO返回的-1
//EWOULDBLOCK是宏,与EAGAIN相同
if(errno == EAGAIN || errno == EWOULDBLOCK)
{
write(1, "No input\n", 9);
sleep(1);
continue;
}
//EINTR表示被信号中断
else if(errno == EINTR)
{
write(1, "Interrupted\n", 12);
sleep(1);
continue;
}
else
{
write(1, "Error\n", 6);
}
}
}
return 0;
}
将这份代码粘贴到自己的电脑上来查看最佳的效果
10万+

被折叠的 条评论
为什么被折叠?



