深入浅出指南:Netty开发【NIO核心组件】

目录

​Netty开发【NIO核心组件】

1.NIO基础概念

2.NIO核心组件

2.1.Channel&&Buffer简介

2.2.Selector

服务器的多线程版本

 服务器的线程池版本

服务器的selector版本

2.3.Buffer

0.ByteBuffer的正确使用流程

1.ByteBuffer类型简介

2.ByteBuffer核心属性说明

3.ByteBuffer核心方法

4.代码示例说明

0.maven配置

1.缓冲区初始化

2.position、limit、flip()、clear()、mark()和reset()、compact()、hasRemaining() 方法详解

5.数据的粘包与包

5.1.什么是粘包和半包?

5.2.粘包和半包如何解决?

小结


 我是山茶君, 想了解更多内容,微信搜索【nlefer】,关注我,你懂得越多,就明白不懂的越多

1.NIO基础概念

NIO(Non-Blocking IO 也称为New IO),JDK提供的新API。从JDK1.4开始,Java提供了一系列改进的输入/输出的新特性,被统称为NIO(Non-Blocking IO 也称为New IO),是同步非阻塞的

2.NIO核心组件

2.1.Channel&&Buffer简介

Channel是一种管道,类似于stream,可以用来数据的传输。但Channel是基于Buffer缓冲区的异步双向的数据读写通道,即可以从Buffer中读取数据,也可以向Buffer中写入数据。

常见的Channel有● FileChannel● DatagramChannel● SocketChannel● ServerSocketChannel
其中SocketChannel和ServerSocketChannel可以被使用在服务端与客户端的通信。Buffer被用作为缓冲区存储数据,读写数据发生的位置,常见的有:● ByteBuffer○ MappedByteBuffer○ DirectByteBuffer○ HeapByteBuffer●  ShortBuffer● IntBuffer● LongBuffer● FloatBuffer● DoubleBuffer● CharBuffer

2.2.Selector

  1. Selector 为IO多路复用选择器,轮询检测注册在selector上的服务端状态,如果有事件发生,则获取对应的事件,针对事件的不同进行分类别的处理。
  2. 也就是说可以使用一个线程管理多个Channel,也就是管理多个请求与连接。
  3. Selector 维护了三个set集合,主要展现为整体步骤中的连接存储、获取连接通道信息、断开连接响应(也就是断开监控)
    1. key set :通道注册时,使用register()方法将对应的cahnnel信息添加到SelectionKey中;
    2. selected key set :在做轮询检测时,通过遍历Set<SelectionKey> selectionKeys = selector.selectedKeys(); 获取相应的key也就是存储的channel信息
    3. canneled key set:客户端主动断开连接或以外断开连接时,该事件已经不存在了,但在服务端的key集合中依旧存在,下次检测的时候会因此而出现问题,所以,需要通过判断客户端发送事件的返回值来确定是否将对应的key的SelectionKey从key set中移除, 将关联channel丢弃掉,不再进行监听;
  4. 通过服务器的设计演变进化,可以进一步了解Selector的功能以及作用

服务器的多线程版本

多线程操作,既使用了一个连接,那就开一个线程去处理这个连接的所有事物,当连接没有事件发生的时候,对应的线程就等待着,一直等到这个连接有事情发生或者是连接断开。可以类比为餐厅服务员与顾客的关系,一个服务员专门服务于一个顾客。

缺点与不足:

  • 当连接多了的时候,开的线程数量较多,会占用较大的内存
  • 线程的上下文切换成本比较高(一个线程被暂停,另一个线程被操作系统选择选中开始执行的过程就叫做上下文切换)
  • 场景使用仅适用于连接较少的情况

 服务器的线程池版本

针对于上述多线程版本的缺点,做了进一步的改进。较多线程版本资源使用有所降低,但是随之而来的是性能的问题。(就好比一个餐厅服务员耳听八方,眼观四路一样,服务于多个顾客,但是当顾客同事发起请求的时候,服务生就只能处理一个请求事件,就会造成阻塞的情况发生。因此才会继续诞生selector模式,线程池中设定固定的线程数)。

