NIO网络通信

阻塞式IO与非阻塞式IO

传统的IO流都是阻塞式的。也就是说,当一个线程调用read() 或write()时,该线程被阻塞,直到有一些数据被读取或写入,该线程在此期间不能执行其他任务。因此,在完成网络通信进行IO操作时,由于线程会阻塞,所以服务器端必须为每个客户端都提供一个独立的线程进行处理,当服务器端需要处理大量客户端时,性能急剧下降。

Java NIO是非阻塞模式的。当线程从某通道进行读写数据时,若没有数据可用时,该线程可以进行其他任务。线程通常将非阻塞IO的空闲时间用于在其他通道上执行I0操作,所以单独的线程可以管理多个输入和输出通道。因此,NIO可以让服务器端使用一个或有限几个线程来同时处理连接到服务器端的所有客户端。

选择器和通道的关系:通道注册到选择器上,选择器监控通道。当某一个通道上,某一个事件准备就绪时,那么选择器才会将这个通道分配到服务器端一个或多个线程上,再继续运行。比如说当客户端发送一些数据给服务器端,只有当客户端的所有数据都准备就绪时,选择器才会将这个注册的通道分配到服务器端的一个或者多个线程上。如果客户端的线程没有将数据准备就绪时,服务器端的线程可以执行其他任务,就不会阻塞在那里。

原先的传统的阻塞IO模式,相当于你没有手机去等快递,算准了EMS每天中午13:00会到你们公司门口,所以你12:50在那里等着他们来,在这10分钟里你被这件事情阻塞着,什么事情都做不了,真是浪费时间;而NIO的这种通道注册选择器,选择器监控通道,等到数据准备就绪才会占用服务器线程的非阻塞IO方式,更像是带着手机等外卖,我在饿了么注册了一个用户(通道在选择器上注册了),然后定完外卖就忙自己的去了,等到外卖送来之后我接到电话下去取就可以了。如果你学习过Linux内核的epoll多路复用模型,这一点应该不难理解,无非就是IO事件就绪通知!

选择器 Selector

选择器( Selector) 是 SelectableChannle 对象的多路复用器, Selector 可以同时监控多个 SelectableChannel 的 IO 状况,也就是说,利用 Selector可使一个单独的线程管理多个 Channel。 Selector 是非阻塞 IO 的核心。

SelectableChannle 的结构如下图(注意:FileChannel不是可作为选择器复用的通道!FileChannel不能注册到选择器Selector!FileChannel不能切换到非阻塞模式!FileChannel不是SelectableChannel的子类!)

当调用 register(Selector sel, int ops) 将通道注册选择器时,选择器对通道的监听事件,需要通过第二个参数 ops 指定,可以监听的事件类型,可使用 SelectionKey 的四个常量表示:

若注册时不止监听一个事件,则可以使用位或操作符连接

SelectionKey: 表示 SelectableChannel 和 Selector 之间的注册关系。每次向选择器注册通道时就会选择一个事件(选择键)。 选择键包含两个表示为整数值的操作集。操作集的每一位都表示该键的通道所支持的一类可选择操作。

Selector的常用API

阻塞式IO示例

阻塞式网络通信

有反馈的阻塞式通信

非阻塞式IO示例

非阻塞式通信,其实与epoll的编程模式很像

上面的功能类似于一个群聊,由于IDEA不支持junit控制台,所以我直接拿eclipse试了一下

UDP使用NIO

其实和上面的示例没啥区别, UDP非阻塞网络,NIO中信息的接收端和发送端都是直接用DatagramChannel的open方法生成一个数据报通道,发送的时候指明地址和端口;接收的一方需要先bind一个端口号,监听着。接收方可能监听到多个发送端向其发送数据,为了单个线程能够处理多个客户端发送的数据,并且在发送的过程中不被某个发送端全程阻塞,同样需要用到选择器。

使用选择器仍然需要将通道注册到选择器上,并指明需要监听的事件,即选择键。

一旦向Selector注册了一或多个通道,就可以调用几个重载的select()方法。这些方法返回你所感兴趣的事件(如连接、接受、读或写)已经准备就绪的那些通道。换句话说,如果你对"读就绪"的通道感兴趣,select()方法会返回读事件已经就绪的那些通道。

  • int select():阻塞到至少有一个通道在你注册的事件上就绪了
  • int select(long timeout):select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数)
  • int selectNow():selectNow()不会阻塞,不管什么通道就绪都立刻返回,此方法执行非阻塞的选择操作。如果自从前一次选择操作后,没有通道变成可选择的,则此方法直接返回零。

管道 Pipe

Java NIO 管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取

通过一个管道,在发送数据的源头线程内获取SinkChannel,将数据写入缓冲区,缓冲区切换成读模式,写入SinkChannel;通过该管道在接收数据的线程内获取SourceChannel,然后从SourceChannel读取数据放到缓冲区,然后切换缓冲区为读模式,将数据取出。

NIO2中的几个实用类

随着 JDK 7 的发布, Java对NIO进行了极大的扩展,增强了对文件处理和文件系统特性的支持,以至于我们称他们为 NIO.2。因为 NIO 提供的一些功能, NIO已经成为文件处理中越来越重要的部分。

java.nio.file.Path 接口代表一个平台无关的平台路径,描述了目录结构中文件的位置。

java.nio.file.Files 用于操作文件或目录的工具类。

参考资料

http://tutorials.jenkov.com/java-nio/index.html

http://hg.openjdk.java.net/jdk/jdk/file/d8327f838b88/src/java.base/linux/classes/sun/nio/ch/EPollSelectorImpl.java