【Linux网络与网络编程】13.五种 IO 模型
前言
在前面的学习中,有一个问题一直没有展开来说,即 IO 问题。 IO 到底有多少种方式呢?什么是高效的 IO 呢?
IO 本质上就是 INPUT 和 OUTPUT 。在网络中 INPUT 就是从网卡中获取数据,而 OUTPUT 就是向网卡中发送数据。 IO 可以理解为等+拷贝。当 IO 事件就绪时就可以进行拷贝了,所以所谓的高效的 IO 就是让 IO 大部分时间处于拷贝而减少等待的时间。
那么如何高效的IO呢?这就提出了五种IO模型来提高 IO 效率。
1. 五种 IO 模型概述
阻塞 IO
阻塞 IO 即在内核将数据准备好之前,系统调用会一直等待。所有的套接字默认都是阻塞方式。阻塞 IO 是最常见的 IO 方式。
非阻塞 IO
非阻塞 IO 即在内核还未将数据准备好时,系统调用仍然会直接返回,并且返回 EWOULDBLOCK 错误码。
信号驱动 IO
信号驱动 IO 即当内核将数据准备好的时,会使用 SIGIO 信号通知应用程序进行 IO 操作。
多路转接 IO
虽然从流程图上看起来和阻塞 IO 类似,但实际上最核心在于 IO 多路转接能够同时等待多个文件描述符的就绪状态。
异步 IO
异步 IO 即由内核在数据拷贝完成时,通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)。
针对上述 IO 方式我们进行分析:
1. 阻塞 IO vs 非阻塞 IO
IO = 等 + 拷贝。它们等的方式不同,但是 IO 效率实际上是相同的。平常所说的非阻塞 IO 的效率高是因为它可以在等的时候做其他的事情。
2. 这五种 IO 模型哪个效率最高
当然是多路转接 IO 了,因为它能接收到数据就绪的概率更大了。
3. 信号驱动 IO 特点是什么?效率如何?
它逆转了获取就绪事件的方式,效率同阻塞 IO 和非阻塞 IO 相同,因为它们只是等的方式不同而已。
4. 同步 IO vs 异步 IO
我们介绍的前四种 IO 方式是同步 IO。同步 IO 和异步 IO 的本质区别就是有没有参与 IO 的过程。
针对上述的五种 IO 模型,我们会具体介绍非阻塞 IO 和多路转接 IO ,其中本篇博客的下一部分介绍非阻塞 IO ,下一篇博客将具体介绍多路转接 IO 。
2. 非阻塞 IO
其实之前学习过的很多函数都可以通过设置标记位的方式来实现非阻塞,但是Linux下一切皆文件,故而就产生了更为通用的设置方式,即对文件描述符进行操作。
#include <fcntl.h>
int fcntl(int fd, int op, ... /* arg */ );// 参数:
// fd:需要被操作的文件描述符
// op:要进行的设置操作
// F_DUPFD: 复制一个现有的描述符
// F_GETFD 或 F_SETFD:获得/设置文件描述符标记
// F_GETFL 或 F_SETFL:获得/设置文件状态标记
// F_GETOWN 或 F_SETOWN:获得/设置异步 I/O 所有权
// F_GETLK 获 F_SETLK 或 F_SETLKW:获得/设置记录锁
// 返回值:
// 成功的话依赖于所请求的内容,失败的话返回 -1
非阻塞如果不输入,数据就不会就绪,并以出错的形式返回。但是 read 不是有读取错误吗,两者如何区分呢?
如果读取错误的话就会被设置错误码 errno ,里面会有更详细的错误信息,但是如果是底层数据没有就绪的话就会触发11号错误 EAGAIN(EWOULDBLOCK)。
补充知识:
read 的阻塞是调用系统调用而进入了浅层睡眠 s 状态,而 s 状态收到信号也可能会被叫醒的!这样的情况错误码就是 EINTR 。
接下来我们写一段demo代码:
#include <iostream>
#include <cstdio>
#include <string>
#include <cerrno>
#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);
}int main()
{std::string tips = "Please Enter# ";char buffer[1024];SetNonBlock(0);while (true){write(0, tips.c_str(), tips.size());int n = read(0, buffer, sizeof(buffer));if (n > 0){buffer[n] = 0;std::cout << "echo# " << buffer << std::endl;}else if (n == 0){std::cout << "read file end!" << std::endl;break;}else{if (errno == EAGAIN || errno == EWOULDBLOCK){std::cout << "底层数据,没有就绪" << std::endl;sleep(1);continue;}else if (errno == EINTR){std::cout << "被中断,从新来" << std::endl;sleep(1);continue;}else{std::cout << "read error: " << n << ", errno: " << errno << std::endl;}}}
}