五种IO模型基本概念
什么是高效的IO?
我们常说的IO,站在体系结构的角度上来看!把数据从内存拷贝到外设就是output,把数据从外设搬到内存就是input!——确实没有错!但是理解还不够!
当我们在网络中,我们通过write进行发送,read进行读取!但是当我们write的时候我们实际不是发送数据!而是将数据拷贝到发送缓冲区里面,所以write的本质就是拷贝!而read进行读取的本质是从TCP的接收缓冲区里面把数据从内核中拷贝到应用层!
可是你想拷贝就要能拷贝呢?
例如:你想write,有没有可能发送缓冲区就已经流量控制的问题,发送缓冲区已经被写满了!没有空间让我们write了!那么此时write操作就会阻塞!直到发送缓冲区空出位置!
如果我们想要read,万一接收缓冲区里面压根就没有数据呢?那么read不就会阻塞住吗?就像在进程间通信,使用管道对方不写入数据!我们读取就会被阻塞! 写网络套接字的时候,如果对方不发送数据!那么服务端就会阻塞卡住!——==read,write的本质是拷贝!而拷贝是有条件的!读取的是要求接收缓冲区有数据!写入的时候要求发送缓冲区有空间!==
==阻塞的本质就是等待某种资源就绪,当操作系统发现接收队列没有数据/发送队列没有空间的时候,就会把进程的PCB放入等待队列里面进行等待!——等待到某种资源就绪,等到有数据之后,就会将数据拷贝,拷贝完毕后就进行返回==
所以我们对于read/recv/write等接口我们不仅要知道本质是拷贝!还要明白是要等待的!
==IO = 等待 + 数据拷贝!==
在系统中,我们对于等待的情况感知并不明确!像是进行文件操作,我们是没有资格知道文件的写入的!只是调用了系统接口!然后系统给我们将数据刷新到文件里面,对于网络,也是一样!也是操作系统来替我们发送的!所以系统和网络在IO的处理上是一致的!
但是在系统上,我们对于等待的情况是不直观的!我们访问一个本地文件,很快就访问完成,写入完成了!看起来就是一瞬间!没有等待的样子!但是其实还是有等待的!
==但对于网络通信,距离长了之后,还有拥塞控制,流量控制的策略之后,等待时间的比重就变得十分的明显了!==
五种IO模型概念
我们先用一个例子来引入
五种IO模型
阻塞IO是最常见的IO模型
非阻塞IO: 如果内核还未将数据准备好, 系统调用仍然会直接返回, 并且返回EWOULDBLOCK错误码
非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这对CPU来说是较大的浪费, 一 般只有特定场景下才使用
高级IO重要概念
同步通信 vs 异步通信
同步和异步关注的是消息通信机制.
阻塞 vs 非阻塞
阻塞和非阻塞关注的是程序在==等待调用结果(消息,返回值)时的状态.==
其他高级IO
非阻塞IO,纪录锁,系统V流机制,I/O多路转接(也叫I/O多路复用),readv和writev函数以及存储映射 IO(mmap,这个可以去了解),这些统称为高级IO
非阻塞IO
fcntl函数
一个文件描述符, 默认都是阻塞IO——所以想要非阻塞IO,我们首先就要将文件描述符设置为非阻塞的!
在我们使用read/send之类接口的时候,都一个一个flag参数选项!
void SetNoBlock(int fd)
{
int fl = fcntl(fd, F_GETFL);//首先获取该该文件的文件标志位
//其实就是原来该文件的设置选项!
if (fl < 0)
{
perror("fcntl");
return;
}
fcntl(fd, F_SETFL, fl | O_NONBLOCK);//新的标志位和旧的标志位一起设置进文件描述符里面!这样子这个文件描述符就变成了非阻塞
}
使用轮询的方式进行读取
#include"Util.hpp"
#include<cstdio>
int main()
{
while (true)
{
printf(">>>>> ");
fflush(stdout);
// 从0号文件描述符(键盘)里面进行读取!
char buffer[1024];
ssize_t s = read(0, buffer,sizeof(buffer)-1);
if(s > 0)
{
buffer[s] = 0;
std::cout <<"echo: " << buffer;
}
else if(s == 0)//读取到文件尾
{
std::cout <<"read end" <<std::endl;
break;
//命令行输入的文件可以用ctrl+ d 来表示文件尾
}
else //为负数
{
}
}
return 0;
}
==这就是一个阻塞的过程!——如果这是一个套接字的话,对方不把数据通过网络发送也会卡主!==
//Util.hpp
#pragma once
#include<iostream>
#include<unistd.h>
#include<cstring>
#include<fcntl.h>
#include<cerrno>
void setNoBlock(int fd)
{
int fl = fcntl(fd,F_GETFL);
if(fl == -1)
{
std::cerr << "fcntl: " << strerror(errno)<<std::endl;
return;
}
fcntl(fd,F_SETFL,fl|O_NONBLOCK);
}
//main.cc
#include<cstdio>
int main()
{
setNoBlock(0);//设置为非阻塞
while (true)
{
printf(">>>>> ");
fflush(stdout);
sleep(1);
// 从0号文件描述符(键盘)里面进行读取!
char buffer[1024];
ssize_t s = read(0, buffer,sizeof(buffer)-1);
if(s > 0)
{
buffer[s] = 0;
std::cout <<"echo: " << buffer ;
}
else if(s == 0)//读取到文件尾
{
std::cout <<"read end" <<std::endl;
break;
}
else //为负数
{
}
}
return 0;
}
如果不加上sleep
==在非阻塞等下,我们可以看到代码会一直不断的进行死循环!而不会因为我们没有输入数据从而导致阻塞等待!read函数会立马返回!==
那么这有什么用呢?——我们可以在我们代码空闲的时候做一些其他的工作!
//Util.hpp
#pragma once
#include<iostream>
#include<unistd.h>
#include<cstring>
#include<fcntl.h>
#include<functional>
void setNoBlock(int fd)
{
int fl = fcntl(fd,F_GETFL);
if(fl == -1)
{
std::cerr << "fcntl: " << strerror(errno)<<std::endl;
return;
}
fcntl(fd,F_SETFL,fl|O_NONBLOCK);
}
using func_t = std::function<void()>;
void printlog()
{
std::cout << "this is a log" <<std::endl;
}
void download()
{
std::cout << "this is a download" <<std::endl;
}
void executeSql()
{
std::cout << "this is a executeSql" <<std::endl;
}
//main.cc
#include"Util.hpp"
#include<cstdio>
#include<vector>
#define INIT(v) do{ \
v.push_back(printlog);\
v.push_back(download);\
v.push_back(executeSql);\
}while(0)
#define EXEX_OTHER(v) do{ \
for(auto const & cb:v) cb();\
}while(0)
int main()
{
std::vector<func_t> callback;
INIT(callback);
setNoBlock(0);//设置为非阻塞
while (true)
{
printf(">>>>> ");
fflush(stdout);
sleep(1);
// 从0号文件描述符(键盘)里面进行读取!
char buffer[1024];
ssize_t s = read(0, buffer,sizeof(buffer)-1);
if(s > 0)
{
buffer[s] = 0;
std::cout <<"echo: " << buffer ;
}
else if(s == 0)//读取到文件尾
{
std::cout <<"read end" <<std::endl;
break;
}
else //为负数
{
}
EXEX_OTHER(callback);
}
return 0;
}
==非阻塞的时候对应的返回值是什么呢?==
#include"Util.hpp"
#include<cstdio>
#include<vector>
int main()
{
INIT(callback);
setNoBlock(0);//设置为非阻塞
while (true)
{
printf(">>>>> ");
fflush(stdout);
sleep(1);
// 从0号文件描述符(键盘)里面进行读取!
char buffer[1024];
ssize_t s = read(0, buffer,sizeof(buffer)-1);
if(s > 0)
{
buffer[s] = 0;
std::cout <<"echo: " << buffer ;
}
else if(s == 0)//读取到文件尾
{
std::cout <<"read end" <<std::endl;
break;
}
else //为负数
{
std::cout << "result: " << s <<std::endl;
}
}
return 0;
}
==我们可以看到返回值就是-1==