根据实例逐行分析NIO到底在做什么

Selector(选择器)是 Channel 的多路复用器,它可以同时监控多个 Channel 的 IO 状况,允许单个线程来操作多个 ChannelChannel在从Buffer中获取数据。

选择器、通道、缓冲池是NIO的核心组件。

一、新建选择器


此时选择器内只包含这一条负责监听连接请求的通道

二、减少阻塞之事件触发送到嘴边

一、不用阻塞等待建立连接

答:最最关键的一步是选择器的存在,同时下图第一个红框,ServerSocketChannel属性设置为非阻塞也有一定作用

选择器监听通道,所监视的正是通道中的事件,key就代表通道中出现的事件,在循环开始后,选择器调用select() 方法监控选择器中通道状态

有 3 种方式可以 select 就绪事件:

1)select() 阻塞方法,只要出现一个就绪事件就会返回。没有则一直保持阻塞状态。

2)select(long timeout) 阻塞方法,有一个就绪事件,或者其它线程调用了 wakeup(),或者当前线程被中断,或者阻塞时长达到了 timeout 时返回。不抛出超时异常。

3)selectNode() 不阻塞,如果无就绪事件,则返回 0;如果有就绪事件,则将就绪事件放到一个集合,返回就绪事件的数量。

那么select方法实现了什么? 这个方法实现了选择器中的通道只有 出现一批就绪事件才会主动去处理,若没有就绪事件就等着啥也不干。

这个是NIO选择器的优势:来活了才干,不来活就等着,没活干了也不占坑死等(存在就绪事件的通道才会占用资源,减少功耗)

当第一次开始循环,选择器中只有一个ServerSocketChannel通道,也就是说这时只能进行客户端到服务端的连接,上图中红框key.channel()就是获得当前事件所在的ServerSocketChannel,

调用accept()方法就相当于:如果没有连接阻塞,成功等来了连接请求后,进行三次握手建立连接,所以这就是

NIO优势:通过选择器可以无需阻塞等待请求到来,因为只有选择器检测到了通道中出现连接请求(ServerSocketChannel)或者传输数据(SocketChannel)才会使用通道进行建立连接(ssChannel1.accept())或者读取数据(sChannel.read(buffer))无需等待无需等待!!!

二、不用阻塞等待数据

上边这个是优势中的无需等待建立连接,那么无需等待请求数据在哪实现的呢?

答:根据ServerSocketChannel建立ServerSocket后,将属性设置为非阻塞

答案在上图, 根据ServerSocket通道中的连接请求,建立出的新连接SocketChannel,属性Blocking设为False, 表明这是一个无需等待的非阻塞数据传输通道

我们后续使用的所有数据传输通道SocketChannel 都是基于这行代码创建出来的。

优势在哪呢, 就是如果是阻塞通道,那么假设已经开始读数据,如果一天之后才发数据下面这条语句就要等待一天直到获取完全部数据。

而因为是非阻塞,所以要是没数据了直接断开就是。

当然这一切都要在最外围的死循环中执行。

三、哪里不能减少阻塞

图中有三个地方,实际可以归结于两个地方。

Selector 作为多路复用 I/O 模型的核心组件,能够同时监控多路 I/O 通道。选择器在 select()方法等待就绪事件地时候会阻塞,在处理 I/O 事件的时候也会阻塞,它的优势在于在阻塞的时候可以等待多路 I/O 就绪,是一种异步阻塞 I/O 模型。与多线程处理多路 I/O 相比,多路复用模型只需要单个线程即可处理万级连接,没有线程切换的开销。

四、用图直观描述

**************************这个图服务端最上边前四个应该是ServerSocket****************************

最开始的时候,服务端选择器开始监听(监听各通道中是否有就绪事件),目前只有一个ServerSocketChannel通道,这个通道也在监听(监听连接请求), 这个通道就在listen这个地方一直等着。 

之后客户端根据IP +  端口像服务端发送连接请求,嗯服务端的通道获得了这个就绪事件(accept事件),选择器也轮询查到了,直接开始处理这个通道,也就是处理这个就绪事件——执行accept()方法,要经过三次握手建立TCP连接。

accept()方法建立完成之后要返回一个SocketChannel通道,也就是从RecV()开始就是SocketChannel再执行了。

这两个SocketChannel就像TCP连接的两个抽象端口,中间有一条看不见的线,我们用这两个套接字通道就可以当成TCP连接对外提供的API,直接用就好,毕竟TCP很复杂,官方提供了一个封装好的API。

1)SelectionKey.OP_ACCEPT 表示 accept 事件就绪。例如:对于 ServerSocketChannel 来说,该事件就绪表示可以调用 accept() 方法来获得与客户端连接的通道 SocketChannel。

2)SelectionKey.OP_CONNECT 表示客户端与服务端连接成功。

3)SelectionKey.OP_READ 表示通道中已经有了可读数据,可以调用 read() 方法从通道中读取数据。

4)SelectionKey.OP_WRITE 表示写事件就绪,可以调用 write() 方法往通道中写入数据。

五、大佬文章

Java NIO - 基础详解 | Java 全栈知识体系

https://www.cnblogs.com/robothy/p/14242971.html

【死磕NIO】— 探索 SocketChannel 的核心原理-CSDN博客

Java NIO 中的 Channel 详解 - 掘金

Java NIO浅析 - 美团技术团队

六、完整实例代码

NIO服务端

public class NIOServer {public static void main(String[] args) throws IOException {Selector selector = Selector.open();ServerSocketChannel ssChannel = ServerSocketChannel.open();ssChannel.configureBlocking(false);ssChannel.register(selector, SelectionKey.OP_ACCEPT);ServerSocket serverSocket = ssChannel.socket();InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888);serverSocket.bind(address);while (true) {selector.select();Set<SelectionKey> keys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = keys.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();if (key.isAcceptable()) {ServerSocketChannel ssChannel1 = (ServerSocketChannel) key.channel();// 服务器会为每个新连接创建一个 SocketChannelSocketChannel sChannel = ssChannel1.accept();sChannel.configureBlocking(false);// 这个新连接主要用于从客户端读取数据sChannel.register(selector, SelectionKey.OP_READ);} else if (key.isReadable()) {SocketChannel sChannel = (SocketChannel) key.channel();System.out.println(readDataFromSocketChannel(sChannel));sChannel.close();}keyIterator.remove();}}}private static String readDataFromSocketChannel(SocketChannel sChannel) throws IOException {ByteBuffer buffer = ByteBuffer.allocate(1024);StringBuilder data = new StringBuilder();while (true) {buffer.clear();int n = sChannel.read(buffer);if (n == -1) {break;}buffer.flip();int limit = buffer.limit();char[] dst = new char[limit];for (int i = 0; i < limit; i++) {dst[i] = (char) buffer.get(i);}data.append(dst);buffer.clear();}return data.toString();}
}

NIO客户端 

public class NIOClient {public static void main(String[] args) throws IOException {Socket socket = new Socket("127.0.0.1", 8888);OutputStream out = socket.getOutputStream();String s = "hello world";out.write(s.getBytes());out.close();}
}

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

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

相关文章

Nginx-记

Nginx是一个高性能的web服务器和反向代理服务器&#xff0c;用于HTTP、HTTPS、SMTP、POP3和IMAP协议。因它的稳定性、丰富的功能集、示例配置文件和低系统资源的消耗而闻名。 &#xff08;1&#xff09;更快 这表现在两个方面&#xff1a;一方面&#xff0c;在正常情况下&…

【OpenGL】使用 python + Qt + OpenGL 的现代渲染

伴随资源 目录 一、说明二、 关于PyQt6.x2.1 QOpenGLWidget详细说明2.2 绘画技巧 三、PyOpenGL四、OpenGL 管线五、Python集成开发环境5.1 Emacs配置5.2 pycharm环境 六、你好&#xff0c;OpenGL&#xff01;七、QGL控件八、平截头体.svg九、定义几何9.1 立即模式与保留模式9…

awk使用场景案例

1、从nginx访问日志统计访问top10的IP awk {a[$1]}END{for (i in a)print i,a[i] } access.log |sort -k2 -nr|head -10解释说明&#xff1a; awk {a[$1]} 用a数列获取第一个数据&#xff0c;表示统计END{for (i in a)print i,a[i] } END用来写循环语句&#xff0c;用for循环…

OpenAI发布Voice Engine模型!用AI合成你的声音!

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;所以创建了“AI信息Gap”这个公众号&#xff0c;专注于分享AI全维度知识…

2024年150道高频Java面试题(八)

15. 什么是 Java 中的注解&#xff1f;它有哪些用途&#xff1f; Java 中的注解&#xff08;Annotation&#xff09;是一种用于为代码添加元数据的机制。简而言之&#xff0c;注解是提供给编译器或运行时环境的信息&#xff0c;用来解释或说明代码的某些方面。注解不会改变代码…

比torchvision更强大,从timm库引用预训练模型和本地加载的方法

1&#xff0c;介绍 torchvision是大家最常用的预训练模型来源&#xff0c;但是其包含的预训练模型种类很少&#xff0c;往往并不能满足研究者们的需求。 而timm库提供了一个更强大的替代选项。 利用如下代码查询 import timmprint(len(timm.list_models())) 输出 1032 可…

c++编译出来的程序a,当在linux 系统终端执行./a 发生了哪些事情?

路径解析&#xff1a; 用户输入 ./a 并按下回车。终端中的 shell&#xff08;如 bash、zsh 等&#xff09;开始解析这个命令&#xff0c;确认 a 是一个可执行文件并且存在于当前目录中。权限检查&#xff1a; Shell 检查 a 文件是否具有执行权限。如果没有执行权限&#xff0c;…

FastAPI+React全栈开发14 FastAPI如何开发REST接口

Chapter03 Getting Started with FastAPI 14 How does FastAPI speak REST FastAPIReact全栈开发14 FastAPI如何开发REST接口 Let’s create a minial FastAPI application, a classic Hello World example, and start examining how FastAPI structures the endpoints. I u…

Android Studio 2023.2.1版本 kotlin编译报错踩坑

1、需求 由于最近在整理项目&#xff0c;做一些公共基础组件Maven仓库封装&#xff0c;由于之前项目jar包和kotlin版本很老&#xff0c;kotlin版本1.3.72版本 Gradle使用5.4.1 Android Studio版本是2023.2.1&#xff0c;分别依次顺序如下图所示。 如下图所示 2、分析编译报错…

如何利用大模型LLM辅助,使用Python完成将CSV快速导入MySQL数据库

目录 一、适合场景 二、开发过程说明 三、功能使用流程 四、代码 1、安装python依赖库 2、完整代码段 一、适合场景 无废话&#xff0c;CSV中有少量不合规数据需要手工处理可利用此方法&#xff0c;适合有点经验的程序员&#xff0c;可以不熟Python&#xff0c;思路还是要…

第三十七章 管理和监控Web网关 - Web 网关缓存表

文章目录 第三十七章 管理和监控Web网关 - Web 网关缓存表Web 网关缓存表手动关闭连接清除缓存测试服务器连接 第三十七章 管理和监控Web网关 - Web 网关缓存表 Web 网关缓存表 第四个状态表列出了 Web Gateway 响应缓存中保存的表单。 Cached Forms - 缓存表单的名称&#x…

图像旋转算法双线性插值法详解

双线性插值法是一种常用的插值算法&#xff0c;用于在图像旋转、缩放等操作中估计目标像素的灰度值。它基于近邻像素之间的灰度变化趋势进行推断&#xff0c;从而提供更平滑和精确的结果。 双线性插值算法的基本思想如下&#xff1a; 首先&#xff0c;根据旋转角度确定旋转中心…

vue2完结

笔记 关于不同版本的Vue: 1.vue.js与vue.runtime.xxx.js的区别&#xff1a;&#xff08;1&#xff09;vue.js是完整版的Vue,包含&#xff1a;核心功能模板解析器&#xff08;2&#xff09;vue.runtime.xxx.js是运行版本的Vue,只包含核心功能&#xff0c;没有模板解析器 2.因为…

docker快速安装单节点和多点MongoDB副本集

文章目录 概要一、单节点副本集二、多节点副本集三、参考 概要 最近项目的MongoDB版本终于升级到了MongoDB4.4了&#xff0c;可以使用使用事务了&#xff0c;但MongoDB的不支持单节点事务&#xff0c;至少是副本集。 测试环境没必要搭建复杂的副本集&#xff0c;还好官方给出了…

【动手学深度学习-pytorch】-9.3深度循环神经网络

到目前为止&#xff0c;我们只讨论了具有一个单向隐藏层的循环神经网络。 其中&#xff0c;隐变量和观测值与具体的函数形式的交互方式是相当随意的。 只要交互类型建模具有足够的灵活性&#xff0c;这就不是一个大问题。 然而&#xff0c;对一个单层来说&#xff0c;这可能具有…

量化交易入门(二十七)回撤、收益率、夏普比率

回撤 一、回撤的定义与计算 回撤是指投资组合或交易账户从历史最高点下跌到后来最低点的幅度,通常用百分比表示。计算公式为: 回撤 (历史最高净值 - 当前净值) / 历史最高净值 100% 例如,某策略历史最高净值为150万,当前净值跌到了100万,则回撤为:(150-100)/150100%33.33%…

synchronized 和 ReentrantLock 的区别是什么

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:synchronized 和 ReentrantLock 的区别是什么 1. 获取锁的方式 synchronized:synchronized的锁获取是隐式的。当线程进入synchronized修饰的代码…

探索SOCKS5代理、代理IP与跨界电商、游戏技术的网络安全实践

介绍数字时代跨境业务的兴起&#xff0c;特别是跨界电商和全球游戏市场的扩张。 简述网络安全在支持跨境业务扩展中的核心作用&#xff0c;特别是SOCKS5代理和代理IP技术的重要性。 第一章&#xff1a;出海业务概述 跨界电商与全球化挑战&#xff1a;介绍跨界电商的基本模式&am…

Oracle 19C RAC集群补丁升级

文章目录 一、补丁包概述二、OPatch检查和更新Grid用户更新OPatchOracle用户更新OPatch 三、验证Oracle Inventory的有效性四、运行 OPatch 冲突检查五、运行opatch命令检查GI HOME下是否有足够的空间六、补丁冲突检测与解决&#xff08;修补程序&#xff09;七、使用root用户应…

Linux - 第三节

改变用户类型 su 仅单纯的进行身份变化 依旧处于普通用户里面 su - 进行重新登录更改身份 退出用exit / ctrld su 用户名 改成成其他身份 对一条命令进行提权 sudo command r:可读 w:可写 x:可执行 -:对应的权限位置&#xff0c;没有权限 去掉所有权限 chmod u…