如何使用Java的Selector实现多路复用网络连接?

在 Java NIO 中,Selector 类是实现多路复用的关键。多路复用是指在单个线程中,同时处理多个 Channel 的能力。这对于开发高并发程序,如服务器,是非常有用的。以下是一个简单的示例,展示如何使用 Selector 实现多路复用:

// 创建一个Selector
Selector selector = Selector.open();

// 创建一个ServerSocketChannel
ServerSocketChannel serverChannel = ServerSocketChannel.open();
InetSocketAddress addr = new InetSocketAddress("localhost", 1111);
serverChannel.bind(addr);
serverChannel.configureBlocking(false); // 非阻塞模式

// 将ServerSocketChannel注册到Selector,关注ACCEPT事件
serverChannel.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
    // 阻塞等待就绪的Channel,这是选择操作的关键
    int readyChannels = selector.select();

    if (readyChannels == 0) continue; // 可能发生阻塞唤醒的情况

    // 获取就绪的SelectionKey集合
    Set<SelectionKey> readyKeys = selector.selectedKeys();
    Iterator<SelectionKey> iterator = readyKeys.iterator();

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

        // 必须手动从集合中移除当前的key
        iterator.remove();

        if (key.isAcceptable()) {
            // 有已经接受的新的到服务端的连接
            ServerSocketChannel server = (ServerSocketChannel) key.channel();
            SocketChannel client = server.accept(); // 接收客户端连接
            client.configureBlocking(false);
            // 客户端连接成功后,也需要注册到Selector,关注READ事件
            client.register(selector, SelectionKey.OP_READ);
        } else if (key.isReadable()) {
            // 有数据可读
            SocketChannel client = (SocketChannel) key.channel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int n = client.read(buffer); // 读取数据
            // 此处省略处理数据的代码...
        }
        // 对于key.isWritable()和key.isConnectable(),根据需要进行处理
    }
}

在以上代码中,我们创建了一个服务器Socket通道,并将其设置为非阻塞模式,然后将其注册到Selector上,关注ACCEPT事件。然后进入一个无限循环,调用 selector.select() 阻塞等待就绪的Channel。一旦有就绪的Channel,我们就处理这些Channel。

如果是ACCEPT事件就绪,我们接受新的客户端连接,然后再将新的客户端Channel注册到Selector上,关注READ事件。如果是READ事件就绪,我们读取数据并进行处理。

注意,我们每次处理完一个 SelectionKey 后,都需要从集合中删除。这是因为,selectedKeys() 获取的是所有就绪的Key的集合,如果不手动删除,下次循环时,这个Key仍然会在集合中。

发表评论

后才能评论