记录Netty LengthFieldBasedFrameDecoder解决TCP粘包与拆包的用法详解

前言
LengthFieldBasedFrameDecoder类是Netty提供的用来解析带长度字段数据包的类,继承自ByteToMessageDecoder类。

一、粘包与拆包问题

大意是,TCP发送的数据顺序是符合不会变但是会有拆分和合并,对端接收到的数据包不是发送方的分包格式,所以一般的做法是在包头固定字节出加入长度域字段,指明包长度,有接受方根据包长度拼接或者剪裁收到的数据形成完整的数据包。

二、LengthFieldBasedFrameDecoder的用法

用一个具体的报文来看
|aaaa| |bbcc| |消息内容|
其中aaaa是数据头,用来标记包的开头,bbcc是长度域字段(数据长度),在这里要注意,长度域字段可能有两种情况,一种是长度域字段是整个包的长度;另一种是长度域字段值是从长度域字段位置起(不包含长度域字段本身),到包末尾的长度。Netty默认是第二种情况。

LengthFieldBasedFrameDecoder就是Netty自带的,用来根据长度字段解析数据包的类,LengthFieldBasedFrameDecoder的完整构造参数如下:

public LengthFieldBasedFrameDecoder(
ByteOrder byteOrder,
int lengthFieldOffset,
int lengthFieldLength,
int lengthAdjustment,
int initialBytesToStrip,
boolean failFast)

byteOrder:是指明长度域字段是大端序还是小端序,因为Netty要读取长度域字段的值,所以大端小端要设置好,默认Netty是大端序ByteOrder.BIG_ENDIAN。(即高位在前为大端模式,低位在前为小端模式)

maxFrameLength:是指最大包长度,如果Netty最终生成的数据包超过这个长度,Netty就会报错。

lengthFieldOffset:是指明Length的偏移位,在这里应该是2,因为长度域前2个字节。

lengthFieldLength:是Length字段长度,这里是2,Length字段占2个字节。
注:长度域字段采用16进制且只支持1,2,3,4,8这五种长度,如果不是16进制或这五种长度需自定义编解码,继承ByteToMessageDecoder类,重写getUnadjustedFrameLength方法。

lengthAdjustment :这个参数很多时候设为负数,这是最让小伙伴们迷惑的。下面我用一整段话来解释这个参数:
当Netty利用lengthFieldOffset(偏移位)和lengthFieldLength(Length字段长度)成功读出Length字段的值后,Netty认为这个值是指从Length字段之后,到包结束一共还有多少字节,如果这个值是13,那么Netty就会再等待13个Byte的数据到达后,拼接成一个完整的包。但是更多时候,Length字段的长度,是指整个包的长度,如果是这种情况,当Netty读出Length字段的时候,它已经读取了包的4个Byte的数据,所以,后续未到达的数据只有9个Byte,即13 - 4 = 9,这个时候,就要用lengthAdjustment来告诉Netty,后续的数据并没有13个Byte,要减掉4个Byte,所以lengthAdjustment要设为 -4!!!

initialBytesToStrip之前的几个参数,已经足够netty识别出整个数据包了,但是很多时候,调用者只关心包的内容,包的头部完全可以丢弃掉,initialBytesToStrip就是用来告诉Netty,识别出整个数据包之后,我只要从initialBytesToStrip起的数据,比如这里initialBytesToStrip设置为4,那么Netty就会跳过包头,解析出包的内容“12345678”。

failFast 参数一般设置为true,当这个参数为true时,netty一旦读到Length字段,并判断Length超过maxFrameLength,就立即抛出异常。

lengthAdjustment计算公式
数据包总长度 = 长度域的值 + lengthFieldOffset + lengthFieldLength + lengthAdjustment

如果是小端模式,且长度域为长度域字段位置起(不包含长度域字段本身),到包末尾

@Component
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Autowired
    ServerChannelHandler serverChannelHandler;

    @Override
    protected void initChannel(SocketChannel socketChannel) {
        ChannelPipeline pipeline = socketChannel.pipeline();
        pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.LITTLE_ENDIAN,1024, 2, 2, 0, 0,true));
        //自定义Handler
        pipeline.addLast("serverChannelHandler", serverChannelHandler);
    }
}

如果是大端模式,且长度域为整个包的长度

@Component
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Autowired
    ServerChannelHandler serverChannelHandler;

    @Override
    protected void initChannel(SocketChannel socketChannel) {
        ChannelPipeline pipeline = socketChannel.pipeline();
        pipeline.addLast(new LengthFieldBasedFrameDecoder(ByteOrder.BIG_ENDIAN,1024, 2, 2, -4, 0,true));
        //自定义Handler
        pipeline.addLast("serverChannelHandler", serverChannelHandler);
    }
}

参数设置错误时,会出现以下错误

错误原因:Adjusted frame length exceeds 1024: 3852 - discarded

参考链接:https://blog.csdn.net/zougen/article/details/79037675

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Netty中的TCP粘包拆包问题是由于底层的TCP协议无法理解上层的业务数据而导致的。为了解决这个问题,Netty提供了几种解决方案。其中,常用的解决方案有四种[1]: 1. 固定长度的拆包器(FixedLengthFrameDecoder):将每个应用层数据包拆分成固定长度的大小。这种拆包器适用于应用层数据包长度固定的情况。 2. 行拆包器(LineBasedFrameDecoder):将每个应用层数据包以换行符作为分隔符进行分割拆分。这种拆包器适用于应用层数据包以换行符作为结束符的情况。 3. 分隔符拆包器(DelimiterBasedFrameDecoder):将每个应用层数据包通过自定义的分隔符进行分割拆分。这种拆包器适用于应用层数据包以特定分隔符作为结束标志的情况。 4. 基于数据包长度的拆包器(LengthFieldBasedFrameDecoder):将应用层数据包的长度作为接收端应用层数据包的拆分依据。根据应用层协议中包含的数据包长度进行拆包。这种拆包器适用于应用层协议中包含数据包长度的情况。 除了使用这些拆包器,还可以根据业界主流协议的解决方案来解决粘包拆包问题[3]: 1. 消息长度固定:累计读取到长度和为定长LEN的报文后,就认为读取到了一个完整的信息。 2. 使用特殊的分隔符:将换行符或其他特殊的分隔符作为消息的结束标志。 3. 在消息头中定义长度字段:通过在消息头中定义长度字段来标识消息的总长度。 综上所述,Netty提供了多种解决方案来解决TCP粘包拆包问题,可以根据具体的业务需求选择合适的解决方案[1][3]。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值