本系列介绍JAVA的NIO(New IO)系列接口。NIO通过几个部件(主要是:selectorchannelbuffer),提供和传统IO不同的读写方式。它 同步非阻塞 IO。由NIO提供的接口看,它的设计思路和Unix的五个IO模型的其中一个:多路转接IO是一样的。所以,在学习具体的NIO代码实现之前,我会先介绍这种IO模型。

Unix的五种IO模型

先看看Unix下可用的5种IO模型:

  • 阻塞I/O
  • 非阻塞I/O
  • 即多路转接IO(又译作:I/O复用)
  • 信号驱动I/O(SIGIO)
  • 异步I/O(Posix.1的aio_系列函数)

对同步,阻塞等概念和这几种IO的区别,本文不再赘述,可以在文末的参考一节找到相关资料。

除了前两种IO模型,Unix对其他三种IO都提供系统调用或系统函数级别的支持。

多路转接IO的使用场景

多路转接IO的适用场景是:需要对多个描述符(文件描述符:Unix对IO地址的抽象,可以是文件,socket等可读写的地址)进行读写。想象我们需要从两个描述符读:

这种场景下,不能在任意描述符上进行阻塞读,因为会导致进程阻塞,使另一个描述符即使有数据也无法处理。

那可以使用多进程 / 线程,让它们分别进行阻塞读吗?当然可以,但会产生新问题:多进程系统中,需要涉及进程间通信问题,如果使用多线程模型,则需要考虑线程间同步问题。

可以使用非阻塞IO读取吗?使用非阻塞的read,分别轮询两个描述符,当任意一个描述符可用时,才处理。这个方法的不足之处是浪费CPU时间。大多数时间实际上是无数据可读的,因此执行read系统调用会浪费时间。

在这种场景下,比较好的技术是使用I/O多路转换(I/O multiplexing)。它的做法是:我们把这两个描述符构造出一张列表,然后调用select函数,这个函数会 阻塞,直到任意一个描述符准备好进行IO时,它才会返回,并告知用户进程,哪个描述符已经准备好了。

过程

再整理一下它的工作过程:

  1. 用户进程构造一张感兴趣的描述符列表,调用select方法,交给内核。
  2. select方法阻塞。
  3. 当这个描述符列表的任意一个可用时,select方法返回,返回这个描述符。
  4. 用户进程收到描述符,调用recvfrom方法把数据从内核拷贝到用户空间。

时序图如下:

Alt

参考

  • 《Unix环境高级编程》
  • 《Unix网络编程》第六章
  • http://shensy.iteye.com/blog/1864781
  • http://tutorials.jenkov.com/java-nio/index.html
  • http://www.cnblogs.com/fanzhidongyzby/p/4098546.html

Updated: