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

21.Netty源码之编码器


highlight: arduino-light

Netty如何实现自定义通信协议

在学习完如何设计协议之后,我们又该如何在 Netty 中实现自定义的通信协议呢?其实 Netty 作为一个非常优秀的网络通信框架,已经为我们提供了非常丰富的编解码抽象基类,帮助我们更方便地基于这些抽象基类扩展实现自定义协议。

首先我们看下 Netty 中常用的编解码器有哪些。

一次编解码器和二次编解码器

Netty中的编解码器分为一次编解码和二次编解码。

一次编解码器:MessageToByteEncoder、ByteToMessageDecoder/ReplyingDecoder

二次编解码器:MessageToMessageEncoder、MessageToMessageDecoder

以解码为例,一次解码器用于解决TCP拆包/粘包问题,解析得到字节数据。

如果需要对解析后的字节数据做对象转换,需要使用二次解码器。同理,编码器是相反过程。

为什么需要二次码/编码

假设我们把解决半包粘包问题的常用三种解码器叫一次解码器:

image.png

那么我们在项目中,除了可选的的压缩解压缩之外,还需要一层解码,因为一次解码的结果是字节,需要和项目中所使用的对象做转化,方便使用,这层解码器可以称为“二次解码器”。

相应的,对应的二次编码器是为了将 Java 对象转化成字节流方便存储或传输。

为什么不合并一次二次解码器

思考:是不是也可以一步到位? 合并 1 次解码(解决粘包、半包)和 2 次解码(解决可操作问题)

可以,但是不建议: •没有分层,不够清晰;分层可以组合。 •耦合性高,不容易置换方案。

常用的编解码方式

-Java 序列化

-Marshaling

-XML

-JSON

-MessagePack

-Protobuf

-其他

选择编解码方式的因素

-空间:编码后占用空间 -时间:编解码速度 -是否追求可读性 -是否支持多语言,例如msgpack的支持:Java\C\Python等

Protobuf

-Protobuf 是一个灵活的、高效的用于序列化数据的协议。

-相比较 XML 和 JSON 格式,Protobuf 更小、更快、更便捷。

-Protobuf 是跨语言的,并且自带了一个编译器(protoc),只需要用它进行编译,可以自动生成 Java、python、C++ 等代码,不需要再写其他代码。

Protobuf使用步骤

第1步:在Maven 项目中引入 Protobuf 坐标,下载相关的jar包。

在pom.xml中 添加依赖

xml <dependencies> <dependency>    <groupId>com.google.protobuf</groupId>    <artifactId>protobuf-java</artifactId>    <version>3.6.1</version> </dependency> </dependencies>

第 2 步: 编写proto文件:Student.proto。

Student.proto的内容

```java syntax = "proto3"; //版本 option javaouterclassname = "StudentPoJO"; //指定生成的Java类名

//内部类的名称,是真正的PoJo 类 message Student{ // message 的规定的   int32 id = 1; //PoJo 类的属性数据类型类型和 序号(不是属性值)   string name = 2; } ```

第 3 步:通过 protoc.exe 根据描述文件生成 Java 类。

说明:protoc-3.6.1-win32 是从网上下载的 google 提供的文件.

cmd执行命令生成StudentPoJO.java: C:\Users\Administrator\Desktop\Netty资料\我的\资料\protoc-3.6.1-win32\bin>protoc.exe --java_out=. Student.proto

第4步:把生成的 StudentPoJo.java 拷贝到自己的项目中打开。

第 5 步:在 Netty 中使用。

java ch.pipeline().addLast(new ProtobufVarint32FrameDecoder()); ch.pipeline().addLast(new ProtobufDecoder(StudentPoJO.Student.getDefaultInstance())); ​ ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender()); ch.pipeline().addLast(new ProtobufEncoder());

抽象编码类

所有的解码器都继承了ChannelInBoundHandler。因为解码是需要解码接收的数据。所以使用In。

所有的编码器都继承了ChannelOutBoundHandler。因为编码是需要将对外发送的数据编码。所以使用Out。

image.png

通过抽象编码类的继承图可以看出编码类是 ChanneOutboundHandler 的抽象类实现,具体操作的是 Outbound 出站数据。

常用编码器类型

  • MessageToByteEncoder 对象编码成字节流;
  • MessageToMessageEncoder 一种对象消息类型编码成另外一种对象消息类型。

