5种网络IO模型(有图,很清楚)( 四 )


图9 多路复用模型的一个执行周期
这种模型的特征在于每一个执行周期都会探测一次或一组事件 , 一个特定的事件会触发某个特定的响应 。 我们可以将这种模型归类为“事件驱动模型” 。
相比其他模型 , 使用select() 的事件驱动模型只用单线程(进程)执行 , 占用资源少 , 不消耗太多 CPU , 同时能够为多客户端提供服务 。 如果试图建立一个简单的事件驱动的服务器程序 , 这个模型有一定的参考价值 。
【5种网络IO模型(有图,很清楚)】 但这个模型依旧有着很多问题 。 首先select()接口并不是实现“事件驱动”的最好选择 。 因为当需要探测的句柄值较大时 , select()接口本身需要消耗大量时间去轮询各个句柄 。 很多操作系统提供了更为高效的接口 , 如linux提供了epoll , BSD提供了kqueue , Solaris提供了/dev/poll , … 。 如果需要实现更高效的服务器程序 , 类似epoll这样的接口更被推荐 。 遗憾的是不同的操作系统特供的epoll接口有很大差异 , 所以使用类似于epoll的接口实现具有较好跨平台能力的服务器会比较困难 。
其次 , 该模型将事件探测和事件响应夹杂在一起 , 一旦事件响应的执行体庞大 , 则对整个模型是灾难性的 。 如下例 , 庞大的执行体1的将直接导致响应事件2的执行体迟迟得不到执行 , 并在很大程度上降低了事件探测的及时性 。
5种网络IO模型(有图,很清楚)文章插图
图10 庞大的执行体对使用select()的事件驱动模型的影响
幸运的是 , 有很多高效的事件驱动库可以屏蔽上述的困难 , 常见的事件驱动库有libevent库 , 还有作为libevent替代者的libev库 。 这些库会根据操作系统的特点选择最合适的事件探测接口 , 并且加入了信号(signal) 等技术以支持异步响应 , 这使得这些库成为构建事件驱动模型的不二选择 。 下章将介绍如何使用libev库替换select或epoll接口 , 实现高效稳定的服务器模型 。
实际上 , Linux内核从2.6开始 , 也引入了支持异步响应的IO操作 , 如aio_read, aio_write , 这就是异步IO 。
4、异步IO(Asynchronous I/O) Linux下的asynchronous IO其实用得不多 , 从内核2.6版本才开始引入 。 先看一下它的流程:
5种网络IO模型(有图,很清楚)文章插图
图11 异步IO
用户进程发起read操作之后 , 立刻就可以开始去做其它的事 。 而另一方面 , 从kernel的角度 , 当它受到一个asynchronous read之后 , 首先它会立刻返回 , 所以不会对用户进程产生任何block 。 然后 , kernel会等待数据准备完成 , 然后将数据拷贝到用户内存 , 当这一切都完成之后 , kernel会给用户进程发送一个signal , 告诉它read操作完成了 。
用异步IO实现的服务器这里就不举例了 , 以后有时间另开文章来讲述 。 异步IO是真正非阻塞的 , 它不会对请求进程产生任何的阻塞 , 因此对高并发的网络服务器实现至关重要 。
到目前为止 , 已经将四个IO模型都介绍完了 。 现在回过头来回答最初的那几个问题:blocking和non-blocking的区别在哪 , synchronous IO和asynchronous IO的区别在哪 。
先回答最简单的这个:blocking与non-blocking 。 前面的介绍中其实已经很明确的说明了这两者的区别 。 调用blocking IO会一直block住对应的进程直到操作完成 , 而non-blocking IO在kernel还在准备数据的情况下会立刻返回 。
在说明synchronous IO和asynchronous IO的区别之前 , 需要先给出两者的定义 。 Stevens给出的定义(其实是POSIX的定义)是这样子的:
* A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes;
* An asynchronous I/O operation does not cause the requesting process to be blocked;
两者的区别就在于synchronous IO做”IO operation”的时候会将process阻塞 。 按照这个定义 , 之前所述的blocking IO , non-blocking IO , IO multiplexing都属于synchronous IO 。 有人可能会说 , non-blocking IO并没有被block啊 。 这里有个非常“狡猾”的地方 , 定义中所指的”IO operation”是指真实的IO操作 , 就是例子中的recvfrom这个系统调用 。 non-blocking IO在执行recvfrom这个系统调用的时候 , 如果kernel的数据没有准备好 , 这时候不会block进程 。 但是当kernel中数据准备好的时候 , recvfrom会将数据从kernel拷贝到用户内存中 , 这个时候进程是被block了 , 在这段时间内进程是被block的 。 而asynchronous IO则不一样 , 当进程发起IO操作之后 , 就直接返回再也不理睬了 , 直到kernel发送一个信号 , 告诉进程说IO完成 。 在这整个过程中 , 进程完全没有被block 。