缺点与不足:

  • 阻塞模式下,线程仅能处理一个socket连接(处理scoket1的时候就不能处理scoket3,只有等代1处理完断开连接后才能处理3)
  • 仅适合短连接(避免线程处理一个socket,造成阻塞,导致下一个socket无法被处理)
服务器的selector版本

如图,依旧以餐厅为例,露天餐厅经历了前两次的失败,进化了成了一个小馆子。channel就是我们的多个顾客,selecor就好比一个监控视频头,能够通知thread服务员。当有channel发生事件时,seletcor会将对应事件通知到服务员(thread)去进行处理。处理完该顾客后,继续等待事件的请求,不会阻塞。与线程池版不同的是,channel是工作在非阻塞环境下的,因此selector的效率有较大提高。

缺点与不足:

  • 如果当前请求和事务的量级较大,线程就会一直处理该事务,其他的channel请求也会排队等待处理。

2.3.Buffer

ByteBuffer为NIO中的字节缓冲区,相对于BIO的Stream流只支持写入或者读取单向操作,ByteBuffer是双向的,支持读和写。

0.ByteBuffer的正确使用流程

  1. 向buffer中写入数据
    • channel的read()方法
    • buffer自身的put方法
  1. 调用filp()方法切换为读取数据模式
  2. 从buffer中读取数据
    • channel的write()方法
    • buffer的get()方法
  1. 调用clear()或compact()切换回写模式继续写入数据
  2. 重复以上步骤
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
// 写入数据到buffer;两种方式均可以
int readByte = channel.read(byteBuffer);
byteBuffer.put((byte) 0x61);
// 切换为读数据模式
byteBuffer.filp();
// 写入数据到buffer;两种方式均可以
int writeByte = channel.write(byteBuffer);
byte b = byteBuffer.get();

1.ByteBuffer类型简介

类型

简介

方法

DirectByteBuffer

使用的是操作系统级别的内存,分配比较慢,但是数据的读写比较快,因为少了一次从系统内存到JVM内存的复制过程

初始化方法ByteBuffer.allocateDirect(1024 * 4);

HeapByteBuffer

使用的是JVM的堆内存,对于JVM来说,分配比较快,但是读写比较慢,因为需要将操作系统内存里的数据复制到JVM内存,且java的gc会对其有影响,因为gc会对内存模块进行整理

初始化方法

ByteBuffer.allocate(1024 * 4);

2.ByteBuffer核心属性说明

属性名称

属性说明

capacity

ByteBuffer的容量,这个值在ByteBuffer初始化的时候就确定下来了。不论是在读还是在写模式下,这个值都不变

position

写模式:该值表示当前写到了ByteBuffer的哪个位置,ByteBuffer初始化时,这个值为0。position的最大值为capacity-1

读模式:当从写模式切换到读模式,会将position重置为0,即从ByteBuffer的起始位置开始读取数据

limit

写模式:limit为最大可写入的数据量,即ByteBuffer的最大容量,值为capacity

读模式:当从写模式切换从读模式,limit将会被设置为读模式下的position值,即可读取的最大数据量

3.ByteBuffer核心方法

方法

详情

flip()

将写模式切换为读模式,会触发的对核心属性的操作:

  • 将position设置为0,即从ByteBuffer起始位置开始读
  • 将limit设置为写模式下position的值,即最大可读取的数据量大小。

clear()

在逻辑上清空ByteBuffer里的数据,实际上不清空数据会触发的动作:

  • 将limit设置为capacity
  • position指向起始位置0
  • 注意:实际上数据并未清理,只是下次是从0的位置开始写入数据,效果上像是数据清空了。如果ByteBuffer中的数据并未完全读完,调用这个方法将忽略那些未读取的数据。

mark()

标记当前position位置【结合reset()使用】

reset()

将position指向上一次mark()所指向的位置,可以从这个位置重复向下读取数据

compact()

