关于LengthFieldBasedFrameDecoder的一些记录
使用网络编程就必定会遇到半包粘包问题,Netty
的LengthFieldBasedFrameDecoder
是个很好的解决方案。
LengthFieldBasedFrameDecoder
遵循LTC
解码方式,可以根据不同的构造方式解析不同的数据包。
# 常用构造属性
参数 | 描述 |
---|---|
maxFrameLength | 帧最大长度 |
lengthFieldOffset | 长度偏移量 偏移多少位后开始表示长度 |
lengthFieldLength | 长度域字段所占的字节数 |
lengthAdjustment | 数据长度修正 因为长度域可以表示数据包长度,也可以只表示数据内容长度,如果表示的是整个数据包长度,那么我们需要修正数据长度。 如果为正:代表长度和内容之前有2位额外信息,需要长度需要修正: +2 如果为负:代表 lengthFieldLength 表示整个数据包长度,假如lengthFieldLength占用4个字节值为15,那么实际数据长度= 15-4 需要修正:-4 |
initialBytesToStrip | 当一个数据包我们不需要数据包的部分信息的时候,可以这部分信息进行剥离, 这个值表示从数据包起始位置截取的位数 |
# 构造函数解析
通常我们使用的构造函数如下:
new LengthFieldBasedFrameDecoder(maxFrameLength,lengthFieldOffset,lengthFieldLength,lengthAdjustment,initialBytesToStrip)
首先提供一段测试代码:
点击查看
@Slf4j
public class LengthFieldBasedFrameTest extends MessageToMessageCodec<ByteBuf,ByteBuf> {
/**
*帧最大长度
*/
private final static int MAX_FRAME_LENGTH = 64;
/**
* 长度偏移量
* 偏移多少位后开始表示长度
*/
private final static int LENGTH_FIELD_OFFSET = 0;
/**
* 长度字段所占的字节数
*/
private final static int LENGTH_FIELD_LENGTH = 2;
/**
* 长度调整
* 如果为正:表示长度后面n个字节才是内容
* 如果为负:表示内容部分需要往前取n个字节
*/
private final static int LENGTH_ADJUSTMENT = 0;
/**
* 需要剥离字符
* 需要截取数据包位数,从数据包起始部分截取
*/
private final static int INITIAL_BYTES_TO_STRIP = 0;
/**
* 是否有额外头
*/
private final static boolean HAS_HEARD = false;
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
log.info("开始编码:{}",msg);
ByteBuf buffer = ctx.alloc().buffer();
buffer.writeBytes(msg);
out.add(buffer);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
log.info("开始解码:{}",in.toString(StandardCharsets.UTF_8));
ByteBuf buffer = in.alloc().buffer();
buffer.writeBytes(in);
out.add(buffer);
}
public static void main(String[] args) throws Exception {
EmbeddedChannel channel = new EmbeddedChannel(
new LoggingHandler(LogLevel.DEBUG),
new LengthFieldBasedFrameDecoder(MAX_FRAME_LENGTH, LENGTH_FIELD_OFFSET, LENGTH_FIELD_LENGTH, LENGTH_ADJUSTMENT, INITIAL_BYTES_TO_STRIP),
new LoggingHandler(LogLevel.DEBUG),
new LengthFieldBasedFrameTest(),
new SimpleChannelInboundHandler<ByteBuf>(){
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf pushMessage) throws Exception {
log.info("接受到消息 pushMessage :{}",pushMessage.toString(StandardCharsets.UTF_8));
}
}
);
byte[] bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".getBytes(StandardCharsets.UTF_8);
//长度偏移填充
ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer();
if(LENGTH_FIELD_OFFSET > 0){
for (int i = 0; i < LENGTH_FIELD_OFFSET; i++) {
buffer.writeByte(bytes[0]);
}
}
//长度
int size = 15;
buffer.writeInt(size);
//长度调整填充
if(HAS_HEARD){
for (int i = 0; i < Math.abs(LENGTH_ADJUSTMENT); i++) {
buffer.writeByte(bytes[1]);
}
}
//内容填充
for (int i = 0; i < size; i++) {
buffer.writeByte(bytes[i%bytes.length]);
}
channel.writeInbound(buffer);
channel.writeInbound(buffer);
}
# 以下是不同构造参数达到的效果
# 只定义数据长度
private final static int LENGTH_FIELD_OFFSET = 0;
private final static int LENGTH_FIELD_LENGTH = 4;
private final static int LENGTH_ADJUSTMENT = 0;
private final static int INITIAL_BYTES_TO_STRIP = 0;
只定义数据长度占4位
LengthFieldBasedFrameTest 处理前:
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 00 00 0f 41 42 43 44 45 46 47 48 49 4a 4b 4c |....ABCDEFGHIJKL|
|00000010| 4d 4e 4f |MNO |
+--------+-------------------------------------------------+----------------+
LengthFieldBasedFrameTest 处理后:
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 00 00 0f 41 42 43 44 45 46 47 48 49 4a 4b 4c |....ABCDEFGHIJKL|
|00000010| 4d 4e 4f |MNO |
+--------+-------------------------------------------------+----------------+
可以看到因为LENGTH_FIELD_OFFSET
为0,所以开始位置即为长度,一共占4位分别对应00 00 00 0f
,也就是15。
然后取了15位字符:ABCDEFGHIJKLMNO
数据包读取完毕。
# 截取数据,只获取内容
private final static int LENGTH_FIELD_OFFSET = 0;
private final static int LENGTH_FIELD_LENGTH = 4;
private final static int LENGTH_ADJUSTMENT = 0;
private final static int INITIAL_BYTES_TO_STRIP = 4;
对于部分数据包,我们只需要内容,并不想知道它的数据长度,时候可以用INITIAL_BYTES_TO_STRIP
剥离数据包的长度。
LengthFieldBasedFrameTest 处理前:
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 00 00 0f 41 42 43 44 45 46 47 48 49 4a 4b 4c |....ABCDEFGHIJKL|
|00000010| 4d 4e 4f |MNO |
+--------+-------------------------------------------------+----------------+
LengthFieldBasedFrameTest 处理后:
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f |ABCDEFGHIJKLMNO |
+--------+-------------------------------------------------+----------------+
此示例在上面的基础上新增了INITIAL_BYTES_TO_STRIP
,也就是说剥离4个字节,所以长度所占用的4个字节被剥离,只剩下了ABCDEFGHIJKLMNO
# 数据长度位整个数据包长度
private final static int LENGTH_FIELD_OFFSET = 0;
private final static int LENGTH_FIELD_LENGTH = 4;
private final static int LENGTH_ADJUSTMENT = -4;
private final static int INITIAL_BYTES_TO_STRIP = 0;
注意
在上面数据包定义中,长度表示的是数据内容长度
但是在某一些数据包中,长度表示的是数据包总长度,上面的示例中:
数据包长度15,所以读取到了数据:ABCDEFGHIJKLMNO
,其中长度自身占了4位,需要修正所以-4,那么实际的数据包长度应该为:15-4=11
,即数据:ABCDEFGHIJK
其中:LENGTH_FIELD_OFFSET
不计入总长度
LengthFieldBasedFrameTest 处理前:
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 00 00 0f 41 42 43 44 45 46 47 48 49 4a 4b 4c |....ABCDEFGHIJKL|
|00000010| 4d 4e 4f |MNO |
+--------+-------------------------------------------------+----------------+
LengthFieldBasedFrameTest 处理后:
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 00 00 00 0f 41 42 43 44 45 46 47 48 49 4a 4b |....ABCDEFGHIJK |
+--------+-------------------------------------------------+----------------+
# 定义一个协议标识
private final static int LENGTH_FIELD_OFFSET = 2;
private final static int LENGTH_FIELD_LENGTH = 4;
private final static int LENGTH_ADJUSTMENT = 0;
private final static int INITIAL_BYTES_TO_STRIP = 0;
修改程序,定义LENGTH_FIELD_OFFSET
为2,即在头部写入AA
表示协议标识。
LengthFieldBasedFrameTest 处理前:
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 41 41 00 00 00 0f 41 42 43 44 45 46 47 48 49 4a |AA....ABCDEFGHIJ|
|00000010| 4b 4c 4d 4e 4f |KLMNO |
+--------+-------------------------------------------------+----------------+
LengthFieldBasedFrameTest 处理后:
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 41 41 00 00 00 0f 41 42 43 44 45 46 47 48 49 4a |AA....ABCDEFGHIJ|
|00000010| 4b 4c 4d 4e 4f |KLMNO |
+--------+-------------------------------------------------+----------------+
# 长度和内容之间添加头信息
private final static int LENGTH_FIELD_OFFSET = 2;
private final static int LENGTH_FIELD_LENGTH = 4;
private final static int LENGTH_ADJUSTMENT = 2;
private final static int INITIAL_BYTES_TO_STRIP = 0;
private final static boolean HAS_HEARD = true;
有的时候长度和内容之间我们可能需要增加头信息定义一些基础信息,还记得上面的LENGTH_ADJUSTMENT
嘛?通过它对数据包长度进行补偿,
在上面的情况中:长度15为一整个数据包长度,数据包长度本身占了4位,所以进行-4补偿,剩余的11为实际数据内容长度。
因此,如果我们有额外的信息,那么这部分数据就不在数据包长度内。需要进行正向补偿。在测试程序里打开HAS_HEARD
表示需要添加额外头(其实就是普通的写入操作,实际使用时可能需要按照你的需求场景进行修改)。
LENGTH_ADJUSTMENT=2
,表示长度后面后接两个字节(LENGTH_ADJUSTMENT 值
)。对照我们的程序也就是在长度和内容之间插入BB
。
LengthFieldBasedFrameTest 处理前:
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 41 41 00 00 00 0f 42 42 41 42 43 44 45 46 47 48 |AA....BBABCDEFGH|
|00000010| 49 4a 4b 4c 4d 4e 4f |IJKLMNO |
+--------+-------------------------------------------------+----------------+
LengthFieldBasedFrameTest 处理后:
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 41 41 00 00 00 0f 42 42 41 42 43 44 45 46 47 48 |AA....BBABCDEFGH|
|00000010| 49 4a 4b 4c 4d 4e 4f |IJKLMNO |
+--------+-------------------------------------------------+----------------+
# 长度为数据包长度且和内容之间添加头信息
问题来了在LENGTH_ADJUSTMENT
为正时,表示额外信息长度,那么LENGTH_FIELD_LENGTH
为数据内容长度。 如果你的协议里面还是需要将LENGTH_FIELD_LENGTH
作为整个数据包长度该怎么办呢?
其实你依旧可以进行如下设置
private final static int LENGTH_FIELD_OFFSET = 2;
private final static int LENGTH_FIELD_LENGTH = 4;
private final static int LENGTH_ADJUSTMENT = -4;
private final static int INITIAL_BYTES_TO_STRIP = 0;
private final static boolean HAS_HEARD = true;
HAS_HEARD
同上表示程序会写入一个额外头,因为此时LENGTH_ADJUSTMENT
为负数,所以LENGTH_FIELD_LENGTH
所代表的长度就是数据包总长度。
我们对这个长度15的数据包进行拆分:
- LENGTH_FIELD_OFFSET ->2位,但不计入长度计算
- 长度占 -> 4位
- LENGTH_ADJUSTMENT修正 -4
剩余数据位:15-4 = 11
, 其中HAS_HEARD
会写入4个额外字符BBBB
(这里的4位是因为我程序指定写入长度为:LENGTH_ADJUSTMENT的绝对值
,具体情况需要按自己程序分析),所以留给内容的长度只有7,将会得到数据:ABCDEFG
。
那么我们推测最终数据包:AA....BBABCDEFG
LengthFieldBasedFrameTest 处理前:
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 41 41 00 00 00 0f 42 42 42 42 41 42 43 44 45 46 |AA....BBBBABCDEF|
|00000010| 47 48 49 4a 4b 4c 4d 4e 4f |GHIJKLMNO |
+--------+-------------------------------------------------+----------------+
LengthFieldBasedFrameTest 处理后:
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 41 41 00 00 00 0f 42 42 42 42 41 42 43 44 45 46 |AA....BBBBABCDEF|
|00000010| 47 |G |
+--------+-------------------------------------------------+----------------+
# 长度为数据包长度且和内容之间添加头信息并修正
其实你依旧可以进行如下设置
private final static int LENGTH_FIELD_OFFSET = 2;
private final static int LENGTH_FIELD_LENGTH = 4;
private final static int LENGTH_ADJUSTMENT = -4;
private final static int INITIAL_BYTES_TO_STRIP = 6;
private final static boolean HAS_HEARD = true;
对于上述程序最终数据包:AA....BBABCDEFG
,我们只保留真正的内容部分,所以需要进行数据剥离,剥离长度为:LENGTH_FIELD_OFFSET + LENGTH_FIELD_LENGTH
LengthFieldBasedFrameTest 处理前:
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 41 41 00 00 00 0f 42 42 42 42 41 42 43 44 45 46 |AA....BBBBABCDEF|
|00000010| 47 48 49 4a 4b 4c 4d 4e 4f |GHIJKLMNO |
+--------+-------------------------------------------------+----------------+
LengthFieldBasedFrameTest 处理后:
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 42 42 42 42 41 42 43 44 45 46 47 |BBBBABCDEFG |
+--------+-------------------------------------------------+----------------+