网络IO模型

IO本质上是对数据缓冲区的读写,主要分为文件IO和网络IO,基本模型有很多,可以从两个方面去认识 同步和异步,阻塞和非阻塞。根据上面分类可以分为下面五类:

  1. 阻塞I/O(blocking I/O)
  2. 非阻塞I/O (nonblocking I/O)
  3. I/O复用(select 、poll和epoll) (I/O multiplexing)
  4. 信号驱动I/O (signal driven I/O (SIGIO))
  5. 异步I/O (asynchronous I/O )

阻塞IO

阻塞IO也是常说的BIO,是一个单线程的阻塞模型,在执行数据拷贝的时候,会阻塞主进程,直到数据拷贝完成。具体模式如下图:
BIO

从图中可以看出,这种模型的比较简单,及时性比较高,但是会阻塞用户进程,在使用的时候常常结合多线程或者多进程来使用。

如果在连接数比较多的情况下,多线程只能缓解,无法彻底解决。总之,多线程可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈。

非阻塞IO

由于BIO的执行效率和阻塞的问题,单机无法承载过多的数据处理。对于用户来说来说,其实不用等待数据的准备过程,只需要返回数据有没有准备好就行。

NIO

从图中可以看出,当一个用户发出read操作的时候,如果数据还没有准备好,并不会阻塞用户进程,而是返回一个等待的标识。从开发的角度来说,当发起一个read操作的时候,并不用等待复制完成,只需要知道什么事时候完成就可以了。一旦数据准备好,就可以进行后续的操作。

网上找到一个用一个线程从多个连接中检测数据是否送达,并且接受数据的模型。
NIO

在非阻塞状态下,recv() 接口在被调用后立即返回,返回值代表了不同的含义。如在本例中,

  • recv() 返回值大于 0,表示接受数据完毕,返回值即是接受到的字节数;
  • recv() 返回 0,表示连接已经正常断开;
  • recv() 返回 -1,且 errno 等于 EAGAIN,表示 recv 操作还没执行完成;
  • recv() 返回 -1,且 errno 不等于 EAGAIN,表示 recv 操作遇到系统错误 errno。

可以看到服务器线程可以通过循环调用recv()接口,可以在单个线程内实现所有连接工作。但是轮询的调用recv()也会带来CPU的消耗。系统有更高效的接口来判断是否操作完成的方法。例如:select 多路复用模式,可以一次检测多个连接是否活跃。

多路复用模式IO

多路复用模式IO其实就是常说的select/epoll,这种IO方式称之为事件驱动。这种模式的好处就是在于单个进程可以同时处理多个网络连接的IO.

MIO

从上图中可以看出如果用户调用的select方法,那么整个进程都会被Block。而同时会开启一个监听,一旦有一个socket的接口准备好了,就立马返回.这个时候在调用read操作可以获取到传输的数据。

此处需要明白一个问题,多路复用的也会阻塞进程,在处理连接数不是很高的网络请求中,性能不一定比多线程+BIO的性能好,反而会有更大的延迟。
所以,再次强调一下,多路复用的IO优势在于处理更多的连接,而不是更高的性能。

相比于其他模型,select时间驱动只用了单线程(进程),消耗的资源少,cpu的使用率低,同时可以为多了客户端服务。

MIO
图中是多路复用模式的一个执行周期,当然也逃脱不了轮询的命运,但这个轮询是一组的轮询,不会占用太多的资源。
但这个模型依然有很大的问题,如果某探测数据准备的时间过长,则会导致消耗大量的时间去轮询,这样会导致系统的响应慢。

信号驱动I/O

信号驱动的IO不是特别常用,简单的来说就在是通信的时候安装一个信号器,进程继续不发生阻塞,一旦数据准备好了,就通知发出一个信号,收到信号后就可以了处理数据。
SIGIO
图中是信号IO的处理模型,这种模型的优势在于第一时间收到数据准备好的消息,免去了select的阻塞和轮询。

异步IO

异步IO被称为AIO,AIO的模型与信号IO类似,当用户发起read操作后,就可以做其他的事情,当数据准备完成后, 将数据拷贝到用户内存,当这个过程完成后,给用户的进程发一个Signal告诉read已经完成。
AIO

上面的图片是展示AIO的处理过程,AIO是真正的异步非阻塞,不会产生任何的阻塞,真正的满足高并发的需求。

【参考资料】

  1. 五种网络IO模型:阻塞IO、非阻塞IO、IO多路复用、信号驱动IO以及异步IO

  2. IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇)