Netty之常见解码器分析

上一篇文章中我们简单的介绍了最顶层解码的流程,并且我们知道真正实现解码逻辑的是它的子类,那么,这节我们就将介绍集中解码器的分析
1、基于固定长度的解码器

public class FixedLengthFrameDecoder extends ByteToMessageDecoder {

    private final int frameLength;
    ...
    }

在这个解码器里面只有一个成员变量frameLength,用来表示固定长度的大小
A decoder that splits the received {@link ByteBuf}s by the fixed number

  • of bytes. For example, if you received the following four fragmented packets:
    解析前

  • ±–±—±-----±—+

  • | A | BC | DEFG | HI |

  • ±–±—±-----±—+
    解析后

  • ±----±----±----+

  • | ABC | DEF | GHI |

  • ±----±----±----+
    比如我们传进来一个字符串如上面解析前的,由于我们将固定长度设定为3,所以解析的时候会以3个为一组进行解析。

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,首先会调用解析的方法返回一个解析出来的对象,如果这个对象不是null,就将他放到容器里面。

protected Object decode(
            @SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, ByteBuf in) throws Exception {
            //判断当前可读字节是否小于我们的固定长度
        if (in.readableBytes() < frameLength) {
            return null;
        } else {
            return in.readRetainedSlice(frameLength);
        }
    }

这个方法首先会判断当前可读字节是否小于我们的固定长度,如果小于,说明当前可读字节并不足以解析出来一个对象就直接返回null。反之,就会真正去解析对象。
2、基于行的解码器

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表示如果超过了这个最大长度是否马上抛出异常
stripDelimiter表示是否带着换行符一起解析
discarding表示当前解析器处于丢弃模式
discardedBytes表示已经丢弃的字节
下面我们着重来看一下这个解码器的decode方法:

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;
        }
    }

这部分逻辑还是挺简单的,但有个地方就是由非丢弃模式转向丢弃模式的时候,我们在这里解释一下:由于在非丢弃模式下,我们没有找到分隔符,并且长度已经大于了最大长度,这部分内容肯定是要丢弃的,那么下一次数据来的时候,第一个分隔符之前的内容已经不完整,这部分不完的内容也要丢弃,所以要修改为丢弃模式,下次数据来了,直接进行丢弃处理。
3、基于分隔符的解码器

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;
    /** Set only when decoding with "\n" and "\r\n" as the delimiter.  */
    private final LineBasedFrameDecoder lineBasedDecoder;
    }

我们看他的成员变量有这么几个:
delimiters:这个数组是用来存储分隔符的,说明我们可以向构造器中传多个分隔符
maxFrameLength:这个解码器能够解析的最大长度
stripDelimiter:是否将分隔符一起解析
failFast:是否立即抛出异常
discardingTooLongFrame:是否处于丢弃模式
tooLongFrameLength:丢弃的长度
lineBasedDecoder:当分隔符是按行的时候,就直接使用行解码器了
下面我们看一下它的decode方法是怎么处理的:

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) {
                // 首先修改成非丢弃状态
                discardingTooLongFrame = false;
                //使读指针越过这之间的数据
                buffer.skipBytes(minFrameLength + minDelimLength);
				//得到已经掠过的长度
                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());
                    //修改为丢弃模式
                    discardingTooLongFrame = true;
                    if (failFast) {
                        fail(tooLongFrameLength);
                    }
                }
            } else {
                // 处于丢弃模式下直接将这段数据丢弃
                tooLongFrameLength += buffer.readableBytes();
                buffer.skipBytes(buffer.readableBytes());
            }
            return null;
        }
    }

