基础
NIO:Non-Blocking IO
非阻塞 IO
,主要用于网络连接中非阻塞的读写,提供多路非阻塞式的高伸缩性网络 I/O
。异步 I/O
的一个优势在于,可以同时根据大量的输入和输出执行 I/O
。同步程序常常需要轮询,或者创建很多线程处理大量的连接。使用异步 I/O
,可以监听任何数量的通道上的事件,不用轮询也不用额外的线程。
Selector
选择器:是 Java NIO
中能够检测一到多个 NIO
通道,是多路复用器,能够监听通道是否为读写事件做好准备。因此一个单独的线程可以管理多个通道,从而管理多个网络连接。Selector
用来支持异步 I/O
操作(非阻塞I/O操作),Channel
必须处于非阻塞模式下(因此 FileChannel, Selector
不能一起使用)。Selector
是非阻塞 I/O
的核心,所有希望采用非阻塞 I/O
进行通信的通道,都应该注册到 Selector
对象。
SelectionKey
SelectionKey
包含监听的不同类型事件:
OP_READ
OP_WRITE
OP_CONNECT
OP_ACCEPT
常用 API
1 | public abstract class SelectionKey { |
常用 API
1 | public abstract class Selector implements Closeable { |
SelectableChannel
支持选择器的通道
表示可以支持多路复用的通道,支持阻塞和非阻塞模式。(默认情况下,所有的 Channel
都是阻塞的),需要设置为非阻塞模式,才能使用 NIO
特性。
1 | public abstract class SelectableChannel |
注册通道
通道必须处于非阻塞模式,可以监听多个事件。
1 | channel.configureBlocking(false); |
参考示例
1 | Selector selector = Selector.open(); |
SocketChannel
对应于 java.net.Socket
类。
创建 SocketChannel
默认创建一个阻塞 SocketChannel
,两种方法的差异:
open()
后续还需要手动配置为非阻塞后,监听连接事件。open(SocketAddress remote)
创建并同步等待连接remote
,直到连接成功后返回。不需要再手动连接。
支持的非阻塞事件
OP_CONNECT
OP_READ
OP_WRITE
不支持 Accept
事件
常见 API
1 | public abstract class SocketChannel |
ServerSocketChannel
对应于 java.net.ServerSocket
类。
创建 ServerSocketChannel
创建 ServerSocketChannel
也很简单:ServerSocketChannel server = ServerSocketChannel.open();
支持的非阻塞事件
仅支持 OP_ACCEPT
事件。
常见 API
1 | public abstract class ServerSocketChannel |
客户端和服务端 TCP
通信流程
整个通信流程参考 TCP
通信流程,只是实现方式不一样。本例实现一个聊天室功能。
服务端初始化
- 创建
Selector
public static Selector open() throws IOException;
,创建选择器。 - 创建
ServerSocketChannel
public static ServerSocketChannel open() throws IOException;
,创建服务端通道,并配置为非阻塞。 - 绑定
bind
public final ServerSocketChannel bind(SocketAddress local){...}
,绑定指定地址和端口。 - 注册
Accept
事件serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
,注册后服务端选择器开始监听客户端的连接。
服务端轮询监听并处理事件
selector.select()
,阻塞等待。监听客户端的连接及输入,从这里可以看出 nio
并不完全是异步,还是会有阻塞。
客户端初始化
- 创建
Selector
public static Selector open() throws IOException;
,创建选择器。 - 创建
SocketChannel
public static SocketChannel open() throws IOException;
,创建客户端通道,并配置为非阻塞。 - 注册
Connect
事件socketChannel.register(selector, SelectionKey.OP_CONNECT);
,注册事件后准备连接。 - 连接
connect
public abstract boolean connect(SocketAddress remote) throws IOException;
,连接服务器。
客户端轮询监听并处理事件
selector.select()
,阻塞等待。监听服务端反馈和输入。
数据通信
- 服务端接受连接后监听客户端写入
服务端接受客户端连接后,拿到客户端SocketChannel
,并同步注册Read
事件,监听客户端输入。通过该通道读写缓冲区实现数据通信。 - 客户端监听服务端发送的消息
客户端接受到服务端连接响应后,客户端SocketChannel
注册Read
事件,监听该通道来自服务端的输入。
服务端和客户端关闭
客户端和服务端分别关闭 Selector, ServerSocketChannel, SocketChannel
。
服务端存在的问题:客户端的
SocketChannel
关闭后,服务端此通道并没有断开连接,并且该通道注册到选择器的读事件,会被反复触发。也就是说服务端select
一直都会返回OP_READ
,但是通道中读入缓冲区的数据实际总是为 -1,即并没有数据。为什么会反复触发?
1 | // 服务端打印信息,服务端 10000,客户端 62676 |
TCP
通信示例
本例实现一个聊天室功能。
服务端
1 | public class TestNIOTCPServer { |
客户端
1 | public class TestNIOTCPClient { |
DatagramChannel
[todo]
http://ifeve.com/datagram-channel/
http://blog.csdn.net/foart/article/details/47608475
http://www.365mini.com/page/java-nio-course-27.htm
网络编程第四版-425