Netty常用招式——ChannelHandler与编解码(一)

本文涉及的产品
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
简介: Netty常用招式——ChannelHandler与编解码(一)

1.学习编解码器,从粘包/拆包开始


1.1为什么会有粘包/拆包


1)MTU 和 MSS 限制


MTU(Maxitum Transmission Unit) 是OSI五层网络模型中 数据链路层 对一次可以发送的最大数据的限制,一般来说大小为 1500 byte。


MSS(Maximum Segement Size) 是指 TCP报文中data部分的最大长度,它是传输层一次发送最大数据的大小限制。

MSS和MTU的关系如下所示:


MSS长度=MTU长度 - IP Header - TCP Header


因此,当 MSS长度 + IP Header + TCP Header > MTU长度 时,就需要拆分多个报文进行发送,会导致“拆包”现象


2)TCP滑动窗口


TCP的流量控制方法就是“滑动窗口”。当A向B发送数据时,B作为接收端会告知发送端A自己可以接受的窗口数值,以此来控制A的发送流量大小,从而达到流量控制的目的。


假设接收方B告知发送方A的窗口大小为256,意味着发送方最多还可以发送256个字节,而由于发送方的数据大小是518字节,因此只能发送前256字节,等到接收方ack后,才能发送剩余字节。会导致“拆包”现象


3)Nagle算法


TCP/IP协议中,无论发送多少大小的数据,都要在数据(DATA)前面加上协议头(TCP Header + IP Header)。如果每次需要发送的数据只有 1 字节,加上 20 个字节 IP Header 和 20 个字节 TCP Header,每次发送的数据包大小为 41 字节,但真正有效的信息只有1个字节,这就造成了非常大的浪费。


因此,TCP/IP中使用Nagle 算法来提高效率。


Nagle 算法核心思想在于“化零为整“。它是在数据未得到确认之前先写入缓冲区,等待数据确认或者缓冲区积攒到一定大小再把数据包发送出去。

多个小数据包合并后一起发送出去,就造成了粘包。


1.2怎么处理粘包/拆包


对于TCP,其实我们都知道它的一个特点就是“面向字节流”的传输协议,本身并没有数据包的界限。所以不管什么原因造成了“粘包/拆包”,TCP协议本身的数据传输是可靠且正确的。


我们首先要明确一点:“粘包/拆包”导致的问题,本质上是应用层的数据解析问题。


因此,解决拆包/粘包问题的核心方法:定义应用层的通信协议。


核心在于定义正确的数据边界。


常见协议的解决方案包括三种:


1)固定长度


每个数据报文都约定一个固定的长度。


当接收方累计读取到固定长度的报文后,就认为已经获得一个完整的消息。


比如我们要发送一个ABCDEFGHIJKLM的消息,约定固定消息长度为4,那么接收方就可以按照4的长度来解析。如下所示。


ABCD

EFGH

IJKL

MN00


当发送方的数据小于固定长度时,比如最后一个数据包,只有MN两个字符,这时候就需要空位补齐。


这种方案非常简单,但是缺点也非常明显,非常不灵活。
如果固定长度定义太长,就会浪费数据传输空间。如果定义太短,就会影响正确的数据传输。
这种方法一般不采用。


2)特定分隔符


除了固定长度外,我们比较容易想到的区分“数据边界”的方法,就是用“特定分隔符”。当接收方读到特定的分隔符,就认为拿到了一个完整的消息。


比如我们使用换行符 \n 来区分。


AB\nCDEFG\nHIJK\nLMN\n


这种方法就比较灵活了,适应不同长度的消息。但是,必须要注意,“特殊分隔符”不能和消息内容重复,否则就会解析失败了。


因此,我们在实践过程中,可以考虑把消息进行编码(如base64),然后用编码字符集之外的符号作为“特定分隔符”。


这种方案一般用在协议比较简单的场景中。


3)消息长度+内容


一般项目开发中,最通用的方式还是采用 消息长度+内容 的方式进行处理。
比如定义一个这样的消息格式:


消息长度(比如4字节长度存储)

消息内容

3

ABC


以这样一个格式存储,消息接收方在解析时,先读取4字节长度的信息作为”消息长度“,这里是3,表示消息长度为3字节。然后就读取3字节的消息内容作为 完整 的消息。


举个例子:


2AB5CDEFG4HIJK3LMN


消息长度+内容 的方式非常灵活,可以应用于各种场景中。


注意,在消息头中,除了定义消息长度外,还可以自定义其他扩展字段,比如消息版本、算法类型等。


2.如何在Netty中实现自定义编解码器


上面我们了解了出现“粘包/拆包”的原因以及常用的解决方法。下面看看如何在Netty中实现自定义编解码器。


