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,管理多个channel
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 非阻塞模式
serverSocketChannel.configureBlocking(false);
// 将serverSocketChannel注册到selector上,将来事件发生后,可以通过SelectionKey来获取事件以及关联的channel
SelectionKey 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();
// 接受连接请求,返回一个新的SocketChannel
SocketChannel socketChannel = channel.accept();
log.debug("socketChannel:{}", socketChannel);
iterator.remove();
}
}
}
}
创建客户端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事件已经被成功监听到。