当前位置: 首页 > web >正文

jdk1.8 nio相关。java对象和epoll三大函数怎么关联的?(有点乱有点跳)

jdk nio

参考视频 参考demo代码

【【Netty精讲】NIO Epoll源码剖析】https://www.bilibili.com/video/BV1cJT9zREb2?vd_source=0b17a38779c085925c505c90e3b719aa

参考版本java8

demo代码

import java.net.InetSocketAddress;

import java.nio.ByteBuffer;

import java.nio.channels.*;

import java.util.Iterator;

public class NioDemo {

public static void main(String[] args) throws Exception {

// 创建一个服务端通道 ServerSocketChannel,用于监听客户端连接

ServerSocketChannel ssc = ServerSocketChannel.open();

// 将通道设置为非阻塞模式(Selector 机制要求所有通道必须是非阻塞的)

ssc.configureBlocking(false);

// 创建一个 Selector(多路复用器),底层封装 epoll/kqueue 等机制

Selector selector = Selector.open();

// 将服务端通道注册到 Selector 上,关注接收连接事件(OP_ACCEPT

ssc.register(selector, SelectionKey.OP_ACCEPT);

// 绑定端口 8080,开始监听客户端连接

ssc.bind(new InetSocketAddress(8080));

// 主事件循环,持续监听并处理就绪事件

while (true) {

// 阻塞直到至少一个事件就绪(或被 wakeup 唤醒)

selector.select();

// 获取所有就绪的事件 key”集合(本轮 select 中准备好的事件)

Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

// 遍历处理所有就绪事件

while (iterator.hasNext()) {

SelectionKey selectionKey = iterator.next();

iterator.remove(); // 一定要移除,防止下一次重复处理

// 如果是接收连接事件

if (selectionKey.isAcceptable()) {

// SelectionKey 中取出服务端通道

ServerSocketChannel serverChannel = (ServerSocketChannel) selectionKey.channel();

// 接受客户端连接,得到一个新的 SocketChannel

SocketChannel clientChannel = serverChannel.accept();

// 设置新连接为非阻塞模式

clientChannel.configureBlocking(false);

// 将客户端通道注册到 Selector 上,关注事件(OP_READ

clientChannel.register(selector, SelectionKey.OP_READ);

}

// 如果是可读事件,说明客户端发送了数据

if (selectionKey.isReadable()) {

// SelectionKey 中取出客户端通道

SocketChannel clientChannel = (SocketChannel) selectionKey.channel();

// 创建一个 ByteBuffer,用于读取客户端发送的数据

ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

// 从通道中读取数据到缓冲区

clientChannel.read(byteBuffer);

// ⚠️注意:这里原始逻辑没有处理读到的内容,比如打印或解码

// 如果你想查看消息内容,可在此加上:

// byteBuffer.flip();

// System.out.println(new String(byteBuffer.array(), 0, byteBuffer.limit()));

}

}

}

}

}

java 中是如何模拟文件描述符以及模拟socket的?

jdk Nio中的channel 和selctor

关注这个方法先。

ServerSocketChannel ssc = ServerSocketChannel.open();

我们可以看到channel的声明方式不是直接New出来的。而是一种类似于调用工厂实现 的。

SelectorProvider是一个抽象类关键是这个

我们可以看到provider是个抽象方法。这个方法返回一个对象。然后对用这个对象的open方法。其中关键的是这句

provider = sun.nio.ch.DefaultSelectorProvider.create();

追进去是一个很明显是一个基于操作系统区分的类。我们先留意下关注这个类的继承

所以这段方法是一段高级的工厂模式加懒汉式加载加支持返回一个子类实例加载的写法......

然后调用的这个对象是实现 这个SelectorProvider相关的类

我们可以看到实现这个抽象类的两个类是两个mpl 还有一个带有明显的windowsselectorProvider相关的类

这个类是继承自 mpl类的

先来看

mpl类的这个方法

追进去叫做ServerSocketChannelImpl

我们可以看到在创建的时候在里会有一个外部的调用去获得fd 和一些相关的信息

追踪这个fd相关的方法?

我们可以发现这个fd的生成是在自于jdk中的io包中的。

最终跟踪到了

