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

根据实例逐行分析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();}
}

相关文章:

  • 安全算法 - 加密算法
  • 【机器学习】《机器学习算法竞赛实战》思考练习(更新中……)
  • C语言编写Linux的Shell外壳
  • C#常见Winform窗体效果
  • SpringBoot+ECharts+Html 地图案例详解
  • 四、Mybatis-查询与删除
  • 内网安全之-kerberos协议
  • 第五篇:3.4 用户归因和受众(User attribution and audience) - IAB/MRC及《增强现实广告效果测量指南1.0》
  • SpringBoot+ECharts+Html 字符云/词云案例详解
  • Redis缓存设计与性能优化【缓存穿透、缓存击穿、缓存雪崩】
  • 【Web】记录Polar靶场<困难>难度题一遍过
  • pytorch中的while for 循环 导出onnx的问题
  • Error: TF_DENORMALIZED_QUATERNION: Ignoring transform forchild_frame_id
  • 3D模型格式转换工具HOOPS Exchange如何将3D文件加载到PRC数据结构中?
  • R统计实战:详解机器学习Adaboost的操作步骤与应用
  • 【译】JS基础算法脚本:字符串结尾
  • $translatePartialLoader加载失败及解决方式
  • ES6--对象的扩展
  • HTTP中的ETag在移动客户端的应用
  • iOS 颜色设置看我就够了
  • iOS动画编程-View动画[ 1 ] 基础View动画
  • MySQL的数据类型
  • node学习系列之简单文件上传
  • Spring技术内幕笔记(2):Spring MVC 与 Web
  • V4L2视频输入框架概述
  • vue从创建到完整的饿了么(18)购物车详细信息的展示与删除
  • weex踩坑之旅第一弹 ~ 搭建具有入口文件的weex脚手架
  • 案例分享〡三拾众筹持续交付开发流程支撑创新业务
  • 初探 Vue 生命周期和钩子函数
  • 基于阿里云移动推送的移动应用推送模式最佳实践
  • 理解 C# 泛型接口中的协变与逆变(抗变)
  • 深度学习中的信息论知识详解
  • 我感觉这是史上最牛的防sql注入方法类
  • 详解NodeJs流之一
  • 异步
  • 格斗健身潮牌24KiCK获近千万Pre-A轮融资,用户留存高达9个月 ...
  • ​用户画像从0到100的构建思路
  • ​云纳万物 · 数皆有言|2021 七牛云战略发布会启幕,邀您赴约
  • #gStore-weekly | gStore最新版本1.0之三角形计数函数的使用
  • #图像处理
  • (42)STM32——LCD显示屏实验笔记
  • (pytorch进阶之路)CLIP模型 实现图像多模态检索任务
  • (附源码)springboot车辆管理系统 毕业设计 031034
  • (附源码)计算机毕业设计ssm电影分享网站
  • (三分钟了解debug)SLAM研究方向-Debug总结
  • (一)使用IDEA创建Maven项目和Maven使用入门(配图详解)
  • ***微信公众号支付+微信H5支付+微信扫码支付+小程序支付+APP微信支付解决方案总结...
  • .【机器学习】隐马尔可夫模型(Hidden Markov Model,HMM)
  • .NET CLR Hosting 简介
  • .NET DataGridView数据绑定说明
  • .NET Framework 的 bug?try-catch-when 中如果 when 语句抛出异常,程序将彻底崩溃
  • .Net Web项目创建比较不错的参考文章
  • .Net小白的大学四年,内含面经
  • // an array of int
  • /etc/X11/xorg.conf 文件被误改后进不了图形化界面