Netty中的ByteBuf使用介绍

news2024/11/17 23:57:19

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操作的ByteBuf
    ByteBuf ioBuffer();
    ByteBuf ioBuffer(int initialCapacity);
    ByteBuf ioBuffer(int initialCapacity, int maxCapacity);
// 返回一个基于堆内存的ByteBuf
    ByteBuf heapBuffer();
    ByteBuf heapBuffer(int initialCapacity);
    ByteBuf heapBuffer(int initialCapacity, int maxCapacity);
// 返回一个基于直接内存的ByteBuf
    ByteBuf directBuffer();
    ByteBuf directBuffer(int initialCapacity);
    ByteBuf directBuffer(int initialCapacity, int maxCapacity);
// 返回一个包含指定数量的ByteBuf的复合ByteBuf
    CompositeByteBuf compositeBuffer();
    CompositeByteBuf compositeBuffer(int maxNumComponents);
// 返回一个包含指定数量的堆内存ByteBuf的负荷ByteBuf
    CompositeByteBuf compositeHeapBuffer();
    CompositeByteBuf compositeHeapBuffer(int maxNumComponents);
// 返回一个包含指定数量的直接内存ByteBuf的负荷ByteBuf
    CompositeByteBuf 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();
// 重置可被读取的索引,就是重置为标记的索引,或是0
    public abstract ByteBuf resetReaderIndex();
// 标记可被写入的开始索引
    public abstract ByteBuf markWriterIndex();
// 重置可被写入的索引,就是重置为标记的索引,或是0
    public 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=12
        byteBuf.writeBytes("天气不错".getBytes(CharsetUtil.UTF_8));
        System.out.println("数据:" + byteBuf.toString(CharsetUtil.UTF_8));
        // 没有读取,readerIndex=0
        System.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));
        // 容量256
        System.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=0
        byteBuf.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);
        // 标记当前的readerIndex
        byteBuf.markReaderIndex();
        // 标记当前的writerIndex
        byteBuf.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=0
        byteBuf.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.coloradmin.cn/o/1799732.html

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

相关文章

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

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

python - DataFrame查询数据操作

