Netty源码(十)之LineBasedFrameDecoder和DelimiterBasedFrameDecoder
作者:互联网
上篇博客我们说了其中一个解码器,就是基于固定长度的解码器FixedLengthFrameDecoder
,这种解码器,不具有通用性,很多时候,都是用不到的,我们来介绍一下新的解码器,就是基于行(\n,\r)的解码器LineBasedFrameDecoder
。由于上篇博客我们已经介绍了一个最简单的解码器,这篇博客不会在介绍前置的流程了,具体的前置的流程可以看上篇博客。我们打开对应的LineBasedFrameDecoder
的源码,发现和FixedLengthFrameDecoder
继承的是同一个父类ByteToMessageDecoder
,所以流程也是一样的,都是先调用父类的channelRead
方法,唯一不同的就是子类的decode
方法。所以我们只需要查看LineBasedFrameDecoder
类的decode
方法,具体的代码如下:
public class LineBasedFrameDecoder extends ByteToMessageDecoder {
/** 解码的最大长度. */
private final int maxLength;
/** 是否在超过maxLength时立即抛出异常。 */
private final boolean failFast;
/**是否解析换行符(\n,\r\n)*/
private final boolean stripDelimiter;
/** 如果因为已经超过maxLength的长度,而丢弃数据,则为True。 */
private boolean discarding;
/**已经丢弃多少字节*/
private int discardedBytes;
/**最后一次扫描的位置 */
private int offset;
@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 {
//返回\r\n的下标位置
final int eol = findEndOfLine(buffer);
if (!discarding) {
if (eol >= 0) {
final ByteBuf frame;
//算出本次要截取数据的长度
final int length = eol - buffer.readerIndex();
//判断\n前面是否是\r 是返回2 不是返回1
final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
//如果读取的长度>指定的最大长度
if (length > maxLength) {
//设置此缓冲区的readerIndex。 跳过这段数据
buffer.readerIndex(eol + delimLength);
fail(ctx, length);
return null;
}
//判断解析的数据是否要带\r\n
if (stripDelimiter) {
//返回从当前readerIndex开始的缓冲区子区域的一个新保留的片
//返回到\r\n的有效数据 不包括\r\n
frame = buffer.readRetainedSlice(length);
//跳过\r\n
buffer.skipBytes(delimLength);
} else {
//截取到\r\n的有效数据 包括\r\n
frame = buffer.readRetainedSlice(length + delimLength);
}
return frame;
} else {
//如果没有找到\r\n
final int length = buffer.readableBytes();
//如果本次数据的可读长度》最大可读长度
if (length > maxLength) {
//设置丢弃的长度为本次buffer的可读取长度
discardedBytes = length;
//跳过本次数据
buffer.readerIndex(buffer.writerIndex());
//设置为丢弃模式
discarding = true;
offset = 0;
if (failFast) {
fail(ctx, "over " + discardedBytes);
}
}
return null;
}
} else {
//找到了\r\n
if (eol >= 0) {
//以前丢弃的数据长度+本次可读的数据长度
final int length = discardedBytes + eol - buffer.readerIndex();
//拿到分隔符的长度
final int delimLength = buffer.getByte(eol) == '\r' ? 2 : 1;
//跳过(丢弃)本次数据
buffer.readerIndex(eol + delimLength);
//设置丢弃数据长度为0
discardedBytes = 0;
//设置非丢弃模式
discarding = false;
if (!failFast) {
fail(ctx, length);
}
} else {
//没找到\r\n
//丢弃的数据长度+本次可读数据的长度
discardedBytes += buffer.readableBytes();
//跳过本次可读取的数据
buffer.readerIndex(buffer.writerIndex());
// 我们跳过缓冲区中的所有内容,需要再次将偏移量设置为0。
offset = 0;
}
return null;
}
}
}
上面的代码走来是调用findEndOfLine(buffer);
方法,我们继续跟进对应的代码,具体如下:
public class LineBasedFrameDecoder extends ByteToMessageDecoder {
/**
* 返回找到的行末尾的缓冲区中的索引。
* 如果缓冲区中没有找到行尾,则返回-1。
*/
private int findEndOfLine(final ByteBuf buffer) {
int totalLength = buffer.readableBytes();
//遍历这个buffer找到\n的位置。ByteProcessor.FIND_LF的值为\n
int i = buffer.forEachByte(buffer.readerIndex() + offset, totalLength - offset, ByteProcessor.FIND_LF);
if (i >= 0) {
offset = 0;
//如果它的前一个位置是\r,直接返回\r的位置
if (i > 0 && buffer.getByte(i - 1) == '\r') {
i--;
}
} else {
offset = totalLength;
}
return i;
}
}
上面的代码找\r\n
的位置,如果找到是只有\n
,就直接返回\n
的位置,如果找到是\r\n
的位置,就直接返回\r
的位置,如果没有找到,就直接返回-1。我们再回到原来的代码地方,将原来的代码拆成两部分,我们先看非丢弃模式代码,具体如下:
public class LineBasedFrameDecoder extends ByteToMessageDecoder {
/** 解码的最大长度. */
private final int maxLength;
/** 是否在超过maxLength时立即抛出异常。 */
private final boolean failFast;
/**是否解析换行符(\n,\r\n)*/
private final boolean stripDelimiter;
/** 如果因为已经超过maxLength的长度,而丢弃数据,则为True。 */
private boolean discarding;
/**已经丢弃多少字节*/
private int discardedBytes;
/**最后一次扫描的位置 */
private int offset;
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
//返回\r\n的下标位置
final int eol = findEndOfLine(buffer);
if (!discarding) {
if (eol >= 0) {
final ByteBuf frame;
//算出本次要截取数据的长度
final int length = eol - buffer.readerIndex();
//判断\n前面是否是\r 是返回2 不是返回1
final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
//如果读取的长度>指定的最大长度
if (length > maxLength) {
//设置此缓冲区的readerIndex。 跳过这段数据
buffer.readerIndex(eol + delimLength);
fail(ctx, length);
return null;
}
//判断解析的数据是否要带\r\n
if (stripDelimiter) {
//返回从当前readerIndex开始的缓冲区子区域的一个新保留的片
//返回到\r\n的有效数据 不包括\r\n
frame = buffer.readRetainedSlice(length);
//跳过\r\n
buffer.skipBytes(delimLength);
} else {
//截取到\r\n的有效数据 包括\r\n
frame = buffer.readRetainedSlice(length + delimLength);
}
return frame;
} else {
//如果没有找到\r\n
final int length = buffer.readableBytes();
//如果本次数据的可读长度》最大可读长度
if (length > maxLength) {
//设置丢弃的长度为本次buffer的可读取长度
discardedBytes = length;
//跳过本次数据
buffer.readerIndex(buffer.writerIndex());
//设置为丢弃模式
discarding = true;
offset = 0;
if (failFast) {
fail(ctx, "over " + discardedBytes);
}
}
return null;
}
}
//省略一部分代码
}
}
上面的代码我们继续拆分。我们先看找到\r\n
情况,具体的代码如下:
public class LineBasedFrameDecoder extends ByteToMessageDecoder {
/** 解码的最大长度. */
private final int maxLength;
/** 是否在超过maxLength时立即抛出异常。 */
private final boolean failFast;
/**是否解析换行符(\n,\r\n)*/
private final boolean stripDelimiter;
/** 如果因为已经超过maxLength的长度,而丢弃数据,则为True。 */
private boolean discarding;
/**已经丢弃多少字节*/
private int discardedBytes;
/**最后一次扫描的位置 */
private int offset;
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
//返回\r\n的下标位置
final int eol = findEndOfLine(buffer);
if (!discarding) {
if (eol >= 0) {
final ByteBuf frame;
//算出本次要截取数据的长度
final int length = eol - buffer.readerIndex();
//判断\n前面是否是\r 是返回2 不是返回1
final int delimLength = buffer.getByte(eol) == '\r'? 2 : 1;
//如果读取的长度>指定的最大长度
if (length > maxLength) {
//设置此缓冲区的readerIndex。 跳过这段数据
buffer.readerIndex(eol + delimLength);
fail(ctx, length);
return null;
}
//判断解析的数据是否要带\r\n
if (stripDelimiter) {
//返回从当前readerIndex开始的缓冲区子区域的一个新保留的片
//返回到\r\n的有效数据 不包括\r\n
frame = buffer.readRetainedSlice(length);
//跳过\r\n
buffer.skipBytes(delimLength);
} else {
//截取到\r\n的有效数据 包括\r\n
frame = buffer.readRetainedSlice(length + delimLength);
}
return frame;
}
}
//省略一部分代码
}
}
上面的代码是在非丢弃模式下的找到的\r\n
的位置,走来我们拿\r\n
的位置减去读指针的位置,得到的就是本次要截取的长度,并且存入到变量length
中去。这个时候在判断返回的下标的值是\r
还是\n
,如果是\r
,就需要跳过两个位置,如果是\n
,只需要跳过一个位置。这儿通过delimLength
变量保存下来,这个时候判断读取到长度是不是大于最大的长度,如果大于最大的长度,直接将读指针设置到\r\n
的后面,表示跳过这段数据。然后抛出异常,直接返回null
。如果读取的长度没有大于最大的长度,这个时候需要判断解析的时候需不需要带上\r\n
,如果不用,直接就读取length
长度的数据,并且跳过delimLength
的长度。如果要解析的话,直接读取length+delimLength
的长度,然后返回出去。
我们再看在非丢弃模式下没有找到\r\n
的位置,具体的代码如下:
public class LineBasedFrameDecoder extends ByteToMessageDecoder {
/** 解码的最大长度. */
private final int maxLength;
/** 是否在超过maxLength时立即抛出异常。 */
private final boolean failFast;
/**是否解析换行符(\n,\r\n)*/
private final boolean stripDelimiter;
/** 如果因为已经超过maxLength的长度,而丢弃数据,则为True。 */
private boolean discarding;
/**已经丢弃多少字节*/
private int discardedBytes;
/**最后一次扫描的位置 */
private int offset;
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
//返回\r\n的下标位置
final int eol = findEndOfLine(buffer);
if (!discarding) {
//省略一部分代码
else {
//如果没有找到\r\n
final int length = buffer.readableBytes();
//如果本次数据的可读长度》最大可读长度
if (length > maxLength) {
//设置丢弃的长度为本次buffer的可读取长度
discardedBytes = length;
//跳过本次数据
buffer.readerIndex(buffer.writerIndex());
//设置为丢弃模式
discarding = true;
offset = 0;
if (failFast) {
fail(ctx, "over " + discardedBytes);
}
}
return null;
}
}
//省略一部分代码
}
}
走来直接读取buffer
的课读取的字节长度存入到length
中去,如果这次到length
大于maxLength
(最大的长度),这个时候将discardedBytes
(丢弃的字节数)设置为length
的长度,然后跳过这次的数据,同时将设置成丢弃模式,然后判断failFast
是不是true
,如果是true
直接抛出异常。至此在非丢弃模式下的两种状态已经全部讲完了。
接下来我们看看丢弃模式下,找到\r\n
的位置,具体的代码如下:
public class LineBasedFrameDecoder extends ByteToMessageDecoder {
/** 解码的最大长度. */
private final int maxLength;
/** 是否在超过maxLength时立即抛出异常。 */
private final boolean failFast;
/**是否解析换行符(\n,\r\n)*/
private final boolean stripDelimiter;
/** 如果因为已经超过maxLength的长度,而丢弃数据,则为True。 */
private boolean discarding;
/**已经丢弃多少字节*/
private int discardedBytes;
/**最后一次扫描的位置 */
private int offset;
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
//返回\r\n的下标位置
final int eol = findEndOfLine(buffer);
//省略一部分代码
else {
//找到了\r\n
if (eol >= 0) {
//以前丢弃的数据长度+本次可读的数据长度
final int length = discardedBytes + eol - buffer.readerIndex();
//拿到分隔符的长度
final int delimLength = buffer.getByte(eol) == '\r' ? 2 : 1;
//跳过(丢弃)本次数据
buffer.readerIndex(eol + delimLength);
//设置丢弃数据长度为0
discardedBytes = 0;
//设置非丢弃模式
discarding = false;
if (!failFast) {
fail(ctx, length);
}
}
//省略一部分代码
return null;
}
}
}
由于这儿是丢弃的模式,所以肯定有丢弃的数据的长度加上本次可读的数据长度,最后和上面一样,拿到分割符的长度,然后将本次的读指针设置为eol + delimLength
,表示丢弃本次的数据,最后设置丢弃的数据长度为0,然后将模式改成非丢弃的模式。failFast
为false
,抛出异常。
最后一种情况就是在丢弃模式下,没有找到\r\n
的位置,具体的代码如下:
public class LineBasedFrameDecoder extends ByteToMessageDecoder {
/** 解码的最大长度. */
private final int maxLength;
/** 是否在超过maxLength时立即抛出异常。 */
private final boolean failFast;
/**是否解析换行符(\n,\r\n)*/
private final boolean stripDelimiter;
/** 如果因为已经超过maxLength的长度,而丢弃数据,则为True。 */
private boolean discarding;
/**已经丢弃多少字节*/
private int discardedBytes;
/**最后一次扫描的位置 */
private int offset;
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
//返回\r\n的下标位置
final int eol = findEndOfLine(buffer);
else {
//没找到\r\n
//丢弃的数据长度+本次可读数据的长度
discardedBytes += buffer.readableBytes();
//跳过本次可读取的数据
buffer.readerIndex(buffer.writerIndex());
// 我们跳过缓冲区中的所有内容,需要再次将偏移量设置为0。
offset = 0;
}
return null;
}
}
}
discardedBytes
(丢弃的数据)加上这次读取到的数据组成新的丢弃数据,然后跳过本次可读取的数据。最后在丢弃模式下返回的数据都是null
。这个基于行的解码器,就讲完了。下面我会讲另外一种解码器,是基于分隔符的解码器DelimiterBasedFrameDecoder
,和基于行的解码器有异曲同工之妙。废话不多说,直接上代码。同样的我们只需要看decode
方法即可,具体的代码如下:
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;
@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);
}
// 尝试所有分隔符并选择产生最短帧的分隔符。
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) {
//设置为非丢弃模式
discardingTooLongFrame = false;
//跳过本次要截取的数据
buffer.skipBytes(minFrameLength + minDelimLength);
//设置丢弃的长度为0
int tooLongFrameLength = this.tooLongFrameLength;
this.tooLongFrameLength = 0;
if (!failFast) {
fail(tooLongFrameLength);
}
return null;
}
//本次要截取的数据大于最大能截取的长度
if (minFrameLength > maxFrameLength) {
//丢弃本次可读数据加分割符
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) {
// 丢弃缓冲区的内容,直到找到分隔符为止。
//丢弃的长度等于本次可读的长度
tooLongFrameLength = buffer.readableBytes();
//丢弃
buffer.skipBytes(buffer.readableBytes());
//设置丢弃模式为true
discardingTooLongFrame = true;
if (failFast) {
fail(tooLongFrameLength);
}
}
} else {
//仍然丢弃缓冲区,因为没有找到分隔符。
tooLongFrameLength += buffer.readableBytes();
buffer.skipBytes(buffer.readableBytes());
}
return null;
}
}
}
我们发现走来判断lineBasedDecoder
是否为空,很明显在其他的地方,有初始化过,具体的代码如下:
public class DelimiterBasedFrameDecoder extends ByteToMessageDecoder {
public DelimiterBasedFrameDecoder(
int maxFrameLength, boolean stripDelimiter, boolean failFast, ByteBuf... delimiters) {
validateMaxFrameLength(maxFrameLength);
if (delimiters == null) {
throw new NullPointerException("delimiters");
}
if (delimiters.length == 0) {
throw new IllegalArgumentException("empty delimiters");
}
//调用isLineBased判断是否要初始化基于行的解码器
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;
}
}
可以看到上面是我们该类的构造函数,我们发现调用isLineBased(delimiters)
来判断是否要初始化基于行的解码器,具体的代码如下:
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';
}
上面的代码就是判断分割符是否是\r\n
或者是\n
,如果是就返回true
,然后会初始化基于行的解码器,如果不是就直接返回false
。会执行else
的代码,将传进来的分割符进行相应的分组。
最后我们再来看的核心的解码方法,具体如下:
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;
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
//省略一部分代码
// 尝试所有分隔符并选择产生最短帧的分隔符。
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(buffer, delim);
方法,具体的代码如下:
//haystack接收到的buffer
//分割符buffer
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;
}
先是遍历接收到的buffer
,也就是这儿的haystack
,这是外层循环做的事,内层循环遍历分割符,如果相等的话,继续遍历内层循环,如果不想等直接结束内层循环。还有内层循环还要考虑边界的问题,当haystackIndex
等于haystack.writerIndex()
的时候,表示读到最后一个了这个时候直接返回到就是-1,如果全部匹配上了,就直接返回i - haystack.readerIndex();
,也就是第一个匹配的下标。会存入minFrameLength
变量中去。这个时候回到我们原来的代码,具体如下:
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
//首先判断行解码器是否被实例化了 被实例化了就使用行处理器
if (lineBasedDecoder != null) {
return lineBasedDecoder.decode(ctx, buffer);
}
// 尝试所有分隔符并选择产生最短帧的分隔符。
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);
//设置丢弃的长度为0
int tooLongFrameLength = this.tooLongFrameLength;
this.tooLongFrameLength = 0;
if (!failFast) {
fail(tooLongFrameLength);
}
return null;
}
//本次要截取的数据大于最大能截取的长度
if (minFrameLength > maxFrameLength) {
//丢弃本次可读数据加分割符
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) {
// 丢弃缓冲区的内容,直到找到分隔符为止。
//丢弃的长度等于本次可读的长度
tooLongFrameLength = buffer.readableBytes();
//丢弃
buffer.skipBytes(buffer.readableBytes());
//设置丢弃模式为true
discardingTooLongFrame = true;
if (failFast) {
fail(tooLongFrameLength);
}
}
} else {
//仍然丢弃缓冲区,因为没有找到分隔符。
tooLongFrameLength += buffer.readableBytes();
buffer.skipBytes(buffer.readableBytes());
}
return null;
}
}
我们将上面的代码分成几种情况,第一种情况:非丢弃模式下找到分割符
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
//如果找到了
if (minDelim != null) {
//分隔符长度
int minDelimLength = minDelim.capacity();
ByteBuf frame;
//省略一部分代码
//本次要截取的数据大于最大能截取的长度
if (minFrameLength > maxFrameLength) {
//丢弃本次可读数据加分割符
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;
}
//省略一部分代码
}
走来先获取分割符的长度存入到变量minDelimLength
中,如果本次读取到长度大于最大的能截取的长度,我们会直接丢弃本次的数据加上分割符。同时抛出异常,并返回null
。如果没有超过最大的能截取的长度,我们先判断是否需要保留分割符,如果不需要直接将此次读取到的长度赋值给frame
,同时改变读指针,跳过对应的分割符。如果要保留,直接读取数据加上分割符,同时赋值给frame
,然后返回farme
。
下面我们看第二种情况:丢弃模式下找到分割符
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
//省略一部分代码
//如果找到了
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);
//设置丢弃的长度为0
int tooLongFrameLength = this.tooLongFrameLength;
this.tooLongFrameLength = 0;
if (!failFast) {
fail(tooLongFrameLength);
}
return null;
}
}
}
上面的代码直接将模式设置成非丢弃模式,然后跳过本次要截取的数据。同时将丢弃的长度设置为0。然后如果failFast
为false
,就直接抛出异常
第三种情况:非丢弃模式下没有找到对应的分割符
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
//省略一部分代码
else {
//如果没有找到分割符
//判断是否处于非丢弃模式
if (!discardingTooLongFrame) {
//如果本次可读取的数据长度大于最大可读取长度
if (buffer.readableBytes() > maxFrameLength) {
// 丢弃缓冲区的内容,直到找到分隔符为止。
//丢弃的长度等于本次可读的长度
tooLongFrameLength = buffer.readableBytes();
//丢弃
buffer.skipBytes(buffer.readableBytes());
//设置丢弃模式为true
discardingTooLongFrame = true;
if (failFast) {
fail(tooLongFrameLength);
}
}
}
//省略一部分代码
return null;
}
}
判断本次可读取的数据长度是否大于最大长度,如果大于最大的长度,直接将丢弃所有的数据,同时将本次丢弃的数据存入tooLongFrameLength
变量中,如果failFast
变量为true
的话,直接抛出异常
第四种情况:丢弃模式下没有找到对应的分割符
protected Object decode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
//省略一部分代码
else {
//省略一部分代码
else {
//仍然丢弃缓冲区,因为没有找到分隔符。
tooLongFrameLength += buffer.readableBytes();
buffer.skipBytes(buffer.readableBytes());
}
return null;
}
}
直接记录下丢弃的字节数,同时丢弃所有的字节数。
至此两个解码器就讲完。下篇博客会讲最后一个解码器:基于长度的解码器。
标签:Netty,丢弃,buffer,private,int,源码,长度,LineBasedFrameDecoder,final 来源: https://blog.csdn.net/qq_33762302/article/details/113748960