2019.9.8笔记——netty解码器源码分析

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/qq_35262405/article/details/100638746

解码器

对于使用netty框架的我们来说,最重要的就是业务逻辑,在netty中就体现在在pipeline中添加对应的处理器。

在处理器中我们可以完成我们需要完成的业务,这种处理器大致可以分为三种,入站、出战和出入站处理器。

我们可以通过实现入站或者出站的处理器接口来自定义处理器,同时netty也提供了大量处理器。

这里我们讨论的是解码器,同时也属于入站处理器,这种处理器可以帮助我们解决粘包拆包的问题,同时也几乎是通信必不可少的处理器。

在此之前我们得先认识以下所有解码器的抽象类ByteToMessageDecoder,可以说解码器就是通过实现这个抽象类来实现解码的功能的。

ByteToMessageDecoder

在这个类中已经实现了channelRead方法,所以实现的解码器都会进入到这个方法接收数据,这里就不细致地解析。

在这个方法中主要做的事就是将数据接收并放入缓存中,如果不是第一次拿到数据就把这次拿到的数据和之前的数据合并,将数据交给callDecode方法判断。

对于数据的判断结果有两个,一个是满足解码条件,另一个是不满足解码条件。

在满足解码条件情况下,会将解码后的数据放在ByteBuf中,这个ByteBuf会放在一个list中,随后这个list便会通过fireChannelRead方法传到下一个处理器。

在不满足解码条件的情况下,也会将数据传到下一个处理器中,但是传输的数据list是空的。

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    if (msg instanceof ByteBuf) {
        CodecOutputList out = CodecOutputList.newInstance();
        try {
            ByteBuf data = (ByteBuf) msg;
            first = cumulation == null;
            if (first) {
                //第一次直接赋值
                cumulation = data;
            } else {
                //累加
                cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
            }
            callDecode(ctx, cumulation, out);
        } catch (DecoderException e) {
            throw e;
        } catch (Exception e) {
            throw new DecoderException(e);
        } finally {
            if (cumulation != null && !cumulation.isReadable()) {
                numReads = 0;
                cumulation.release();
                cumulation = null;
            } else if (++ numReads >= discardAfterReads) {
                // We did enough reads already try to discard some bytes so we not risk to see a OOME.
                // See https://github.com/netty/netty/issues/4275
                numReads = 0;
                discardSomeReadBytes();
            }

            int size = out.size();
            decodeWasNull = !out.insertSinceRecycled();
            fireChannelRead(ctx, out, size);
            out.recycle();
        }
    } else {
        ctx.fireChannelRead(msg);
    }
}

接下来是decodeRemovalReentryProtection方法

protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
    try {
        while (in.isReadable()) {
            int outSize = out.size();

            if (outSize > 0) {
                fireChannelRead(ctx, out, outSize);
                out.clear();

                // Check if this handler was removed before continuing with decoding.
                // If it was removed, it is not safe to continue to operate on the buffer.
                //
                // See:
                // - https://github.com/netty/netty/issues/4635
                if (ctx.isRemoved()) {
                    break;
                }
                outSize = 0;
            }

            int oldInputLength = in.readableBytes();
            decodeRemovalReentryProtection(ctx, in, out);

            // Check if this handler was removed before continuing the loop.
            // If it was removed, it is not safe to continue to operate on the buffer.
            //
            // See https://github.com/netty/netty/issues/1664
            if (ctx.isRemoved()) {
                break;
            }

            if (outSize == out.size()) {
                if (oldInputLength == in.readableBytes()) {
                    break;
                } else {
                    continue;
                }
            }

            if (oldInputLength == in.readableBytes()) {
                throw new DecoderException(
                        StringUtil.simpleClassName(getClass()) +
                                ".decode() did not read anything but decoded a message.");
            }

            if (isSingleDecode()) {
                break;
            }
        }
    } catch (DecoderException e) {
        throw e;
    } catch (Exception cause) {
        throw new DecoderException(cause);
    }
}

在这个方法中真正调用了解码的方法

final void decodeRemovalReentryProtection(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
        throws Exception {
    decodeState = STATE_CALLING_CHILD_DECODE;
    try {
        //调用子类的解码方法
        decode(ctx, in, out);
    } finally {
        boolean removePending = decodeState == STATE_HANDLER_REMOVED_PENDING;
        decodeState = STATE_INIT;
        if (removePending) {
            handlerRemoved(ctx);
        }
    }
}

不过这个解码的方法是一个抽象方法,需要子类实现,之所以有那么多解码器就是因为实现了各自的decode方法
在这里插入图片描述
接下来会介绍几个特殊的解码器如下

  • FixedLengthFrameDecoder
  • LineBasedFrameDecoder
  • DelimiterBasedFrameDecoder
  • LengthFieldBasedFrameDecoder

FixedLengthFrameDecoder

FixedLengthFrameDecoder是长度解码器,通过传输过来的长度来切分数据,只有达到指定的长度才会将数据传下去。

注意每次传输的数据长度都是指定的长度

FixedLengthFrameDecoder fixedLengthFrameDecoder = new FixedLengthFrameDecoder(10);

具体的解码方法,判断是否到达指定的长度,如果到达就会读取指定长度的数据(ByteBuf的读指定会移动)。

如果没有到达就会返回null

@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(
        @SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, ByteBuf in) throws Exception {
    if (in.readableBytes() < frameLength) {
        return null;
    } else {
        return in.readRetainedSlice(frameLength);
    }
}

LineBasedFrameDecoder

基于换行的解码器,遇到\n或者\n\t就会开始解码,将数据传递下去,并且会见\t或者\n\t给丢弃掉。

//参数是接收的数据的最大值
LineBasedFrameDecoder lineBasedFrameDecoder = new LineBasedFrameDecoder(100);

解码方法

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

下面才是真正的解码方法

这里先通过findEndOfLine方法将ByteBuf中的换行符找出来,之后就是读取一行数据(移动读指针),并且判断换行符是\n还是\r\n移动指针将这些换行符丢弃。

之后就是判断是否超过最大长度,如果超过就会将数据直接丢弃。

如果没有找到换行符还是会判断数据是否超过最大长度,如果超过还是会丢弃,并且会设置为丢弃模式。

如果是丢弃模式状态下找到换行符并不会将数据传递下去,而是丢弃数据并将丢弃模式取消。

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

判断缓冲区中是否有需要的换行符,有就返回行末尾的索引,没有就返回-1

/**
 * 返回找到的行末尾的缓冲区中的索引。
 * 如果缓冲区中没有找到行尾,则返回-1。
 */
private int findEndOfLine(final ByteBuf buffer) {
    int totalLength = buffer.readableBytes();
    int i = buffer.forEachByte(buffer.readerIndex() + offset, totalLength - offset, ByteProcessor.FIND_LF);
    if (i >= 0) {
        offset = 0;
        if (i > 0 && buffer.getByte(i - 1) == '\r') {
            i--;
        }
    } else {
        offset = totalLength;
    }
    return i;
}

猜你喜欢

转载自blog.csdn.net/qq_35262405/article/details/100638746