Netty中的ByteBuf使用介绍

2024-06-07 22:36
文章标签 使用 介绍 netty bytebuf

本文主要是介绍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自己封装的用于处理实现了引用计数接口对象的工具类。

这篇关于Netty中的ByteBuf使用介绍的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1040486

相关文章

性能测试介绍

性能测试是一种测试方法,旨在评估系统、应用程序或组件在现实场景中的性能表现和可靠性。它通常用于衡量系统在不同负载条件下的响应时间、吞吐量、资源利用率、稳定性和可扩展性等关键指标。 为什么要进行性能测试 通过性能测试,可以确定系统是否能够满足预期的性能要求,找出性能瓶颈和潜在的问题,并进行优化和调整。 发现性能瓶颈:性能测试可以帮助发现系统的性能瓶颈,即系统在高负载或高并发情况下可能出现的问题

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

图神经网络模型介绍(1)

我们将图神经网络分为基于谱域的模型和基于空域的模型,并按照发展顺序详解每个类别中的重要模型。 1.1基于谱域的图神经网络         谱域上的图卷积在图学习迈向深度学习的发展历程中起到了关键的作用。本节主要介绍三个具有代表性的谱域图神经网络:谱图卷积网络、切比雪夫网络和图卷积网络。 (1)谱图卷积网络 卷积定理:函数卷积的傅里叶变换是函数傅里叶变换的乘积,即F{f*g}