五种IO模型与非阻塞IO
目录
五种IO模型与非阻塞IO
一、五种IO模型
1.1.IO效率问题
1.2.五种IO模型
1.3.同步与异步
1.4.阻塞IO
1.5.非阻塞IO
1.6.信号驱动IO
1.7.多路转接
1.8.异步IO
二、非阻塞IO
2.1.fcntl函数
2.2.实现阻塞与非阻塞读取
一、五种IO模型
1.1.IO效率问题
IO == 等待 + 拷贝
read() → Input缓冲区 → 等待 + 拷贝
write()→ Output缓冲区 → 等待 + 拷贝
单位时间内,传递的数据量越大,IO效率越高
等待时间久,单位时间拷贝量少,IO效率降低
高效IO:单位时间内,减少IO中,等待的比重
1.2.五种IO模型
钓鱼 == 等鱼上钩 + 提鱼进桶 == IO
人 == 进程,鱼 == 数据,鱼竿 == 网络套接字,河 == 操作系统 ,桶 == 用户缓冲区
张三:专注钓鱼,鱼漂不动,他也不动(阻塞IO)
李四:不会因为鱼没有上钩而卡在检测鱼漂上(非阻塞IO)
王五:在鱼竿顶部装上铃铛,鱼上钩反向通知我(信号驱动IO)
赵六:拉一卡车的鱼竿,共100只,在岸边来回等待(多路复用/多路转接)
田七:发起钓鱼过程,让其他人钓鱼,自己干自己的事情(异步IO)
阻塞 VS 非阻塞
IO条件不具备,阻塞就会卡住,直到条件就绪
IO条件不具备,非阻塞检测到后,会出错返回
在钓鱼效率上两者没有任何区别,而非阻塞做了其他工作,效率才高,但IO效率一样
谁的IO效率最高
站在鱼的角度,抬头看 100 / 104 个鱼竿是赵六的
单位时间内,等的比重很低,提高了鱼上钩的效率
王五是否要等待
王五只是不需要检测,但他需要参与钓鱼的过程,需要提鱼进桶
1.3.同步与异步
同步IO:阻塞、非阻塞、信号驱动、多路复用
异步IO:异步IO
同步 VS 异步
IO == 等待 + 拷贝
同步IO:参与IO等待或者拷贝的任意一个或多个阶段
异步IO:只发起IO,但IO的工作流与自身工作流无关
线程同步 VS IO同步
线程同步:多执行流执行具有一定顺序性
IO同步:该执行流是否参与IO的任意过程
1.4.阻塞IO
在内核将数据准备好之前,系统调用一直等待,所有的套接字默认都是阻塞方式
1.5.非阻塞IO
如果内核数据没有准备好,系统调用会直接返回EWOULDBLOCK错误码
非阻塞IO往往需要以循环的方式,反复地尝试读写文件描述符,也称为非阻塞轮询
1.6.信号驱动IO
内核将数据准备好时,使用SIGIO信号通知应用程序进行IO操作
1.7.多路转接
能够同时等待多个文件描述符的就绪状态
传统接口read/write/recv/send/recvfrom/sendto......既负责等待,又负责拷贝
select专门负责等待,数据准备好后,再调用read/write/recv/send/recvfrom/sendto......拷贝
select允许一次等待多个文件描述符
1.8.异步IO
由内核在数据拷贝完成时,通知应用程序
二、非阻塞IO
2.1.fcntl函数
作用:设置一个文件描述符的属性
- cmd = F_DUPFD:复制一个现有的描述符
- cmd = F_GETFD/F_SETFD:获得/设置文件描述符标记
- cmd = F_GETFL/F_SETFL:获得/设置文件状态标记
- cmd = F_GETOWN/F_SETOWN:获得/设置异步IO所有权
- cmd = F_GETLK/F_SETLK:获得/设置记录锁
2.2.实现阻塞与非阻塞读取
阻塞IO
#include <iostream> #include <unistd.h> int main() { char buffer[1024]; while(true) { ssize_t n = read(0, buffer, sizeof(buffer)); if(n > 0) { buffer[n - 1] = 0; // 去掉回车 std::cout << buffer << std::endl; } //TODO } }非阻塞IO
#include <iostream> #include <cstdio> #include <unistd.h> #include <fcntl.h> void SetNonBlock(int fd) { int fl = fcntl(fd, F_GETFL); if(fl < 0) { perror("fcntl"); return; } fcntl(fd, F_SETFL, fl | O_NONBLOCK); // 将fd设置为非阻塞 } int main() { SetNonBlock(0); char buffer[1024]; while(true) { // Linux中ctrl + d表示读取结束, read返回值是0, 类似读到文件结尾 ssize_t n = read(0, buffer, sizeof(buffer)); if(n > 0) { buffer[n - 1] = 0; // 去掉回车 std::cout << buffer << std::endl; } else if(n < 0) { if(errno == EAGAIN || errno == EWOULDBLOCK) // 错误码 { // 1. 底层数据没有准备好 std::cout << "数据还未准备好......" << std::endl; sleep(1); // TODO continue; } else if(errno == EINTR) { // 2. 读取被信号中断 continue; } else { // 3. 读取出错 } } else { break; } // sleep(1); // std::cout << ". : " << n << std::endl; // C++也有语言层缓冲区,需要进行刷新才能打印 // TODO } }