MST

星途 面试题库

面试题:Java中NIO选择器如何实现非阻塞I/O的监控

请描述在Java NIO中,选择器(Selector)是怎样对多个通道(Channel)进行非阻塞I/O操作的监控的?举例说明如何注册通道到选择器并监听特定的事件。
20.9万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试
  1. 选择器监控多个通道的原理

    • 选择器(Selector)是Java NIO中的核心组件,它能够使一个单独的线程管理多个通道(Channel)。
    • 每个通道(例如SocketChannel等)可以注册到选择器上,并指定该通道希望Selector监控的事件类型,如连接就绪(SelectionKey.OP_CONNECT)、接受就绪(SelectionKey.OP_ACCEPT)、读就绪(SelectionKey.OP_READ)、写就绪(SelectionKey.OP_WRITE)。
    • 选择器通过调用select()方法阻塞等待,直到注册在其上的通道有感兴趣的事件发生。当有事件发生时,select()方法返回,返回值表示有事件发生的通道数量。
    • 然后可以通过selectedKeys()方法获取发生事件的SelectionKey集合,遍历该集合处理对应的通道事件。
  2. 注册通道到选择器并监听特定事件的示例

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 NIOExample {
    public static void main(String[] args) {
        try {
            // 创建Selector
            Selector selector = Selector.open();
            // 创建ServerSocketChannel并设置为非阻塞模式
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            // 绑定端口
            serverSocketChannel.socket().bind(new InetSocketAddress(8080));
            // 将ServerSocketChannel注册到Selector上,监听OP_ACCEPT事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            while (true) {
                // 阻塞等待事件发生
                int readyChannels = selector.select();
                if (readyChannels == 0) continue;

                // 获取发生事件的SelectionKey集合
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> keyIterator = selectedKeys.iterator();

                while (keyIterator.hasNext()) {
                    SelectionKey key = keyIterator.next();

                    if (key.isAcceptable()) {
                        // 处理新连接
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();
                        SocketChannel client = server.accept();
                        client.configureBlocking(false);
                        // 将新连接的SocketChannel注册到Selector上,监听OP_READ事件
                        client.register(selector, SelectionKey.OP_READ);
                    } else if (key.isReadable()) {
                        // 处理读事件
                        SocketChannel client = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        client.read(buffer);
                        buffer.flip();
                        System.out.println("Received: " + new String(buffer.array(), 0, buffer.limit()));
                    }

                    // 处理完事件后从集合中移除
                    keyIterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在上述示例中:

  • 首先创建了一个Selector实例和一个ServerSocketChannel,并将ServerSocketChannel设置为非阻塞模式并绑定到8080端口。
  • 然后将ServerSocketChannel注册到Selector上,监听OP_ACCEPT事件。
  • 在循环中,通过selector.select()等待事件发生,当有事件发生时,获取selectedKeys集合并遍历处理。如果是OP_ACCEPT事件,接受新连接并将新的SocketChannel注册到Selector上监听OP_READ事件;如果是OP_READ事件,则读取客户端发送的数据。