上图即整个jdk相关的文件描述类

我们追入一程可以看到是当前的selctroprovidew的provider相关的方法只返回的。

我们先来研究什么是channel?

先来看我们的demo源码把channel追踪到顶点之后就是一个接口。我们可以到最原始的的channel只包含两个功能就是打开和抛出异常?

我们关注之前的

其中这个传入的参数是来自于 一个nat调用

也就是说我们的channel基于socket 的一个一对一的文件描述符对应的实体。

我们思考一个事情?仅仅只有jdk 可以实现nio 吗?

还记得之前我们研究的liunx。也就是文件如同流?

java中应该如何理解这一点。

selector比较长而且理解起来比较复杂。我们先关注它继承的这一个接口

我们看这个接口的描述是一个和流绑定相关的类?

(tip, 我们讨论流的时候不要狭隘的认为是一种传统的字节流。而是要结合liunx源码中流与文件描述符互相支持实现的思考)

回到我们的demo代码

关注这个代码

追入一层

这个openSelector有印象吗?

也就是我们的window相关的类

可以看到selector是要基于不同的操作系统的。而从socket分配或者是获取到fd则是可以跨系统的?

本质上是因为流才是一个操作系统产生差距的地方?

然后我们区分一下有个叫做一个叫做EPollSelectorProvider相关的类open的。

然后我们回忆下有个类有个父类叫做SelectorProvider提供channel的open的。

然后这些provider各自又有channel作为属性 或者seletor属性这些属性各自基于不同操作系统实现

注册逻辑

回到我们的demo代码

关注

ssc.register(selector, SelectionKey.OP_ACCEPT);

首先注意到我们是调用ssc也就是我们的channel的注册方法。

然后我们追入一层

同样是个抽象方法

我们关注它的几个传参和它这一个返回的对象 。

这段代码是 Java NIO 中 SelectableChannel 的核心方法之一,用于将当前的通道(Channel)注册到某个 Selector 上,并指定它感兴趣的 I/O 事件(如读、写、连接等)。

传参解释

  • sel: 目标 Selector。
  • ops: 感兴趣的事件(如 SelectionKey.OP_READ)。
  • att: 附加对象,可以绑定用户定义的上下文信息(比如 Socket 对象、请求 ID 等)。

我们来精读这段代码首先这是段加锁的代码

然后几个If的流程是判断channel有没有打开,传入的事件是否合理。以及blocking: 如果是阻塞模式,NIO 不允许注册,需要改为非阻塞后才能注册 Selector。

然后基于传入的selector查找是否存在并尝试获取到已有 SelectionKey

如果没有旧的 Key,就新注册一个

然后返回一个SelectionKey对象。

也就是SelectionKey获取方式我们判断SelectionKey 是基于selector对象的。我们来研究下key和selector的区别?

首先这是一个抽象类

然后里面包含了selector和channel两个类

还实现了一些事件的说明以及一些钩子类判断函数

我们来看看它的注释

这第一句很重要

这个类表示使用选择器注册SelectableChannel的token(令牌)

然后还有

一个key里面包含两个表示为整数值的操作集。操作集的每一位都表示该channel支持的可选操作的类别。

关注这两段话

key的就绪集表明它的channel已经为某些操作类别做好了准备,这是一个提示,但不是保证,这样一个类别中的操作可以由线程执行而不会导致线程阻塞。在选择操作完成后,一个现成的集合最有可能是准确的。外部事件和在相应channel上调用的I/O操作可能会使它变得不准确。

该类定义了所有已知的操作集位,但是给定通道支持哪些位取决于通道的类型。SelectableChannel的每个子类都定义了一个validOps()方法,该方法返回一组标识通道支持的操作的集合。试图设置或测试密钥通道不支持的操作集位将导致适当的运行时异常。

我们来问个问题为什么这里的标识并不可靠。那为什么key还需要定义操作集呢?

这是因为 I/O 是一个 高度并发、系统级别不确定性很强的过程

  • 你在 Selector.select() 后看到 OP_READ,是因为内核告诉你:这时 socket 上有数据。
  • 你还没 read() 的时候,别的线程可能已经把数据读光了
  • 或者,对端把连接关了,这时候 read() 也可能返回 -1。
  • 又或者你一看 OP_WRITE 为真,但写入数据时发现 buffer 满了,还是得阻塞。

