NIO Selector
Summary::同时检测多个IO通道的对象
概念定义
在 Java 中,Selector 是 NIO(New Input/Output)库中的一种对象,用于监控多个通道的状态,例如文件 I/O 或者网络 I/O。
Selector 的工作原理是使用 select()
方法轮询已注册的通道,获取它们的就绪状态,并返回一个已准备好进行 I/O 操作的通道集合。通过使用此机制,可以监视几个通道的状态,并且只有当至少一个通道处于就绪状态时才会执行 I/O 操作,从根本上避免了 CPU 的浪费。
总之,Selector 是一种强大的工具,可实现高效的 I/O 操作和网络编程,因为它能够轻松地监视多个通道的状态并在需要时对它们进行操作。
相关概念
Selector的几个核心的概念
- Channel(通道):用于进行网络传输的通道,网络传输的数据都放在通道中,可以进行写入,也可以进行读取。Channel主要有两种:ServerSocketChannel和SocketChannel,其中ServerSocketChannel是用于服务端开发的,而SocketChannel是用于客户端开发的。
- Selector(选择器):用于进行监控多个通道数据状态。
- SelectableChannel(可选择通道):可以被选择器选择的通道,继承了抽象类SelectableChannel的Channel,而FileChannel没有继承此类,所以不可以被选择器选择。
- SelectionKey(选择键):用于表示通道可以被选择的某种就绪事件状态。选择键的事件主要有以下几种:
- Connect, 即连接事件(TCP 连接), 对应于SelectionKey.OP_CONNECT
- Accept, 即确认事件, 对应于SelectionKey.OP_ACCEPT
- Read, 即读事件, 对应于SelectionKey.OP_READ, 表示 buffer 可读.
- Write, 即写事件, 对应于SelectionKey.OP_WRITE, 表示 buffer 可写.
使用
1.创建选择器
Selector selector = Selector.open();
2.获取通道
ServerSocketChannel channel = ServerSocketChannel.open(); // 创建通道
channel.bind(new InetSocketAddress(8080)); // 绑定端口
channel.configureBlocking(false); // 设置为非阻塞,注册到selector上的通道一定设置为非阻塞,否则会报IllegalBlockingModeException错误
3.将通道注册到选择器上
如果一个 Channel 要注册到 Selector 中, 那么这个 Channel 必须是非阻塞的, 即channel.configureBlocking(false);
因为 Channel 必须要是非阻塞的, 因此 FileChannel 是不能够使用选择器的, 因为 FileChannel 都是阻塞的.
channel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE); // 将通道注册到选择器上,监听可接收事件,对于监听多个事件可以用“按位或”来操作
4.轮询已就绪的事件,并对不同的事件进行处理
while (true) {
int count = selector.select(); // 获取已就绪事件的数量
if (count == 0) {
continue;
}
Set<SelectionKey> selectionKeys = selector.selectedKeys(); // 获取已就绪键集
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if(key.isAcceptable()) {
// 接收处理
} else if (key.isConnectable()) {
// 连接处理
} else if (key.isReadable()) {
// 读取处理
} else if (key.isWritable()) {
// 写入处理
}
key.remove(); // 移除键,防止下次重复处理
}
}
// 注意:key.isWritable只要建立连接,就会触发,所以在设置可写入事件时,在写入之后要改回可监听事件,否则就会死循环
注意, 在每次迭代时, 我们都调用 "keyIterator.remove()" 将这个 key 从迭代器中删除, 因为 select() 方法仅仅是简单地将就绪的 IO 操作放到 selectedKeys 集合中, 因此如果我们从 selectedKeys 获取到一个 key, 但是没有将它删除, 那么下一次 select 时, 这个 key 所对应的 IO 事件还在 selectedKeys 中.
5.客户端断开连接时处理,避免读buffer异常导致程序终止
if (key.isReadable()) {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(16);
try {
// 读取buffer内容需要先判断buffer是否正常
// 在客户端主动断开连接的时候, read()方法会返回-1,需要关闭客户端连接
int read = channel.read(buffer);
if (read == -1) {
key.cancel();
channel.close();
} else {
buffer.flip();
debugRead(buffer);
buffer.clear();
}
iter.remove();
} catch (IOException e) {
e.printStackTrace();
key.cancel();
channel.close();
iter.remove();
}
}
源码解析
Selector的创建
创建的语句为Selector selector = Selector.open();
。
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
SelectorProvider.provider()
:
public static SelectorProvider provider() {
synchronized (lock) {
if (provider != null)
return provider;
return AccessController.doPrivileged(
new PrivilegedAction<SelectorProvider>() {
public SelectorProvider run() {
if (loadProviderFromProperty())
return provider;
if (loadProviderAsService())
return provider;
provider = sun.nio.ch.DefaultSelectorProvider.create();
return provider;
}
});
}
}
- 如果配置了“java.nio.channels.spi.SelectorProvider”属性,则通过该属性值load对应的SelectorProvider对象,如果构建失败则抛异常。
- 如果provider类已经安装在了对系统类加载程序可见的jar包中,并且该jar包的源码目录META-INF/services包含有一个java.nio.channels.spi.SelectorProvider提供类配置文件,则取文件中第一个类名进行load以构建对应的SelectorProvider对象,如果构建失败则抛异常。
- 如果上面两种情况都不存在,则返回系统默认的SelectorProvider,即,
sun.nio.ch.DefaultSelectorProvider.create()
; - 随后在调用该方法,即SelectorProvider.provider()。则返回第一次调用的结果。
sun.nio.ch.DefaultSelectorProvider.create()
的返回在每个平台的JDK都不一样,但他们对上层应用都提供统一的Selector接口,这使得Java能够跨平台运行。
windows返回WindowsSelectorProvider
mac返回KQueueSelectorProvider
Linux返回EPollSelectorProvider
参考Java网络编程(七):浅谈 Linux 中 Selector 的实现原理_interestops-CSDN博客
后续看这篇保证你彻底搞懂Java NIO的Selector事件选择器 - 沧海一滴 - 博客园
参考文章
【Netty】「NIO」(三)剖析 Selector-腾讯云开发者社区-腾讯云
Java Nio中Selector是什么?Selector怎么使用呢?_虚类selector是什么-CSDN博客
Java网络编程(七):浅谈 Linux 中 Selector 的实现原理_interestops-CSDN博客