背景:最近使用Netty来做项目,过程中与服务器通信出现了Tcp协议的沾包/半包问题。其中,我还被大端小端给坑了一次,特写此文记录一下自己的解决办法。
说明:一般来说,使用Tcp进行通信会出现沾包/半包现象,这是因为Tcp协议是流式协议,没有明确的消息帧的分界线。
TCP消息格式
图片解读:一个消息帧(frame)由 消息头(Head) 和 消息体(Body) 组成。其中,消息头总长度由长度域(Length)和一个 其他(others)组成,其总长度固定。
Length:长度域,一般代表消息体(body)的字节数,也就是长度,一般是大端模式(指低字节存放长度的高位)。
others: 消息头的其他组成部分,一般用于区分消息的用途。这个根据项目需求来设计。
以上就是我使用的消息帧格式,但是如何确保每次接收到的消息帧都是完整的呢,这就要用到我们的主角 LengthFieldBasedFrameDecoder() 解码。
LengthFieldBasedFrameDecoder() 解码器
简介:该解码器是Netty提供给开发者的解码器之一,其使用场景是用于区分本文中提供的这种消息帧格式。下图是其多个构造函数
其参数比较多,我们一一介绍。
参数 | 作用 |
---|---|
@NotNull ByteOrder byteOrder | 这是一个隐藏的参数,用于表示,长度域的大小端模式,如果不填写,默认为大端模式。比如你的长度域是大端模式(低字节放置int的高位),则该参数设置为ByteOrder.BIG_ENDIAN;反之,小端模式设置为,ByteOrder.LITTLE.ENDIAN。这里需要注意的是,若使用该参数,则必须使用图中参数最多的构造函数 |
int maxFrameLength | 代表每一个消息帧的最大支持长度,根据自己项目需要设计,可设置大一些,设置小了,如果消息总长度超出这个值,Netty会报错 |
int lengthFieldOffset | 长度域的起始位置下标,比如Head的头四个字节代表了body的长度,那么长度域就是从下标为0的位置开始,此时该值为0 |
int lengthFieldLength | 长度域的长度,也就是说长度域占用了几个字节,比如我占用了4个字节表示body,那么,此时为4 |
int lengthAdjustment | 该参数简单来说,就是指Head里除了长度域还剩下几位,比如我的Head由Length和others组成,Length占用4个字节,others占用10个字节,那么这个参数就是10,这个参数的作用就是告诉Netty你的Head的长度是多少 |
int initialBytesToStrip | 这个参数是指,解码器解码后,你需要多少数据,就是指舍弃的字节的数量,这里是从前到后舍弃的。比如说,解码后我只想要整个消息帧的body段,那么你就要舍弃掉head的长度,也就说这个数就填写head + others共占用的字节数量,在本文中,我填写的是14 |
boolean failFast | 默认为True。这个参数为True:当Netty监测到接收到的字节数量大于maxFrameLength时,立马thrown处一个Exception。 设置为False:当Netty监测到接收到的字节数量大于maxFrameLength后,等到把全部数量接收完,再thrown出一个Exception,这里一般我们设置为true即可 |
比如说,我使用的协议是, Head长度为14个字节,其中长度域从下标0开始,占用4个字节;others从下标4开始,占用10个字节。剩下的就是body。而且我的长度域是小端模式,另外解码后我不想舍弃掉任何字节。那么我们的参数这样设置即可。
private final LengthFieldBasedFrameDecoder FRAME_DECODER =
new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN,1024,0,4,32,0,true);
以上文章,如有纰漏或错误,还望指正。
如若转载,请注明出处。