• Netty 学习(九):解码源码说明


    Netty 学习(九):解码源码说明

    作者: Grey

    原文地址:

    博客园:Netty 学习(九):解码源码说明

    CSDN:Netty 学习(九):解码源码说明

    解码就是不断地从TCP缓冲区中读取数据,每次读取完都需要判断是否为一个完整的数据包。

    1. 如果当前读取的数据不足以拼接成一个完整的业务数据包,那就保留该数据,继续从TCP缓冲区中读取,直到得到一个完整的数据包。

    2. 如果当前读到的数据加上已经读取的数据足够拼接成一个数据包,那就将已经读取的数据拼接上本次读取的数据,构成一个完整的业务数据包传递到业务逻辑,多余的数据仍然保留,以便和下次读到的数据尝试拼接。

    使用 Netty 的话,整个过程就变的简单了,不需要用户自己处理粘包的问题。Netty 中定义了一个拆包的基类ByteToMessageDecoder,定义了两个变量

    public static final Cumulator MERGE_CUMULATOR = ......
    public static final Cumulator COMPOSITE_CUMULATOR = ......
    

    其中MERGE_CUMULATOR的原理是每次都将读取到的数据通过内存拷贝的方式,拼接到一个大的字节容器中,对于不够的情况,还进行了扩容处理

    if (required > cumulation.maxWritableBytes() ||
                        required > cumulation.maxFastWritableBytes() && cumulation.refCnt() > 1 ||
                        cumulation.isReadOnly()) {
                            // 扩容!
                        // Expand cumulation (by replacing it) under the following conditions:
                        // - cumulation cannot be resized to accommodate the additional data
                        // - cumulation can be expanded with a reallocation operation to accommodate but the buffer is
                        //   assumed to be shared (e.g. refCnt() > 1) and the reallocation may not be safe.
                        return expandCumulation(alloc, cumulation, in);
                    }
                    cumulation.writeBytes(in, in.readerIndex(), required);
                    in.readerIndex(in.writerIndex());
                    return cumulation;
    

    接下来是ByteToMessageDecoder.channelRead()方法是每次从TCP缓冲区读到数据都会调用的方法,主要逻辑包括如下几个

    第一步:累加数据。

    cumulation = cumulator.cumulate(ctx.alloc(),
                            first ? Unpooled.EMPTY_BUFFER : cumulation, (ByteBuf) msg);
    

    第二步:将累加的数据传递给业务进行拆包。

    // 将数据拆分成业务数据包,塞到业务数据容器out中
    callDecode(ctx, cumulation, out);
    

    第三步:清理字节容器。

    if (cumulation != null && !cumulation.isReadable()) {
                            numReads = 0;
                            try {
                                cumulation.release();
                            } catch (IllegalReferenceCountException e) {
                                //noinspection ThrowFromFinallyBlock
                                throw new IllegalReferenceCountException(
                                        getClass().getSimpleName() + "#decode() might have released its input buffer, " +
                                                "or passed it down the pipeline without a retain() call, " +
                                                "which is not allowed.", e);
                            }
                            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();
    firedChannelRead |= out.insertSinceRecycled();
    fireChannelRead(ctx, out, size);
    

    以上就是拆包器基类的主要方法,Netty 封装了一些特定的拆包器,使用起来也比较方便。

    包括

    行拆包器:LineBasedFrameDecoder

    特定分隔符拆包器:DelimiterBasedFrameDecoder

    基于长度的拆包器:LengthFieldBasedFrameDecoder

    完整代码见:hello-netty

    本文所有图例见:processon: Netty学习笔记

    更多内容见:Netty专栏

    参考资料

    跟闪电侠学 Netty:Netty 即时聊天实战与底层原理

    深度解析Netty源码

  • 相关阅读:
    算法大佬推荐
    学习的两个docker指令
    ie兼容问题解决记录
    缓存函数,curry与偏函数
    uni-app开发时遇到的注意点
    let,var,const区别
    递归的简单理解
    宏任务和微任务的进一步理解
    简单实现一个观察者模式
    业余时间
  • 原文地址:https://www.cnblogs.com/greyzeng/p/16767141.html
Copyright © 2020-2023  润新知