2021SC@SDUSC
前言
在这一篇博客中,将会介绍netty的LengthFieldBasedFrameDecoder类,通过自定义长度解决TCP粘包拆包问题。
一、LengthFieldBasedFrameDecoder类
package io.netty.handler.codec;
import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static io.netty.util.internal.ObjectUtil.checkPositive;
import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
import java.nio.ByteOrder;
import java.util.List;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
public class LengthFieldBasedFrameDecoder extends ByteToMessageDecoder {
private final ByteOrder byteOrder;
private final int maxFrameLength;
private final int lengthFieldOffset;
private final int 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;
public LengthFieldBasedFrameDecoder(
int maxFrameLength,
int lengthFieldOffset, int lengthFieldLength) {
this(maxFrameLength, lengthFieldOffset, lengthFieldLength, 0, 0);
}
public LengthFieldBasedFrameDecoder(
int maxFrameLength,
int lengthFieldOffset, int lengthFieldLength,
int lengthAdjustment, int initialBytesToStrip) {
this(
maxFrameLength,
lengthFieldOffset, lengthFieldLength, lengthAdjustment,
initialBytesToStrip, true);
}
public LengthFieldBasedFrameDecoder(
int maxFrameLength, int lengthFieldOffset, int lengthFieldLength,
int lengthAdjustment, int initialBytesToStrip, boolean failFast) {
this(
ByteOrder.BIG_ENDIAN, maxFrameLength, lengthFieldOffset, lengthFieldLength,
lengthAdjustment, initialBytesToStrip, failFast);
}
public LengthFieldBasedFrameDecoder(
ByteOrder byteOrder, int maxFrameLength, int lengthFieldOffset, int lengthFieldLength,
int lengthAdjustment, int initialBytesToStrip, boolean failFast) {
this.byteOrder = checkNotNull(byteOrder, "byteOrder");
checkPositive(maxFrameLength, "maxFrameLength");
checkPositiveOrZero(lengthFieldOffset, "lengthFieldOffset");
checkPositiveOrZero(initialBytesToStrip, "initialBytesToStrip");
if (lengthFieldOffset > maxFrameLength - lengthFieldLength) {
throw new IllegalArgumentException(
"maxFrameLength (" + maxFrameLength + ") " +
"must be equal to or greater than " +
"lengthFieldOffset (" + lengthFieldOffset + ") + " +
"lengthFieldLength (" + lengthFieldLength + ").");
}
this.maxFrameLength = maxFrameLength;
this.lengthFieldOffset = lengthFieldOffset;
this.lengthFieldLength = lengthFieldLength;
this.lengthAdjustment = lengthAdjustment;
this.lengthFieldEndOffset = lengthFieldOffset + lengthFieldLength;
this.initialBytesToStrip = initialBytesToStrip;
this.failFast = failFast;
}
@Override
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
Object decoded = decode(ctx, in);
if (decoded != null) {
out.add(decoded);
}
}
private void discardingTooLongFrame(ByteBuf in) {
long bytesToDiscard = this.bytesToDiscard;
int localBytesToDiscard = (int) Math.min(bytesToDiscard, in.readableBytes());
in.skipBytes(localBytesToDiscard);
bytesToDiscard -= localBytesToDiscard;
this.bytesToDiscard = bytesToDiscard;
failIfNecessary(false);
}
private static void failOnNegativeLengthField(ByteBuf in, long frameLength, int lengthFieldEndOffset) {
in.skipBytes(lengthFieldEndOffset);
throw new CorruptedFrameException(
"negative pre-adjustment length field: " + frameLength);
}
private static void failOnFrameLengthLessThanLengthFieldEndOffset(ByteBuf in,
long frameLength,
int lengthFieldEndOffset) {
in.skipBytes(lengthFieldEndOffset);
throw new CorruptedFrameException(
"Adjusted frame length (" + frameLength + ") is less " +
"than lengthFieldEndOffset: " + lengthFieldEndOffset);
}
private void exceededFrameLength(ByteBuf in, long frameLength) {
long discard = frameLength - in.readableBytes();
tooLongFrameLength = frameLength;
if (discard < 0) {
// buffer contains more bytes then the frameLength so we can discard all now
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);
}
private static void failOnFrameLengthLessThanInitialBytesToStrip(ByteBuf in,
long frameLength,
int initialBytesToStrip) {
in.skipBytes((int) frameLength);
throw new CorruptedFrameException(
"Adjusted frame length (" + frameLength + ") is less " +
"than initialBytesToStrip: " + initialBytesToStrip);
}
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
if (discardingTooLongFrame) {
discardingTooLongFrame(in);
}
if (in.readableBytes() < lengthFieldEndOffset) {
return null;
}
int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset;
long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);
if (frameLength < 0) {
failOnNegativeLengthField(in, frameLength, lengthFieldEndOffset);
}
frameLength += lengthAdjustment + lengthFieldEndOffset;
if (frameLength < lengthFieldEndOffset) {
failOnFrameLengthLessThanLengthFieldEndOffset(in, frameLength, lengthFieldEndOffset);
}
if (frameLength > maxFrameLength) {
exceededFrameLength(in, frameLength);
return null;
}
// never overflows because it's less than maxFrameLength
int frameLengthInt = (int) frameLength;
if (in.readableBytes() < frameLengthInt) {
return null;
}
if (initialBytesToStrip > frameLengthInt) {
failOnFrameLengthLessThanInitialBytesToStrip(in, frameLength, initialBytesToStrip);
}
in.skipBytes(initialBytesToStrip);
// extract frame
int readerIndex = in.readerIndex();
int actualFrameLength = frameLengthInt - initialBytesToStrip;
ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength);
in.readerIndex(readerIndex + actualFrameLength);
return frame;
}
protected long getUnadjustedFrameLength(ByteBuf buf, int offset, int length, ByteOrder order) {
buf = buf.order(order);
long frameLength;
switch (length) {
case 1:
frameLength = buf.getUnsignedByte(offset);
break;
case 2:
frameLength = buf.getUnsignedShort(offset);
break;
case 3:
frameLength = buf.getUnsignedMedium(offset);
break;
case 4:
frameLength = buf.getUnsignedInt(offset);
break;
case 8:
frameLength = buf.getLong(offset);
break;
default:
throw new DecoderException(
"unsupported lengthFieldLength: " + lengthFieldLength + " (expected: 1, 2, 3, 4, or 8)");
}
return frameLength;
}
private void failIfNecessary(boolean firstDetectionOfTooLongFrame) {
if (bytesToDiscard == 0) {
// Reset to the initial state and tell the handlers that
// the frame was too large.
long tooLongFrameLength = this.tooLongFrameLength;
this.tooLongFrameLength = 0;
discardingTooLongFrame = false;
if (!failFast || firstDetectionOfTooLongFrame) {
fail(tooLongFrameLength);
}
} else {
// Keep discarding and notify handlers if necessary.
if (failFast && firstDetectionOfTooLongFrame) {
fail(tooLongFrameLength);
}
}
}
protected ByteBuf extractFrame(ChannelHandlerContext ctx, ByteBuf buffer, int index, int length) {
return buffer.retainedSlice(index, length);
}
private void fail(long frameLength) {
if (frameLength > 0) {
throw new TooLongFrameException(
"Adjusted frame length exceeds " + maxFrameLength +
": " + frameLength + " - discarded");
} else {
throw new TooLongFrameException(
"Adjusted frame length exceeds " + maxFrameLength +
" - discarding");
}
}
}
二、属性及构造函数
首先介绍一下LengthFieldBasedFrameDecoder类中的属性。
- byteOrder:数据采用大端法还是小端法。
- maxFrameLength:这是数据的最大长度。
- lengthFieldOffset:长度域位于数据中的偏移量。
- lengthFieldLength:长度域的长度。
- lengthFieldEndOffset:在构造函数中可以看到
this.lengthFieldEndOffset = lengthFieldOffset + lengthFieldLength;
,实际指向数据中位于长度之后的下一个字节的偏移。 - lengthAdjustment:文档中给出的解释是,加到长度域的惩罚值。之后会解释。
- initialBytesToStrip:在解析数据时,第一次从数据中去掉的字节数,一般地,由于长度域在解码后就没有什么作用了,会丢弃长度域。
- failFast:一个布尔变量,当它是true时,当解码器发现数据超出
maxFrameLength
时,会立刻抛出TooLongFrameExecption
异常,反之,会在解析完整个数据后,才抛出异常。 - discardingTooLongFrame:在decode中使用,一个布尔值。
- tooLongFrameLength:在decode中使用。
- bytesToDiscard:在decode中使用,表示抛弃掉的字节数。
在LengthFieldBasedFrameDecoder中,重载了许多构造函数。与之前的许多解码器一样,实际上为这些值设置了默认值,方便用户使用。
三、例子
在这一节中,会就长度域的使用来解释一些参数的具体使用方式。其实就是翻译一下文档。
(1)例一:
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” |
±----------±------------------------+ ±----------±-------------------------+
最简单的情况,如之前所说,lengthFieldOffset=0是长度域开始的下标,lengthFieldLength=2表示长度域的大小是2个字节。
(2)例二:
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” |
±-------±---------------+ ±---------------+
在这里,区别于上一个例子的是initialBytesToStrip=2,在解析时抛弃掉长度域,实际上2就是长度域的字节。
(3)例三:
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” |
±-------±---------------+ ±-------±---------------+
在这里,区别于例一的是lengthAdjustment=-2,首先这里长度域中的值是0x000E,等于十进制的14,实际场景中,一些协议会将长度域的字节数计入数据长度,所以,添加惩罚值,14+(-2)=12。
(四)例四:
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” |
±---------±---------±---------------+ ±---------±---------±---------------+
在长度域之前添加头信息。实际上,在计算机网络中,大多数协议都需要头部信息。lengthFieldLength=3是长度域长度。lengthFieldOffset=2表示长度域的开始下标是2,也就是第三个字节,注意到,头部的长度也是2。
(五)例五:
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” |
±---------±---------±---------------+ ±---------±---------±---------------+
在本例中,头部域不再是出现在长度域之前,而是出现在长度域之后,数据域之前。首先,长度域字节为3,长度为0x000000C,等于12。通过增加惩罚项12+2=14,这样解码器将额外的头部字节计入了要解析的字节数。
(六)例六:
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” |
±-----±-------±-----±---------------+ ±-----±---------------+
在这一例中,有两个头部分别出现在长度域之前和之后,实际上就是将之前的两个例子结合起来使用。最后会抛弃前三个字节,即HDR1和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” |
±-----±-------±-----±---------------+ ±-----±---------------+
这里区别于前一个例子的地方在于,lengthAdjustment=-3,实际上就是将HDR1和Length域的长度计入,这里将其减去,方便之后解码器解析数据。
四、decode方法
与之前的几个解码器一样,有两个decode方法,这里会简单分析protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception
,关于这里的一些判断就不分析了。
这里首先回去判断discardingTooLongFrame
这个标识是不是true,如果是的话,执行discardingTooLongFrame(in);
;否则,跳过。而在这个方法中,首先找到缓冲区的可读字节和bytesToDiscard
的最小值,之后跳过这些字节,然后去更新bytesToDiscard
。
之后是从缓冲区找到长度域的开始字节。然后,通过getUnadjustedFrameLength
方法从缓冲区中读出数据的长度。默认长度域的字节为1/2/3/4/8,如果不是的话,抛出异常,可以通过继承来扩充功能。
之后,就是根据协议计算实际的数据长度,原因就在于获取的frameLength可能包含了头部字节、长度域字节等,如之前例子所说的,这里会去获得真正要读取的字节数。
在跳过需要抛弃的字节数initialBytesToStrip
后,从缓冲区获取数据,返回。
五、总结
在本篇博客中,分析了LengthFieldBasedFrameDecoder类,包括它的属性、构造函数、使用场景分析,最后,分析了它解析数据的逻辑。LengthFieldBasedFrameDecoder能够根据自定义字节数来解决TCP的粘包半包问题,而且处理灵活,相对其它解码器更加常用。