网络IO的模式包括同步IO,异步IO,阻塞IO,非阻塞IO;这些IO模式之间的区别和联系是任何一个搞网络编程(Socket编程)的人必须弄清楚的问题,下面我们就来一层层的揭开他们的面纱吧。在Unix网络编程第一卷第六章中讨论了五种IO模型:
1. 阻塞IO(Blocking IO)
2. 非阻塞IO(Nonblocking IO)
3. IO多路复用(IO multiplexing)
4. Signal Driven IO(实际中一般不使用)
5. 异步IO(Asynchronous IO)
由于第4种在实际中一般不使用,所以接下来在本文中我们就讨论其余4种模型;在讨论模型之前,我们首先要明确IO过程中两个非常重要的步骤(所有的IO模型都是用户进程和内核在这两个步骤上交互模式不同):
1. 内核等待数据准备(内核等待协议栈中运输层接收缓冲区或发送缓冲区就绪)
2. 内核将缓冲区中的数据拷贝用户态进程的地址空间中
Blocking IO
在Linux中,默认的Socket通信模式就是阻塞IO,该模式的过程如下图所示:
从上图可以看出,当用户进程开始调用recvfrom的时候,内核就开始等待数据,当数据准备好之后,内核立即将数据从内核缓存中拷贝到进程的地址空间中,整个过程完成之后,recvfrom调用返回;在此之间,用户进程被阻塞。
Nonblocking IO
当用户希望以非阻塞的方式进行IO通信时,可以设置Socket为非阻塞,非阻塞的模式图如下:
当用户进程调用recvfrom时,假如此时内核正在等待数据,则该调用会立即返回错误;假如此时数据已经准备好了,则内核会立即将数据拷贝到用户进程的地址空间中并成功返回;上面图示的是应用进程在不断的轮询,直到内核中数据准备好并成功返回为止。
IO multiplexing
IO多路复用也被称为事件驱动IO(Event Driven IO),我们常用的select, poll, epoll等系统调用就是这种模型;这种模型的优点是可以同时处理多个socket连接。其基本原理是select/epoll系统调用会不断轮询所负责的所有socket连接,一旦某个socket的数据已经准备好,select便立即返回;然后用户进程调用recvfrom触发内核将该socket对应的数据从内核空间拷贝到应用进程的地址空间中并成功返回。
在IO multiplexing Model中,socket一般都被设置成non-blocking,但是,如上图所示,内核在等待数据就绪的时候,用户进程其实是block的,只不过进程是被select这个函数block,而不是被socket IO给block。
该模型用于服务器中对多个网络并发连接进行处理会非常有效,分布式缓存Memcached的服务端及常用的Web服务器就采用这种模型,Libevent就是基于这个模型实现的库。
异步IO
Linux中异步IO其实用的非常少,它的模型如下图所示:
从图中我们可以看出,当用户进程发起IO请求时,如果内核数据没有准备好,则会立刻返回;返回后用户进程继续做之后的事情,而内核则等待数据就绪并将数据从内核拷贝到用户空间,同时发送aio_read中指定的信号给用户进程,告诉它数据已经成功发送给它。
下面我们来看看阻塞和非阻塞、同步和异步的内涵及区别:
阻塞、非阻塞是指当调用一个函数时,他是否能够立即返回;
同步、异步是指进程间通信是否经过协调,是进程间通信的模型;比如进程A需要等待进程B执行完某件事情之后才能继续执行,这就是同步;假如进程A的执行和进程B的执行好不相干,这就是异步;
因此,我们可以说阻塞调用或非阻塞调用,但不能说同步调用及异步调用,只能说同步通信或异步通信