Netty作为一个优秀的网络通信框架,已经提供了非常丰富的处理编解码的抽象类,我们只需要自定义编解码算法扩展即可。


2.1自定义编码器


我们先来看看自定义编码器。因为编码器比较简单,不需要关注「粘包/拆包问题」。


常用的编码抽象类包括MessageToByteEncoder 和 MessageToMessageEncoder,继承自ChannelOutboundHandlerAdapter,操作的是Outbound相关数据。

107.png


1)MessageToByteEncoder<I>


这个编码器用于消息对象编码成字节流。它提供了encode的抽象方法,我们只需要实现encode方法,就能进行自定义编码了。


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


我们举一个栗子,将String类型消息转换为字节流:


108.png


2)MessageToMessageEncoder


这个编码器用于将一种消息对象编码成另一种消息对象。这里的第二个Message可以理解为任意一个对象。如果是使用ByteBuf对象的话,就和上面的MessageToByteEncoder是一样的了。


我们找一个Netty自带的栗子看看,StringEncoder:


109.png


2.2自定义解码器


解码器比编码器要复杂一些,因为需要考虑“拆包/粘包”问题。


由于接收方有可能没有接收到完整的消息,所以解码框架需要对入站的数据做缓冲操作,直至获取到完整的消息。


常用的解码器抽象类包括 ByteToMessageDecoder 和 MessageToMessageDecoder,继承自ChannelInboundHandlerAdapter,操作的是Inbbound相关数据。

110.png


一般通用的做法是使用 ByteToMessageDecoder 解析 TCP 协议,解决拆包/粘包问题。解析得到有效的 ByteBuf 数据,然后传递给后续的 MessageToMessageDecoder 做数据对象的转换。


111.png


1)ByteToMessageDecoder


ByteToMessageDecoder解码器用于字节流解码成消息对象。


拿上面的“固定长度法”解决“粘包/拆包”举一个栗子,Netty自带的FixedLengthFrameDecoder。

112.png


通过固定长度frameLength,来对消息进行解析。


生产实践中,可能会使用更加复杂的协议来实现自定义编解码,比如protobuf。


2)MessageToMessageDecoder


MessageToMessageDecoder解码器用于将一种消息对象解码成另一种消息对象。如果你需要对解析后的字节数据做对象模型的转换,这时候便需要用到这个解码器。

目录
相关文章
|
3月前
|
Java 调度
Netty运行原理问题之ChannelHandler在Netty中扮演什么角色
Netty运行原理问题之ChannelHandler在Netty中扮演什么角色
|
6月前
|
网络协议 Java 容器
《跟闪电侠学Netty》阅读笔记 - ChannelHandler 生命周期
《跟闪电侠学Netty》阅读笔记 - ChannelHandler 生命周期
77 0
《跟闪电侠学Netty》阅读笔记 - ChannelHandler 生命周期
|
6月前
|
编解码 开发者
Netty Review - 深入理解Netty: ChannelHandler的生命周期与事件处理机制
Netty Review - 深入理解Netty: ChannelHandler的生命周期与事件处理机制
144 0
|
6月前
|
编解码
Netty Review - 优化Netty通信:如何应对粘包和拆包挑战_自定义长度分包编解码码器
Netty Review - 优化Netty通信:如何应对粘包和拆包挑战_自定义长度分包编解码码器
96 0
|
6月前
|
编解码 JSON 网络协议
Netty使用篇:Http协议编解码
Netty使用篇:Http协议编解码
|
存储 前端开发 Java
Netty 爱好者必看!一文详解 ChannelHandler 家族,助你快速掌握 Netty 开发技巧!
Netty 爱好者必看!一文详解 ChannelHandler 家族,助你快速掌握 Netty 开发技巧!
401 0
|
移动开发 安全 Java
Netty实战(十二)预置的ChannelHandler和编解码器(二)
HTTPS、WebSocket的添加使用和大型数据写入以及几种常见的序列化)
107 0
|
XML 存储 编解码
如何修正Netty编解码的缺陷
如何修正Netty编解码的缺陷
126 0
如何修正Netty编解码的缺陷
|
存储 缓存 NoSQL
跟着源码学IM(十一):一套基于Netty的分布式高可用IM详细设计与实现(有源码)
本文将要分享的是如何从零实现一套基于Netty框架的分布式高可用IM系统,它将支持长连接网关管理、单聊、群聊、聊天记录查询、离线消息存储、消息推送、心跳、分布式唯一ID、红包、消息同步等功能,并且还支持集群部署。
13503 1
|
6月前
|
消息中间件 Oracle Dubbo
Netty 源码共读(一)如何阅读JDK下sun包的源码
Netty 源码共读(一)如何阅读JDK下sun包的源码
132 1