netty十八罗汉之——挖耳罗汉(Decoder)

佛教中除不听各种淫邪声音之外,更不可听别人的秘密。因他论耳根最到家,故取挖耳之形,以示耳根清净。

来看看netty的核心组件解码器Decoder

  •  Decoder的作用
  • 半包,粘包问题
  • 从模板和装饰器模式看Decoder解码原理

1.Decoder作用

 最根本的就是将网络中的二进制数据,解析成为目标对象(Object)。

抽象出来本质就是上图,其它任何动作都是在完成这个目的。

对吧,学习一定要抓住本质。

先来看看网络数据再网络和操作系统底层是怎么传输的

a.网络数据是以二进制的数据包进行传输的,在netty中是以bytefuf数据包进行传递。这个概念之     后  章节会讲到。

b.会涉及tcp/ipc传输协议。

 先来看看TCP/IP协议的数据封装和分用过程,大致如下图所示:

 

可以看到没往下传输一层,会加上一个每层的首部。这个可以理解为,唐僧西天取经的过程中,通关文牒都要盖一个章一样。不加这个人家不认你。用的时候在解析出来报文体。

这里就不得不提一个概念MSS(TCP[数据包]每次能够传输的最大数据分段)

后面在传输过程中,超过这个值,数据包会进行拆分;小于这个值,数据包会进行合并。

c.dma复制

简单讲 就是绕过cpu,直接操作内存在设备间传输数据。

具体流程如下:

dam传输数据时,cpu不参与,dma处理完之后通知cpu进行后续处理。

理解这几个点之后,我们再来看一看正真的数据传播流程:

1.首先发送端,在用户缓存区里的bytebuf数据包会进行一次cpu复制;

2.cpu复制之后数据会缓存在发送缓冲区的内核空间里,这时候数据是完整的;

3.内核缓冲区进行一次DMA复制,数据被写入网卡设备中,此时网卡里面的数据包会进行重组;

4.写入网卡的数据通过TCP/IP协议进行传输,组装成新的二进制数据包,有最大数据限制;

5.接收端通过TCP/IP协议接收到二进制数据包,此时数据是完整的;

6.接收端在内核缓冲区进行一次DMA复制,形成新的bytebuf数据包;

7.接收端进行一次cpu复制,bytebuf数据包被写入用户缓冲区。

具体流程如下图:

这个过程会涉及两次分割二进制包:

a.传输过程中的分割;

b.系统复制过程中的分割;

所以粘包,和半包问题的出现就发生在这两个过程中,数据包少了,多个合在一起就会造成粘包。数据包多了,就会进行分割,分割就会打破原来的数据结构,出现半包问题。

来看看netty Decoder是怎么解决这个问题的

1.通过ByteToMessageDecoder 将二进制数据转换成对象

2.通过MessageToMessageDecoder将对象数据转换成对象

先来看看ByteToMessageDecoder 它是一个基类,主要使用模板方法接受管理bytebuf,子类通过实现钩子方法,处理业务逻辑,处理完业务逻辑将写入List<Object>中,之后在流水线上发送到下一站。流水线概念会在之后一章单独讲解。

public class Byte2IntegerDecoder extends ByteToMessageDecoder {//钩子实现@Overridepublic void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {while (in.readableBytes() >= 4) {Integer anInt = in.readInt();Logger.info("解码出一个整数: " + anInt);out.add(anInt);}}
}

来看看它的具体使用,继承ByteToMessageDecoder,钩子方法里实现业务逻辑。

回顾一下什么是模板方法:

核心点:抽象类会实现一系列公共方法共子类使用。

1.抽象类会定义一系列的模板方法,子类自动继承。

2.子类通过钩子方法处理具体业务逻辑。

好,我们再看一个它的核心子类ReplayingDecoder(回放解码器)。

什么是回放呢?

我们读取数据的时候是在操作一个二进制数组,数组元素完整的话,指针移动没有问题。但是数据元素不完整,再次读取的时候记数指针会回到开头重新执行。

