LengthFieldBasedFrameDecoder使用示例

本文详细介绍了Netty中的LengthFieldBasedFrameDecoder解码器,并通过自定义的CustomMsg类来展示如何使用该解码器进行消息的解析与编码。
转载自:http://blog.youkuaiyun.com/z69183787/article/details/52980699
              http://blog.163.com/linfenliang@126/blog/static/127857195201210821145721/

       LengthFieldBasedFrameDecoder是Netty中一个很重要的解码器,因为相比于其他的普通的解码器,这个解码器用的场景更多。

定义一个这样的协议类:
public class CustomMsg {  
      
    //类型  系统编号 0xAB 表示A系统,0xBC 表示B系统  
    private byte type;  
      
    //信息标志  0xAB 表示心跳包    0xBC 表示超时包  0xCD 业务信息包  
    private byte flag;  
      
    //主题信息的长度  
    private int length;  
      
    //主题信息  
    private String body;  
      
    ……
}  
规定两个系统通过Netty去发送这样的一个格式的信息,CustomMsg中包含这样的几类信息:type表示发送端的系统类型;flag表示发送信息的类型,是业务数据,还是心跳包数据;length表示主题body的长度;body表示主题信息。
LengthFieldBasedFrameDecoder的构造方法中有几个重要的参数:
maxFrameLength:解码时,处理每个帧数据的最大长度

lengthFieldOffset :存放帧数据的长度数据的起始位(偏移位) 

lengthFieldLength:长度属性的长度,即存放整个大数据包长度的字节所占的长度

lengthAdjustmen:长度调节值,在总长被定义为包含包头长度时,修正信息长度。

initialBytesToStrip:跳过的字节数,根据需要跳过lengthFieldLength个字节,以便接收端直接接受到不含“长度属性”的内容

failFast :为true,当frame长度超过maxFrameLength时立即报TooLongFrameException异常,为false,读取完整个帧再报异常
简单举个例子说下含义,例如对于数据:
  BEFORE DECODE (14 bytes)
+---------+----------------+
 | Length | Actual Content|
 | 0x000C | "HELLO, WORLD"|
+---------+----------------+  
0x000C表示有效数据是12个字节,如下设置时:
lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = 0
initialBytesToStrip = 0
解析完数据为:
| 0x000C | "HELLO, WORLD" |
此时数据格式不做任何改变(没有跳过任何字节)。

如下设置时:
lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = 0
initialBytesToStrip = 2
解析完数据为:
| "HELLO, WORLD" |
但由于前两个(lengthFieldOffset = 0,lengthFieldLength = 2)字节是表示帧长度的字节,不计入数据,故真实的数据是除去了长度数据后剩下的12个字节。

例如对于数据:
  BEFORE DECODE (14 bytes)
+---------+----------------+
 | Length | Actual Content|
 | 0x000E | "HELLO, WORLD"|
+---------+----------------+  
此处定义的Length为0x000E,占了两个字节,表示的整个帧长度为14(2 + 12)个字节。如下设置时:
lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = -2
initialBytesToStrip = 0
解析完数据为:
| 0x000E | "HELLO, WORLD" |
由于设置的lengthAdjustment = -2 (即 the length of the Length field),故修正的信息实际长度补2,即解码时往前推2个字节,解码后还是14个字节长度。更具体的还请自行百度。


现在自定义一个decoder,这个类继承LengthFieldBasedFrameDecoder,来解析刚才定义的CustomMsg:
public class CustomDecoder extends LengthFieldBasedFrameDecoder {  
      
    //判断传送客户端传送过来的数据是否按照协议传输,头部信息的大小应该是 byte+byte+int = 1+1+4 = 6  
    private static final int HEADER_SIZE = 6;  
      
    private byte type;  
      
    private byte flag;  
      
    private int length;  
      
    private String body;  
  
