「我正在参与掘金会员专属活动-源码共读第一期,点击参与」
前言
工欲善其事必先利其器, 讲了好几篇文章的Netty
相关, 今天讲一下原始的 Java NIO 的Selector
选择器, 本篇将从基本介绍到API相关全部介绍一遍
Netty
往期文章:
- Netty源码分析(一) backlog 参数 - 掘金 (juejin.cn)
- Netty服务端初始化详解 - 掘金 (juejin.cn)
- Netty服务端启动流程分析 - 掘金 (juejin.cn)
- Netty之第一次 TCP 连接时发生了什么 - 掘金 (juejin.cn)
- Netty之服务启动且注册成功之后 - 掘金 (juejin.cn)
- Netty之服务端channel的初始化 - 掘金 (juejin.cn)
- Netty「源码阅读」之 EventLoop 简单介绍到源码分析 - 掘金 (juejin.cn)
- Netty「源码阅读」之怎么解决 Java 的 epoll 空轮询 bug - 掘金 (juejin.cn)
- 什么是零拷贝, 从 Java 到 Netty - 掘金 (juejin.cn)
- ByteBuf 和 ByteBuffer 的区别, ByteBuf 动态扩容源码分析 - 掘金 (juejin.cn)
基本介绍
Java
的NIO
使用非阻塞的 IO 方式, 可以使用一个线程去处理多个客户端连接, 这个时候就会使用到Selector
选择器
Selector
可以检测到多个注册的通道上是否有事件发生(多个Channel
以事件的方式可以注册到一个Selector
上). 如果有事件发生, 就去获取事件然后针对每一个事件进行相应的处理, 这样就实现了只用一个线程去管理多个通道
通过Selector
使得只有在连接真正有读写事件发生的时候, 才会进行读写, 大大减少了系统开销
在
Netty
中的 IO线程NioEventLoop
聚合了Selector
, 可以同时并发的处理成百上千个客户端的连接
API
可以看到Selector
是一个抽象类, 如图所示, 是Selector
的所有方法, 接下来我们就讲解一下Selector
的常用方法
open():
得到一个选择器对象select():
无超时时间的select
过程, 一直等待, 直到发现有CHannel
可以进行 IO操作select(long timeout):
监控所有注册的Channel
, 当其中的CHannel
有 IO操作 可以进行时, 将这些Channel
对应的SelectionKey
找到, 参数用户设置超时时间wakup():
唤醒selector
selectNow():
不阻塞, 立即返回selectKeys():
返回所有发生事件的Channel
对应的SelectionKey
的集合, 通过SelectionKey
可以找到对应的Channel
keys():
返回所有CHannel
对应的SelectionKey
的集合, 通过SelectionKey
能找到对应的CHannel
NIO 中存在
ServerSocketChannel
功能类似于ServerSocket
,SocketCahnnel
类似于Select
客户端发起连接时服务端工作流程
示例代码
public static void main(String[] args) throws IOException {
// 异常向上抛出
ServerSocketChannel server= ServerSocketChannel.open();
// 获取一个选择器对象
Selector selector = Selector.open();
// 注册 serverSocketChannel 到 selector, 关注 OP_ACCEPT 事件
server.register(selector, SelectionKey.OP_ACCEPT);
// 绑定端口
server.socket().bind(new InetSocketAddress(8888));
// 设置 serverSocketChannel 为非阻塞模式
server.configureBlocking(false);
for (;;){
// 没有事件发生
if (selector.select(1000) == 0){
continue;
}
// 有事件发生, 找到发生事件的 Channel 对应的 SelectionKey 的集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
// 遍历
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while(iterator.hasNext()){
final SelectionKey next = iterator.next();
// 发生 OP_ACCEPT 事件, 处理连接请求
if (next.isAcceptable()){
final SocketChannel accept = server.accept();
// 将 socketChannel 注册到 selector, 关注 OP_READ 事件, 并给 socketChannel 关联 Buffer
accept.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(2048));
}
// 如果是发生读事件, 客户端读取数据
else if (next.isReadable()){
final SocketChannel channel = (SocketChannel) next.channel();
final ByteBuffer buffer =(ByteBuffer) next.attachment();
channel.read(buffer);
}
// 手动从集合中移除当前的 selectionKey, 防止重复处理事件
iterator.remove();
}
}
}
在上述服务端示例代码中, 服务端的工作流程为:
- 当客户端发起连接时, 会通过
ServerSocketChannel
创建对应的SocketChannel
- 调用
SelectChannel
的注册方法将其注册到Selector
上, 注册方法会返回一个SelectionKey
, 将该SelectionKey
放入到SelectionKeys
集合中, 此时,SelectKey
和Selector
关联, 也和SocketChannel
关联 Selector
调用select(), select(timeout), selectNow()
方法对内部的SelectionKeys
中的SelectionKey
所对应的SocketChannel
进行监听- 通过
SelectionKey
找到有事件发生的SocketChannel
, 完成数据处理
本文内容到此结束了
如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。
如有错误❌疑问💬欢迎各位大佬指出。
我是 宁轩 , 我们下次再见