Netty中的ByteBuf使用介绍

ByteBuf有三类:

  • 堆缓存区:JVM堆内存分配
  • 直接缓冲区:有计算机内存分配,JVM只是保留分配内存的地址信息,相对于堆内存方式较为昂贵;
  • 复合缓冲区:复合缓冲区CompositeByteBuf,它为多个ByteBuf 提供一个聚合视图。比如HTTP 协议,分为消息头和消息体,这两部分可能由应用程序的不同模块产生,各有各的 ByteBuf,将会在消息被发送的时候组装为一个ByteBuf,此时可以将这两个ByteBuf聚 合为一个CompositeByteBuf,然后使用统一和通用的ByteBuf API来操作;

ByteBufAllocator

当在需要ByteBuf时,用这个类进行获取,它提供了3中类型的ByteBuf获取。

    // 返回一个基于堆或直接内存的ByteBuf
ByteBuf buffer();ByteBuf buffer(int initialCapacity);ByteBuf buffer(int initialCapacity, int maxCapacity);
// 返回一个适用于IO操作的ByteBufByteBuf ioBuffer();ByteBuf ioBuffer(int initialCapacity);ByteBuf ioBuffer(int initialCapacity, int maxCapacity);
// 返回一个基于堆内存的ByteBufByteBuf heapBuffer();ByteBuf heapBuffer(int initialCapacity);ByteBuf heapBuffer(int initialCapacity, int maxCapacity);
// 返回一个基于直接内存的ByteBufByteBuf directBuffer();ByteBuf directBuffer(int initialCapacity);ByteBuf directBuffer(int initialCapacity, int maxCapacity);
// 返回一个包含指定数量的ByteBuf的复合ByteBufCompositeByteBuf compositeBuffer();CompositeByteBuf compositeBuffer(int maxNumComponents);
// 返回一个包含指定数量的堆内存ByteBuf的负荷ByteBufCompositeByteBuf compositeHeapBuffer();CompositeByteBuf compositeHeapBuffer(int maxNumComponents);
// 返回一个包含指定数量的直接内存ByteBuf的负荷ByteBufCompositeByteBuf compositeDirectBuffer();CompositeByteBuf compositeDirectBuffer(int maxNumComponents);
// 判断是否池化的直接内存对象boolean isDirectBufferPooled();
// 根据最小和最大容量计算出一个新的容量int calculateNewCapacity(int minNewCapacity, int maxCapacity);

netty中使用方式例如下面再入站里的handler调用:

    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {System.out.println("客户端收到:" + byteBuf.toString(CharsetUtil.UTF_8));ByteBuf bb = channelHandlerContext.alloc().heapBuffer();ByteBuf db = channelHandlerContext.alloc().directBuffer();channelHandlerContext.channel();}

它由上下文对象ChannelHandlerContext调用alloc()方法获取ByteBufAllocator

API

我们先看下下面这几个API,需要熟悉理解的:

// 返回一个ByteBufAllocator,创建ByteBuf使用
public abstract ByteBufAllocator alloc();
// 返回可以被读取的字节的开始索引public abstract int readerIndex();
public abstract ByteBuf readerIndex(int readerIndex);
// 返回可被写入字节的开始索引public abstract int writerIndex();public abstract ByteBuf writerIndex(int writerIndex);
// 可被读取的字节数public abstract int readableBytes();
// 可被写入的字节数public abstract int writableBytes();
// 是否可读public abstract boolean isReadable();
// 是否可读,参数是是否可读入指定字节数public abstract boolean isReadable(int size);
// 是否可写public abstract boolean isWritable();
// 是否可写,参数是是否可读入指定字节数public abstract boolean isWritable(int size);
// 清空数据public abstract ByteBuf clear();
// 标记当前的可被读取的开始索引public abstract ByteBuf markReaderIndex();
// 重置可被读取的索引,就是重置为标记的索引,或是0public abstract ByteBuf resetReaderIndex();
// 标记可被写入的开始索引public abstract ByteBuf markWriterIndex();
// 重置可被写入的索引,就是重置为标记的索引,或是0public abstract ByteBuf resetWriterIndex();
// 丢弃读取过的字节(0到readerIndex的部分)public abstract ByteBuf discardReadBytes();

虽然上面注释有写过,但还是再提醒一遍;

readerIndex表示可以被读取数据的开始索引,或者说已经读取了readerIndex个字节;
writerIndex表示可以被写入数据的开始索引,或者说已经写入了writerIndex个字节;

discardReadBytes丢弃的是读取过的字节数据,同时writerIndex会相应减少对应的字节长度;

