目录
5种IO模型
阻塞IO
在内核将数据准备好之前,系统会一直等待,所有的套接字一般都默认是阻塞方式
非阻塞IO
如果内核还未将数据准备好,系统调用会直接返回,不过会将错误码设置为EWOULDBLOCK
非阻塞IO需要循环的方式反复读写文件描述符,对CPU是比较大的浪费
信号驱动 IO
内核将数据准备好时会发送信号,由对应的信号通知程序需要进行IO操作
IO多路转接
从流程上来看和阻塞IO差不多,但是最大的区别是阻塞IO一次等待一个文件描述符,而多路转接则是一次等待多个文件描述符
异步 IO
内核将数据拷贝完成时通知程序(也就是将IO处理完以后再告诉程序,信号是告诉程序什么时候可以处理IO)
任何IO的处理过程都分为等待与拷贝两部分,需要等待数据,数据到来才能拷贝
实际的场景中,等待所消耗的时间远远多于拷贝的时间,所以提高效率的办法就是尽量减少等待的时间
高级 IO
同步通信 vs 异步通信
这里的同步与异步与我们多线程时的同步不同,这里是通信的概念,关注的时消息通信机制
同步:一个调用在发出后一直等待,直到得到结果之后再返回
异步:一个调用在发出后直接返回,被调用方通过某种方式通知调用方结果已经处理完了
我们在多线程时提过同步与互斥,这主要解决的时访问共享资源的问题
进程/线程之间的同步时进程/线程之间的直接制约关系
为了完成任务而创建的多个进程/线程需要一定的工作次序,我们需要让他们按照某种次序去而等待,传递信息,尤其是访问共享资源的时候,我们为了实现同步而采用的方法为互斥,一个资源在同一时间最多只能被一个进程/线程访问到
阻塞 vs 非阻塞
阻塞和非阻塞关注的是程序在等待调用结果(消息, 返回值) 时的状态
1.阻塞调用是指调用结果返回之前, 当前线程会被挂起. 调用线程只有在得到结果之后才会返回
2.非阻塞调用指在不能立刻得到结果之前, 该调用不会阻塞当前线程
四者之间的关系
我们采取同步通信的方式,在调用结果出来之前一直等待,就是阻塞的方式,在阻塞期间程序没有办法接下来往后执行。
我们采用异步通信,使用的是非阻塞的方式,我们当前并不紧急需要该调用的结果,我们接下来执行其他方法即可,在调用结果处理完之后会通知程序,我们只需要在结果处理完之后直接拿到结果即可,这种方式就是非阻塞。
非阻塞 IO
我们此处主要讨论的是io多路转接
fcntl
该函数用于控制文件描述符
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );
//传入的cmd不同,后面的参数也不同
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)
我们此处主要使用第三种功能,也就是将文件描述符设置为非阻塞
void SetNonBlock(int fd)
{
int f1=fcntl(fd,F_GETFL);
if(f1<0)
{
perror("fcntl");
return;
}
fcntl(fd,F_SETFL,f1|O_NONBLOCK);
}
这里取出来的文件状态标记也是一个位图,我们从文件描述符中取出来后为该标记增加上O_NONBLOCK(非阻塞标记)
轮询读取标准输入
void SetNonBlock(int fd)
{
int f1=fcntl(fd,F_GETFL);
if(f1<0)
{
perror("fcntl");
return;
}
fcntl(fd,F_SETFL,f1|O_NONBLOCK);
}
int main()
{
SetNonBlock(0);
char buffer[1024];
while(1)
{
int n=read(0,buffer,sizeof(buffer));
if(n>0)
printf("%s",buffer);
else if(n<0)
//read本身是阻塞的
//当我们读取一个非阻塞的文件描述符,他就出错了直接返回-1
{
perror("read");
sleep(1);
continue;
}
}
}
上面的read主要是因为标准输入相当于程序启动时输入端始终打开,处于如果标准输入为阻塞文件描述符的话那就表现为阻塞状态,而非阻塞则表现为“暂时没数据”,因此返回的是-1