如果并未读取完ByteBuffer中的数据,调用compact()会将position~limit之间的数据拷贝到ByteBuffer的起始处,并且position为剩余数据量的大小,下次再往ByteBuffer中写入数据时,将在position位置继续往下写,不会覆盖历史数据。

hasRemaining()

判断缓冲区中是否还有未读数据

写入ByteBuffer

byteBuffer.put(x)

channel.read(byteBuffer)

  • 从通道上读取数据,写入缓冲区

读取ByteBuffer

byteBuffer.get()

channel.write(bytebuffer)

  • 从缓冲区中读取数据,写入通道

4.代码示例说明

0.maven配置

测试时需要创建一个maven项目,增加配置信息

<dependencies><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.39.Final</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.18</version></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.5</version></dependency><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>19.0</version></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.3</version></dependency><dependency><groupId>com.google.protobuf</groupId><artifactId>protobuf-java</artifactId><version>3.11.3</version></dependency></dependencies>
1.缓冲区初始化
public static void main(String[] args) {// 1.創建ByteBuffer缓冲区,使用allocate()初始化操作,设定大小为10ByteBuffer byteBuffer = ByteBuffer.allocate(10);// 2.向缓冲区写入一个数字a,占一个字节       byteBuffer.put((byte) 0x61);// 3.打印结果debugAll(byteBuffer);}

只进行了写入的操作,目前position的位置是处于写入数据的当前位置1,limit的位置是出于初始化容量大小的位置

2.position、limit、flip()、clear()、mark()和reset()、compact()、hasRemaining() 方法详解

在Bytebuffer流程中有一个读取数据,使用get()方法会使得position位置后移,limit位置不会发生变化

 public static void main(String[] args) {try {// 1.創建ByteBuffer缓冲区,使用allocate()初始化操作,设定大小为10ByteBuffer byteBuffer = ByteBuffer.allocate(10);// 2.向缓冲区写入一个数字a,占一个字节byteBuffer.put((byte) 0x61);byteBuffer.put((byte) 0x62);byteBuffer.put((byte) 0x63);byteBuffer.put((byte) 0x64);// 3.打印结果debugAll(byteBuffer);// 4.切换为读取操作byteBuffer.flip();// 5.读取数据while(byteBuffer.hasRemaining()){ // 6. 判断是否还有剩余未读取的数据byte b = byteBuffer.get();System.out.println("对应数据是:"+(char)b);}debugAll(byteBuffer);// 7. 数据切换为写模式,clear()方式byteBuffer.clear();debugAll(byteBuffer);// 8.写入数据到缓冲区,使用channel方法,然后重复切换操作FileChannel channel = new FileInputStream("data.txt").getChannel();channel.read(byteBuffer);byteBuffer.flip();while(byteBuffer.hasRemaining()){ // 9. 判断是否还有剩余未读取的数据byte b1 = byteBuffer.get();System.out.println("新的对应数据是:"+(char)b1);debugAll(byteBuffer);}debugAll(byteBuffer);} catch (Exception e) {e.printStackTrace();}}

从图中可知,在向缓冲区塞入数据且未开始切换为读模式时,从第一步到第七步,position和limit的位置分别从开始未读取的4和10变化为读取后的4和4,再次切换为写入数据,直接将position和limit的位置切换为0和10,既覆盖模式,从零开始,将上一次的数据覆盖掉。

为了避免出现未读完数据被覆盖的情况,使用compact()方法可以直接继续上一次的数据后面继续写入,如图所示,未读完的数据bcd被移至前面,然后继续写入数据

 public static void main(String[] args) {try {// 1.創建ByteBuffer缓冲区,使用allocate()初始化操作,设定大小为10ByteBuffer byteBuffer = ByteBuffer.allocate(10);// 2.向缓冲区写入一个数字a,占一个字节byteBuffer.put((byte) 0x61);byteBuffer.put((byte) 0x62);byteBuffer.put((byte) 0x63);byteBuffer.put((byte) 0x64);// 3.打印结果debugAll(byteBuffer);// 4.切换为读取操作byteBuffer.flip();// 5.0 单次读取数据System.out.println("单次读取数据:"+byteBuffer.get());debugAll(byteBuffer);// 6.避免数据出现覆盖的情况,使用compact()方法操作byteBuffer.compact();byteBuffer.put((byte) 0x66);byteBuffer.put((byte) 0x67);byteBuffer.put((byte) 0x68);debugAll(byteBuffer);} catch (Exception e) {e.printStackTrace();}}

mark和reset()方法联合使用的方法示例

public static void main(String[] args) {try {// 1.創建ByteBuffer缓冲区,使用allocate()初始化操作,设定大小为10ByteBuffer byteBuffer = ByteBuffer.allocate(10);// 2.向缓冲区写入一个数字a,占一个字节byteBuffer.put((byte) 0x61);byteBuffer.put((byte) 0x62);byteBuffer.put((byte) 0x63);byteBuffer.put((byte) 0x64);// 3.打印结果debugAll(byteBuffer);// 4.切换为读取操作byteBuffer.flip();// 5.0 单次读取数据System.out.println("第一次读取数据:"+byteBuffer.get());byteBuffer.mark();debugAll(byteBuffer);System.out.println("第二次读取数据:"+byteBuffer.get());debugAll(byteBuffer);byteBuffer.reset();debugAll(byteBuffer);} catch (Exception e) {e.printStackTrace();}}

在字符a的做了标记,当读完b的时候,选择返回到a的位置,从新读取数据,实现了反复读取数据的需求与操作,具体看下图position和limit的位置

5.数据的粘包与包

5.1.什么是粘包和半包?

粘包:在缓冲区有足够大的空间时,且存在多条数据同时存储到缓冲区内,且无按照一定的分割符进行分割,那么在读取数据时,多条数据就会粘连到一起,这种现象就较多粘包。比如分别传入“9000000”、“世界你好”,可能读取的数据就会变为9000000世界你好,

半包:在缓冲区数据被读取时,无法解析成一句完整的数据会出现乱码等现象,这就是半包。例如,缓冲区是4个字节,我存储“中国”,在读取数据的时候读到的是“中*”,“中国”是6个字节,缓冲区较小存不下,国子无法被正常解析。

网络上有多条数据发送给服务端,数据之间使用 \n 进行分隔
但由于某种原因这些数据在接收时,被进行了重新组合,例如原始数据有3条为Hello,world\n
I'm zhangsan\n
How are you?\n变成了下面的两个 byteBuffer (粘包,半包)Hello,world\nI'm zhangsan\nHo
w are you?\n
5.2.粘包和半包如何解决?

通过对"1.0粘包、半包示例"解决来学习如何解决对应的粘包以及半包,用以下代码来解析内容

  public static void main(String[] args) {
//      1.创建对应的数据ByteBuffer byteBuffer = ByteBuffer.allocate(32);byteBuffer.put("Hello,world\nI'm zhangsan\nHo".getBytes());splitbyteBuffer(byteBuffer);byteBuffer.put("w are you?\n".getBytes());splitbyteBuffer(byteBuffer);}public static void splitbyteBuffer(ByteBuffer source){
//      1.先写模式切换,既读模式和写模式source.flip();
//      2.遍历数据for (int i = 0;i < source.limit();i++){
//          3。找到完整的一条数据if (source.get(i) == '\n') {
//               4. 根据数据长度开辟缓冲区空间大小int length = i + 1 - source.position();ByteBuffer byteBufferCatch = ByteBuffer.allocate(length);
//              5.遍历获取的数据,写入到新的缓冲区中for (int j = 0;j < length;j++){byteBufferCatch.put(source.get());}
//                6.展示完整数据,既就是通过读物的数据debugAll(byteBufferCatch);}}source.compact();}

分散集中写的方式就不继续介绍,可以在网络上 查询相应的一些资料。

小结

ByteBuffer在编写过程中不仅仅只有以上的单个方法的使用,可能会涉及到多个场景的应用,及多个数据场景结合使用,因此在使用的时候,需要根据具体业务具体操作。

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

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

相关文章

记录vue的一些踩坑日记

记录vue的一些踩坑日记 安装Jq npm install jquery --save vue列表跳转到详情页&#xff0c;再返回列表的时候不刷新页面并且保持原位置不变&#xff1b; 解决&#xff1a;使用keepAlive 在需要被缓存的页面的路由中添加&#xff1a;keepAlive: true, {path: /viewExamine,nam…

ubuntu环境安装centos7虚拟机网络主机不可达,ping不通

【NAT模式下解决】1.首先vi /etc/sysconfig/network-scripts/ifcfg-ens33检查ONBOOTyes&#xff0c;保存 2.输入systemctl restart network命令重启网关

flutter:轮播

前言 介绍几个比较有不错的轮播库 swipe_deck 与轮播沾边&#xff0c;但是更多的是一种卡片式的交互式界面设计。它的主要概念是用户可以通过左右滑动手势浏览不同的卡片&#xff0c;每张卡片上都有不同的信息或功能。 Swipe deck通常用于展示图片、产品信息、新闻文章、社…

第四代SHARC® ADSP-21479KBCZ-2A、ADSP-21479BSWZ-2A、ADSP-21479KSWZ-2A高性能DSP(数字信号处理器)

第四代SHARC Processors 现在内置低功耗浮点DSP产品&#xff08;ADSP-21478和ADSP-21479&#xff09;&#xff0c;可提供改进的性能、基于硬件的滤波器加速器、面向音频与应用的外设以及能够支持单芯片解决方案的新型存储器配置。所有器件都彼此引脚兼容&#xff0c;而且与以往…

Appium+python自动化(二十二)- 控件坐标获取(超详解)

简介 有些小伙伴或者是童鞋可能会好奇会问上一篇中的那个monkey脚本里的坐标点是如何获取的&#xff0c;不是自己随便蒙的猜的&#xff0c;或者是自己用目光或者是尺子量出来的吧&#xff0c;答案当然是&#xff1a;NO。获取控件坐标点的方式这里宏哥给小伙伴们分享和讲解三种方…

2024届IC秋招兆易创新数字IC后端笔试面试题

数字IC后端实现PR阶段设计导入需要哪些文件&#xff1f; 设计导入需要的文件如下图所示。这个必须熟练掌握。只要做过后端训练营项目的&#xff0c;对这个肯定是比较熟悉的。大家还要知道每个input文件的作用是什么。 在吾爱IC后端训练营Cortexa7core项目中&#xff0c;你认为…

Jmeter接口自动化生成测试报告html格式

jmeter自带执行结果查看的插件&#xff0c;但是需要在jmeter工具中才能查看&#xff0c;如果要向领导提交测试结果&#xff0c;不够方便直观。 笔者刚做了这方面的尝试&#xff0c;总结出来分享给大家。 这里需要用到ant来执行测试用例并生成HTML格式测试报告。 一、ant下载安…

整数0 强制转化为指针

整数0强制转化为指针的巧用 在工程中看到以下代码&#xff1a; #define my_container_of(ptr,type,member) \ ((type*)((char *) (ptr) - (unsigned long)(&((type*)0)->member))) ->的优先级高于&。 因此 &((type*)0)->member)的解…

微信小程序quickstartFunctions中云函数的应用

1、在quickstartFunctions文件中新建文件夹和文件 2、index.js 文件书写 const cloud require(wx-server-sdk);cloud.init({env: cloud.DYNAMIC_CURRENT_ENV }); const db cloud.database();// 链表查询试卷和对应的题库 exports.main async (event, context) > {retu…

Terraform学习日记-AWS-EC2

terraform install https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli 这里我们使用 aws-linux-2022 作为执行环境 # sudo yum install -y yum-utils# sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/…

Linux内核中的链表、红黑树和KFIFO

lLinux内核代码中广泛使用了链表、红黑树和KFIFO。 一、 链表 linux内核代码大量使用了链表这种数据结构。链表是在解决数组不能动态扩展这个缺陷而产生的一种数据结构。链表所包含的元素可以动态创建并插入和删除。链表的每个元素都是离散存放的&#xff0c;因此不需要占用连…

VBA技术资料MF35:VBA_在Excel中过滤数据

【分享成果&#xff0c;随喜正能量】好马好在腿&#xff0c;好人好在嘴。不会烧香得罪神&#xff0c;不会讲话得罪人。慢慢的你就会发现&#xff0c;一颗好心&#xff0c;永远比不上一张好嘴。。 我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#…

Spring 6【方法参数校验、SpingAOP介绍、Schema-based方式实现AOP 】(十四)-全面详解(学习总结---从入门到深化)

目录 4.方法参数校验 SpingAOP介绍 Schema-based方式实现AOP 4.方法参数校验 Spring框架提供了一种校验方法参数的方法&#xff0c;在调用一个方法传入参数后&#xff0c;会判断参数是否满足数据校验。如果满足方法执行&#xff0c;如果不满足&#xff1a;不执行方法&…

使用开源免费AI绘图工具神器-Stable Diffusion懒人整合包

使用开源免费AI绘图工具神器-Stable Diffusion懒人整合包 Stable Diffusion 是什么 Stable Diffusion (简称 SD) 是一款开源免费的以文生图的 AI 扩散模型&#xff0c;它和付费的 Midjourney 被人称为当下最好用的 AI 绘画工具。你在网上看到的绝大多数优秀 AI 图片作品&…

ROS 基础知识汇总

How to learn ROS ROS for Beginners: How to Learn ROS - The Construct ROSwiki 界面介绍 ROS/Tutorials/NavigatingTheWiki - ROS Wiki ROS要学会哪些&#xff1f;如何学习Ros&#xff1f; - 知乎 setup.bash 的作用 ROS中的setup.bash_泠山的博客-CSDN博客 包的层级架构 …

详细介绍 React 中如何使用 redux

在使用之前要先了解它的配套插件&#xff1a; 在React中使用redux&#xff0c;官方要求安装其他插件 Redux Toolkit 和 react-redux Redux Toolkit&#xff1a;它是一个官方推荐的工具集&#xff0c;旨在简化 Redux 的使用和管理。Redux Toolkit 提供了一些提高开发效率的工具…

MybatisPlus拓展篇

文章目录 逻辑删除通用枚举字段类型处理器自动填充功能防全表更新与删除插件MybatisX快速开发插件插件安装逆向工程常见需求代码生成 乐观锁问题引入乐观锁的使用效果测试 代码生成器执行SQL分析打印多数据源 逻辑删除 逻辑删除的操作就是增加一个字段表示这个数据的状态&…

用JavaScript和HTML实现一个精美的计算器

文章目录 一、前言二、技术栈三、功能实现3.1 引入样式3.2 编写显示页面3.2 美化计算器页面3.3 实现计算器逻辑 四、总结 一、前言 计算器是我们日常生活中经常使用的工具之一&#xff0c;可以帮助我们进行简单的数学运算。在本博文中&#xff0c;我将使用JavaScript编写一个漂…

剑指 Offer 26. 树的子结构

思路&#xff1a; 先统计B数的非空节点数countB。然后前序遍历A&#xff0c;当遇到A的值和B的第一个值相等时&#xff0c;则进行统计左右结构和值都相等的节点数和sum&#xff0c;如果sum countB&#xff0c;则true。 /*** Definition for a binary tree node.* public class…

android framework车载桌面CarLauncher的TaskView详细源码分析

1、构建相关的TaskView&#xff0c;装载到对应的ViewGroup b站免费视频教程讲解&#xff1a; https://www.bilibili.com/video/BV1wj411o7A9/ //packages/apps/Car/Launcher/src/com/android/car/carlauncher/CarLauncher.java void onCreate() { //ignoresetContentView(R.…