阻塞IO:在内核将数据准备好之前,系统调用会一直等待。所有的套接字,默认都是阻塞状态的。
阻塞IO是最常见的IO模型。

非阻塞IO:如果内核将数据还没有准备好,系统调用仍会直接返回,并且返回EWOULDBLOCK错误码。
非阻塞IO往往需要程序猿以循环的方式反复尝试读写文件描述符,这个过程称为轮询。这对CPU来说是较大的浪费,一般只有特定场景下才使用。

信号驱动IO:内核将数据准备好的时候,使用SIGIO信号通知应用程序进行IO操作。

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

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

小结:任何IO过程,都包含两个步骤:第一个是等待,第二个是拷贝。而且在实际应用的场景中,等待消耗的时间往往高于拷贝的时间。让IO更高效,最核心的办法就是让等待的时间减少。
同步和异步关注的是消息通信机制。
所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了; 换句话说,就是由调用者主动等待这个调用的结果;
异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果;换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果;而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。
对于一个文件描述符,默认都是阻塞的,可以通过系统调用fcntl将其设置为非阻塞状态。

函数原型如下:
C #include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd, ... /* arg */ );
fcntl函数有五种功能:
我们此处只是用第三种功能,获取/设置文件状态标记,就可以将一个文件描述符设置为非阻塞。
//将文件描述符设置为非阻塞
void setNonBlock(int fd)
{
int fl=fcntl(fd,F_GETFL);
if(fl<0)
{
perror("fcntl erro");
return;
}
fcntl(fd,F_SETFL,fl|O_NONBLOCK);
}#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <cstdio>
//将文件描述符设置为非阻塞
void setNonBlock(int fd)
{
int fl=fcntl(fd,F_GETFL);
if(fl<0)
{
perror("fcntl erro");
return;
}
fcntl(fd,F_SETFL,fl|O_NONBLOCK);
}
int main()
{
setNonBlock(0);
char buffer[1024];
while(true)
{
int n=read(0,buffer,sizeof(buffer)-1);
//读取成功
if(n>0)
{
buffer[n-1]=0;
std::cout<<buffer<<std::endl;
}
else if(n<0)
{
//1,读取出错 2,底层没有数据准备好
if(errno==EAGAIN||errno==EWOULDBLOCK)
{
std::cout<<"数据还没有准备好..."<<std::endl;
sleep(1);
//做其他事情
}
else if(errno==EINTR)
{
//读取时被信号中断
continue;
}
else
{
//真正的读取出错了
break;
}
}
else
{
break;
}
}
return 0;
}