21.Netty源码之编码器
highlight: arduino-light
Netty如何实现自定义通信协议
在学习完如何设计协议之后,我们又该如何在 Netty 中实现自定义的通信协议呢?其实 Netty 作为一个非常优秀的网络通信框架,已经为我们提供了非常丰富的编解码抽象基类,帮助我们更方便地基于这些抽象基类扩展实现自定义协议。
首先我们看下 Netty 中常用的编解码器有哪些。
一次编解码器和二次编解码器
Netty中的编解码器分为一次编解码和二次编解码。
一次编解码器:MessageToByteEncoder、ByteToMessageDecoder/ReplyingDecoder
二次编解码器:MessageToMessageEncoder、MessageToMessageDecoder
以解码为例,一次解码器用于解决TCP拆包/粘包问题,解析得到字节数据。
如果需要对解析后的字节数据做对象转换,需要使用二次解码器。同理,编码器是相反过程。
为什么需要二次码/编码
假设我们把解决半包粘包问题的常用三种解码器叫一次解码器:
那么我们在项目中,除了可选的的压缩解压缩之外,还需要一层解码,因为一次解码的结果是字节,需要和项目中所使用的对象做转化,方便使用,这层解码器可以称为“二次解码器”。
相应的,对应的二次编码器是为了将 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。
通过抽象编码类的继承图可以看出编码类是 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() 方法,其主要逻辑分为以下几个步骤:
- acceptOutboundMessage 判断是否有匹配的消息类型,如果匹配需要执行编码流程,如果不匹配直接继续传递给下一个 ChannelOutboundHandler。
- 分配 ByteBuf 资源,默认使用堆外内存。
- 调用子类实现的 encode 方法完成数据编码,一旦消息被成功编码,会通过调用ReferenceCountUtil.release(cast) 自动释放。
- 如果 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如何实现自定义通信协议 在学习完如何设计协议之后,我们又该如何在 Netty 中实现自定义的通信协议呢?其实 Netty 作为一个非常优秀的网络通信框架,已经为我们提供了非常丰富的编解码抽象基类,帮助我…...

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

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

NeRF-SLAM: Real-Time Dense Monocular SLAM with Neural Radiance Fields 论文阅读
论文信息 题目:NeRF-SLAM: Real-Time Dense Monocular SLAM with Neural Radiance Fields 作者:Antoni Rosinol, John J. Leonard, Luca Carlone 代码:https://github.com/ToniRV/NeRF-SLAM 来源:arxiv 时间ÿ…...
机器学习之弹性网络(Elastic Net)
弹性网络 代码原文 下面代码参考scikit-learn中文社区,链接在上面。 但是由于scikit-learn中文社区上的代码有些地方跑不通,故对此代码做了修改,输出结果与社区中显示的结果相同。 对弹性网络进行简单的介绍: ElasticNet是一个训…...

嵌入式入门教学——C51
一、前期准备 1、硬件设备 2、软件设备 二、预备知识 1、什么是单片机? 在一片集成电路芯片上集成微处理器、存储器、IO接口电路,从而构成了单芯片微型计算机,及单片机。STC89C52单片机: STC:公司89:所属…...
2023-08-03力扣每日一题
链接: 722. 删除注释 题意: 如题,特殊规则见链接 解: 字符串处理,嗯写就完事了,主要是判断指针位置和特殊规则 实际代码: #include<bits/stdc.h> using namespace std; vector<string> …...

【蓝桥杯备考资料】如何进入国赛?
目录 写在前面注意事项数组、字符串处理BigInteger日期问题DFS 2013年真题Java B组世纪末的星期马虎的算式振兴中华黄金连分数有理数类(填空题)三部排序(填空题)错误票据幸运数字带分数连号区间数 2014年真题蓝桥杯Java B组03猜字…...

QtWebApp开发https服务器,完成客户端与服务器基于ssl的双向认证
引言:所谓http协议,本质上也是基于TCP/IP上服务器与客户端请求和应答的标准,web开发中常用的http server有apache和nginx。Qt程序作为http client可以使用QNetworkAccessManager很方便的进行http相关的操作。Qt本身并没有http server相关的库…...
动态IP代理的优势展现与应用场景
在当今数字化时代,网络安全和隐私保护变得愈发重要。作为一家动态IP代理产品供应商,我们深知在保护个人隐私和提高网络安全性方面的重要性。本文将会分享动态IP代理的优势及其在不同应用场景下的实际应用案例,帮助更好地了解和应用动态IP代理…...