看几个例子,再次加深记忆:

 ByteBuf byteBuf = new PooledByteBufAllocator().buffer();System.out.println("--------------测试get/set 与 read/write方法的区别");byteBuf.setBytes(0, "qwer".getBytes());System.out.println("数据:" + byteBuf.toString(CharsetUtil.UTF_8));System.out.println("set 之后 readIndex:" + byteBuf.readerIndex());System.out.println("set 之后 wirteIndex:" + byteBuf.writerIndex());System.out.println("get 之后 readIndex:" + byteBuf.readerIndex());System.out.println("get 之后 wirteIndex:" + byteBuf.writerIndex());// 没有数据被写进去System.out.println(byteBuf.toString(CharsetUtil.UTF_8));// 写入12个字节数据,writerIndex=12byteBuf.writeBytes("天气不错".getBytes(CharsetUtil.UTF_8));System.out.println("数据:" + byteBuf.toString(CharsetUtil.UTF_8));// 没有读取,readerIndex=0System.out.println("write 之后 readIndex:" + byteBuf.readerIndex());System.out.println("write 之后 wirteIndex:" + byteBuf.writerIndex());// get方式获取字节,readerIndex不会移动byteBuf.getByte(3);System.out.println("get 之后 readIndex:" + byteBuf.readerIndex());System.out.println("get 之后 wirteIndex:" + byteBuf.writerIndex());// read方式读取,readerIndex=3,没有涉及写入,writerIndex不变byteBuf.readBytes(3);System.out.println("read 之后 readIndex:" + byteBuf.readerIndex());System.out.println("read 之后 wirteIndex:" + byteBuf.writerIndex());// 因为读取了3个字节(一个汉字),可被读取的数据从第二个汉字开始System.out.println("数据:" + byteBuf.toString(CharsetUtil.UTF_8));// 容量256System.out.println("容量:" + byteBuf.capacity());// 将数据的第6个索引开始替换为指定的字节数据,注意,这个长度要在指定索引和writerIndex差值内,不然会报异常(因为没有数据可以被操作)byteBuf.setBytes(6, "123".getBytes());System.out.println("setBytes 之后:" + byteBuf.toString(CharsetUtil.UTF_8));System.out.println("-------------测试byteBuf其他的一些方法");System.out.println("readableBytes 可被读取的字节数:" + byteBuf.readableBytes());System.out.println("writableBytes 可被写入的字节数:" + byteBuf.writableBytes());System.out.println("isReadable 是否可读:" + byteBuf.isReadable());System.out.println("isWritable 是否可写:" + byteBuf.isWritable());System.out.println("-----------测试标记与重置");// 重置也就是readerIndex=writerIndex=0byteBuf.resetReaderIndex();byteBuf.resetWriterIndex();System.out.println("reset 之后 readIndex:" + byteBuf.readerIndex());System.out.println("reset 之后 wirteIndex:" + byteBuf.writerIndex());// 重新写入数据,测试后面的方法byteBuf.writeBytes("天气真好".getBytes(CharsetUtil.UTF_8));// 再次读取3个字节byteBuf.readBytes(3);// 标记当前的readerIndexbyteBuf.markReaderIndex();// 标记当前的writerIndexbyteBuf.markWriterIndex();// 重置,只会重置为上一次mark的索引byteBuf.resetReaderIndex();byteBuf.resetWriterIndex();System.out.println("mark-reset 之后 readIndex:" + byteBuf.readerIndex());System.out.println("mark-reset 之后 wirteIndex:" + byteBuf.writerIndex());System.out.println("-------------测试丢弃");// 丢弃数据,释放内存,原来是写入了12个字节,writerIndex=12,执行丢弃,会把已经读取的丢弃(3个字节)// 所以,执行后的writerIndex=9,readerIndex=0byteBuf.discardReadBytes();System.out.println("容量:" + byteBuf.capacity());System.out.println("丢弃 之后 readIndex:" + byteBuf.readerIndex());System.out.println("丢弃 之后 wirteIndex:" + byteBuf.writerIndex());

结果如下:

image-20240528001333652

对于上面的操作,可以看下面这个图解:

image-20240528002756774

资源的释放

资源释放针对的主要是ByteBuf这个对象;

为什么说要释放ByteBuf这个对象,这个对象不是在方法中被创建的吗,方法结束后不就会被JVM回收吗?

如果说ByteBuf是一般对象的话,这个说法是对的,可是,这个对象ByteBufnetty实现的,并且实现于ReferenceCounted,而这个接口是用于引用计数管理对象生命周期的,需要我们手动进行计数管理;

我们看下这个接口提供的方法,对这个管理便会更加清晰:

