Loading

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源码

posted @ 2022-10-07 21:10  Grey Zeng  阅读(123)  评论(0编辑  收藏  举报