使用一次编码器IntegerEncoder和二次编码器IntegerToStringEncoder,将消息从Integer编码为String。

java class IntegerEncoder extends MessageToByteEncoder<Integer> {    @Override    public void encode(ChannelHandlerContext ctx, Integer msg, ByteBuf out) throws Exception {        out.writeInt(msg);   } } ​ class IntegerToStringEncoder extends MessageToMessageEncoder<Integer> {    @Override    public void encode(ChannelHandlerContext ctx, Integer message, List<Object> out) throws Exception {        out.add(message.toString());   } }

使用一次编码器StringEncoder和二次编码器StringToIntegerEncoder,将消息从String编码为Integer。

class StringEncoder extends MessageToByteEncoder<String> {    @Override    public void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) throws Exception {        out.writeCharSequence(msg, Charset.defaultCharset());   } } ​ class StringToIntegerEncoder extends MessageToMessageEncoder<String> {    @Override    public void encode(ChannelHandlerContext ctx, String message, List<Object> out) throws Exception {        out.add(Integer.parseInt(message));   } }

编码器MessageToByteEncoder

MessageToByteEncoder用于将对象编码成字节流,MessageToByteEncoder 提供了唯一的 encode 抽象方法,我们需要实现encode 方法即可完成自定义编码。那么encode() 方法是在什么时候被调用的呢?

我们一起看下MessageToByteEncoder 的核心源码片段,如下所示。

MessageToByteEncoder继承自ChannelOutboundHandlerAdapter。

ChannelOutboundHandlerAdapter 实现了 ChannelOutboundHandler接口,重写了write方法。

这里使用了模板模式:encode方法交给具体的子类实现。

