Java NIO阻塞与非阻塞模式及Selector使用问题咨询
Java NIO 阻塞模式与Selector协作原理解答
核心问题解答
1. 非阻塞模式设置的作用
对的,将通道设置为非阻塞模式,核心目的之一就是配合Selector实现IO多路复用。
Selector的工作逻辑是单线程轮询多个注册的通道,只处理已经就绪的IO事件,避免为每个连接单独分配线程带来的资源浪费。如果通道是阻塞模式,调用accept()、read()、write()等IO方法时会阻塞当前线程,Selector根本无法完成多通道的轮询调度。
2. 阻塞模式通道能否注册到Selector
不能。JDK NIO的实现中明确规定:只有非阻塞模式的通道才能注册到Selector上。如果通道的configureBlocking(true),调用register()方法注册到Selector时会直接抛出IllegalBlockingModeException异常,不存在检测的可能性。
完整示例代码
基于你给出的服务端代码片段,补全可运行的非阻塞NIO服务端示例:
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; public class NioServer { public void start() throws IOException { // 1. 打开Selector Selector selector = Selector.open(); // 2. 打开服务端通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.bind(new InetSocketAddress(8000)); // 3. 必须设置为非阻塞才能注册到Selector serverSocketChannel.configureBlocking(false); // 4. 注册通道到Selector,监听新连接接入事件 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); System.out.println("NIO服务端启动,监听端口8000"); while (true) { // 阻塞等待有就绪事件的通道 selector.select(); // 获取所有就绪的事件Key Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectedKeys.iterator(); while (iterator.hasNext()) { SelectionKey key = iterator.next(); // 处理完必须移除,避免重复处理 iterator.remove(); if (key.isAcceptable()) { // 处理新连接 ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel(); SocketChannel socketChannel = serverChannel.accept(); socketChannel.configureBlocking(false); // 注册客户端通道到Selector,监听可读事件 socketChannel.register(selector, SelectionKey.OP_READ); System.out.println("新客户端接入:" + socketChannel.getRemoteAddress()); } else if (key.isReadable()) { // 处理客户端发送的数据 SocketChannel socketChannel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int readLen = socketChannel.read(buffer); if (readLen > 0) { buffer.flip(); byte[] data = new byte[buffer.remaining()]; buffer.get(data); System.out.println("收到客户端数据:" + new String(data)); // 回写响应 socketChannel.write(ByteBuffer.wrap("收到你的消息".getBytes())); } else if (readLen < 0) { // 客户端断开连接 socketChannel.close(); System.out.println("客户端断开连接"); } } } } } public static void main(String[] args) throws IOException { new NioServer().start(); } }
开发建议
- 只要是需要注册到Selector的通道,不管是服务端的
ServerSocketChannel还是客户端的SocketChannel,都必须先调用configureBlocking(false)再执行注册操作,否则会直接抛出异常。 - 通道注册时指定的感兴趣事件可以用位或操作组合,比如同时监听可读和可写事件可以传
SelectionKey.OP_READ | SelectionKey.OP_WRITE。 - 每次处理完
SelectionKey后必须从selectedKeys集合中移除,否则Selector会重复通知该事件,导致逻辑异常。 - 如果需要处理高并发场景,不要在Selector的轮询线程中执行耗时业务逻辑,应该把就绪的IO事件交给业务线程池处理,避免阻塞Selector轮询。
内容的提问来源于stack exchange,提问作者Võ Khắc Bảo
相关产品推荐
相关产品推荐

