综述
A decoder that splits the received {@link ByteBuf}s dynamically by the
value of the length field in the message. It is particularly useful when you
decode a binary message which has an integer header field that represents the
length of the message body or the whole message.
{@link LengthFieldBasedFrameDecoder} has many configuration parameters so
that it can decode any message with a length field, which is often seen in
proprietary client-server protocols. Here are some example that will give
you the basic idea on which option does what.
2 bytes length field at offset 0, do not strip header
The value of the length field in this example is 12 (0x0C) which
represents the length of "HELLO, WORLD". By default, the decoder assumes
that the length field represents the number of the bytes that follows the
length field. Therefore, it can be decoded with the simplistic parameter
combination.
lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = 0
initialBytesToStrip = 0 (= do not strip header)
BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes)
+--------+----------------+ +--------+----------------+
| Length | Actual Content |----->| Length | Actual Content |
| 0x000C | "HELLO, WORLD" | | 0x000C | "HELLO, WORLD" |
+--------+----------------+ +--------+----------------+
2 bytes length field at offset 0, strip header
Because we can get the length of the content by calling
{@link ByteBuf#readableBytes()}, you might want to strip the length
field by specifying initialBytesToStrip. In this example, we
specified 2, that is same with the length of the length field, to
strip the first two bytes.
lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = 0
initialBytesToStrip = 2 (= the length of the Length field)
BEFORE DECODE (14 bytes) AFTER DECODE (12 bytes)
+--------+----------------+ +----------------+
| Length | Actual Content |----->| Actual Content |
| 0x000C | "HELLO, WORLD" | | "HELLO, WORLD" |
+--------+----------------+ +----------------+
2 bytes length field at offset 0, do not strip header, the length field
represents the length of the whole message
In most cases, the length field represents the length of the message body
only, as shown in the previous examples. However, in some protocols, the
length field represents the length of the whole message, including the
message header. In such a case, we specify a non-zero
lengthAdjustment. Because the length value in this example message
is always greater than the body length by 2, we specify -2
as lengthAdjustment for compensation.
lengthFieldOffset = 0
lengthFieldLength = 2
lengthAdjustment = -2 (= the length of the Length field)
initialBytesToStrip = 0
BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes)
+--------+----------------+ +--------+----------------+
| Length | Actual Content |----->| Length | Actual Content |
| 0x000E | "HELLO, WORLD" | | 0x000E | "HELLO, WORLD" |
+--------+----------------+ +--------+----------------+
3 bytes length field at the end of 5 bytes header, do not strip header
The following message is a simple variation of the first example. An extra
header value is prepended to the message. lengthAdjustment is zero
again because the decoder always takes the length of the prepended data into
account during frame length calculation.
lengthFieldOffset = 2 (= the length of Header 1)
lengthFieldLength = 3
lengthAdjustment = 0
initialBytesToStrip = 0
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" |
+----------+----------+----------------+ +----------+----------+----------------+
3 bytes length field at the beginning of 5 bytes header, do not strip header
This is an advanced example that shows the case where there is an extra
header between the length field and the message body. You have to specify a
positive lengthAdjustment so that the decoder counts the extra
header into the frame length calculation.
lengthFieldOffset = 0
lengthFieldLength = 3
lengthAdjustment = 2 (= the length of Header 1)
initialBytesToStrip = 0
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" |
+----------+----------+----------------+ +----------+----------+----------------+
2 bytes length field at offset 1 in the middle of 4 bytes header,
strip the first header field and the length field
This is a combination of all the examples above. There are the prepended
header before the length field and the extra header after the length field.
The prepended header affects the lengthFieldOffset and the extra
header affects the lengthAdjustment. We also specified a non-zero
initialBytesToStrip to strip the length field and the prepended
header from the frame. If you don't want to strip the prepended header, you
could specify 0 for initialBytesToSkip.
lengthFieldOffset = 1 (= the length of HDR1)
lengthFieldLength = 2
lengthAdjustment = 1 (= the length of HDR2)
initialBytesToStrip = 3 (= the length of HDR1 + LEN)
BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes)
+------+--------+------+----------------+ +------+----------------+
| HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
| 0xCA | 0x000C | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" |
+------+--------+------+----------------+ +------+----------------+
2 bytes length field at offset 1 in the middle of 4 bytes header,
strip the first header field and the length field, the length field
represents the length of the whole message
Let's give another twist to the previous example. The only difference from
the previous example is that the length field represents the length of the
whole message instead of the message body, just like the third example.
We have to count the length of HDR1 and Length into lengthAdjustment.
Please note that we don't need to take the length of HDR2 into account
because the length field already includes the whole header length.
lengthFieldOffset = 1
lengthFieldLength = 2
lengthAdjustment = -3 (= the length of HDR1 + LEN, negative)
initialBytesToStrip = 3
BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes)
+------+--------+------+----------------+ +------+----------------+
| HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
| 0xCA | 0x0010 | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" |
+------+--------+------+----------------+ +------+----------------+
@see LengthFieldPrepender
关键属性
private final ByteOrder byteOrder;
// 消息最大长度
private final int maxFrameLength;
// 长度域的offset
private final int lengthFieldOffset;
// 长度域自身占用的长度
private final int lengthFieldLength;
// 长度域的结束offset, lengthFieldOffset + lengthFieldLength
private final int lengthFieldEndOffset;
// 长度修正
private final int lengthAdjustment;
// 初始需要跳过的字节数
private final int initialBytesToStrip;
private final boolean failFast;
// 丢弃模式
private boolean discardingTooLongFrame;
// 超过最大阈值的消息长度
private long tooLongFrameLength;
// 此次消息丢弃还剩的字节数
private long bytesToDiscard;
解码
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
// 如果处于丢弃模式
if (discardingTooLongFrame) {
// 拿到当前还剩多少要丢弃
long bytesToDiscard = this.bytesToDiscard;
// 看是不是到头了,如果不是跳过这批数据
int localBytesToDiscard = (int) Math.min(bytesToDiscard, in.readableBytes());
in.skipBytes(localBytesToDiscard);
// 继续计算还剩多少要丢弃
bytesToDiscard -= localBytesToDiscard;
this.bytesToDiscard = bytesToDiscard;
failIfNecessary(false);
}
// 如果当前可读的字节如果小于长度域,那么也没有继续下去的必要了。
if (in.readableBytes() < lengthFieldEndOffset) {
return null;
}
// 拿到实际长度域的起始index
int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset;
// 解析长度域中实际长度
long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);
if (frameLength < 0) {
in.skipBytes(lengthFieldEndOffset);
throw new CorruptedFrameException(
"negative pre-adjustment length field: " + frameLength);
}
// 整个消息的长度当然是等于实际长度+长度域长度+lengthAdjustment
frameLength += lengthAdjustment + lengthFieldEndOffset;
// 如果整个消息的长度还小于长度域结尾,只有一种可能,就是lengthAdjustment设置得不对
if (frameLength < lengthFieldEndOffset) {
in.skipBytes(lengthFieldEndOffset);
throw new CorruptedFrameException(
"Adjusted frame length (" + frameLength + ") is less " +
"than lengthFieldEndOffset: " + lengthFieldEndOffset);
}
// 如果长度大于消息最大长度
if (frameLength > maxFrameLength) {
// 还剩多少要丢弃
long discard = frameLength - in.readableBytes();
// 记录总的丢弃字节数
tooLongFrameLength = frameLength;
// 说明消息的总长要小于可读字节数,那么直接跳过当前消息,准备开始读取下一条消息
if (discard < 0) {
in.skipBytes((int) frameLength);
} else {
// 否则消息的总长要大于或等于可读字节数,
// 那么进入丢弃模式,并直接跳过这一批数据
// Enter the discard mode and discard everything received so far.
discardingTooLongFrame = true;
// 记录还需要丢弃的量
bytesToDiscard = discard;
in.skipBytes(in.readableBytes());
}
// 是否需要抛出异常
failIfNecessary(true);
return null;
}
// 如果当前可读的字节还不到我期望的消息长度的时候,什么都不做,直接返回null
// 等待下一次处理
int frameLengthInt = (int) frameLength;
if (in.readableBytes() < frameLengthInt) {
return null;
}
// 如果初始跳过的字节数大于消息长度,那么说明initialBytesToStrip设置得有问题
if (initialBytesToStrip > frameLengthInt) {
in.skipBytes(frameLengthInt);
throw new CorruptedFrameException(
"Adjusted frame length (" + frameLength + ") is less " +
"than initialBytesToStrip: " + initialBytesToStrip);
}
// 否则,先跳过initialBytesToStrip
in.skipBytes(initialBytesToStrip);
// extract frame
int readerIndex = in.readerIndex();
// 拿到跳过initialBytesToStrip后的消息长度
int actualFrameLength = frameLengthInt - initialBytesToStrip;
// 解析数据从readerindex开始,读取actualFrameLength个字节
// 返回retainedslice给out容器
ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength);
// 将readerindex移动到消息尾部,准备下次读取
in.readerIndex(readerIndex + actualFrameLength);
return frame;
}
failIfNecessary
private void failIfNecessary(boolean firstDetectionOfTooLongFrame) {
// 如果期望丢弃的数据已经处理完毕
if (bytesToDiscard == 0) {
// 重置tooLongFrameLength为0,退出丢弃模式
long tooLongFrameLength = this.tooLongFrameLength;
this.tooLongFrameLength = 0;
discardingTooLongFrame = false;
// 是否需要抛出异常
if (!failFast ||
failFast && firstDetectionOfTooLongFrame) {
fail(tooLongFrameLength);
}
} else {
// 是否首次就抛出异常
// Keep discarding and notify handlers if necessary.
if (failFast && firstDetectionOfTooLongFrame) {
fail(tooLongFrameLength);
}
}
}