行解码器LineBasedFrameDecoder,我们首先看这个类的一些成员变量:
/**
* A decoder that splits the received {@link ByteBuf}s on line endings.
* <p>
* Both {@code "\n"} and {@code "\r\n"} are handled.
* For a more general delimiter-based decoder, see {@link DelimiterBasedFrameDecoder}.
*/
public class LineBasedFrameDecoder extends ByteToMessageDecoder {
/** Maximum length of a frame we're willing to decode. */
private final int maxLength;
/** Whether or not to throw an exception as soon as we exceed maxLength. */
private final boolean failFast;
private final boolean stripDelimiter;
/** True if we're discarding input because we're already over maxLength. */
private boolean discarding;
private int discardedBytes;
maxLength:行解码器最长解析的字符长度,超过这个长度将不解析。
failFast:当到达maxLength的时候,是否抛出异常,还是等到有换行符的时候才抛出异常。
stripDelimiter:解析字符串成对象的时候,是否把字符串也加入到对象中。
discarding:是否进入丢弃模式。
discardedBytes:需要丢弃的字节数。
在继续下面的分析之前,我们还要知道一个概念:
\n是换行符,\r\n也是换行符
现在开始分析。
它的decode方法和基于固定长度解码器一样,也是把解析到的对象放到out列表里面:
@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);
}
}
最重要的是它实现的deocode(ctx,in)方法:
/**
* 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 {
final int eol = findEndOfLine(buffer);
if (!discarding) {
if (eol >= 0) {
final ByteBuf frame;
final int length = eol - buffer.readerIndex();
final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
if (length > maxLength) {
buffer.readerIndex(eol + delimLength);
fail(ctx, length);
return null;
}
if (stripDelimiter) {
frame = buffer.readRetainedSlice(length);
buffer.skipBytes(delimLength);
} else {
frame = buffer.readRetainedSlice(length + delimLength);
}
return frame;
} else {
final int length = buffer.readableBytes();
if (length > maxLength) {
discardedBytes = length;
buffer.readerIndex(buffer.writerIndex());
discarding = true;
if (failFast) {
fail(ctx, "over " + discardedBytes);
}
}
return null;
}
} else {
if (eol >= 0) {
final int length = discardedBytes + eol - buffer.readerIndex();
final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
buffer.readerIndex(eol + delimLength);
discardedBytes = 0;
discarding = false;
if (!failFast) {
fail(ctx, length);
}
} else {
discardedBytes += buffer.readableBytes();
buffer.readerIndex(buffer.writerIndex());
}
return null;
}
}
我们可以分为下面几点分析:
首先看final int eol = findEndOfLine(buffer);这个方法:
private static int findEndOfLine(final ByteBuf buffer) {
int i = buffer.forEachByte(ByteProcessor.FIND_LF);
if (i > 0 && buffer.getByte(i - 1) == '\r') {
i--;
}
return i;
}
看ByteProcessor.FIND_LF是什么,其实就是一个换行符:
ByteProcessor FIND_LF = new IndexOfProcessor((byte) '\n');
所以findEndOfLine做的事情很简单,就是找到\n换行符,如果前面有\r,就返回前一个。
下面就是分几种情况。
1、如果是非丢弃模式
A、非丢弃模式下读到了换行符
B、非丢弃模式下没有读到换行符
2、如果是丢弃模式
A、丢弃模式下读到了换行符
B、丢弃模式下没有读到换行符
我们一点一点分析,其实也很简单:
1、如果是非丢弃模式
一开始的时候肯定还没有进入丢弃模式,只有下面的一种情况里面的一种小情况才会进入丢弃模式,我们下面会分析。
A、非丢弃模式下读到了换行符
看下面的图了解这种情况:
eol就是end of line,读到了换行符,有两种情况都属于。看源码:
final ByteBuf frame;
final int length = eol - buffer.readerIndex();
final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
length就是readIndex到eol的这段距离,而delimLength就是分隔符的长度,有\r就是2,没有就是1
然后,如果readIndex到eol的这段距离岛屿maxLength,也就是length>maxLength:
if (length > maxLength) {
buffer.readerIndex(eol + delimLength);
fail(ctx, length);
return null;
}
那么我们就要抛弃length这一段加上delimLength这一段,也就是把readIndex变为eol+delimLength,然后fail来抛出异常,并且返回空。
继续:
if (stripDelimiter) {
frame = buffer.readRetainedSlice(length);
buffer.skipBytes(delimLength);
} else {
frame = buffer.readRetainedSlice(length + delimLength);
}
return frame;
走到上面这一段,说明这些解析到的数据是正常的了,通过stripDelimiter判断需不需要跳过跳过换行符,然后在返回得到的数据。
B、非丢弃模式下没有读到换行符
看源码:
final int length = buffer.readableBytes();
if (length > maxLength) {
discardedBytes = length;
buffer.readerIndex(buffer.writerIndex());
discarding = true;
if (failFast) {
fail(ctx, "over " + discardedBytes);
}
}
return null;
这里无论如何都是返回 空的,因为没有读到换行符。到时如果读到的数据length岛屿maxLength,那么就开始进入丢弃模式了,把需要求其的字节个数记录下来,然后把readIndex移动到,witerIndex,也就是readIndex到writeIndex这一段都是要抛弃的,看这里
这段数据都不要。
设置为丢弃模式:discarding=true
如果是快速失败模式,那这个时候就可以fail来抛出异常了,否则,就等到下面读到换行符再调用fail方法。
2、如果是丢弃模式
A、丢弃模式下读到了换行符
图还是用这个图就行了:
如果是这种情况,那么我们就要抛弃readIndex到eol这一段数据再加上之前记录的discardedBytes,并传递到fail函数里面,然后设置discardedBytes为0,并且设置丢弃模式结束,也就是false。如果非快速失败,这个时候就可以执行fail了。上面执行fail和这里执行fail是互斥事件,上面执行这里就不会执行,否则这里不执行上面就执行。看源码:
final int length = discardedBytes + eol - buffer.readerIndex();
final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
buffer.readerIndex(eol + delimLength);
discardedBytes = 0;
discarding = false;
if (!failFast) {
fail(ctx, length);
}
B、丢弃模式下没有读到换行符
这种情况比较简单,如果是丢弃模式,并且没有读到换行符,那就继续继续需要失败的字节,并抛弃这些字节,也就是把readIndex移动到wirteIndex这里
看源码:
discardedBytes += buffer.readableBytes();
buffer.readerIndex(buffer.writerIndex());