public interface ReferenceCounted {/*** 返回对象的引用计数; 如果计数=0,表示对象不被引用可以被安全回收*/int refCnt();/*** 引用计数+1*/ReferenceCounted retain();/*** 引用计数+increment(增加指定的计数)*/ReferenceCounted retain(int increment);/*** 记录当前的访问位置;* 如果发生内存泄漏,返由 ResourceLeakDetector(资源泄漏探测器)返回这些信息*/ReferenceCounted touch();/*** 记录当前的访问位置,以及额外的信息*/ReferenceCounted touch(Object hint);/*** 引用次数-1;释放当前资源*/boolean release();/*** 引用次数-decrement(减少指定计数)*/boolean release(int decrement);
}

那为什么netty要实现这么一个需要手动释放的对象?

主要几点:

  • 优化内存管理:ByteBuf支持池化(Pooled),可以重用之前分配,但已回收的内存块,减少内存分配和垃圾回收的开销;非池化(Unpooled)每次使用时都要创建对象实例,分配内存,相对于池化对象,它过于频繁的分配内存和释放操作;
  • 引用计数机制/性能提升:更精准的控制对象的生命周期,在JVM中,利用各种算法,如标记清除、标记整理、复制等算法决定哪些对象可以被回收,并且在某些场景下,如一个方法中的创建并且被使用的变量,需要在变量离开作用域或方法执行完,也或是被明确复制为null时,才能被判定为无引用,而ByteBuf可以决定什么时候不被引用,做到在需要时及时回收,提高系统整体性能和响应能力;
  • 诊断内存泄漏:netty提供了ResourceLeakDetector类来跟踪ByteBuf的分配,在检测到内存泄漏时打印相关日志信息;

有人会问:netty这个框架不就是为了方便于开发,对socket进行封装,对业务流程步骤进行抽象,它就不能做到自动释放?

哎,netty确实对ByteBuf做了自动释放,只是ByteBufhandler之间流转时,这个经过业务处理,可能已经不是原来的ByteBuf,这个过程中可能创建了新的ByteBuf,而旧的ByteBuf就需要我们手动释放;

piple中有一个handler链,我们可以自由添加handler,但是头尾handler都是默认添加的,我们来看下面代码:

image-20240526210421409

这部分是piple实例化时执行的,它默认会添加TailContextHeadContext两个handler,尾部的handler就负责释放ByteBuf对象,也就是在这个handler链中,除了我们自己添加的handler,还有两个handler分别在头部和尾部,而尾部的handler其中一个功能就是释放handler链中传递的ByteBuf对象。

位置:io.netty.channel.DefaultChannelPipeline.TailContext#channelRead

image-20240526212947226

image-20240526213015187

可以看到ReferenceCountUtil.release(msg);的,这里就是释放对象的地方;

ReferenceCountUtil这个是netty自己封装的用于处理实现了引用计数接口对象的工具类。

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

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

相关文章

VS2019创建c++动态链接库dll与调用方法

VS2019创建c动态链接库dll与调用方法 1.点击文件-》新建-》项目,输入dll,选择具有导出项的(DLL)动态链接库 2.输入一个文件名:dll2 头文件.h 3.添加加减法函数: // 下列 ifdef 块是创建使从 DLL 导出更简单的 // 宏的标准方法。此 DLL 中的…

题解web

1.[LitCTF 2023]Follow me and hack me 1)进入题目环境,提示get传参,post传参 2)看看源码,也没啥 3)直接用hackbar,传入对应参数即可得到FLAG 3)但是扫描出来它后端还有东西&#x…

Vue 学习笔记 总结

Vue.js 教程 | 菜鸟教程 (runoob.com) 放一下课上的内容 Vue练习 1、练习要求和实验2的用户注册一样,当用户输入后,能在下方显示用户输入的各项内容(不需要实现【重置】按钮) 2、实验报告中的实验小结部分来谈谈用JS、jQuery和…

618数码产品有什么推荐?四大2024“宝藏”数码产品推荐!

随着618购物节的热情逐渐升温,你是否在繁多且诱人的商品海洋中迷失方向,难以找到那最心仪的宝贝?团团在此特别为你精心挑选了一系列经过亲身体验的优质好物。这些推荐不仅时尚前沿,更贴合你的日常生活需求,确保实用与品…

霸气的短视频:成都科成博通文化传媒公司

霸气的短视频:瞬间的力量与魅力 在数字化浪潮中,短视频以其独特的魅力迅速崛起,成为社交媒体的新宠。而在众多短视频中,那些充满霸气、让人热血沸腾的作品,总能引起广泛的关注和讨论。成都科成博通文化传媒公司将从内…

CSS基础知识汇总

目录 CSS 基础知识1. CSS 的基本结构2. 选择器3. 常用 CSS 属性4. CSS 单位5. CSS 盒模型 总结 学习 CSS(Cascading Style Sheets)是前端开发的重要部分,它用于控制网页的样式和布局。以下是学习 CSS 过程中需要掌握的基本概念、符号和对应的…

Vue08-数据代理