    public CustomDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength,  
            int lengthAdjustment, int initialBytesToStrip, boolean failFast) {  
        super(maxFrameLength, lengthFieldOffset, lengthFieldLength,  
                lengthAdjustment, initialBytesToStrip, failFast);  
    }  
      
    @Override  
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {  
        if (in == null) {  
            return null;  
        }  
        if (in.readableBytes() < HEADER_SIZE) {  
            throw new Exception("可读信息段比头部信息都小,你在逗我?");  
        }  
          
        //注意在读的过程中,readIndex的指针也在移动  
        type = in.readByte();  
          
        flag = in.readByte();  
          
        length = in.readInt();  
          
        if (in.readableBytes() < length) {  
            throw new Exception("body字段你告诉我长度是"+length+",但是真实情况是没有这么多,你又逗我?");  
        }  
        ByteBuf buf = in.readBytes(length);  
        byte[] req = new byte[buf.readableBytes()];  
        buf.readBytes(req);  
        body = new String(req, "UTF-8");  
          
        CustomMsg customMsg = new CustomMsg(type,flag,length,body);  
        return customMsg;  
    }  
  
}  
头部信息的大小是6,因为byte是一个字节,int是四个字节,那么头部大小就是6个字节,构造函数的入参数据是:
maxFrameLength = 1024 * 1024
lengthFieldOffset = 2
lengthFieldLength = 4
lengthAdjustment = 0
initialBytesToStrip = 0
lengthFieldLength指的就是CustomMsg中length这个属性的大小,是int型,所以是4;lengthFieldOffset指的就是length字段的起始位置,因为前面有type和flag两个属性,且这两个属性都是byte,两个就是2字节,所以偏移量是2;lengthAdjustment指的是length这个属性的值,假如body长度是40,有时候,有些人喜欢将length写成44,因为length本身还占有4个字节,这样就需要调整一下,那么就需要-4,我们这边没有这样做,所以写0就可以了。

自定义一个encoder来编码CustomMsg,代码如下:
public class CustomEncoder extends MessageToByteEncoder<CustomMsg> {  
  
    @Override  
    protected void encode(ChannelHandlerContext ctx, CustomMsg msg, ByteBuf out) throws Exception {  
        if(null == msg){  
            throw new Exception("msg is null");  
        }  
          
        String body = msg.getBody();  
        byte[] bodyBytes = body.getBytes(Charset.forName("utf-8"));  
        out.writeByte(msg.getType());  
        out.writeByte(msg.getFlag());  
        out.writeInt(bodyBytes.length);  
        out.writeBytes(bodyBytes);  
    
    }  
}  


### UDP协议中使用 `LengthFieldBasedFrameDecoder` 的方法 在UDP协议中,虽然其本身是面向无连接的、基于数据报的传输方式,不会像TCP那样出现粘包或拆包问题。然而,在某些特定场景下(如多个消息被封装在一个UDP数据报中),仍然可能需要处理类似的问题。此时可以借助 Netty 提供的 `LengthFieldBasedFrameDecoder` 来解析具有长度字段的消息格式。 #### 数据帧结构要求 `LengthFieldBasedFrameDecoder` 适用于自定义协议中包含长度字段的情况,通过该字段确定完整消息的长度,从而正确地将消息从数据流中分割出来。这种机制非常适合用于处理封装了多条消息的UDP数据报[^2]。 假设每个UDP数据报中包含一条或多条固定格式的消息: ``` +----------------+------------------+ | 长度字段 (4字节)| 实际消息内容 | +----------------+------------------+ ``` 其中,长度字段表示整个消息的长度(包括长度字段自身)。 #### 配置示例 为了正确解析上述结构的消息,可以在Netty的 `ChannelPipeline` 中添加 `LengthFieldBasedFrameDecoder`,配置如下参数: - `maxFrameLength`:设置最大允许的数据帧长度。 - `lengthFieldOffset`:指定长度字段在数据帧中的起始偏移量。 - `lengthFieldLength`:指定长度字段的字节数。 - `lengthAdjustment`:调整长度字段值以获取实际消息体长度。 - `initialBytesToStrip`:解码后跳过的前导字节数(通常为长度字段的大小)。 ```java int maxFrameLength = 1024; int lengthFieldOffset = 0; // 长度字段位于数据帧起始位置 int lengthFieldLength = 4; // 长度字段占4个字节 int lengthAdjustment = 0; // 长度字段直接表示整个数据帧的长度 int initialBytesToStrip = 4; // 跳过前4个字节(即长度字段) pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder( maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip )); ``` #### 处理逻辑说明 当UDP数据报到达时,`LengthFieldBasedFrameDecoder` 会首先读取长度字段,判断当前数据是否完整。如果当前数据不足,则等待后续数据到达;如果数据完整,则提取出对应长度的消息,并跳过指定的前导字节(如长度字段),将有效数据传递给后续处理器[^3]。 这种方式可以很好地应对UDP数据报中包含多条消息或部分消息的情况,确保每条消息都能被正确解析。 #### 注意事项 - **长度字段需准确**:发送方必须严格按照协议格式填写长度字段,否则可能导致解析失败。 - **数据完整性依赖应用层**:由于UDP本身不保证数据的可靠传输,因此仍需在应用层进行校验和重传等处理。 - **合理设置 `maxFrameLength`**:避免因接收超大帧而导致内存溢出等问题。 ###
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值