LengthFieldBasedFrameDecoder
1. LengthFieldBasedFrameDecoder是什么LengthFieldBasedFrameDecoder是 Netty 中的一个解码器用于处理粘包和拆包情况。 它能根据指定的长度字段解析数据帧将输入的字节流分割成一系列固定大小的帧 Frames并且每个帧的大小可以根据帧头信息中指定的长度进行动态调整。 通过这种方式LengthFieldBasedFrameDecoder 能够自动地识别和处理 TCP 协议中存在的粘包和拆包情况。2. 参数publicLengthFieldBasedFrameDecoder(intmaxFrameLength,intlengthFieldOffset,intlengthFieldLength,intlengthAdjustment,intinitialBytesToStrip,booleanfailFast){this(ByteOrder.BIG_ENDIAN,maxFrameLength,lengthFieldOffset,lengthFieldLength,lengthAdjustment,initialBytesToStrip,failFast);}publicLengthFieldBasedFrameDecoder(ByteOrderbyteOrder,intmaxFrameLength,intlengthFieldOffset,intlengthFieldLength,intlengthAdjustment,intinitialBytesToStrip,booleanfailFast)2.1. byteOrder表示协议中Length字段的字节是大端还是小端2.2. maxFrameLength表示协议中Content字段的最大长度如果超出则抛出TooLongFrameException异常。 maxFrameLength不仅是性能参数更是安全防线。 设得太小会导致合法数据被拒绝太大则可能引发 OOM。 经验值 内网服务10MB 上限 公网 API1MB 上限 IoT 设备64KB 上限2.3. lengthFieldOffset表示Length字段的偏移量即在读取一个二进制流时跳过指定长度个字节之后的才是Length字段。 如果Length字段之前没有其他报文头指定为0即可。 如果Length字段之前还有其他报文头则需要跳过之前的报文头的字节数。2.4. lengthFieldLength表示Length字段占用的字节数。 指定为多少需要看实际要求不同的字节数限制了Content字段的最大长度。 如果lengthFieldLength是1个字节那么限制为128bytes 如果lengthFieldLength是2个字节那么限制为32767bytes(约等于32K) 如果lengthFieldLength是3个字节那么限制为8388608bytes(约等于8M) 如果lengthFieldLength是4个字节那么限制为2147483648bytes(约等于2G)。 lengthFieldLength与maxFrameLength并不冲突。 例如我们现在希望限制报文Content字段的最大长度为32M。 显然我们看到了上面的四种情况没有任何一个值能刚好限制Content字段最大值刚好为32M。 那么我们只能指定lengthFieldLength为4个字节其最大限制2G是大于32M的因此肯定能支持。 但是如果Content字段长度真的是2G server 端接收到这么大的数据如果都放在内存中很容易造成内存溢出。 为了避免这种情况我们就可以指定maxFrameLength字段来精确的指定Content部分最大字节数显然其值应该小于lengthFieldLength指定的字节数最大可以表示的值。场景lengthFieldLength典型协议示例短消息协议1SMS, 即时通讯传统二进制协议2Modbus, 游戏协议自定义企业协议4金融交易系统超大文件传输8视频流分片协议2.5. lengthAdjustmentLength字段补偿值。 对于绝大部分协议来说Length字段的值表示的都是Content字段占用的字节数。 但是也有一些协议Length字段表示的是Length字段本身占用的字节数Content字段占用的字节数。 由于Netty中在解析Length字段的值是默认是认为其只表示Content字段的长度因此解析可能会失败所以要进行补偿。 主要用于处理Length字段前后还有其他报文头的情况。 当 lengthAdjustment 计算错误时最常见的症状是解析出的数据要么缺少尾部要么包含了下个包的头部。2.6. initialBytesToStrip解码后跳过的初始字节数表示获取完一个完整的数据报文之后忽略前面指定个数的字节。 例如报文头只有Length字段占用2个字节在解码后我们可以指定跳过2个字节。 这样封装到ByteBuf中的内容就只包含Content字段的字节内容不包含Length字段占用的字节。2.7. failFast如果为true则表示读取到Length字段时如果其值超过maxFrameLength就立马抛出一个 TooLongFrameException 而为false表示只有当真正读取完长度域的值表示的字节之后才会抛出 TooLongFrameException 默认情况下设置为true建议不要修改否则可能会造成内存溢出。3. 案例3.1.1. 公共代码/** * 执行长度字段解码器测试并返回解码后的原始字节数组 * * param decoder 待测试的长度字段解码器 * param testByteBuf 测试用的二进制报文 * return 解码完成后的字节数据 */privatebyte[]decodeByLengthFieldDecoder(LengthFieldBasedFrameDecoderdecoder,ByteBuftestByteBuf){// 固定模板逻辑EmbeddedChannelchannelnewEmbeddedChannel(newLoggingHandler(【解码前 - 原始报文】,LogLevel.DEBUG),// 1. 先打印原始数据decoder,// 2. 再解码newLoggingHandler(【解码后 - 结果报文】,LogLevel.DEBUG)// 3. 后打印解码结果);//EmbeddedChannel channel new EmbeddedChannel(decoder, new LoggingHandler(LogLevel.DEBUG));channel.writeInbound(testByteBuf);ByteBufresultBufchannel.readInbound();byte[]resultBytesnewbyte[resultBuf.readableBytes()];resultBuf.readBytes(resultBytes);// 资源释放resultBuf.release();channel.close();returnresultBytes;}3.2. lengthAdjustment03.2.1. 协议编码前 (16 bytes) 编码后 (16 bytes) ---------------------------- ---------------------------- | Length | Actual Content |-----| Length | Actual Content | | 0x0000000C | Hello, World | | 0x0000000C | Hello, World | ---------------------------- ----------------------------3.2.2. 参数设置lengthFieldOffset 0 //因为报文以Length字段开始不需要跳过任何字节所以offset为0 lengthFieldLength 4 //因为我们规定Length字段占用字节数为2所以这个字段值传入的是2 lengthAdjustment 0 //这里Length字段值不需要补偿因此设置为0 initialBytesToStrip 0 //不跳过初始字节意味着解码后的ByteBuf中包含LengthContent所有内容3.2.3. 代码示例Testpublicvoidtest00(){LengthFieldBasedFrameDecoderdecodernewLengthFieldBasedFrameDecoder(1024,// maxFrameLength0,// lengthFieldOffset4,// lengthFieldLength0,// lengthAdjustment0// initialBytesToStrip);byte[]bytesHello, World.getBytes(StandardCharsets.UTF_8);ByteBufbyteBufUnpooled.buffer();byteBuf.writeInt(bytes.length);byteBuf.writeBytes(bytes);decodeByLengthFieldDecoder(decoder,byteBuf);}3.3. lengthAdjustment03.3.1. 协议BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes) ------------------------------------ ---------------------- | HDR1 | Length | HDR2 | Actual Content |-----| HDR2 | Actual Content | | 0xCA | 0x000C | 0xFE | HELLO, WORLD | | 0xFE | HELLO, WORLD | ------------------------------------ ----------------------3.3.2. 参数设置lengthFieldOffset 1 //跳过HDR1占用的1个字节读取Length lengthFieldLength 2 //Length字段占用2个字段其值为0x000C(12)表示Content字段长度 lengthAdjustment 1 //由于Length字段之后还有HDR2字段因此需要1个字节读取HDR2Content的内容 initialBytesToStrip 3 //解码后跳过前3个字节3.3.3. 代码示例Testpublicvoidtest01(){LengthFieldBasedFrameDecoderdecodernewLengthFieldBasedFrameDecoder(1024,// maxFrameLength1,// lengthFieldOffset2,// lengthFieldLength1,// lengthAdjustment3// initialBytesToStrip);byte[]bytesHello, World.getBytes(StandardCharsets.UTF_8);ByteBufbyteBufUnpooled.buffer();byteBuf.writeByte(0xCA);byteBuf.writeShort(bytes.length);byteBuf.writeByte(0xFE);byteBuf.writeBytes(bytes);decodeByLengthFieldDecoder(decoder,byteBuf);}3.4. lengthAdjustment03.4.1. 协议BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes) ------------------------------------ ---------------------- | HDR1 | Length | HDR2 | Actual Content |-----| HDR2 | Actual Content | | 0xCA | 0x0010 | 0xFE | HELLO, WORLD | | 0xFE | HELLO, WORLD | ------------------------------------ ----------------------3.4.2. 参数设置lengthFieldOffset 1 //跳过HDR1占用的1个字节读取Length lengthFieldLength 2 //Length字段占用2个字段其值为0x0010(16)表示HDR1LengthHDR2Content长度 lengthAdjustment -3 //由于Length表示的是整个报文的长度减去HDR1Length占用的3个字节后读取HDR2Content长度 initialBytesToStrip 3 //解码后跳过前3个字节3.4.3. 代码示例Testpublicvoidtest02(){LengthFieldBasedFrameDecoderdecodernewLengthFieldBasedFrameDecoder(1024,// maxFrameLength1,// lengthFieldOffset2,// lengthFieldLength-3,// lengthAdjustment3// initialBytesToStrip);byte[]bytesHello, World.getBytes(StandardCharsets.UTF_8);ByteBufbyteBufUnpooled.buffer();byteBuf.writeByte(0xCA);//包括自己的长度byteBuf.writeShort(bytes.length4);byteBuf.writeByte(0xFE);byteBuf.writeBytes(bytes);decodeByLengthFieldDecoder(decoder,byteBuf);}参考LengthFieldBasedFrameDecoder当LengthFieldBasedFrameDecoder解码失败一份完整的Netty帧解码器问题排查清单浅谈 LengthFieldBasedFrameDecoder如何实现可靠的消息分割