Java NIO中的
Selector是一个可以检查一个或多个SelectableChannel实例是否准备好进行I/O操作的对象。使用Selector可以让你用一个线程来管理多个Channel,从而提高应用程序的效率。
使用Selector的基本步骤:
1. 打开一个Selector
首先,需要通过调用Selector.open()方法来创建一个Selector。
Selector selector = Selector.open();
2. 将Channel注册到Selector上
为了使用Selector,必须将Channel注册到Selector上,并指定要监听的事件。可以通过调用SelectableChannel.register()方法来实现。
channel.configureBlocking(false); // 设置为非阻塞模式
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
在这里,channel是一个SelectableChannel的实例,比如SocketChannel或ServerSocketChannel。第二个参数是一个“兴趣集合”,表示你想要监听的事件类型(SelectionKey.OP_READ、SelectionKey.OP_WRITE、SelectionKey.OP_CONNECT、SelectionKey.OP_ACCEPT)。
3. 轮询就绪的Channel
接下来,可以通过调用Selector的select()方法来检查是否有就绪的Channel。
select(): 阻塞直到至少有一个Channel准备好进行操作。select(long timeout): 阻塞直到至少有一个Channel准备好进行操作,或者超时。selectNow(): 不阻塞,立即返回就绪Channel的数量。
int num = selector.select(); // 阻塞直到至少有一个Channel准备好
4. 处理就绪的Channel
一旦select()方法返回,表示至少有一个Channel准备好了。可以通过调用selectedKeys()方法来获取就绪Channel的SelectionKey集合。
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {SelectionKey key = keyIterator.next();if(key.isAcceptable()) {// 处理OP_ACCEPT事件} else if (key.isConnectable()) {// 处理OP_CONNECT事件} else if (key.isReadable()) {// 处理OP_READ事件} else if (key.isWritable()) {// 处理OP_WRITE事件}keyIterator.remove(); // 处理完事件后,从selectedKeys集合中移除SelectionKey
}
5. 关闭Selector
当不再需要Selector时,应该调用其close()方法来释放它可能占用的资源。
selector.close();
注意事项
- Channel必须设置为非阻塞模式,因为
Selector只适用于非阻塞Channel。 - 在处理完每个事件后,应该从
selectedKeys集合中移除对应的SelectionKey,否则它会在下次select调用时再次出现在该集合中。
通过上述步骤,可以有效地使用Selector来管理多个Channel,实现高效的I/O多路复用。
实例演示
创建服务端TestSelector类来接收连接请求:
import lombok.extern.slf4j.Slf4j;import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.*;
import java.util.Iterator;@Slf4j
public class TestSelector {public static void main(String[] args) throws IOException {// 创建selector,管理多个channelSelector selector = Selector.open();ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();// 非阻塞模式serverSocketChannel.configureBlocking(false);// 将serverSocketChannel注册到selector上,将来事件发生后,可以通过SelectionKey来获取事件以及关联的channelSelectionKey sscSelectionKey = serverSocketChannel.register(selector, 0, null);// 监听accept事件sscSelectionKey.interestOps(SelectionKey.OP_ACCEPT);log.debug("register key:{}", sscSelectionKey);// 绑定到指定端口serverSocketChannel.bind(new InetSocketAddress(8080));while (true) {// select方法,阻塞,直到有事件发生selector.select();// 处理事件,拿到selectionKey集合,内部包含了所有发生的事件Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();log.debug("key:{}", key);// 获取发生事件的通道ServerSocketChannel channel = (ServerSocketChannel) key.channel();// 接受连接请求,返回一个新的SocketChannelSocketChannel socketChannel = channel.accept();log.debug("socketChannel:{}", socketChannel);}}}
}
创建客户端Client类来发送链接请求:
import lombok.extern.slf4j.Slf4j;import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel;@Slf4j
public class Client {public static void main(String[] args) throws IOException {SocketChannel socketChannel = SocketChannel.open();socketChannel.connect(new InetSocketAddress("localhost", 8080));System.out.println("waiting......");}
}
之后先启动服务端:

可以看到注册key已经成功返回。
再启动客户端:

查看服务端控制台:

可以看到accept事件已经被成功监听到。