netty通过checkpoint指针来完成指针回放

再来看看ReplayingDecoder怎么结合模板模式和装饰器模式完成整个解码操作的:

使用模板模式主要是它继承ByteToMessageDecoder父类解码器,实现decode方法。重点看看包装器的使用:

回放解码器在解码的时候会对传进来的bytebuf进行一次包装:

就是这个 replayable.setCumulation(in);

netty 实现了一个 ReplayingDecoderByteBuf,它继承了ByteBuf,所以可以对对传进来的bytebuf进行操作,这个过程就是包装器在起作用。

protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {replayable.setCumulation(in);try {while (in.isReadable()) {int oldReaderIndex = checkpoint = in.readerIndex();int outSize = out.size();if (outSize > 0) {fireChannelRead(ctx, out, outSize);out.clear();

简化一下看个简单的包装器的例子

public class WrapperModelDemo {// Pojo -- 被包装的类型static class Pojo implements Sth {public void a() {System.out.println("a");}public void b() {System.out.println("b");}public void c() {System.out.println("c");}public void X() {System.out.println("ALL - X");}}// Wrapper模式static class PojoWrapper implements Sth {private Sth inner;protected PojoWrapper() {}public void setInner(Sth inner) {this.inner = inner;}@Overridepublic void a() {System.out.println("PojoWrapper - a");inner.a();}@Overridepublic void b() {System.out.println("PojoWrapper - a");inner.b();}@Overridepublic void c() {throw new UnsupportedOperationException("... c  ");}@Overridepublic void X() {throw new UnsupportedOperationException("... X");}}@Testpublic void testWrapper() throws Exception {Sth pojo = new Pojo();pojo.a();pojo.b();pojo.c();PojoWrapper pojoWrapper = new PojoWrapper();pojoWrapper.setInner(pojo);// 可以尝试注释掉上面的某一行代码, 查看输出结果pojoWrapper.a();pojoWrapper.b();pojoWrapper.c();}}

运行结果:

核心是包装类在实现sth接口时,又把它作为了一个属性设置进来了。

这样结合着看,对回放解码器的解码过程理解起来就会更加清楚。

现在来看看 MessageToMessageDecoder解码器,它的传入对象不再是一个bytebuf,而是一个具体的对象

public class Integer2StringDecoder extends MessageToMessageDecoder<Integer> {@Overridepublic void decode(ChannelHandlerContext ctx, Integer msg, List<Object> out) throws Exception {String target = String.valueOf(msg);out.add(target);}
}

它有几个重要的子类:

LineBasedFrameDecoder:基于行的解码器,通过换行符(\n 或者 \r\n)来作为帧的分隔符。当接收到数据时,它会在数据中查找换行符,一旦找到,就将从上次解码位置到换行符之间的数据作为一个完整的帧进行解码并返回。如果在接收到的数据中没有找到换行符,那么数据会被缓存起来,直到换行符出现或者缓冲区满了才进行处理。
DelimiterBasedFrameDecoder:基于分隔符的解码器,它允许用户自定义帧的分隔符。在接收到数据后,它会根据用户提供的分隔符(一个或多个字节数组)在数据中进行查找。一旦找到分隔符,就将从上次解码位置到分隔符之前的数据作为一个完整的帧返回。同样,如果没有找到分隔符,数据会被缓存,直到分隔符出现或缓冲区达到一定条件。
LengthFieldBasedFrameDecoder:通过消息中定义的长度字段来确定帧的长度。解码器会首先读取指定长度的字节,这些字节表示后续帧数据的长度。然后,根据这个长度值,从数据流中读取相应长度的数据作为一个完整的帧。这种方式可以精确地定位每个帧的边界,即使数据存在粘包和拆包的情况。

 下面是几个具体实例,可以跑跑看看效果

public class NettyOpenBoxDecoder {public static final int MAGICCODE = 9999;public static final int VERSION = 100;static final String SPLITER = "\r\n";static final String SPLITER_3 = "\n";static final String SPLITER_2 = "\t";static final String CONTENT = "netty:18罗汉系列!";/*** LineBasedFrameDecoder 使用实例*/@Testpublic void testLineBasedFrameDecoder() {try {ChannelInitializer i = new ChannelInitializer<EmbeddedChannel>() {protected void initChannel(EmbeddedChannel ch) {ch.pipeline().addLast(new LineBasedFrameDecoder(1024));ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new StringProcessHandler());}};EmbeddedChannel channel = new EmbeddedChannel(i);for (int j = 0; j < 100; j++) {//1-3之间的随机数int random = RandomUtil.randInMod(3);ByteBuf buf = Unpooled.buffer();for (int k = 0; k < random; k++) {buf.writeBytes(CONTENT.getBytes("UTF-8"));}buf.writeBytes(SPLITER.getBytes("UTF-8"));channel.writeInbound(buf);}Thread.sleep(Integer.MAX_VALUE);} catch (InterruptedException e) {e.printStackTrace();} catch (UnsupportedEncodingException e) {e.printStackTrace();}}/*** LineBasedFrameDecoder 使用实例*/@Testpublic void testDelimiterBasedFrameDecoder() {try {final ByteBuf delimiter = Unpooled.copiedBuffer(SPLITER_2.getBytes("UTF-8"));ChannelInitializer i = new ChannelInitializer<EmbeddedChannel>() {protected void initChannel(EmbeddedChannel ch) {ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, true, delimiter));ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new StringProcessHandler());}};EmbeddedChannel channel = new EmbeddedChannel(i);for (int j = 0; j < 100; j++) {//1-3之间的随机数int random = RandomUtil.randInMod(3);ByteBuf buf = Unpooled.buffer();for (int k = 0; k < random; k++) {buf.writeBytes(CONTENT.getBytes("UTF-8"));}buf.writeBytes(SPLITER_2.getBytes("UTF-8"));channel.writeInbound(buf);}Thread.sleep(Integer.MAX_VALUE);} catch (InterruptedException e) {e.printStackTrace();} catch (UnsupportedEncodingException e) {e.printStackTrace();}}/*** LineBasedFrameDecoder 使用实例*/@Testpublic void testLengthFieldBasedFrameDecoder() {try {// 1、单字节(无符号):0到255;(有符号):-128到127。
//
//2、双字节(无符号):0到65535;(有符号):-32768 到 32765。
//
//3、四字节(无符号):0到42 9496 7295;(有符号):-21 4748 3648到21 4748 3647。//定义一个 基于长度域解码器final LengthFieldBasedFrameDecoder decoder =new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4);ChannelInitializer i = new ChannelInitializer<EmbeddedChannel>() {protected void initChannel(EmbeddedChannel ch) {ch.pipeline().addLast(decoder);ch.pipeline().addLast(new StringDecoder(Charset.forName("UTF-8")));ch.pipeline().addLast(new StringProcessHandler());}};EmbeddedChannel channel = new EmbeddedChannel(i);for (int j = 0; j < 100; j++) {//1-3之间的随机数int random = RandomUtil.randInMod(3);ByteBuf buf = Unpooled.buffer();byte[] bytes = CONTENT.getBytes("UTF-8");//首先 写入头部  head,也就是后面的数据长度buf.writeInt(bytes.length * random);//然后 写入contentfor (int k = 0; k < random; k++) {buf.writeBytes(bytes);}channel.writeInbound(buf);}Thread.sleep(Integer.MAX_VALUE);} catch (InterruptedException e) {e.printStackTrace();} catch (UnsupportedEncodingException e) {e.printStackTrace();}}/*** LengthFieldBasedFrameDecoder 使用实例*/@Testpublic void testLengthFieldBasedFrameDecoder1() {try {final LengthFieldBasedFrameDecoder spliter =new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4);ChannelInitializer i = new ChannelInitializer<EmbeddedChannel>() {protected void initChannel(EmbeddedChannel ch) {ch.pipeline().addLast(spliter);ch.pipeline().addLast(new StringDecoder(Charset.forName("UTF-8")));ch.pipeline().addLast(new StringProcessHandler());}};EmbeddedChannel channel = new EmbeddedChannel(i);for (int j = 1; j <= 100; j++) {ByteBuf buf = Unpooled.buffer();String s = j + "次发送->" + CONTENT;byte[] bytes = s.getBytes("UTF-8");buf.writeInt(bytes.length);System.out.println("bytes length = " + bytes.length);buf.writeBytes(bytes);channel.writeInbound(buf);}Thread.sleep(Integer.MAX_VALUE);} catch (InterruptedException e) {e.printStackTrace();} catch (UnsupportedEncodingException e) {e.printStackTrace();}}/*** LengthFieldBasedFrameDecoder 使用实例*/@Testpublic void testLengthFieldBasedFrameDecoder2() {try {final LengthFieldBasedFrameDecoder spliter =new LengthFieldBasedFrameDecoder(1024, 0, 4, 2, 6);ChannelInitializer i = new ChannelInitializer<EmbeddedChannel>() {protected void initChannel(EmbeddedChannel ch) {ch.pipeline().addLast(spliter);ch.pipeline().addLast(new StringDecoder(Charset.forName("UTF-8")));ch.pipeline().addLast(new StringProcessHandler());}};EmbeddedChannel channel = new EmbeddedChannel(i);for (int j = 1; j <= 100; j++) {// 分配一个bytebufByteBuf buf = Unpooled.buffer();String s = j + "次发送->" + CONTENT;byte[] bytes = s.getBytes("UTF-8");//首先 写入头部  head,包括 后面的数据长度buf.writeInt(bytes.length);buf.writeChar(VERSION);//然后 写入  contentbuf.writeBytes(bytes);channel.writeInbound(buf);}Thread.sleep(Integer.MAX_VALUE);} catch (InterruptedException e) {e.printStackTrace();} catch (UnsupportedEncodingException e) {e.printStackTrace();}}/*** LengthFieldBasedFrameDecoder 使用实例 3*/@Testpublic void testLengthFieldBasedFrameDecoder3() {try {final LengthFieldBasedFrameDecoder spliter =new LengthFieldBasedFrameDecoder(1024, 2, 4, 4, 10);ChannelInitializer i = new ChannelInitializer<EmbeddedChannel>() {protected void initChannel(EmbeddedChannel ch) {ch.pipeline().addLast(spliter);ch.pipeline().addLast(new StringDecoder(Charset.forName("UTF-8")));ch.pipeline().addLast(new StringProcessHandler());}};EmbeddedChannel channel = new EmbeddedChannel(i);for (int j = 1; j <= 100; j++) {ByteBuf buf = Unpooled.buffer();String s = j + "次发送->" + CONTENT;byte[] bytes = s.getBytes("UTF-8");buf.writeChar(VERSION);buf.writeInt(bytes.length);buf.writeInt(MAGICCODE);buf.writeBytes(bytes);channel.writeInbound(buf);}Thread.sleep(Integer.MAX_VALUE);} catch (InterruptedException e) {e.printStackTrace();} catch (UnsupportedEncodingException e) {e.printStackTrace();}}}

最后总结一下:

本文主要是对netty解码器的理解,核心点在 数据在网络和操作系统间的传播和netty结合模板模式装饰器模式解决netty解码过程中的数据问题。

预告一下,下一篇是18罗汉的第二章,netty编码器。

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

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

相关文章

51单片机学习之旅——定时器

打开软件 1与其它等于其它&#xff0c;0与其它等于0 1或其它等于1&#xff0c;0或其它等于其它 TMODTMOD&0xF0;//0xF01111 0000进行与操作&#xff0c;高四位保持&#xff0c;低四位清零&#xff0c;高四位定时器1&#xff0c;低四位定时器0 TMODTMOD|0x01;//0x010000 0…

内容中台重构智能服务:人工智能技术驱动精准决策

内容概要 现代企业数字化转型进程中&#xff0c;内容中台与人工智能技术的深度融合正在重构智能服务的基础架构。通过整合自然语言处理、知识图谱构建与深度学习算法三大技术模块&#xff0c;该架构实现了从数据采集到决策输出的全链路智能化。在数据层&#xff0c;系统可对接…

【redis】redis内存管理,过期策略与淘汰策略

一&#xff1a;Redis 的过期删除策略及处理流程如下&#xff1a; 1. 过期删除策略 Redis 通过以下两种策略删除过期键&#xff1a; 1.1 惰性删除 触发时机&#xff1a;当客户端访问某个键时&#xff0c;Redis 会检查该键是否过期。执行流程&#xff1a; 客户端请求访问键。…

tp6上传文件大小超过了最大值+验证文件上传大小和格式函数

问题&#xff1a; 最近用tp6的文件上传方法上传文件时报文件过大错误。如下所示&#xff1a; $file $this->request->file(file);{"code": 1,"msg": "上传文件大小超过了最大值&#xff01;","data": {"code": 1,&q…

Kreuzberg:本地OCR+多格式解析!Kreuzberg如何用Python暴力提取30+文档格式?程序员看完直呼内行!

嗨&#xff0c;大家好&#xff0c;我是小华同学&#xff0c;关注我们获得“最新、最全、最优质”开源项目和高效工作学习方法 我们经常需要从各种不同类型的文档中提取文本内容&#xff0c;无论是办公文档、图像还是PDF文件。而Kreuzberg这个Python库的出现&#xff0c;为我们提…

Windows程序设计29:对话框之间的数据传递

文章目录 前言一、父子对话框之间的数据传递1.父窗口获取子窗口数据2.子窗口获取父窗口数据 二、类外函数调用窗口的操作1.全局变量方式2.参数传递方式 总结 前言 Windows程序设计29&#xff1a;对话框之间的数据传递。 在Windows程序设计28&#xff1a;MFC模态与非模态对话框…

【C语言】第八期——指针

目录 1 初始指针 2 获取变量的地址 3 定义指针变量、取地址、取值 3.1 定义指针变量 3.2 取地址、取值 4 对指针变量进行读写操作 5 指针变量作为函数参数 6 数组与指针 6.1 指针元素指向数组 6.2 指针加减运算&#xff08;了解&#xff09; 6.2.1 指针加减具体数字…

为 Power Automate 注册 Adobe PDF Services

前言 最近&#xff0c;再测试如何将HTML转换成PDF&#xff0c;然后发现Adobe有一个免费的操作可以用&#xff0c;好开心&#xff0c;赶紧注册一下。 正文 1.先注册一个账号&#xff0c;然后登录到Adobe Developer 注册链接&#xff1a;https://www.adobe.com/go/getstarted_pow…

BY组态:工业自动化的未来,触手可及

1. BY组态软件的核心优势 简单易用&#xff1a;图形化界面&#xff0c;降低学习成本&#xff0c;快速上手。 高效灵活&#xff1a;支持多种设备协议&#xff0c;兼容性强&#xff0c;适用于多种行业。 实时监控&#xff1a;提供实时数据采集与可视化&#xff0c;助力高效决策…

有哪些开源大数据处理项目使用了大模型

以下是一些使用了大模型的开源大数据处理项目&#xff1a; 1. **RedPajama**&#xff1a;这是一个开源项目&#xff0c;使用了LLM大语言模型数据处理组件&#xff0c;对GitHub代码数据进行清洗和处理。具体流程包括数据清洗、过滤低质量样本、识别和删除重复样本等步骤。 2. …

网络安全之攻防笔记--通用安全漏洞SQL注入sqlmapOraclemongodbDB2

通用安全漏洞SQL注入&sqlmap&Oracle&mongodb&DB2 数据库类型 ACCESS 特性 没数据库用户 没数据库权限 没数据库查询参数 没有高权限注入说法 暴力猜解&#xff0c;借助字典得到数据 注入方式 联合注入 偏移注入 表名列名猜解不到 偏移注入 MySQL 低权限 常…

【信息系统项目管理师-案例真题】2022下半年案例分析答案和详解

更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 试题一(24分)【问题1】(6分)【问题2】(10分)【问题3】(8分)试题二(26分)【问题1】(8分)【问题2】(8分)【问题3】(4分)【问题4】(6分)试题三(25分)【问题1】(12分)【问题2】(7分)【问题…

正点原子[第三期]Arm(iMX6U)Linux系统移植和根文件系统构建-5.3 xxx_defconfig过程

前言&#xff1a; 本文是根据哔哩哔哩网站上“arm(iMX6U)Linux系统移植和根文件系统构键篇”视频的学习笔记&#xff0c;在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。 引用&#xff1a; …

C++初阶——简单实现list

目录 1、前言 2、List.h 3、Test.cpp 1、前言 1. 简单实现std::list&#xff0c;重点&#xff1a;迭代器&#xff0c;模板类&#xff0c;运算符重载。 2. 并不是&#xff0c;所有的类&#xff0c;都需要深拷贝&#xff0c;像迭代器类模板&#xff0c;只是用别的类的资源&am…

conda环境中运行“python --version“所得的版本与环境中的python版本不一致----deepseek并非全能

conda环境中运行python —version所得python版本与conda环境中的python版本不一致------deepseek并非全能 问题 conda环境中运行python —version所得python版本与conda环境中的python版本不一致 我所做的探索 1 网页搜索 2 求助于DeepSeek 可以用四个字来形容deepseek给出…

HarmonyOS学习第5天: Hello World的诞生之旅

鸿蒙初印象&#xff1a;开启探索之门 在操作系统的广袤天地中&#xff0c;HarmonyOS&#xff08;鸿蒙系统&#xff09;宛如一颗冉冉升起的新星&#xff0c;自诞生起便备受瞩目。它由华为倾力打造&#xff0c;是一款基于微内核的全场景分布式操作系统&#xff0c;以其独特的技术…

centos9安装k8s集群

以下是基于CentOS Stream 9的Kubernetes 1.28.2完整安装流程&#xff08;containerd版&#xff09;&#xff1a; 一、系统初始化&#xff08;所有节点执行&#xff09; # 关闭防火墙 systemctl disable --now firewalld# 关闭SELinux sed -i "s/SELINUXenforcing/SELINU…

CIG容器重量级监控系统

1.介绍 CAdvisorinfluxDBGranfana docker 原生命令 监控docker容器状态 docker stats 2.CAdvicsor 3.InfluxDB 4.Granafana 5.搭建 volumes:grafana_data: services:influxdb:image: tutum/influxdbrestart: alwaysenvironment:- PRE_CREATE_DBcadvisorports:- "8083…

REACT学习DAY02(恨连接不上服务器)

受控表单绑定 概念&#xff1a;使用React组件的状态&#xff08;useState&#xff09;控制表单的状态 1. 准备一个React状态值 const [value,setValue] useState() 2. 通过value属性绑定状态&#xff0c;通过onChange属性绑定状态同步的函数 <input type"text&quo…

python——GUI图形用户界面编程

GUI简介 我们前面实现的都是基于控制台的程序&#xff0c;程序和用户的交互通过控制台来完成 本章&#xff0c;我们来学习GUI图形用户界面编程&#xff0c;我们可以通过python提供的丰富的组件&#xff0c;快速的视线使用图形界面和用户交互 GUI变成类似于“搭积木”&#x…