2021SC@SDUSC
前言
在这一篇博客中,将会介绍netty的DelimiterBasedFrameDecoder类,以特殊字符作为结束消息的结束符,用以解决粘包拆包问题。
一、DelimiterBasedFrameDecoder.java
package io.netty.handler.codec;
import static io.netty.util.internal.ObjectUtil.checkPositive;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.internal.ObjectUtil;
import java.util.List;
public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
private final ByteBuf[] delimiters;
private final int maxFrameLength;
private final boolean stripDelimiter;
private final boolean failFast;
private boolean discardingTooLongFrame;
private int tooLongFrameLength;
private final LineBasedFrameDecoder lineBasedDecoder;
public DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf delimiter) {
this(maxFrameLength, true, delimiter);
}
public DelimiterBasedFrameDecoder(
int maxFrameLength, boolean stripDelimiter, ByteBuf delimiter) {
this(maxFrameLength, stripDelimiter, true, delimiter);
}
public DelimiterBasedFrameDecoder(
int maxFrameLength, boolean stripDelimiter, boolean failFast,
ByteBuf delimiter) {
this(maxFrameLength, stripDelimiter, failFast, new ByteBuf[] {
delimiter.slice(delimiter.readerIndex(), delimiter.readableBytes())});
}
public DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf... delimiters) {
this(maxFrameLength, true, delimiters);
}
public DelimiterBasedFrameDecoder(
int maxFrameLength, boolean stripDelimiter, ByteBuf... delimiters) {
this(maxFrameLength, stripDelimiter, true, delimiters);
}
public DelimiterBasedFrameDecoder(
int maxFrameLength, boolean stripDelimiter, boolean failFast, ByteBuf... delimiters) {
validateMaxFrameLength(maxFrameLength);
ObjectUtil.checkNonEmpty(delimiters, "delimiters");
if (isLineBased(delimiters) && !isSubclass()) {
lineBasedDecoder = new LineBasedFrameDecoder(maxFrameLength, stripDelimiter, failFast);
this.delimiters = null;
} else {
this.delimiters = new ByteBuf[delimiters.length];
for (int i = 0; i < delimiters.length; i ++) {
ByteBuf d = delimiters[i];
validateDelimiter(d);
this.delimiters[i] = d.slice(d.readerIndex(), d.readableBytes());
}
lineBasedDecoder = null;
}
this.maxFrameLength = maxFrameLength;
this.stripDelimiter = stripDelimiter;
this.failFast = failFast;
}
private static boolean isLineBased(final ByteBuf[] delimiters) {
if (delimiters.length != 2) {
return false;
}
ByteBuf a = delimiters[0];
ByteBuf b = delimiters[1];
if (a.capacity() < b.capacity()) {
a = delimiters[1];
b = delimiters[0];
}
return a.capacity() == 2 && b.capacity() == 1
&& a.getByte(0) == '\r' && a.getByte(1) == '\n'
&& b.getByte(0) == '\n';
}
private boolean isSubclass() {
return getClass() != DelimiterBasedFrameDecoder.class;
}
@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);
}
}
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
if (lineBasedDecoder != null) {
return lineBasedDecoder.decode(ctx, buffer);
}
// Try all delimiters and choose the delimiter which yields the shortest frame.
int minFrameLength = Integer.MAX_VALUE;
ByteBuf minDelim = null;
for (ByteBuf delim: delimiters) {
int frameLength = indexOf(buffer, delim);
if (frameLength >= 0 && frameLength < minFrameLength) {
minFrameLength = frameLength;
minDelim = delim;
}
}
if (minDelim != null) {
int minDelimLength = minDelim.capacity();
ByteBuf frame;
if (discardingTooLongFrame) {
// We've just finished discarding a very large frame.
// Go back to the initial state.
discardingTooLongFrame = false;
buffer.skipBytes(minFrameLength + minDelimLength);
int tooLongFrameLength = this.tooLongFrameLength;
this.tooLongFrameLength = 0;
if (!failFast) {
fail(tooLongFrameLength);
}
return null;
}
if (minFrameLength > maxFrameLength) {
// Discard read frame.
buffer.skipBytes(minFrameLength + minDelimLength);
fail(minFrameLength);
return null;
}
if (stripDelimiter) {
frame = buffer.readRetainedSlice(minFrameLength);
buffer.skipBytes(minDelimLength);
} else {
frame = buffer.readRetainedSlice(minFrameLength + minDelimLength);
}
return frame;
} else {
if (!discardingTooLongFrame) {
if (buffer.readableBytes() > maxFrameLength) {
// Discard the content of the buffer until a delimiter is found.
tooLongFrameLength = buffer.readableBytes();
buffer.skipBytes(buffer.readableBytes());
discardingTooLongFrame = true;
if (failFast) {
fail(tooLongFrameLength);
}
}
} else {
// Still discarding the buffer since a delimiter is not found.
tooLongFrameLength += buffer.readableBytes();
buffer.skipBytes(buffer.readableBytes());
}
return null;
}
}
private void fail(long frameLength) {
if (frameLength > 0) {
throw new TooLongFrameException(
"frame length exceeds " + maxFrameLength +
": " + frameLength + " - discarded");
} else {
throw new TooLongFrameException(
"frame length exceeds " + maxFrameLength +
" - discarding");
}
}
private static int indexOf(ByteBuf haystack, ByteBuf needle) {
for (int i = haystack.readerIndex(); i < haystack.writerIndex(); i ++) {
int haystackIndex = i;
int needleIndex;
for (needleIndex = 0; needleIndex < needle.capacity(); needleIndex ++) {
if (haystack.getByte(haystackIndex) != needle.getByte(needleIndex)) {
break;
} else {
haystackIndex ++;
if (haystackIndex == haystack.writerIndex() &&
needleIndex != needle.capacity() - 1) {
return -1;
}
}
}
if (needleIndex == needle.capacity()) {
// Found the needle from the haystack!
return i - haystack.readerIndex();
}
}
return -1;
}
private static void validateDelimiter(ByteBuf delimiter) {
ObjectUtil.checkNotNull(delimiter, "delimiter");
if (!delimiter.isReadable()) {
throw new IllegalArgumentException("empty delimiter");
}
}
private static void validateMaxFrameLength(int maxFrameLength) {
checkPositive(maxFrameLength, "maxFrameLength");
}
}
二、属性
首先介绍一下DelimiterBasedFrameDecoder类中的属性。
- maxFrameLength:这是数据的最大长度,超出这个限制,将会抛出
TooLongFrameExecption
异常。 - stripDelimiter:一个布尔变量,当变量为true时,解析数据会去除分隔符,反之不会。
- failFast:一个布尔变量,当它是true时,当解码器发现数据超出
maxFrameLength
时,会立刻抛出TooLongFrameExecption
异常,反之,会在解析完整个数据后,才抛出异常。 - delimiters:类型是ByteBuf数组,指定多个分隔符,但解析数据时,选择划分方式使得数据最短。比如,数据是’ABC\nDEF\r\n’,根据换行符划分数据,那么数据会被划分成’ABC’和’DEF’,而不是 ‘ABC\nDEF’。
- discardingTooLongFrame
- tooLongFrameLength
- lineBasedDecoder:LineBasedDecoder类型的变量,被final修饰,仅当分隔符为换行符时才会被设置。
在这些变量中,除了discardingTooLongFrame
和discardingTooLongFrame
以外,其它变量都在构造方法中完成了初始化,虽然DelimiterBasedFrameDecoder提供了六个构造方法,但最终都会走到public DelimiterBasedFrameDecoder( int maxFrameLength, boolean stripDelimiter, boolean failFast, ByteBuf... delimiters)
方法中。
在该构造方法中,首先对传入的参数进行验证,然后,判断是否应该使用LineBasedDecoder类,判断条件是isLineBased(delimiters) && !isSubclass()
,与的左边是判断传入的delimiters是否是 ‘\n’和’\r\n’,与的右边是判断调用该方法的类是否不是DelimiterBasedFrameDecoder类的子类,具体原因我不是很清楚,但可能是考虑到子类在扩展时,需要实现一些自定义的操作,所以在这里,不采用LineBasedDecoder。之后,就是对其它变量赋值。
三、decode
与之前分析的其它解码器一样,有两个decode方法,其中,一个decode用于真正处理解析逻辑。
在这个decode方法中,首先判断lineBasedDecoder
变量是否为null,如果不是,说明指定的分隔符是换行符,调用lineBasedDecoder
来处理。这里不做分析。之后,先获取分割后最短的数据长度,以及对应的分隔符minDelim
。
根据minDelim
是否为null
判断,这里假设它是null
,即没有读到换行符,或者说,数据并不完整,还有数据需要在之后的数据包中获得,首先判断discardingTooLongFrame
是否为true,如果不是,意味着还在读数据,判断读取的数据是否大于maxFrameLength
,如果大于,抛弃这些数据,并且将discardingTooLongFrame
设置为true,否则,什么也不做。接着,如果discardingTooLongFrame
为true,意味着之前的数据已经超过了长度但由于当时没有分隔符,所以在这个数据中也是需要抛弃的。总之,当minDelim
是null
时,返回null
,或者说,不返回数据。
接着,假设minDelim
不是null
,也就是在这个数据中读取到了分隔符,进入另外一条分支,首先判断discardingTooLongFrame
是否为true,如果是,这意味着之前的数据被抛弃了,但由于没有读到分隔符,没有完全抛弃,所以这一部分数据也应该被抛弃,跳过数据,tooLongFrameLength
和discardingTooLongFrame
置回初始值,返回null
。否则,接着往下执行,如果minFrameLength
大于maxFrameLength
,则这个数据被丢弃,否则,读取数据并返回。
四、总结
在本篇博客中,分析了DelimiterBasedFrameDecoder类,该解析器能够指定多个分隔符并且根据分隔符分割数据,我认为,这种解析器在实际使用时,能提供更高的灵活性。