1.半包和粘包问题
TCP协议是基于字节流的数据通讯协议,数据被看做是一连串的字节流;不具备边界信息,给接收方带来半包和粘包问题。
半包:TCP传输时,将数据切割成一个个数据包进行传输。接收方一次读取操作,如果没有接受完完整的数据包而只能读取部分数据时,出现半包问题。
粘包:发送方发送的两个或者多个数据包在接受方接受时被合并成了一个数据包。
半包和粘包问题可以通过业务层解决。
2.常见处理器
Netty中引入了4个常见的处理器,用于解决不同场景下的TCP粘包和半包问题。以下分小节分别介绍使用方式。
2.1 FixedLengthFrameDecoder
数据流过FixedLengthFrameDecoder后,消息会被裁接或者拼接为一个个固定长度的ByteBuf消息对象,通过构造参数确定消息长度。
// 消息长度为2
channel.pipeline().addLast(new FixedLengthFrameDecoder(2));
// 打印消息详情
channel.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
客户端向服务器发送123456消息时,服务器消息显示如下:
#消息片段【12】
io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0x5368287f, L:/127.0.0.1:9998 - R:/127.0.0.1:53981] READ: 2B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 31 32 |12 |
+--------+-------------------------------------------------+----------------+
#消息片段【34】
io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0x5368287f, L:/127.0.0.1:9998 - R:/127.0.0.1:53981] READ: 2B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 33 34 |34 |
+--------+-------------------------------------------------+----------------+
#消息片段【56】
io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0x5368287f, L:/127.0.0.1:9998 - R:/127.0.0.1:53981] READ: 2B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 35 36 |56 |
+--------+-------------------------------------------------+----------------+
服务端程序修改为:
channel.pipeline().addLast(new FixedLengthFrameDecoder(2));
channel.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
channel.pipeline().addLast(new FixedLengthFrameDecoder(4));
channel.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
客户端向服务器发送123456消息时,服务器消息显示如下:
#消息片段【12】
io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0x7940d10b, L:/127.0.0.1:9998 - R:/127.0.0.1:54420] READ: 2B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 31 32 |12 |
+--------+-------------------------------------------------+----------------+
#消息片段【34】
io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0x7940d10b, L:/127.0.0.1:9998 - R:/127.0.0.1:54420] READ: 2B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 33 34 |34 |
+--------+-------------------------------------------------+----------------+
#消息片段【1234】
io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0x7940d10b, L:/127.0.0.1:9998 - R:/127.0.0.1:54420] READ: 4B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 31 32 33 34 |1234 |
+--------+-------------------------------------------------+----------------+
#消息片段【56】
io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0x7940d10b, L:/127.0.0.1:9998 - R:/127.0.0.1:54420] READ: 2B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 35 36 |56 |
+--------+-------------------------------------------------+----------------+
#消息片段【78】保存在内存中。
2.2 LineBasedFrameDecoder
数据流过LineBasedFrameDecoder后,按换行符(\r或\r\n)裁接消息对象,可通过构造参数确定最大消息长度。
// 最大消息长度为1024
channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
当截取的消息长度超过设定的1024时,将消息丢弃。
测试使用如下案例:
// 最大长度设置为10
channel.pipeline().addLast(new LineBasedFrameDecoder(10));
channel.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
客户端发送如下消息时:
15:53:03 发送数据:1234567890123
456
789
服务器日志如下:
#【对1234567890123超出长度部分消息报错,并丢弃】
TooLongFrameException: frame length (16) exceeds the allowed maximum (10)
# ...省略报错详细信息
#【消息片段456】
io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0x0bade5de, L:/127.0.0.1:9998 - R:/127.0.0.1:61868] READ: 3B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 34 35 36 |456 |
+--------+-------------------------------------------------+----------------+
#【消息片段789】
io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0x0bade5de, L:/127.0.0.1:9998 - R:/127.0.0.1:61868] READ: 3B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 37 38 39 |789 |
+--------+-------------------------------------------------+----------------+
注意点:
LineBasedFrameDecoder提供了两个构造函数:
public LineBasedFrameDecoder(final int maxLength) {
this(maxLength, true, false);
}
public LineBasedFrameDecoder(final int maxLength, final boolean stripDelimiter, final boolean failFast) {
// 最大消息长度
this.maxLength = maxLength;
// 超出最大消息长度时,是否尽快抛出异常
this.failFast = failFast;
// 是否删除换行符,默认为删除
this.stripDelimiter = stripDelimiter;
}
上述测试案例中构造的LineBasedFrameDecoder,处理消息后会取出换行符。
如业务需要保留换行符,可使用如下方式:
// 裁切后的消息对象包括换行符
new LineBasedFrameDecoder(65535, true, true)
2.3 DelimiterBasedFrameDecoder
DelimiterBasedFrameDecoder可通过指定分隔符裁切消息。
包含的属性有:
// 最大数据帧长度
private final int maxFrameLength;
// 分隔符列表
private final ByteBuf[] delimiters;
// 是否保留分隔符
private final boolean stripDelimiter;
// 消息超出最大长度,是否快速抛出异常
private final boolean failFast;
对应提供了以下多个构造函数:
public DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf delimiter) {//...}
public DelimiterBasedFrameDecoder(int maxFrameLength, boolean stripDelimiter, ByteBuf delimiter) {//...}
public DelimiterBasedFrameDecoder(int maxFrameLength, boolean stripDelimiter, boolean failFast, ByteBuf delimiter) {//...}
public DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf... delimiters) {//...}
public DelimiterBasedFrameDecoder(int maxFrameLength, boolean stripDelimiter, ByteBuf... delimiters) {//...}
public DelimiterBasedFrameDecoder(int maxFrameLength, boolean stripDelimiter, boolean failFast, ByteBuf... delimiters) {//...}
分隔符可以设置一个,也可设置多个。多个分隔符的实现逻辑是遍历分隔符,找到能将当前消息切割长度最小的分隔符分隔。
服务端测试使用如下案例
// 分隔符号为@
channel.pipeline().addLast(new DelimiterBasedFrameDecoder(10, Unpooled.copiedBuffer("@".getBytes())));
channel.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
客户端发送如下消息时:
1@2@3
服务器日志如下:
#【消息片段 1】
io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0xc5f14089, L:/127.0.0.1:9998 - R:/127.0.0.1:55915] READ: 2B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 33 31 |31 |
+--------+-------------------------------------------------+----------------+
#【消息片段 2】
io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0xc5f14089, L:/127.0.0.1:9998 - R:/127.0.0.1:55915] READ: 1B
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 32 |2 |
+--------+-------------------------------------------------+----------------+
#【消息片段 3保存在内存中等待累加】
2.4 LengthFieldBasedFrameDecoder
一般,消息长度不是固定的;也并非所有的消息都通过分隔符分隔,且使用分隔符分隔消息的策略本身也存在风险,消息内容本身中可能包含分隔符。
可以通过TLV或者LV方式更安全地解码消息,LengthFieldBasedFrameDecoder选择基于LV方式。
LV:L指代长度域,V指代数据域; 通过长度域得到消息内容的长度,从而确定每个消息帧的长度。
使用LengthFieldBasedFrameDecoder的核心在于配置以下4个属性:
// 长度域偏移量(起始位置)
private final int lengthFieldOffset;
// 长度域数据长度
private final int lengthFieldLength;
// 消息长度的修正值
private final int lengthAdjustment;
// 消息内容需要跳过的字节数
private final int initialBytesToStrip;
通过不同的组合方式,可以应对多种场景(消息格式)。
对应提供了如下构造函数:
// maxFrameLength 和 failFast属性的意义与LineBasedFrameDecoder和DelimiterBasedFrameDecoder相同;
public LengthFieldBasedFrameDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength);
public LengthFieldBasedFrameDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip);
public LengthFieldBasedFrameDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip, boolean failFast);
public LengthFieldBasedFrameDecoder(ByteOrder byteOrder, int maxFrameLength, int lengthFieldOffset, int lengthFieldLength,int lengthAdjustment, int initialBytesToStrip, boolean failFast);
以下结合LengthFieldBasedFrameDecoder类注释内容,整理出如下几个测试案例.
case1:
BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes)
+--------+----------------+ +--------+----------------+
| Length | Actual Content |----->| Length | Actual Content |
| 0x000C | "HELLO, WORLD" | | 0x000C | "HELLO, WORLD" |
+--------+----------------+ +--------+----------------+
lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = 0
initialBytesToStrip = 0
分析:
长度域为0x000C,从第0位(第1个字节)开始,长度为2:
lengthFieldOffset=0, lengthFieldLength = 2
0x000C值为12,表示数据域长度为12,而"HELLO, WORLD"字符串长度为12,此时,长度值修正值为0
lengthAdjustment = 0
编码后要求得到的数据内容包括长度域和数据域,因此内容跳过0字节
initialBytesToStrip = 0
case2:
BEFORE DECODE (14 bytes) AFTER DECODE (12 bytes)
+--------+----------------+ +----------------+
| Length | Actual Content |----->| Actual Content |
| 0x000C | "HELLO, WORLD" | | "HELLO, WORLD" |
+--------+----------------+ +----------------+
lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = 0
initialBytesToStrip = 2
分析:
长度域为0x000C,从第0位开始,长度为2
lengthFieldOffset=0, lengthFieldLength = 2
0x000C值为12,等于内容域长度,因此长度修正值为0
lengthAdjustment = 0
编码后要求得到的数据内容仅包括数据域,因此内容跳过2字节(跳过0x000C)
initialBytesToStrip = 2
case3:
BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes)
+--------+----------------+ +--------+----------------+
| Length | Actual Content |----->| Length | Actual Content |
| 0x000E | "HELLO, WORLD" | | 0x000E | "HELLO, WORLD" |
+--------+----------------+ +--------+----------------+
lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = -2
initialBytesToStrip = 0
分析:
长度域为0x000E,从第0位开始,长度为2
lengthFieldOffset=0, lengthFieldLength = 2
0x000E值为14,比内容域长度大2,因此长度修正值为-2
lengthAdjustment = -2
编码后要求得到的数据内容包括长度域和数据域,因此内容跳过0字节
initialBytesToStrip = 0
case4:
BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes)
+----------+----------+----------------+ +----------+----------+----------------+
| Header 1 | Length | Actual Content |----->| Header 1 | Length | Actual Content |
| 0xCAFE | 0x00000C | "HELLO, WORLD" | | 0xCAFE | 0x00000C | "HELLO, WORLD" |
+----------+----------+----------------+ +----------+----------+----------------+
lengthFieldOffset = 2
lengthFieldLength = 3
lengthAdjustment = 0
initialBytesToStrip = 0
分析:
长度域为0x00000C,从第2位开始,长度为3
lengthFieldOffset=2, lengthFieldLength = 3
0x00000C值为12,等于内容域长度,因此长度修正值为0
lengthAdjustment = 0
编码后要求得到的数据内容包括Header1、长度域和数据域,因此内容跳过0字节
initialBytesToStrip = 0
case5:
BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes)
+----------+----------+----------------+ +----------+----------+----------------+
| Length | Header 1 | Actual Content |----->| Length | Header 1 | Actual Content |
| 0x00000C | 0xCAFE | "HELLO, WORLD" | | 0x00000C | 0xCAFE | "HELLO, WORLD" |
+----------+----------+----------------+ +----------+----------+----------------+
lengthFieldOffset = 0
lengthFieldLength = 3
lengthAdjustment = 2
initialBytesToStrip = 0
分析:
长度域为0x00000C,从第0位开始,长度为3
lengthFieldOffset=0, lengthFieldLength = 3
0x00000C值为12,等于内容域长度,但不包含Header1,因此长度修正值为2(可以将长度域和内容之间的数据理解成内容域的一部分)
lengthAdjustment = 2
编码后要求得到的数据内容包括Header1、长度域和数据域,因此内容跳过0字节
initialBytesToStrip = 0
case6:
BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes)
+------+--------+------+----------------+ +------+----------------+
| HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
| 0xCA | 0x000C | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" |
+------+--------+------+----------------+ +------+----------------+
lengthFieldOffset = 1
lengthFieldLength = 2
lengthAdjustment = 1
initialBytesToStrip = 3
分析:
长度域为0x000C,从第1位开始,长度为2
lengthFieldOffset=1, lengthFieldLength = 2
0x0000C值为12,等于内容域长度,但不包含HDR2,因此长度修正值为1(HDR2长度)
lengthAdjustment = 1
编码后要求得到的数据内容包括Header2、数据域,因此内容跳过3字节(HDR1和长度域)
initialBytesToStrip = 3
case7:
BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes)
+------+--------+------+----------------+ +------+----------------+
| HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
| 0xCA | 0x0010 | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" |
+------+--------+------+----------------+ +------+----------------+
lengthFieldOffset = 1
lengthFieldLength = 2
lengthAdjustment = -3
initialBytesToStrip = 3
分析:
长度域为0x0010,从第1位开始,长度为2
lengthFieldOffset=1, lengthFieldLength = 2
0x0010值为16, 大于内容域长度,包含了HDR1+长度域+HDR2+内容域,实际应为HDR2+内容域,因此长度修正值为-3(HDR1+长度域)
lengthAdjustment = -3
编码后要求得到的数据内容包括Header2、数据域,因此内容跳过3字节(HDR1和长度域)
initialBytesToStrip = 3
3.实现原理
2章节介绍的解码器有一个共同的父类ByteToMessageDecoder,核心逻辑在ByteToMessageDecoder中,四个解码器针对不同使用场景进行了定制化扩展(实现decode方法)。
本章将结合ByteToMessageDecoder源码对实现原理进行介绍,之后基于ByteToMessageDecoder对FixedLengthFrameDecoder(最简单、清晰易懂)进行介绍。
3.1 ByteToMessageDecoder
ByteToMessageDecoder的核心逻辑是未读数据累加机制,以及通过预留decode给子类实现定制功能,是一个模板抽象类。ByteToMessageDecoder本身是一个Inbound事件的ChannelHandler, 可以处理可读事件。
当消息可读时,消息进入ByteToMessageDecoder的channelRead方法,对数据进行累加处理,然后调用子类的decode方法(decode接口声明如下所示):
// in累加的数据流, out解码结果列表
protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception;
子类可以从数据流中取出一部分解码出消息,也可以不取;此时未被取出的数据流将保存在内存中。下次数据流到达时,累加器会从内存中取出的未读部分并进行数据累加,然后继续调用子类的decode方法。以下将多这一部分逻辑结合源码进行详细介绍。
3.1.1 累加器和累加数据
// 累加器
private Cumulator cumulator = MERGE_CUMULATOR;
// 累加属性,未被读取的数据
ByteBuf cumulation;
// 提供了一个设值方法,自定义累加器(一般直接使用默认的MERGE_CUMULATOR即可)
public void setCumulator(Cumulator cumulator) {
this.cumulator = ObjectUtil.checkNotNull(cumulator, "cumulator");
}
Cumulator和MERGE_CUMULATOR定义如下:
// Cumulator仅定义了cumulate一个方法
public interface Cumulator {
ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in);
}
// cumulate实现将in的数据读取到cumulation中并返回,必要时对cumulation进行扩容
public static final Cumulator MERGE_CUMULATOR = new Cumulator() {
@Override
public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
// cumulation没有可读数据时,将in返回,并回收cumulation
if (!cumulation.isReadable() && in.isContiguous()) {
cumulation.release();
return in;
}
try {
final int required = in.readableBytes();
// cumulation装不下in的数据时,对cumulation进行扩容
if (required > cumulation.maxWritableBytes() || (required > cumulation.maxFastWritableBytes() && cumulation.refCnt() > 1) || cumulation.isReadOnly()) {
return expandCumulation(alloc, cumulation, in);
}
// 从in的可取位置读取in的数据至cumulation
cumulation.writeBytes(in, in.readerIndex(), required);
// 将in设置为已读完
in.readerIndex(in.writerIndex());
return cumulation;
} finally {
in.release();
}
}
};
逻辑较为清晰: cumulate实现将in的数据读取到cumulation中并返回,必要时对cumulation进行扩容。
扩容时,新创建一个ByteBuf对象,将in数据和oldCumulation读取到新的cumulation并返回。
3.1.2 解码过程
消息可读时,进入解码器的channelRead方法:
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
// 1.构造out列表临时存放解码后的对象
CodecOutputList out = CodecOutputList.newInstance();
try {
// 2.使用cumulator将cumulation数据与msg数据整合成新的cumulation
first = cumulation == null;
cumulation = cumulator.cumulate(ctx.alloc(), first ? Unpooled.EMPTY_BUFFER : cumulation, (ByteBuf) msg);
// 3.调用callDecode从cumulation数据中解码出对象,保存到out列表中
callDecode(ctx, cumulation, out);
} catch (DecoderException e) {
throw e;
} catch (Exception e) {
throw new DecoderException(e);
} finally {
//4.解码后处理工作
try {
// 如果cumulation数据已被读完,重置cumulation
if (cumulation != null && !cumulation.isReadable()) {
numReads = 0;
cumulation.release();
cumulation = null;
} else if (++numReads >= discardAfterReads) {
// 重复多次(默认16)为解析完数据,丢弃这部分数据,防止OOM
numReads = 0;
discardSomeReadBytes();
}
int size = out.size();
firedChannelRead |= out.insertSinceRecycled();
// 遍历out列表,对所有元素向pipeline触发channelRead事件
fireChannelRead(ctx, out, size);
} finally {
// 回收再利用out列表
out.recycle();
}
}
} else {
ctx.fireChannelRead(msg);
}
}
最外层有个判断逻辑,只有消息类型是ByteBuf才会被处理,否则将消息向后传递。
整体上可以分为以下几个步骤:
[1] 构造out列表临时存放解码后的对象;
[2] 使用cumulator将cumulation数据与msg数据整合成新的cumulation;
[3] 调用callDecode从cumulation数据中解码出对象,保存到out列表中;
[4] 解码后的处理工作, 包括:将解码的消息触发为channelRead事件、cumulation处理、out的回收再利用;
其中第三步是核心逻辑:
protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
try {
// 循环从in中读取数据,直到读完或者被break中断
while (in.isReadable()) {
// 如果out中已积累了解码后的元素,先触发并清空out列表
int outSize = out.size();
if (outSize > 0) {
fireChannelRead(ctx, out, outSize);
out.clear();
if (ctx.isRemoved()) {
break;
}
outSize = 0;
}
int oldInputLength = in.readableBytes();
decodeRemovalReentryProtection(ctx, in, out);
// 通道已被移除,不再读取数据
if (ctx.isRemoved()) {
break;
}
// 此次循环没有解码出新的数据
if (outSize == out.size()) {
// 数据源没变
if (oldInputLength == in.readableBytes()) {
// 中断提取,防止陷入死循环
break;
} else {
continue;
}
}
// 数据源没被消耗,却读取了新的数据,抛出异常
if (oldInputLength == in.readableBytes()) {
throw new DecoderException(StringUtil.simpleClassName(getClass()) + ".decode() did not read anything but decoded a message.");
}
//每次消息达到,仅解码一次, 默认为false,一直提取
if (isSingleDecode()) {
break;
}
}
} catch (DecoderException e) {
throw e;
} catch (Exception cause) {
throw new DecoderException(cause);
}
}
对异常场景跳出循环或者抛出异常,重点在decodeRemovalReentryProtection方法:
final void decodeRemovalReentryProtection(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
decodeState = STATE_CALLING_CHILD_DECODE;
try {
// 调用子类的decode方法
decode(ctx, in, out);
} finally {
// 如果通道正在被移除,这里触发解码出的消息
boolean removePending = decodeState == STATE_HANDLER_REMOVED_PENDING;
decodeState = STATE_INIT;
if (removePending) {
fireChannelRead(ctx, out, out.size());
out.clear();
handlerRemoved(ctx);
}
}
}
所有继承ByteToMessageDecoder的解码器必须要实现decode方法。
3.2 FixedLengthFrameDecoder
FixedLengthFrameDecoder源码如下:
public class FixedLengthFrameDecoder extends ByteToMessageDecoder {
private final int frameLength;
public FixedLengthFrameDecoder(int frameLength) {
// 正数
checkPositive(frameLength, "frameLength");
this.frameLength = frameLength;
}
@Override
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// 调用decode解码,如果结果不为空,添加到out列表
Object decoded = decode(ctx, in);
if (decoded != null) {
out.add(decoded);
}
}
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
// 可读取的数据长度小于frameLength,不读取
if (in.readableBytes() < frameLength) {
return null;
} else {
// 可读取的数据长度大于或等于frameLength,从ByteBuf中读取frameLength长度的数据
return in.readRetainedSlice(frameLength);
}
}
}
FixedLengthFrameDecoder中有一个frameLength属性,用于确定每次读取的数据长度。
decode方法逻辑较为简单,当到达的可读数据长度大于等于frameLength时,从中读取frameLength长度的数据,并转为ByteBuf对象返回,未读取的字段保存在内存中累加;可读数据长度小于frameLength时,不进行读取。
由此,数据经过FixedLengthFrameDecoder后,将会被拼接或裁剪为一个个长度为frameLength的ByteBuf对象,并沿着Pipeline向后传递。
4.扩展
可基于上述内容,设计一个消息解码器,实现解码TLV格式的消息。