一、Object.defineProperty() Object.defineProperty() 是 JavaScript 中的一个方法,用于直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。 这个方法允许你精确地控制一个对象的属性,包括它的值、是…

数据库存储过程和锁机制

存储过程 存储过程是事先经过编译并存储在数据库中的一段SQL语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的,存储过程思想上很简单,就是数据库SQL语言层面的代码封装与有重用 …

Flutter开发效率提升1000%,Flutter Quick教程之对Widget进行删除,剪切,粘贴

一,删除操作 1,首先我们选中要删除的Widget。 2,在左边的侧边栏,点击删除按钮,即可完成对组件的删除操作。 二,剪切。剪切是相同的道理,都是先选中,再点击对应的按钮。 1&#xff…

javaWeb4 Maven

Maven-管理和构建java项目的工具 基于POM的概念 1.依赖管理:管理项目依赖的jar包 ,避免版本冲突 2.统一项目结构:比如统一eclipse IDEA等开发工具 3.项目构建:标准跨平台的自动化项目构建方式。有标准构建流程,能快速…

java 原生http服务器 测试JS前端ajax访问实现跨域传post数据

后端 java eclipse 字节流转字符 package Httpv3;import com.sun.net.httpserver.Headers; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer;import java.io.IOException; import java.i…

【全开源】JAVA打车小程序APP打车顺风车滴滴车跑腿源码微信小程序打车源码

:构建便捷出行新体验 一、引言:探索打车系统小程序源码的重要性 在数字化快速发展的今天,打车系统小程序已成为我们日常生活中不可或缺的一部分。它以其便捷、高效的特点,极大地改变了我们的出行方式。而背后的关键,…

KOL+UGC:解锁品牌内容营销新策略,增强用户参与与互动

在当今数字化时代,品牌与受众之间的互动性和参与感变得至关重要。其中,KOL和UGC(用户生成内容)的结合,为品牌创造了一种全新的内容创作模式。这种模式不仅增强了内容的吸引力,还极大地提升了品牌与受众之间…

win键盘键位映射:欧版键盘->国内布局|是否应该买国外版笔记本?

最近买了个欧版笔记本电脑,键盘是欧版键盘: 键盘上的按钮实体和国内不完全一样键盘按钮上印的字母符号和国内不完全一样 具体差异 如图: shift处欧版是两个按键。可通过键盘键位映射解决。回车Enter、| 、布局不同。非常别扭,…

跨境反向海淘系统:业务流程解析与未来发展展望

随着全球化的深入发展和互联网技术的飞速进步,跨境购物已经成为越来越多消费者日常生活中的一部分。在这个过程中,反向海淘系统以其独特的优势,逐渐崭露头角,成为跨境电商领域的新星。作为一名在跨境反向海淘系统业务中耕耘了10年…

编写程序,提示用户输入以米/秒(m/s)为单位的速度v和以米/秒的平方(m/s)为单位的加速度 a,然后显示最短跑道长度。

(物理:求出跑道长度)假设一个飞机的加速度是a而起飞速度是v,那么可以使用下 面的公式计算出飞机起飞所需的最短跑道长度: 编写程序,提示用户输入以米/秒(m/s)为单位的速度v和以米/秒的平方(m/s)为单 位的加速度 a,然后显示最短跑道长度。下面…

LCM — Least Common Multiple 最小公倍数

因为任何一个数都可以表示为若干个质数幂的乘积。 比如75 3*5*5,即 2^0 * 3^1 * 5^2 * 7^0 ... 那么对于两个数来说,gcd就是他们取每个质数的较小幂的乘积,lcm则相反。显然,这些幂加起来就是他们乘积。 gcd(a,b) * lcm(a,b) a…

CorelDRAW2024发布更新啦!设计师们的得力助手

在数字化的今天,视觉设计已经成为我们生活中不可或缺的一部分。从手机界面到广告海报,从网页布局到包装设计,每一个细节都离不开设计师们的专业与创意。然而,面对日益增长的设计需求和不断提升的审美标准,许多设计师开…

【算法专题--栈】最小栈--高频面试题(图文详解,小白一看就会!!)

目录 一、前言 二、题目描述 三、解题方法 ⭐解题方法--1 ⭐解题方法--2 四、总结 五、共勉 一、前言 最小栈这道题,可以说是--栈专题--,比较经典的一道题,也是在面试中频率较高的一道题目,通常在面试中,面试官可…

OpenAI发布GPT-4思维破解新策略,Ilya亦有贡献!

OpenAI正在研究如何破解GPT-4的思维,并公开了超级对齐团队的工作,Ilya Sutskever也在作者名单中。 论文地址:https://cdn.openai.com/papers/sparse-autoencoders.pdf 代码:https://github.com/openai/sparse_autoencoder 特征可…