基于分隔符解码器DelimiterBasedFrameDecoder也算是比较简单的,和基于行解码器差不多一样分析既可,首先我们看一个构造函数:
public DelimiterBasedFrameDecoder(int maxFrameLength, ByteBuf... delimiters) {
this(maxFrameLength, true, delimiters);
}
这个构造函数有个maxFrameLength,这个也就是我们能解析的字符最大多大,大于这个的解析出来的内容都要丢弃。
然后这个ByteBuf... delimiters的参数,表示可以有多个分隔符。
我们还是来到它的decode方法:
@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);
}
}
一样的把解析到的数据放到decode里面,然后我们看最重要的自己的decode方法:
/**
* Create a frame out of the {@link ByteBuf} and return it.
*
* @param ctx the {@link ChannelHandlerContext} which this {@link ByteToMessageDecoder} belongs to
* @param buffer the {@link ByteBuf} from which to read data
* @return frame the {@link ByteBuf} which represent the frame or {@code null} if no frame could
* be created.
*/
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;
}
}
其实也不难,分三步分析它:
1、行处理器
2、找到最小分隔符
3、解码
一、行处理器
看这个源码:
if (lineBasedDecoder != null) {
return lineBasedDecoder.decode(ctx, buffer);
}
如果行处理器不为空,则调用行处理器。我们可以很简单地知道,他之前判断过分隔符是不是行分隔符,如果是,就给lineBaaseDecoder赋值,到了这里,就会进入这个if。lineBasedDecoder在什么地方初始化呢?
在DelimiterBasedFrameDecoder的构造函数里面有这样一个逻辑:
if (isLineBased(delimiters) && !isSubclass()) {
lineBasedDecoder = new LineBasedFrameDecoder(maxFrameLength, stripDelimiter, failFast);
this.delimiters = null;
} else {
如果判断是行分隔符那就实例化行处理器,给lineBasedDecoder赋值。看isLineBase方法的源码:
/** Returns true if the delimiters are "\n" and "\r\n". */
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';
}
很简单地逻辑,delimiters长度必须为2并且里面的内容必须为\n、\r\n,就可以判断为行处理器了。
二、找到最小分隔符
这个也是比较简单的几行代码
// 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;
}
}
通过indexOf找到当前分隔符分割的长度,和最小的比较,如果小于最小就交换,并且把当前的分隔符作为最小分隔符。
三、解码
这个看起来复杂,其实很简单。和上篇文章一样分析,源码自行查看,很简单。
1、如果找到了分隔符(这个分隔符是最小分隔符)
A、如果是丢弃模式
设置为非丢弃模式;丢弃掉之前记录的数据;如果不是快速失败,就在这里抛出异常。这个抛出异常的fail也很常见:
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");
}
}
B、如果不是丢弃模式
如果解析到的数据大于maxFrameLength,丢弃掉这一段数据,并且用fail来抛出异常。
如果解析到的数据小于等于maxFrameLength,说明这个数据正常,判断是否需要加上分割符之后返回。
2、如果没有找到分隔符
A、如果不是丢弃模式
如果readableBytes长度已经超过了最大可允许的长度,那就把要丢弃的长度记录下来,并且跳过这一段,并且标记进入丢弃模式,并且如果是快速丢弃,就从fail里面抛出异常;
如果readableBytes长度没有超过了最大可允许的长度,不做任何操作,继续等以后的数据。
B、如果是丢弃模式
添加需要丢弃的字节到tooLongFrameLength里面,然后跳过这段数据。