前言
本文将介绍五种常见的IO模型:阻塞、非阻塞、信号驱动IO、多路复用、异步IO
文章的重点在于非阻塞与多路复用。而多路复用IO又有三种常见的方式。后续将会详细介绍。
这里就先熟悉一下IO模型、以及认识一下非阻塞IO的编写。
建立一个认识
IO分为:等+拷贝。比如往磁盘中写数据,会先等待,等待底层就绪(打开文件或者文件可写),然后才将用户的数据拷贝到内核缓冲区再由内核刷新到磁盘上。
所以什么是IO?IO就是等待条件就绪,然后进行拷贝。
而在实际的IO操作中,最耗时间的其实是等待,拷贝其实不花什么时间。提供IO的效率问题,就是围绕着如何减少等待的时间。
基于等待的方式不同,衍生出五种IO模型,下面就来认识一下。
阻塞IO
阻塞IO最常见,比如建立连接的俩台主机,主机A调用read ,而B主机没有发送数据,就会导致主机A阻塞等待消息的到来。
阻塞IO:在内核将数据准备好之前,系统调用会进入等待状态。所有的套接字,默认都是阻塞
阻塞IO是平常用的最多的模型,并且编写是最简单的。
以一个 例子总结阻塞IO
int main(){
char buffer [1024];
while(true){
int n=read(0,&buffer,sizeof(buffer)-1);
if(n>0){
buffer[n]=0;
std::cout<<"echo#"<<buffer;
}
else if(n==0){
std::cout<<"read over"<<std::endl;
}
else{
std::cout<<"出错!"<<std::endl;
}
}
return 0;
}
如果没有输入数据,就会阻塞等待
非阻塞IO
如果数据没有就绪,就以出错的方式返回
非阻塞IO:内核没有将数据准备好,就以出错的方式返回,EAGAIN错误码被设置。
缺点:非阻塞会一直去轮询文件描述符,检测数据是否就绪, 对CPU来说是一直资源浪费
详解非阻塞IO
如果要将文件描述符设置为非阻塞状态,只需要fcntl系统调用即可。
设为非阻塞方式的步骤
SYNOPSIS
#include <fcntl.h>
int fcntl(int fd, int op, ... /* arg */ );
1.fcntl获取文件标记状态
2.对fd设置为非阻塞NON_BLOCK
fcntl的五种功能
- 复制一个现有的描述符(cmd=F_DUPFD).
- 获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD).
- 获得/设置文件状态标记(cmd=F_GETFL或F_SETFL).
- 获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN).
- 获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW)
常用到的只有获取和设置
关于OP NONBLOCK表示为非阻塞
下面依旧以读取为例子,介绍一下非阻塞
//设置文件描述符为非阻塞
void SetNonBlock(int fd){
//获取状态标志位
int fl=fcntl(fd,F_GETFL);
if(fl<0){
std::cout<<"文件描述符获取失败!"<<std::endl;
exit(1);
}
fcntl(fd,F_SETFL,O_NONBLOCK|fl);
}
int main(){
//设置文件为非阻塞
SetNonBlock(0);
char buffer [1024];
while(true){
int n=read(0,&buffer,sizeof(buffer)-1);
if(n>0){
buffer[n]=0;
std::cout<<"echo#"<<buffer;
}
else if(n==0){
std::cout<<"read over"<<std::endl;
}
else{
if(errno==EAGAIN){
std::cout<<"EAGAIN中...."<<std::endl;
}
else if(errno==EINTR) //硬件中断
{
std::cout<<"EINTR中...."<<std::endl;
}
else{
std::cout<<"读取出错!"<<std::endl;
}
sleep(1);
}
}
return 0;
}
先将文件描述符设置为非阻塞,然后往外设中读数据,为了便于观察,每次轮询就休眠一秒
观察到每隔一秒就出错返回一次。
为了将NONBLOCK返回与实际出错分开
以errno错误码为标记
- EAGAIN:再试一次
- EINTR:信号中断
设置文件描述符为非阻塞的操作
信号驱动IO
内核将数据准备好,通过信号的方式来通知程序IO
信号驱动也是同步吗?
这里介绍一下同步和异步的概念:
如果一个IO模型,同时进行了等待+拷贝的任意一种就是同步反之任何都不进行的就是异步
信号驱动虽然没有实际的等,但是拷贝是由自己(本线程/进程)执行的,也是同步。
同时阻塞IO和异步IO:不仅进行等待,还进行拷贝,属于同步型IO。
IO多路转接
多路转接:允许一个进程同时监听多个IO事件,并且在任意一个IO事件就绪时进行处理。
多路转接看上去与阻塞IO差不多,只不过通过系统调用,同时等待多个文件描述符。
常见的多路转接方式:
- select
- poll
- epoll
也是后续介绍的重点
异步IO
内核完成数据拷贝,并告知应用程序拷贝完成,可以操作数据
IO的实质就是等待+拷贝,实际上最影响性能的就是等待过程,为了提高效率。总是围绕着如何减少等待的时间。
同步通信 vs 异步通信
同步:发起一个调用,在这个调用没有处理完毕时候,就会进入等待状态,直到将结果拿到。
异步:异步则是在发起调用时,不需要等待获取结果,而是直接返回。什么时候获取结果?在被调用者处理完毕后,通过回调函数来告知结果。
标签:异步,模型,阻塞,描述符,五种,IO,拷贝,等待 From: https://blog.csdn.net/m0_73299809/article/details/141651949后续将详细介绍多路转接的模型,并且编写服务器echo,以及基于epoll的reator模型.