# 11. IO各种模型

## introduction

我知道我讲了一些玄学东西, 上一节在对比进程/线程的时候我特意留了一嘴说redis/memcached, 立刻我就意识到如果说了这两者的线程模型, 那么是不可能说他们的IO模型的, 因为他们所表现出来的特性是由线程与IO两种模型结合在一起决定的.

我们从一个最简单的情况开始讲, 假设你新建了一个socket监听某端口 (tcpudp都行), **如果有人给你发消息, 消息就会从网卡哪儿先被拷贝到内核缓冲区里, 然后你从内核区域里\[读]到进程的虚拟内存下**. socket描述符你已经有了, 如果你现在就要读, 会发生什么呢? 这就是IO模型最核心的问题 → 会发生什么

## Model1 - BlockingIO

在你什么都不做的情况下, 一个socket默认是堵塞型的, 意思就是如果你现在通过 **recvfrom** 函数从这个socket里读数据, 如果有数据就直接返回, 如果没有数据就一直卡在哪儿, 一直卡到有数据再返回 (消息从网卡拷贝到内核里)

![](https://2314591241-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MC_ucxnzAClkrgsGdFP%2Fsync%2F95c654d795be1edeb7bb5e682597e903a5a23bfd.png?generation=1605711930571087\&alt=media)

## Model2 - NonBlockingIO

但你也可以选择把这个socket设置成非阻塞模式, 此时在网卡没有收到数据之前, 你一调用recvfrom就会立刻返回并报错, 你就在一个for循环里一直查(这种反复查的动作我们叫它polling), 直到网卡已经收到数据了, 此时不立刻报错了, 内核开始将数据拷贝到内核buffer里, 然后还给你的程序.

非阻塞IO确实不会产生恶心的阻塞, 但是为什么很少见到呢? 因为如果你是阻塞状态, CPU就不会让你跑, 但是在非阻塞的IO下, CPU还得让你一直跑着, 效果都是收信息, 后者还得浪费CPU的时间

![](https://2314591241-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MC_ucxnzAClkrgsGdFP%2Fsync%2F35770afb9d94fb2c5fb002f06a9e3d69a6f02335.png?generation=1605711930926867\&alt=media)

## Model3 - MultiplexingIO

这个模型不是针对某单个socket了, 这个模型处理一组socket, 以select函数为例, select函数接受一组socket, 函数调用以后就卡着, 直到里面某一个socket变的可读了就返回这个可读的socket并同时解除阻塞.

那么既然你已经持有一个可读的socket了, 你就可以通过函数 recvfrom 去里面读东西了. multiplexing 里面大有玩头, 另类玩法比如poll/epoll的对比(这个我们会在下一节讲), 正是这个原因促成了redis如此出色的表现

![](https://2314591241-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MC_ucxnzAClkrgsGdFP%2Fsync%2Fd0b1ebea4d92c73ac60d179bd42a6a6d8c6ff93f.png?generation=1605711931372508\&alt=media)

## Model4 - SingnalDrivenIO

既然内核知道什么时候可读, 那为什么不找一种方式来通知我们呢? 信号驱动IO要求你先通过sigaction函数为这个socket注册上信号, 等网卡收到数据了, 也拷贝到内核buffer里了, 这个时候通过一个SIGIO信号告诉你的程序, 这个时候你的程序再来通过 recvfrom 的方式去取

![](https://2314591241-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MC_ucxnzAClkrgsGdFP%2Fsync%2Ffb38b69e641ef0a0726cec5a4c788481c7fe0522.png?generation=1605711931331143\&alt=media)

## Model5 - AsynchronousIO

这种IO模型是5个模型里唯一真正异步IO的模型, 我们首先通过 aio\_read 的方式传入

* 一个socket(fd)
* 一个byte数组(buffer) + 数组长度
* 就绪以后如何通知我们 (比如golang的 `done<-struct{}{}`)

接下来是纯自动的: 网卡收到数据 → 拷贝到内核缓冲区 → 从内核缓冲区读到你的byte数组里 → 通知你, 纯自动纯的, 这么纯自动的东西为什么很少用呢? 因为不是很多系统都支持AsyncIO的

![](https://2314591241-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MC_ucxnzAClkrgsGdFP%2Fsync%2Fc07ab47e77f90a74128be40dc58129ab0997c3f3.png?generation=1605711930508747\&alt=media)

## 如何区分同步/异步IO

* **"异步的" IO**: 在调用读请求的时候不会产生阻塞, 仅有最后一种Model5- AIO是符合要求的
* **"同步的" IO**: 在调用读请求的时候会产生阻塞, 直到拷贝到byte数组的动作完全搞定, 前四种全都是同步的, 因为尽管通知(内核buffer已就绪的)机制各有不同, 但是实际从内核buffer拷贝到用户byte数组的时候, 还是阻塞的, 一直阻塞到拷贝完全ok位置

> 本文译自: [这里](http://www.masterraghu.com/subjects/np/introduction/unix_network_programming_v1.3/ch06lev1sec2.html)
>
> 这是一篇非常好的文章, 阅读完我只用了半小时
>
> 但感觉, 非常的澄澈