换句话说:

由于并发访问 + 操作瞬变性 + 内核不可控事件,NIO 不能保证 readyOps 是永久准确的。

虽然 ready set 不保证“操作一定不会阻塞”,但它仍然极大提高了性能与控制力

readyOps 是“hint”,但:

  • 操作集本身是用户和内核/底层通信的协议桥梁
  • 即便是 hint,它也是反映了内核在你 select 时刻返回的 I/O 状态。
  • 它可以极大减少你盲目轮询或阻塞的开销

回到代码

我们发现key本身是又是在selectionKeympl中实现的。

所以本质上来说key本质是从selector获取也就是说在selector的层实现的一个运行时关联?

所以整个注册过程就是依赖复用key对象或者说是创建key对象

我们再关注注册逻辑中的这一句调用时。我们发现本质是通过传入的selectror使用它的注册方法

所以本质上注册基于selector注册

观察实现

重点一句调用key实现这里调用一个new逻辑find找不到复用 key对象selector一个新建key我们可以看到key新建需要借助key channel 更加说明key channel selectro关联桥梁

回调通知机制

观察demo代码的

我们追一层是是一个抽象方法

在selectormpl实现 注意返回Int类型

大概是一个等待超时抛出异常的分支。

我们关注这个方法

一个状态校验之后 重点是这个方法。

这又是一个抽象类

注意这里是mpl和上文的windowmpl是一个层级和windowprovider相关的类是不相属的。

在对应的操作系统类实现 。我们把jdk切成liunx版本的

直接看epollmpl的

有点复杂我们精读下先判断这个是否合法状态

this.processDeregisterQueue();

如果是合法状态执行这一句。这一句是什么意思?

在selectormpl实现可以理解为属selector层的行为

这段代码中有一句

Set var1 = this.cancelledKeys();

我们再来看看这一句是什么?