ad+硬件每日学习十个知识点(22)23.8.2(LDO datasheet手册解读)
文章目录 1.LDO的概述、features2.LDO的绝对参数(功率升温和结温)3.LDO的引脚功能4.LDO的电气特性5.LDO的典型电路(电容不能真用1uF,虽然按比例取输出值,但是R2的取值要考虑释放电流)6.LDO的开关速度和线性…...

这可是全网最全的网络工程师零基础实战视频整理,最新版分享
互联网中每一项傍身的技能都是需要从如何入门开始的,网络技术也是如此! 网络技术区别其他互联网技能的一点是学习需要从设备开始,只有认识了解了路由器、交换机、防火墙这些网络设备,才开始从网络通信原理开始,这使得网…...

笔记本WIFI连接无网络【实测有效解决方案,不用重启电脑】
笔记本Wifi连接无网络实测有效解决方案 问题描述: 笔记本买来一段时间后,WIFI网络连接开机一段时间还正常连接,但是过一段时间显示网络连接不上解决方案: 1.编写网络重启bat脚本,将以下内容写到文本文件,把…...
js 正则表达式配合replace进行过滤html字符串遇到的性能问题
问题场景复现: 博主要实现一个邮箱列表,其中列表中的每一封邮件都有一个摘要,但是摘要是要自己从后端提供的content内容区自己过滤掉所有,只留下纯文本内容的前面几行作为摘要。 性能问题 当我测试到一个邮箱,其中的…...
2022牛客寒假算法基础集训营1
B题 炸鸡块君与FIFA22 题目大意: 给出胜负序列,每次询问区间 (l,r,s) ,回答在经历 (l-r) 之后积分是多少,初始积分为 (s) 胜 (1) 积分,平 (0) 积分,败的时候如果此时积分为 (3) 的倍数则 (-0) ,…...
API对接:构建连接不同系统的技术桥梁
API(Application Programming Interface)是一种用于不同软件系统之间进行通信和数据交换的技术。本文将介绍API对接的基本概念和原理,并通过代码示例演示如何使用API对接不同系统,解决数据传输与通信的难题。 在当今数字化时代&a…...

【MySQL】仓储--维护出入库流水、库存,去重数量逻辑修正
系列文章 C#底层库–MySQLBuilder脚本构建类(select、insert、update、in、带条件的SQL自动生成) 本文链接:https://blog.csdn.net/youcheng_ge/article/details/129179216 C#底层库–MySQL数据库操作辅助类(推荐阅读࿰…...

用Log4j 2记录日志
说明 maven工程中增加对Log4j 2的依赖 下面代码示例的maven工程中的pom.xml文件中需要增加对Log4j 2的依赖: <dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.20.0&…...
【Java面试】Paxos和Raft协议的区别?
面试官:你简历上说了解Paxos和Raft协议,说一下你对这两个协议的了解? 我:Paxos算法和Raft算法都是用于实现分布式系统中的一致性的算法,确保不同节点之间的数据一致。 我:Paxos算法它的目标是使多个节点能…...

手机浏览器H5打开微信小程序支付,自定义传参
微信官方提供的开放文档如下: 静态网站 H5 跳小程序 | 微信开放文档 想必大家都能看懂官网提供的文档,但实战时却遇到很多问题,博主总结一下遇到的坑,如果您也有遇到,希望可以帮到您。 1.小程序已经发布上线了&…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...

QT开发技术【ffmpeg + QAudioOutput】音乐播放器
一、 介绍 使用ffmpeg 4.2.2 在数字化浪潮席卷全球的当下,音视频内容犹如璀璨繁星,点亮了人们的生活与工作。从短视频平台上令人捧腹的搞笑视频,到在线课堂中知识渊博的专家授课,再到影视平台上扣人心弦的高清大片,音…...
深度解析:etcd 在 Milvus 向量数据库中的关键作用
目录 🚀 深度解析:etcd 在 Milvus 向量数据库中的关键作用 💡 什么是 etcd? 🧠 Milvus 架构简介 📦 etcd 在 Milvus 中的核心作用 🔧 实际工作流程示意 ⚠️ 如果 etcd 出现问题会怎样&am…...