Java Nio核心概念理解

nc localhost port

Selector

以Mac为例,初始化得到的Selector的实例为KQueueSelectorImpl

public abstract class SelectorImpl extends AbstractSelector{// The set of keys with data ready for an operationprotected Set<SelectionKey> selectedKeys;// 注册在 Selector 上的全部channel:客户端 、服务端protected HashSet<SelectionKey> keys;// Public views of the key setsprivate Set<SelectionKey> publicKeys;             // Immutableprivate Set<SelectionKey> publicSelectedKeys;     // Removal allowed, but not additionprotected SelectorImpl(SelectorProvider sp) {super(sp);keys = new HashSet<>();selectedKeys = new HashSet<>();publicKeys = Collections.unmodifiableSet(keys);publicSelectedKeys = Util.ungrowableSet(selectedKeys);}
}

如上伪代码所示:属性只存在 keys & publicKeys 、selectedKeys & publicSelectedKeys 两种类型。SelectionKey接口的唯一实现类为SelectionKeyImpl


ServerSocketChannel 绑定端口& SocketChannel 建立连接 均选择在同一个Selector注册自身感兴趣的事件:最后都会被抽象化为 SelectionKeyImpl 类型【channel & Selector】。

public abstract class SelectorImpl{protected final SelectionKey register(AbstractSelectableChannel ch,int ops,Object attachment){//SelectionKeyImpl 是抽象类SelectionKey的唯一实现类SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this);k.attach(attachment);synchronized (publicKeys) {implRegister(k);}k.interestOps(ops);return k;}
}public class KQueueSelectorImpl extends SelectorImpl{private HashMap<Integer,MapEntry> fdMap;protected void implRegister(SelectionKeyImpl ski) {// 获取当前channel对应的文件描述符int fd = IOUtil.fdVal(ski.channel.getFD());fdMap.put(Integer.valueOf(fd), new MapEntry(ski));totalChannels++;keys.add(ski);} 
}

ServerSocketChannel负责监听全部客户端连接;每个SocketChannel连接建立之后将自身注册至Selector之上。

属性fdMap是一个非常重要的变量,Selector监听到相关IO事件之后会根据文件描述符fd获取对应的SelectionKey,进而得到对应的channel。


selector.select:通常情况下如果SelectionKey注册的众多Channel尚未存在相关IO事件则一直阻塞等待。

class KQueueSelectorImpl extends SelectorImpl{protected int doSelect(long timeout){...begin();//Selector 阻塞之处,entries即代表当前 Selector 上存在IO事件的channel个数int entries = kqueueWrapper.poll(timeout);...return updateSelectedKeys(entries);}private int updateSelectedKeys(int entries){// 该变量是指最终返回的channel个数int numKeysUpdated = 0;boolean interrupted = false;updateCount++;for (int i = 0; i < entries; i++) {int nextFD = kqueueWrapper.getDescriptor(i);if (nextFD == fd0) {interrupted = true;} else {// 根据文件描述符获取与之对应的 SelectionKeyMapEntry me = fdMap.get(Integer.valueOf(nextFD));if (me != null) {// 从 kqueueWrapper 获取当前channel准备好的事件类型,Ready Opsint rOps = kqueueWrapper.getReventOps(i);SelectionKeyImpl ski = me.ski;if (selectedKeys.contains(ski)) {//如果selectedKeys集合没有显式移除,则该条件始终满足if (me.updateCount != updateCount) {// 设置当前channel中 ReadyOps,正常情况下其实就是 InterestOps 的值if (ski.channel.translateAndSetReadyOps(rOps, ski)) {numKeysUpdated++;me.updateCount = updateCount;}} else {ski.channel.translateAndUpdateReadyOps(rOps, ski);}} else {// 设置当前channel中 ReadyOps,正常情况下其实就是 InterestOps 的值ski.channel.translateAndSetReadyOps(rOps, ski);//正常情况下,当前channel的 readyOps ,interestOps 是一致的if ((ski.nioReadyOps() & ski.nioInterestOps()) != 0) {selectedKeys.add(ski);numKeysUpdated++;// 表明当前key对应的channel是符合条件me.updateCount = updateCount;}}}}}...return numKeysUpdated;}
}

正常流程:

首先,ServerSocketChannel 在Selector注册 interestOps = 16事件,当然同时会被Selector的属性 keys & publicKeys 持有。

其次,SocketChannel发起连接后在ServerSocketChannel监听到其连接事件,将interestOps = 16事件类型重置到readyOps = 16,此时表明 ServerSocketChannel 存在真正的Accept事件,并且将ServerSocketChannel对应的SelectionKey 添加至Selector属性之selectedKeys & publicSelectedKeys 中。

然后,在ServerSocketChannel处理Accept事件中获取到客户端SocketChannel,同样在相同Selector上注册SocketChannel的interestOps = 1事件,同时会被Selector的属性 keys & publicKeys 持有,进一步remove即将集合selectedKeys & publicSelectedKeys元素之ServerSocketChannel对应的SelectionKey删除。

最后,客户端发送数据后Selector监听到集合keys & publicKeys众多channel中部分SocketChannel存在IO事件,此时就将SocketChannel之interestOps = 1重置到readyOps = 1,并且将SocketChannel对应的SelectionKey 添加至Selector属性之selectedKeys & publicSelectedKeys 中。

最终,遍历 publicSelectedKeys元素处理 SocketChannel 对应的 readyOps = 1 事件,最后remove即将集合 publicSelectedKeys 元素之SocketChannel对应的SelectionKey删除。


但是存在两个普遍的反馈:SelectionKey集合没有remove的后果 以及 select 始终返回0的问题

结论:背后原因都是因为没有显式remove导致的。

需要知道的是translateAndSetReadyOps方法返回true的条件是:当前channel对应的interestOps & readyOps不一样。进而导致 局部变量 numKeysUpdated 始终为初始值0。

而此时 属性 selectedKeys 中元素SelectionKey对应的channel其 interestOps & readyOps 值是一样的。

截止当前,可能会有人疑问select方法时阻塞的啊,没有事件的前提下select为啥持续返回0呢?

for (;;){int count = selector.select();if (count == 0) {System.out.println("count = 0");continue;}遍历selectedKeys{... 没有显式remove}}

如上处理方式:在处理某个SocketChannel IO事件时,由于translateAndSetReadyOps方法返回false导致无法真正处理其IO事件,所以每次select都能再次接收到该channel的事件类型,导致不断循环处理该channel的无效事件。

for (;;){int count = selector.select();遍历selectedKeys{... 没有显式remove}}

如果是如上处理方式则可以接收ServerSocketChannel & SocketChannel 各类事件,但是 导致遍历selectedKeys集合中所有SelectionKey元素。出现问题包含:

  • 任何一个SocketChannel IO 事件都会触发遍历selectedKeys集合中所有SelectionKey元素,其中就包括ServerSocketChannel的Accept事件,但是此时是无法从ServerSocketChannel中accept客户端SocketChannel。
  • 除了当前SocketChannel,遍历其他SelectionKey对应的SocketChannel时从其缓存区得到的数据为空,但也会自动触发向客户端SocketChannel发送空字符串。

SelectionKey

SelectionKey:A token representing the registration of a SelectableChannel with a Selector。A selection key is created each time a channel is registered with a selector。

A selection key is created each time a channel is registered with a selector。 A key remains valid until it is cancelled by invoking its cancel method, by closing its channel, or by closing its selector。 Cancelling a key does not immediately remove it from its selector; it is instead added to the selector’s ancelled-key set for removal during the next selection operation。The validity of a key may be tested by invoking isValid method。

SelectionKey包含两种operation sets:interest set & ready set。

  • Interest set:兴趣集,表示已注册的事件集合,下一次调用方法,将测试是否有此事件的加入。通过SelectionKey的 int interestOps() 方法,可以获取当前 SelectionKey的感兴趣事件。
  • Ready set:准备集,表示已准备就绪的事件集合。通过SelectionKey的 int readyOps()方法,可以获取当前 SelectionKey的准备就绪事件。

Nio中 SocketChannel 如果设置为非阻塞方式,在其发起connect连接事件后返回的永远为false,必须显式调用finishConnect来判断连接是否成功建立。相反,如果 SocketChannel 设置为阻塞方式,则connect方法会根据连接情况返回true or false。

SocketChannel 执行connect事件之后,此时连接处于pending(待定)状态,只有显式调用finishConnect之后则连接更新为CONNECTED状态。否则出现异常NotYetConnectedException。

客户端关闭【异常、正常】(直接中断idea进程)后服务端同样会监听到该事件,并必须处理该事件,否则会导致服务器端一直不断收到该客户端的OP_READ事件【该事件如果没有处理则一直处理可读状态】,即selector.select()会直接通过,并且是可读状态,但是实际上读到的数据是空的。

服务端处理方式:SelectionKey#cancel or SocketChannel#close。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/41604.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实验3-Spark基础-Spark的安装

文章目录 1. 下载安装 Scala1.1 下载 Scala 安装包1.2 基础环境准备1.3 安装 Scala 2. 下载安装 Spark2.1 下载 Spark 安装包2.2 安装 Spark2.3 配置 Spark2.4 创建配置文件 spark-env.sh 3. pyspark 启动4. 建立/user/spark文件夹 1. 下载安装 Scala 1.1 下载 Scala 安装包 下…

2.5 C#视觉程序开发实例1----IO_Manager实现切换程序

2.5 C#视觉程序开发实例1----IO_Manager实现切换程序 1 IO_Manager中输入实现 1.0 IO_Manager中输入部分引脚定义 // 设定index 目的是为了今后可以配置这些参数、 // 输入引脚定义 private int index_trig0 0; // trig index private int index_cst 7; //cst index priva…

构建滑块组件_第 1 部分

前言 ● 本次将和大家一起学习实现滑块的功能 ● 由于这有些错乱&#xff0c;我们将用图片来代替&#xff0c;以实现功能 ● 这里我们简单的说一下原理&#xff0c;如下图所示&#xff0c;通过改变tanslateX的值来达到滑动的效果&#xff0c;所以最核心的就是我们需要通过…

Yarn Plug‘n‘Play:现代化JavaScript依赖管理的革命

标题&#xff1a;Yarn Plug’n’Play&#xff1a;现代化JavaScript依赖管理的革命 Yarn的Plug’n’Play&#xff08;简称PnP&#xff09;模式是一种创新的依赖管理技术&#xff0c;旨在提高JavaScript项目的性能和可靠性。PnP模式通过重新思考依赖安装和解析的方式&#xff0c…

FreeBSD@ThinkPad x250因电池耗尽关机后无法启动的问题存档

好几次碰到电池耗尽FreeBSD关机&#xff0c;再启动&#xff0c;网络通了之后到了该出Xwindows窗体的时候&#xff0c;屏幕灭掉&#xff0c;网络不通&#xff0c;只有风扇在响&#xff0c;启动失败。关键是长按开关键后再次开机&#xff0c;还是启动失败。 偶尔有时候重启到单人…

如何使用深度学习进行实时目标检测:速度与精度的双重挑战

如何使用深度学习进行实时目标检测&#xff1a;速度与精度的双重挑战 目标检测作为计算机视觉领域的核心任务之一&#xff0c;其目的是在图像或视频中识别和定位感兴趣的对象。随着深度学习技术的发展&#xff0c;基于深度学习的目标检测算法在实时性、准确性方面取得了显著进…

dolphinscheduler-搭建本地环境

后端搭建开发环境 一. 基础插件 maven&#xff08;3.9.7&#xff09; maven必须升级到3.9.x版本&#xff0c;不然打包会异常jdk&#xff08;1.8&#xff09;zookeeper&#xff08;3.8.4&#xff09;mysql或者pg&#xff08;使用mysql&#xff09; 二. 代码修改点 链接&…

Swagger的原理及应用详解(八)

本系列文章简介&#xff1a; 在当今快速发展的软件开发领域&#xff0c;特别是随着微服务架构和前后端分离开发模式的普及&#xff0c;API&#xff08;Application Programming Interface&#xff0c;应用程序编程接口&#xff09;的设计与管理变得愈发重要。一个清晰、准确且易…

NLP篇1

场景&#xff1a;假设给你一篇文章。 目标&#xff1a;说白了&#xff0c;就是数学的分类。但是如何实现分类呢。下面将逐步一 一 分析与拆解。先把目标定好了和整体框架定好了。而不是只见树木而不见森林。 情感分类&#xff08;好评、差评&#xff0c;中性&#xff09; 整体…

掌握 Postman 脚本:入门指南

在探索 API 测试自动化环墁下&#xff0c;Postman 脚本显现其强大功能和灵活性&#xff0c;它不仅仅是 API 测试的工具&#xff0c;更是一个综合性的自动化平台。 Postman 脚本简介 Postman 允许用户在 API 请求生命周期中运行 JavaScript 脚本&#xff0c;这些脚本分为以下三…

Java:Java发展史

Java发展史 Java&#xff0c;作为一门广泛使用的编程语言&#xff0c;自其诞生以来&#xff0c;已经走过了数十年的发展历程&#xff0c;并对全球的软件开发领域产生了深远的影响。Java的发展史可以大致分为以下几个阶段&#xff1a; 起源与诞生&#xff08;1991-1995年&#…

【C++题解】1413. 切割绳子

问题&#xff1a;1413. 切割绳子 类型&#xff1a;贪心&#xff0c;二分&#xff0c;noip2017普及组初赛 题目描述&#xff1a; 有 n 条绳子&#xff0c;每条绳子的长度已知且均为正整数。绳子可以以任意正整数长度切割&#xff0c;但不可以连接。现在要从这些绳子中切割出 m…

C++11|列表初始化 声明

目录 一、C11简介 二、列表初始化 2.1{}初始化 2.2std::initializer_list 2.2.1原理 2.2.2使用场景 三、声明 3.1auto && typeid().name() 3.2decltype 一、C11简介 小故事&#xff1a; 1998年是C标准委员会成立的第一年&#xff0c;本来计划以后每5年实际需…

React面试题之setState的执行机制

setState 是 React 中用于更新组件状态的方法。 1. setState 的基本用法 在 React 类组件中&#xff0c;你可以使用 setState 来更新组件的状态。setState 接受一个对象或一个返回对象的函数作为参数 2. 合并状态更新 当调用 setState 时&#xff0c;React 不会立即更新 th…

Java三个线程轮流打印20次

如何实现Java多线程交替打印20次数据&#xff1f; 使用synchronized共享信号量 解题思路&#xff0c;synchronized拿到锁&#xff0c;检查线程是否要执行业务代码&#xff0c;如果是&#xff0c;打印&#xff0c;并更改共享信号量&#xff0c;如果不是&#xff0c;wait交出锁…

AndroidKille不能用?更新apktool插件-cnblog

AndroidKiller不更新插件容易报错 找到apktool管理器 填入apktool位置&#xff0c;并输入apktool名字 选择默认的apktool版本 x掉&#xff0c;退出重启 可以看到反编译完成了

巨人的数量

题目 巨人是像泰坦那么大的人形生物&#xff0c;并且对人类虎视眈眈。人类筑起三堵圆形高墙&#xff0c;里外三层用来抵御巨人的侵袭。调查兵团是一只大约十五人的队伍&#xff0c;负责高墙外围的警戒&#xff0c;一旦发现来犯的巨人&#xff0c;必须立刻向上级汇报。上级会对…

Shell echo命令

Shell echo命令 在Shell编程中,echo命令是一个常用的内置命令,用于在终端或控制台上显示文本或变量的值。它是与用户交互的一种基本方式,经常用于输出信息、创建文件内容或与脚本的其他部分进行通信。本文将详细介绍echo命令的用法、选项和实际应用示例。 基本用法 echo命…

JavaDS预备知识

集合框架 Java 集合框架 Java Collection Framework &#xff0c;又被称为容器 container &#xff0c;是定义在 java.util 包下的一组接口 interfaces和其实现类 classes 。 其主要表现为将多个元素 element 置于一个单元中&#xff0c;对数据进行创建(Create)、读取(Retrieve…

振动分析-12-轴承数据库之深度学习一维故障分类CNN-Transformer

Python轴承故障诊断 (15)基于CNN-Transformer的一维故障信号识别模型 1 制作数据集 import pandas as pd filename = "CWRU_1797.csv" df = pd.read_csv(filename)from sklearn.model_selection import train_test_split df_x=df.drop(labels=1024,axis=1)