java public abstract class MessageToByteEncoder<I> extends ChannelOutboundHandlerAdapter { ​ @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {    ByteBuf buf = null;    try {   // 1.消息类型是否匹配 不匹配不会处理        // 即传入的是String        if (acceptOutboundMessage(msg)) {            //I是泛型            @SuppressWarnings("unchecked")            I cast = (I) msg;            // 2. 分配 ByteBuf 资源            buf = allocateBuffer(ctx, cast, preferDirect);            try {            // 3. 执行 encode 方法完成数据编码                encode(ctx, cast, buf);           } finally {                ReferenceCountUtil.release(cast);           }            if (buf.isReadable()) {            // 4. 向后传递写事件                ctx.write(buf, promise);           } else {                buf.release();                ctx.write(Unpooled.EMPTY_BUFFER, promise);           }            buf = null;       } else {            ctx.write(msg, promise);       }   } catch (EncoderException e) {        throw e;   } catch (Throwable e) {        throw new EncoderException(e);   } finally {        if (buf != null) {            buf.release();       }   } } } //供子类重写 protected abstract void encode(ChannelHandlerContext ctx, I msg, ByteBuf out) throws Exception;

MessageToByteEncoder 重写了 ChanneOutboundHandler 的 write() 方法,其主要逻辑分为以下几个步骤:

  1. acceptOutboundMessage 判断是否有匹配的消息类型,如果匹配需要执行编码流程,如果不匹配直接继续传递给下一个 ChannelOutboundHandler。
  2. 分配 ByteBuf 资源,默认使用堆外内存。
  3. 调用子类实现的 encode 方法完成数据编码,一旦消息被成功编码,会通过调用ReferenceCountUtil.release(cast) 自动释放。
  4. 如果 ByteBuf 可读,说明已经成功编码得到数据,然后写入 ChannelHandlerContext 交到下一个节点。如果 ByteBuf 不可读,则释放 ByteBuf 资源,向下传递空的 ByteBuf 对象。

实现类:StringToByteEncoder

编码器实现非常简单,不需要关注拆包/粘包问题。

如下例子,展示了如何将字符串类型的数据写入到 ByteBuf 实例,ByteBuf 实例将传递给 ChannelPipeline 链表中的下一个 ChannelOutboundHandler。

java package io.netty.example.Encode; ​ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToByteEncoder; ​ public class StringToByteEncoder extends MessageToByteEncoder<String> { ​    @Override    protected void encode(ChannelHandlerContext channelHandlerContext, String data, ByteBuf byteBuf) throws Exception {      byteBuf.writeBytes(data.getBytes());   } }

编码器MessageToMessageEncoder

https://www.javajike.com/book/essential-netty-in-action/chapter4/66cf00f545a4c73fa3fd2fad8d0b7a1d.html

MessageToMessageEncoder 与 MessageToByteEncoder 类似,同样只需要实现 encode 方法。

与 MessageToByteEncoder 不同的是,MessageToMessageEncoder 是将一种格式的消息转换为另外一种格式的消息。

其中第二个 Message 所指的可以是任意一个对象,如果该对象是 ByteBuf 类型,那么和 MessageToByteEncoder 的实现原理是一致的。

MessageToMessageEncoder继承自ChannelOutboundHandlerAdapter。

ChannelOutboundHandlerAdapter 实现了 ChannelOutboundHandler接口,重写了write方法。

这里使用了模板模式:encode方法交给具体的子类实现。

java    @Override    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {        CodecOutputList out = null;        try {            // 1. 消息类型是否匹配 不匹配不会处理       // 即传入的是String            if (acceptOutboundMessage(msg)) {                out = CodecOutputList.newInstance();                 //I是泛型                @SuppressWarnings("unchecked")                I cast = (I) msg;                try {                    //执行子类encode完成具体编码操作                    encode(ctx, cast, out);               } finally {                    ReferenceCountUtil.release(cast);               } //如果输出结果是对象列表out是空                if (out.isEmpty()) {                    out.recycle();                    out = null;                    throw new EncoderException(" must produce at least one message.");               }           } else {                ctx.write(msg, promise);           }       } catch (EncoderException e) {            throw e;       } catch (Throwable t) {            throw new EncoderException(t);       } finally {            if (out != null) {                final int sizeMinusOne = out.size() - 1;                // sizeMinusOne等于0说明 out长度是1                if (sizeMinusOne == 0) {                    //写出去                    ctx.write(out.getUnsafe(0), promise);               } else if (sizeMinusOne > 0) {                    //遍历写出去                    if (promise == ctx.voidPromise()) {                        writeVoidPromise(ctx, out);                   } else {                        writePromiseCombiner(ctx, out, promise);                   }               }                //回收                out.recycle();           }       }   }

此外 MessageToByteEncoder 的输出结果是对象列表out,编码后的结果属于中间对象,最终仍然会转化成 ByteBuf 进行传输。

MessageToMessageEncoder 常用的实现子类有 StringEncoder、LineEncoder、Base64Encoder 等。以 StringEncoder 为例看下 MessageToMessageEncoder 的用法。

实现类:StringEncoder

java @Sharable public class StringEncoder extends MessageToMessageEncoder<CharSequence> { ​    // TODO Use CharsetEncoder instead.    private final Charset charset; ​    /**     * Creates a new instance with the current system character set.     */    public StringEncoder() {        this(Charset.defaultCharset());   } ​    /**     * Creates a new instance with the specified character set.     */    public StringEncoder(Charset charset) {        if (charset == null) {            throw new NullPointerException("charset");       }        this.charset = charset;   } ​    @Override    protected void encode(ChannelHandlerContext ctx, CharSequence msg, List<Object> out) throws Exception {        if (msg.length() == 0) {            return;       } //编码以后加入out列表 由父类写出即可        out.add(ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(msg), charset));   } } ​

思考:现在有1个Java对象要编码为json字符串后转换为byte传输 如何做呢?

1.继承MessageToMessageEncoder

2.重写encode方法

3.将对象序列化为json

4.将json转为ByteBuffer发送:ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(json), charset)

相关文章:

21.Netty源码之编码器

highlight: arduino-light Netty如何实现自定义通信协议 在学习完如何设计协议之后&#xff0c;我们又该如何在 Netty 中实现自定义的通信协议呢&#xff1f;其实 Netty 作为一个非常优秀的网络通信框架&#xff0c;已经为我们提供了非常丰富的编解码抽象基类&#xff0c;帮助我…...

Linux 快速创建桌面图标

在安装 tar.gz 这类型压缩文件时&#xff0c;通常启动文件是.sh文件。文章主要记录快速添加到桌面图标。 1、解压 tar -zxvf XXX.tar.gz 2、创建桌面图标文件 touch XXX.desktop 3、文件中配置 [Desktop Entry] NameXXX CommentZZZ Exec/软件可执行文件所在目录/可执行文…...

数据结构—哈夫曼树及其应用

5.6哈夫曼树及其应用 5.6.1哈夫曼树的基本概念 路径&#xff1a;从树中一个结点到另一个结点之间的分支构成这两个结点间的路径。 结点的路径长度&#xff1a;两结点间路径上的分支数。 树的路径长度&#xff1a;从树根到每一个结点的路径长度之和。记作 TL 结点数目相同的…...

NeRF-SLAM: Real-Time Dense Monocular SLAM with Neural Radiance Fields 论文阅读

论文信息 题目&#xff1a;NeRF-SLAM: Real-Time Dense Monocular SLAM with Neural Radiance Fields 作者&#xff1a;Antoni Rosinol, John J. Leonard&#xff0c; Luca Carlone 代码&#xff1a;https://github.com/ToniRV/NeRF-SLAM 来源&#xff1a;arxiv 时间&#xff…...

机器学习之弹性网络(Elastic Net)

弹性网络 代码原文 下面代码参考scikit-learn中文社区&#xff0c;链接在上面。 但是由于scikit-learn中文社区上的代码有些地方跑不通&#xff0c;故对此代码做了修改&#xff0c;输出结果与社区中显示的结果相同。 对弹性网络进行简单的介绍&#xff1a; ElasticNet是一个训…...

嵌入式入门教学——C51

一、前期准备 1、硬件设备 2、软件设备 二、预备知识 1、什么是单片机&#xff1f; 在一片集成电路芯片上集成微处理器、存储器、IO接口电路&#xff0c;从而构成了单芯片微型计算机&#xff0c;及单片机。STC89C52单片机&#xff1a; STC&#xff1a;公司89&#xff1a;所属…...

2023-08-03力扣每日一题

链接&#xff1a; 722. 删除注释 题意&#xff1a; 如题&#xff0c;特殊规则见链接 解&#xff1a; 字符串处理&#xff0c;嗯写就完事了,主要是判断指针位置和特殊规则 实际代码&#xff1a; #include<bits/stdc.h> using namespace std; vector<string> …...

【蓝桥杯备考资料】如何进入国赛?

目录 写在前面注意事项数组、字符串处理BigInteger日期问题DFS 2013年真题Java B组世纪末的星期马虎的算式振兴中华黄金连分数有理数类&#xff08;填空题&#xff09;三部排序&#xff08;填空题&#xff09;错误票据幸运数字带分数连号区间数 2014年真题蓝桥杯Java B组03猜字…...

QtWebApp开发https服务器,完成客户端与服务器基于ssl的双向认证

引言&#xff1a;所谓http协议&#xff0c;本质上也是基于TCP/IP上服务器与客户端请求和应答的标准&#xff0c;web开发中常用的http server有apache和nginx。Qt程序作为http client可以使用QNetworkAccessManager很方便的进行http相关的操作。Qt本身并没有http server相关的库…...

动态IP代理的优势展现与应用场景

在当今数字化时代&#xff0c;网络安全和隐私保护变得愈发重要。作为一家动态IP代理产品供应商&#xff0c;我们深知在保护个人隐私和提高网络安全性方面的重要性。本文将会分享动态IP代理的优势及其在不同应用场景下的实际应用案例&#xff0c;帮助更好地了解和应用动态IP代理…...

ad+硬件每日学习十个知识点(22)23.8.2(LDO datasheet手册解读)

文章目录 1.LDO的概述、features2.LDO的绝对参数&#xff08;功率升温和结温&#xff09;3.LDO的引脚功能4.LDO的电气特性5.LDO的典型电路&#xff08;电容不能真用1uF&#xff0c;虽然按比例取输出值&#xff0c;但是R2的取值要考虑释放电流&#xff09;6.LDO的开关速度和线性…...

这可是全网最全的网络工程师零基础实战视频整理,最新版分享

互联网中每一项傍身的技能都是需要从如何入门开始的&#xff0c;网络技术也是如此&#xff01; 网络技术区别其他互联网技能的一点是学习需要从设备开始&#xff0c;只有认识了解了路由器、交换机、防火墙这些网络设备&#xff0c;才开始从网络通信原理开始&#xff0c;这使得网…...

笔记本WIFI连接无网络【实测有效解决方案,不用重启电脑】

笔记本Wifi连接无网络实测有效解决方案 问题描述&#xff1a; 笔记本买来一段时间后&#xff0c;WIFI网络连接开机一段时间还正常连接&#xff0c;但是过一段时间显示网络连接不上解决方案&#xff1a; 1.编写网络重启bat脚本&#xff0c;将以下内容写到文本文件&#xff0c;把…...

js 正则表达式配合replace进行过滤html字符串遇到的性能问题

问题场景复现&#xff1a; 博主要实现一个邮箱列表&#xff0c;其中列表中的每一封邮件都有一个摘要&#xff0c;但是摘要是要自己从后端提供的content内容区自己过滤掉所有&#xff0c;只留下纯文本内容的前面几行作为摘要。 性能问题 当我测试到一个邮箱&#xff0c;其中的…...

2022牛客寒假算法基础集训营1

B题 炸鸡块君与FIFA22 题目大意&#xff1a; 给出胜负序列&#xff0c;每次询问区间 (l,r,s) &#xff0c;回答在经历 (l-r) 之后积分是多少&#xff0c;初始积分为 (s) 胜 (1) 积分&#xff0c;平 (0) 积分&#xff0c;败的时候如果此时积分为 (3) 的倍数则 (-0) &#xff0c…...

API对接:构建连接不同系统的技术桥梁

API&#xff08;Application Programming Interface&#xff09;是一种用于不同软件系统之间进行通信和数据交换的技术。本文将介绍API对接的基本概念和原理&#xff0c;并通过代码示例演示如何使用API对接不同系统&#xff0c;解决数据传输与通信的难题。 在当今数字化时代&a…...

【MySQL】仓储--维护出入库流水、库存,去重数量逻辑修正

系列文章 C#底层库–MySQLBuilder脚本构建类&#xff08;select、insert、update、in、带条件的SQL自动生成&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/129179216 C#底层库–MySQL数据库操作辅助类&#xff08;推荐阅读&#xff0…...

用Log4j 2记录日志

说明 maven工程中增加对Log4j 2的依赖 下面代码示例的maven工程中的pom.xml文件中需要增加对Log4j 2的依赖&#xff1a; <dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.20.0&…...

【Java面试】Paxos和Raft协议的区别?

面试官&#xff1a;你简历上说了解Paxos和Raft协议&#xff0c;说一下你对这两个协议的了解&#xff1f; 我&#xff1a;Paxos算法和Raft算法都是用于实现分布式系统中的一致性的算法&#xff0c;确保不同节点之间的数据一致。 我&#xff1a;Paxos算法它的目标是使多个节点能…...

手机浏览器H5打开微信小程序支付,自定义传参

微信官方提供的开放文档如下&#xff1a; 静态网站 H5 跳小程序 | 微信开放文档 想必大家都能看懂官网提供的文档&#xff0c;但实战时却遇到很多问题&#xff0c;博主总结一下遇到的坑&#xff0c;如果您也有遇到&#xff0c;希望可以帮到您。 1.小程序已经发布上线了&…...

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…...

智慧医疗能源事业线深度画像分析(上)

引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)

宇树机器人多姿态起立控制强化学习框架论文解析 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架&#xff08;一&#xff09; 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

Python如何给视频添加音频和字幕

在Python中&#xff0c;给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加&#xff0c;包括必要的代码示例和详细解释。 环境准备 在开始之前&#xff0c;需要安装以下Python库&#xff1a;…...

今日科技热点速览

&#x1f525; 今日科技热点速览 &#x1f3ae; 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售&#xff0c;主打更强图形性能与沉浸式体验&#xff0c;支持多模态交互&#xff0c;受到全球玩家热捧 。 &#x1f916; 人工智能持续突破 DeepSeek-R1&…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

Springboot社区养老保险系统小程序

一、前言 随着我国经济迅速发展&#xff0c;人们对手机的需求越来越大&#xff0c;各种手机软件也都在被广泛应用&#xff0c;但是对于手机进行数据信息管理&#xff0c;对于手机的各种软件也是备受用户的喜爱&#xff0c;社区养老保险系统小程序被用户普遍使用&#xff0c;为方…...

在QWebEngineView上实现鼠标、触摸等事件捕获的解决方案

这个问题我看其他博主也写了&#xff0c;要么要会员、要么写的乱七八糟。这里我整理一下&#xff0c;把问题说清楚并且给出代码&#xff0c;拿去用就行&#xff0c;照着葫芦画瓢。 问题 在继承QWebEngineView后&#xff0c;重写mousePressEvent或event函数无法捕获鼠标按下事…...