这是一个selectorKey 的set(集合也就是说(取消的)key和selector是可以多对一多对一

然后后面的逻辑大概是从这个set中取一个key然后走

这个调用

同类的抽象方法在epollmpl实现

大致是一系列递归的移除注册的逻辑

回到

begin() 通过注册 interruptor 钩子,使得当前线程在阻塞期间可以被其他线程中断,从而调用 wakeup() 打破 select() 的阻塞。

然后这句调用很关键

this.pollWrapper.poll(var1);

追进一层有个epollwait

epollwait追进去就是native了系统调用

然后回到

int var3 = this.updateSelectedKeys();

有句这个调用我百度了一下是将wait返回的fd转换为key

而在系统调用前后

调用两次 processDeregisterQueue() 是为了确保:

在执行 epoll_wait 之前和之后,都清除掉所有被取消的 SelectionKey

我们怀疑其他系统的调用的时机是什么样的?

epollctl

我们通过检索发现ctl是在这里调用的

还有

针对

initInterrupt

针对方法我们发现这个方法的上层。居然是在构造器实现的。也就是说selector的创建的时候就已经在操作系统层面预注册了?

但是我们关注它的这个构造器传参这是一个provider这个眼熟吗?这个类又是什么时候被初始化的呢?

则是要在这个初始化的去做但是我们发现这条链路传参没有关联信息也就是这是一个仅仅一个初始化epollctl和我目的不符合

针对updateRegistrations

我们发现我们发现epollwait时候也就是这段代码

调用我们目标方法所以ctlwait方法同一个方法中处理

而且我们业务相关ctl真正发生节点selector阻塞方法做到

那么我们保留一个问题就是为什么ctl这里实现还有就是

我们可以看到ctl参数都是一些类似事件方法数组进行获取无参输入

我们不禁要问的是支撑ctl完成业务的数据结构到底是什么样的?

先来这个var3

这是一个64数组

然后var2是一个

一个角标

var4一个比较复杂方法

大致某种运算返回一个byte

var5一个标志位我们暂时关注我们关注这个数组这个计数

除了当前方法这个数据结构还有做出操作就是

这个方法

这个方法mpl唯一引用就是这个Puteventops之类方法

这个方法引用地方则是

doselector 方法

我们关注传参

this.pollWrapper.interruptedIndex()

方法

返回一个角标

传入这个角标和0之后

进入这个函数(warpper)

一个运算然后传入一个putint方法

其中这个对象

我们才知道0 1 其实标志位

往下一层

往下一层

navite方法

我们研究这个对象两个出口

第二个出口我们发现一个老熟人

至此以及闭环我们反思一下流程大概什么

我们研究什么数据结构支撑ctl然后我们定位到一个标志位一个数组

然后我们盘问这个数据结构哪里引用除了方法还有一个 putevent方法这个方法调用则是do selector然后这个方法但是do selector方法调用worpper方法传入两个数字其中一个角标一个标志位然后通过navite之类方法一个adress子类数据然后这个数据又被我们poll方法调用

我们直到poll方法selecotr关联

我们可以明确我们跟踪 putevent 时候走错但是我们仍然能够挖掘价值东西

驱动整个eventops 分支

if (this.pollWrapper.interrupted()) {

this.pollWrapper.putEventOps(this.pollWrapper.interruptedIndex(), 0);

synchronized(this.interruptLock) {

this.pollWrapper.clearInterrupted();

IOUtil.drain(this.fd0);

this.interruptTriggered = false;

}

}

一个中断回顾整个上下游代码

我们其实不难发现eventpoll真实poll之后触发

这个方法作用也是某种更新上下机制

然后这个方法影响put这个数据结构影响poll方法这里 分支

也就是我们可以推断这个数据结构本质调用在于中断时候保存上下文下一次重入poll时候通过这个for

for(int var3 = 0; var3 < this.updated; ++var3) {

if (this.getDescriptor(var3) == this.incomingInterruptFD) {

this.interruptedIndex = var3;

this.interrupted = true;

break;

}

}

对齐我们index 我们upted标志位

然后回顾全局想要解决问题又有两个分支一个研究selectorputevent调用时机一个则是研究这个维护index影响那些东西

终于找到slector这个方法调用

然后这个方法上游

然后上游

这个

至此终于闭环这个数据结构注册时候写入维护一个数据结构

epollcreate

epollcreate构造器引用

在selector实现

然后

再往

就是我们open方法......

http://www.xdnf.cn/news/15390.html

相关文章:

  • Redis技术笔记-从三大缓存问题到高可用集群落地实战
  • 【计算机网络架构】环型架构简介
  • 【保姆级图文详解】Spring AI 中的工具调用原理解析,工具开发:文件操作、联网搜索、网页抓取、资源下载、PDF生成、工具集中注册
  • DETRs与协同混合作业训练之CO-DETR论文阅读
  • spring--@Autowired
  • Wireshark的安装和基本使用
  • 第七章 算法题
  • Docker从环境配置到应用上云的极简路径
  • 【micro:bit】从入门到放弃(一):在线、离线版本的使用
  • 第三章-提示词-探秘大语言基础模型:认知、分类与前沿洞察(9/36)
  • C++:宏
  • 从零开始学习深度学习-水果分类之PyQt5App
  • LLaMA-Factory的webui快速入门
  • NLP-迁移学习
  • 海豚远程控制APP:随时随地,轻松掌控手机
  • [Rust 基础课程]选一个合适的 Rust 编辑器
  • Vue 3 动态ref问题
  • 如何将FPGA设计的验证效率提升1000倍以上(4)
  • MailSpring
  • python excel处理
  • python-enumrate函数
  • 字母异位词分组
  • Linux驱动09 --- 环境搭建
  • 计算机毕业设计Java停车场管理系统 基于Java的智能停车场管理系统开发 Java语言实现的停车场综合管理平台
  • 如何检测自动化设备中的直线导轨品质是否优良?
  • UE5多人MOBA+GAS 19、创建升龙技能,以及带力的被动,为升龙技能添加冷却和消耗
  • 【408考研知识点全面讲解计算机学科专业基础综合(408)】——数据结构之排序
  • SELECT ... INTO OUTFILE和LOAD DATA INFILE
  • 请求服务端获取broker的机房归属信息异常
  • 【C#】GraphicsPath的用法