学习目标 掌握获取df一列或多列数据的方法 知道loc和iloc的区别以及使用方法 知道df的query函数的使用方法 知道isin函数的作用和使用方法 获取DataFrame子集的基本方法 1.1 从前从后获取多行数据 案例中用到的数据集在文章顶部 LJdata.csv 前景回顾 head() & tail(…

西门子PLC学习之数据块的单个实例,多重实例与参数实例间的区别

首先介绍下函数,函数块与数据块这三个概念。 数据块 数据块里可以存储各种类型的参数。有人可能会问,m寄存器不是可以存储布尔值,8位,16位,32位变量吗,为什么要多此一举?因为虽然m寄存器能存储以…

超详细的java Comparable,Comparator接口解析

前言 Hello大家好呀,在java中我们常常涉及到对象的比较,不同于基本数据类型,对于我们的自定义对象,需要我们自己去建立比较标准,例如我们自定义一个People类,这个类有name和age两个属性,那么问…

QT creator c动态链接库的创建与调用

QT creator c动态链接库的创建与调用 QT5.15.2 1.创建dll项目 确保两类型选择正确 2.选择MinGW 64-bit 3.点击完成 pro文件参考: QT - guiTEMPLATE lib DEFINES QT_DLL_DEMO_LIBRARYCONFIG c17# You can make your code fail to compile if it uses deprecat…

计算机组成结构—IO系统概述

目录 一、I/O 系统的发展 1. 早期阶段 2. 接口模块和 DMA 阶段 3. 通道结构阶段 4. 处理机阶段 二、I/O 系统的组成 1. I/O 软件 2. I/O 硬件 三、I/O 设备 1. I/O 设备分类 2. I/O 设备的组成 在计算机中,除 CPU 和主存两大模块之外,第三个重…

Vue项目安装axios报错npm error code ERESOLVE npm error ERESOLVE could not resolve解决方法

在Vue项目中安装axios时报错 解决方法:在npm命令后面加--legacy-peer-deps 例如:npm install axios --save --legacy-peer-deps 因为别的需求我把node版本重装到了最新版(不知道是不是这个原因),后来在项目中安装axi…

STM32作业实现(四)光敏传感器

目录 STM32作业设计 STM32作业实现(一)串口通信 STM32作业实现(二)串口控制led STM32作业实现(三)串口控制有源蜂鸣器 STM32作业实现(四)光敏传感器 STM32作业实现(五)温湿度传感器dht11 STM32作业实现(六)闪存保存数据 STM32作业实现(七)OLED显示数据 STM32作业实现(八)触摸按…

【Python爬虫单点登录实战】PyExecJS破解慧职教:过河源技术学院单点登录统一身份认证

目录 前言大致分析PyExecJS 使用案例pip 安装:Demo:输出:案例1.访问目标网站的登录页面并查看源码2.将js放到和py脚本同一级目录下3. 编写Python脚本来调用js破解单点登录实战提取密钥参数清洗数据登陆测试单点登录获取ticket获取jsessionid获取token成功我的专栏前言 博主提供…

Python 知识图谱补全,Knowledge Graph Completion,基于大模型的知识图谱补全,基于LLMs的KGC任务

今天讲一篇文章《Exploring Large Language Models for Knowledge Graph Completion》 ,这篇文章主题:基于大模型做知识图谱补全 1.文章主要思想: 本章描述知识图谱补全中的三个任务:三元组分类、关系预测和实体(链接)预测&…

微信如何防止被对方拉黑删除?一招教你解决!文末附软件!

你一定不知道,微信可以防止被对方拉黑删除,秒变无敌。只需一招就能解决!赶快来学!文末有惊喜! 惹到某些重要人物(比如女朋友),被删除拉黑一条龙,那真的是太令人沮丧了&a…

Ubuntu server 24 (Linux) AdGuard Home +SmartDNS 安装配置 搭建去广告快速DNS

一 SmartDNS 安装 ,可参考:Ubuntu server 24 (Linux) 安装部署smartdns 搭建智能DNS服务器-CSDN博客 二 安装AdGuard 1 下载地址:GitHub - AdguardTeam/AdGuardHome: Network-wide ads & trackers blocking DNS server 2 解压安装 #下…

路由器重启真的好吗?多久重启一次更好?

前言 小白前段时间发现自己家的OpenWRT软路由上网特别慢,有时候通话还有点卡顿。 然而有个朋友用的普通路由器也有类似的问题,而且有时候根本上不去网。 解决的办法很简单:重启路由器。 重启路由器? 但路由器重启是真的好吗&a…

链表反转--理解链表指针的基本操作

链表反转--理解链表指针的基本操作 链表反转的方法--主要是理解链表指针链表心得类节点是对象和指针区别: 链表反转的方法–主要是理解链表指针 根据值创建新列表 用一个链表指针代替整个新链表 两个链表的赋值 递归求解反向链表 用一个链表代替前后链表数…

将div渲染成textarea框,类似于ant design 的TextArea

一 先看效果 原始效果 输入时效果 二 代码如下 1. html 代码 <div className{style.divTextArea} contentEditable"true"></div> 2. Css(Less)代码 .divTextArea {width: 90%;margin-top: 10px;line-height: 28px;min-height: 60px;border: 1px solid …

优雅谈大模型10:MoE

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;则提…

牛客java基础(一)

A 解析 : java源程序只允许一个public类存在 &#xff0c;且与文件名同名 ; D hashCode方法本质就是一个哈希函数&#xff0c;这是Object类的作者说明的。Object类的作者在注释的最后一段的括号中写道&#xff1a;将对象的地址值映射为integer类型的哈希值。但hashCode()并不…

使用python绘制桑基图

使用python绘制桑基图 桑基图效果代码 桑基图 桑基图&#xff08;Sankey Diagram&#xff09;是一种用来表示流动&#xff08;如能源、资金、材料等&#xff09;在不同实体之间转移的图表。 每个流的宽度与流量成正比&#xff0c;通常用于显示能量或成本流动的分布情况。 桑基…

【TB作品】MSP430F149单片机,广告牌,滚动显示

LCD1602滚动显示切换播放暂停字符串 显示Public Places 显示No Smoking 播放 暂停 部分代码 char zifu1[] "Public Places "; char zifu2[] "Class Now "; char zifu3[] "No admittance "; char *zifu[] { zifu1, zifu2, zifu3 }…

初识C++ · 模板进阶

目录 前言&#xff1a; 1 非类型模板参数 2 按需实例化 3 模板特化 4 模板的分离编译 前言&#xff1a; 前面模板我们会了简单的使用&#xff0c;这里带来模板的进阶&#xff0c;当然&#xff0c;也就那么几个知识点&#xff0c;并不太难。 1 非类型模板参数 先来看这样…