0%

IO 模型简介


最近工作中接触到关于网络编程的一些东西,发现对于网络、IO编程、socket、进程、线程、协程、TCP/IP等基本知识理解不够深入。所以需要从头到尾总结一下。

什么是 IO

IO 的英文来源是 Input/Output,即输入/输出,我们的程序和数据在运行过程中会在内存中驻留,由 CPU 来计算,涉及到数据交换的地方,就需要IO接口,IO 包括网络 IO, 内存 IO,磁盘IO等。

IO编程中,Stream(流)是一个很重要的概念,可以把流想象成一个水管,数据就是水管里的水,但是只能单向流动。Input Stream就是数据从外面(磁盘、网络)流进内存,Output Stream就是数据从内存流到外面去。对于浏览网页来说,浏览器和服务器之间至少需要建立两根水管,才可以既能发数据,又能收数据。(摘自http://www.liaoxuefeng.com/)

IO 模型大概分为

  • 阻塞 IO(blocking IO)
  • 非阻塞 IO(non-blocking IO)
  • IO 复用(IO multiplexing)
  • 异步 IO(asynchronous IO)
  • 同步 IO(synchronous IO)

但是这几种 IO 模型到底是什么,又分别有什么区别。这里我搜索了一些资料并融入自己的理解详细解释一下。

阻塞 IO(blocking IO)

这里先从阻塞 IO 说起,因为 Linux 中默认的网路模型基本都是阻塞 IO。根据调用关系粗略画的时序图如下:

这里写图片描述

当用户进程调用 recvform 这个系统调用,linux 内核 kernel 开始工作:准备接受数据, 数据一开始往往还没有到达,(例如,网络IO还没有接受到一个完整的 UDP包),这个时候 kernel 需要等待一段时间来接受完数据。在这个过程中,用户进程就会什么也做不了,只能等 kernel 接受完数据并拷贝数据到用户内存中,然后返回消息给用户进程,进程才能继续操作。对于用户进程来说,等待 kernel 返回数据的过程就叫阻塞(block)。等数据返回才能解除阻塞。
所以,阻塞 IO 模型的特点就是在 IO 执行的输入和输出,都被阻塞掉了。

打个比喻:去餐厅吃饭,点餐完,你在柜台一直等饭,柜台接受到你的订单开始准备做饭,这时候你只能一直等哪里也不能去,就像是被阻塞,等饭出来了,你拿着饭才走。解除阻塞。

非阻塞 IO non-blocking IO

了解了阻塞 IO,我们来看看非阻塞IO模型的具体流程。还是以用户进程的一次调用为例,调用时序图大致如下:

这里写图片描述

当用户进程调用系统调用,kernel 内核开始准备接受数据,如果一开始没有接受到数据,会立刻返回 error 给用户进程,用户进程就知道数据还没有准备好,就会再次发送调用,直到数据准备好,然后立刻会将数据拷贝到内存中,并返回信息给用户进程,在这个过程中用户进程并不需要等待,每次调用开始到结束的过程,如果出错,kernel 都会立刻返回 error 消息给它。这种模型被称为非阻塞 IO 模型。
非阻塞IO 模型的特点是不需要等待,但是需要用户进程不断的去调用。

需要注意的是, 在我的理解下,在系统调用时,如果没有准备好数据就立刻返回给进程 error 信息,这个过程确实是非阻塞的,但是,当数据准备好之后,kernel 开始将数据拷贝到内存中的这段时间内,用户进程其实还是阻塞的。

又打个比喻:还是上面说的去餐馆吃饭,当在柜台点完餐,这个时候服务员说现在做饭需要的食材还没有准备好,你知道了这个信息后,隔一会就去重新点一次餐,最终服务员说食材准备好了,并做好了饭给你。在做饭的过程中可能会不断遇到各种问题不能下单,你只能去不断的重新点餐。

IO 复用

非阻塞模型中存在的问题是用户进程需要不断去调用内核,IO 复用模型的出现就是来解决这个问题的,IO 复用模型是建立在内核提供的多路分离函数 select 函数之上的,一个 select 中可以同时处理多个 socket 请求。所以用户一次调用可以注册多个 socket 请求。时序图大致如下:

这里写图片描述

用户进程使用select 函数注册多个 socket 请求,这时候整个进程就会被阻塞,kernel会“监视”所有select负责的socket,只要其中的一个 socket 请求的数据准备成功,select 就会返回给用户进程可读的消息,这个时候用户进程再调用 read 操作去讲数据拷贝到 内存中。在这个模型中,我们一般设置 select 中的每个 socket 为非阻塞的,但是其实整个进程是被 select 阻塞的。
IO 复用的特点是需要两次系统调用(system),并且需要调用 select ,可以同时处理多个 socket 连接。
如果处理的连接数不是很高的话,使用select/epoll的 web server 不一定比使用 multi-threading + blocking IO 的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。

异步IO(asynchronous IO)

异步IO模型中,当用户发起请求之后,就会立刻得到返回,并去做其他事情去了,从 kernel 角度来讲,他在接受到请求会立刻返回给用户进程消息,不会对其造成任何的阻塞,然后 kernel 会自己等待数据接受完毕并将数据拷贝到内存中,当这一切都完成后 kernel 会发送一个消息给用户进程,告诉它已经操作完成。时序图如下:

这里写图片描述

相比于IO多路复用模型,异步IO并不十分常用,不少高性能并发服务程序使用IO多路复用模型+多线程任务处理的架构基本可以满足需求。况且目前操作系统对异步IO的支持并非特别完善,更多的是采用IO多路复用模型模拟异步IO的方式(IO事件触发时不直接通知用户线程,而是将数据读写完毕后放到用户指定的缓冲区中)。

同步IO

同步IO 的概念是:一个同步 IO 的操作会导致请求的进程被阻塞,直到整个进程完成。
有的人可能要说了,阻塞IO 和 IO 多路复用是同步 IO, 非阻塞IO 就是同步 IO。我的理解不是这样的,阻塞IO 和多路复用 IO 肯定是同步 IO,但非阻塞IO 其实本质上也是同步 IO。
在非阻塞IO 那里,最后写了需要注意的一个问题,就是当数据准备好之后,kernel 拷贝数据到内存的过程中对于进程来说其实还是阻塞的。并不完全是非阻塞的。

所以,阻塞 IO、非阻塞 IO、IO 多路复用都是同步 IO

区别

阻塞 IO 和非阻塞 IO 的区别:请求发起调用 IO 会一直被 block,直到操作完成。而 non-blocking IO在kernel还准备数据的情况下会立刻返回。
同步 IO 和异步 IO 的区别:一个IO操作有没有对进程造成阻塞。

参考

  1. https://segmentfault.com/a/1190000003063859#articleHeader11
  2. http://blog.csdn.net/historyasamirror/article/details/5778378