逻辑大致上和行解码器差不多,只不过多了一步找一个最近的分隔符的操作。
3、基于长度域的解码器
Netty之常见解码器分析
我们首先看这两个重要的参数,第一个参数的意思是我们的长度域在数据流中的偏移量是多少,第二个参数表示我们的长度域是由几个字节表示的十进制数,而这个十进制数就表示我们的内容是接下来的几个字节,这里面还有两个比较重要的参数:
lengthAdjustment:需要再向后读几个字节
initialBytesToStrip:略过前面几个字节
我们接下来还是看他的decode方法:

 protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        //如果当前为丢弃模式
        if (discardingTooLongFrame) {
        	//获得需要丢弃的字节数目
            long bytesToDiscard = this.bytesToDiscard;
            //从可读字节和还需要丢弃的字节长读中选择一个最小的
            int localBytesToDiscard = (int) Math.min(bytesToDiscard, in.readableBytes());
            //略过这部分
            in.skipBytes(localBytesToDiscard);
            //去掉丢弃的长度
            bytesToDiscard -= localBytesToDiscard;
            this.bytesToDiscard = bytesToDiscard;

            failIfNecessary(false);
        }
		//如果可读字节的长度小于我们的长度域偏移量+长度域的长度,说明数据缺少,继续读
        if (in.readableBytes() < lengthFieldEndOffset) {
            return null;
        }
		//得到真正的可读数据的位置,就是当前读指针的位置+长度域的偏移量
        int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset;
        //得到一个完整的数据帧的长度:数据的长度
        long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);
		//如果这个长度为0,需要抛出异常
        if (frameLength < 0) {
            in.skipBytes(lengthFieldEndOffset);
            throw new CorruptedFrameException(
                    "negative pre-adjustment length field: " + frameLength);
        }
		//得到一个最终的数据的长度:长度域偏移量+长度域的长度+长度域确定的长度+需要调整的长度
        frameLength += lengthAdjustment + lengthFieldEndOffset;
		//如果获得的需要读的数据小于长度域+偏移量的长度,肯定有问题,需要抛出异常
        if (frameLength < lengthFieldEndOffset) {
            in.skipBytes(lengthFieldEndOffset);
            throw new CorruptedFrameException(
                    "Adjusted frame length (" + frameLength + ") is less " +
                    "than lengthFieldEndOffset: " + lengthFieldEndOffset);
        }
		//如果需要读的长度大于了最大的长度,需要将这些丢弃
        if (frameLength > maxFrameLength) {
        	//去掉frameLength后,获得还需要丢弃多少字节
            long discard = frameLength - in.readableBytes();
            tooLongFrameLength = frameLength;
			//如果小于0,说明需要丢弃的字节全部位于当前可读字节内部,直接略过这部分就好了
            if (discard < 0) {
                // buffer contains more bytes then the frameLength so we can discard all now
                in.skipBytes((int) frameLength);
            } else {
                //否则当前可读字节不够丢弃的,需要从下一次读完后继续丢弃,将当前解码器的模式修改为丢弃模式,并且略过当前可读字节
                discardingTooLongFrame = true;
                bytesToDiscard = discard;
                in.skipBytes(in.readableBytes());
            }
            failIfNecessary(true);
            return null;
        }

        // 最终要读的字节数小于最大长度,说明是合法的,但是当前可读字节小于需要读的字节长度,那么不处理,等待下一次解码的时候处理
        int frameLengthInt = (int) frameLength;
        if (in.readableBytes() < frameLengthInt) {
            return null;
        }
		//如果略过的字节大于了需要读的字节,那么抛出异常
        if (initialBytesToStrip > frameLengthInt) {
            in.skipBytes(frameLengthInt);
            throw new CorruptedFrameException(
                    "Adjusted frame length (" + frameLength + ") is less " +
                    "than initialBytesToStrip: " + initialBytesToStrip);
        }
        //否则从头部略过指定的长度
        in.skipBytes(initialBytesToStrip);
        int readerIndex = in.readerIndex();
        //得到最终需要读的数据的长度
        int actualFrameLength = frameLengthInt - initialBytesToStrip;
        ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength);
        in.readerIndex(readerIndex + actualFrameLength);
        return frame;
    }

这个方法做了这么几件事:
1、计算需要抽取的数据包的长度
2、跳过字节逻辑处理
3、丢弃模式下的处理
我们常见的几个解码器就介绍完了,让我们回顾一下解码的步骤结束这部分内容:
1、累加字节流
2、调用子类的decode方法进行解析
3、将解析得到ByteBuf向下传播