NIO 网络编程(通信)学习笔记
演示
第一阶段
既然是通信,那必然有客户端和服务端
要进行 NIO 的网络通信,首先要创建一个服务端,如下:
//1. 打开 serverSocketChannel 通道并绑定端口
System.out.println("open serverSocketChannel ...");
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8000));
// 保存所有客户端链接
ArrayList<SocketChannel> socketChannelList = new ArrayList<>();
// 保存客户端传来的数据
ByteBuffer buffer = ByteBuffer.allocate(20);
while (true) {
System.out.println("wait for connect ...");
//2. 接受客户端链接,会阻塞
SocketChannel channel = serverSocketChannel.accept();
socketChannelList.add(channel);
System.out.println(channel.toString() + " connected");
//3. 遍历客户端链接,获取数据
for (SocketChannel socketChannel : socketChannelList) {
System.out.println("read data ...");
socketChannel.read(buffer);
buffer.flip();
//4. 数据处理
System.out.println("StandardCharsets.UTF_8.decode(buffer) = " + StandardCharsets.UTF_8.decode(buffer));
buffer.clear();
}
}
然后创建客户端
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress(8000));
// encode 方法生成的 buffer 会切换到 读模式
socketChannel.write(StandardCharsets.UTF_8.encode("hello nio"));
System.out.println("wait other operation ...");
在上述服务端的代码中,有两处阻塞
- 连接阻塞 --> accept 方法 --> ServerSocketChannel
- IO 阻塞 --> read 方法 --> SocketChannel
第二阶段
要解决阻塞的问题,我们可以做以下处理
serverSocketChannel.configureBlocking(false);
channel.configureBlocking(false);
配置之后会发现,不再会有阻塞发生
但缺点也很明显,因为即使没有任何的客户端连接,也会一直死循环处理,导致 cpu 的空转,造成资源浪费
那么这时候需要引入一个“管理者”,让程序在有链接或数据进来的时候,再去处理
第三阶段
- 谁来做管理者? Selector
- 管理哪些东西? accept(serverSocketChannel) read(socketChannel) write(socketChannel)
- 怎么管理?
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8000));
// selector 只有在 非阻塞模式 下才能使用
serverSocketChannel.configureBlocking(false);
Selector selector = Selector.open();
// 将 serverSocketChannel 注册到 selector 的 keys 中
System.out.println("serverSocketChannel: " + serverSocketChannel);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT, null);
while (true) {
// 阻塞,等待 keys 中的 channel 发生特定事件
System.out.println("select ...");
selector.select();
// 每当发生事件时,都会把相应的 key 放到 selectedKeys 中
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator();
if (selectionKeyIterator.hasNext()) {
SelectionKey selectionKey = selectionKeyIterator.next();
// 当前 key 的事件处理完后从 selectedKeys 中删掉
selectionKeyIterator.remove();
if (selectionKey.isAcceptable()) {
System.out.println("isAcceptable ...");
SocketChannel socketChannel = serverSocketChannel.accept();
System.out.println("socketChannel: " + socketChannel);
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ, null);
} else if (selectionKey.isReadable()) {
System.out.println("isReadable ...");
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(20);
socketChannel.read(buffer);
buffer.flip();
System.out.println("StandardCharsets.UTF_8.decode(buffer) = " + StandardCharsets.UTF_8.decode(buffer));
}
}
}
selector 中
- keys 存储所有注册到 selector 中的 channel 对应的 key
- selectedKeys 存储 keys 中当前有特定(注册时指定)事件发生的 channel 对应的 key
结论
- NIO 不是没有阻塞,ServerSocketChannel 的 accept 方法和 SocketChannel 的 read 方法都会导致阻塞