如何使用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仍然会在集合中。