Netty 学习(九):解码源码说明
作者: Grey
原文地址:
解码就是不断地从TCP缓冲区中读取数据,每次读取完都需要判断是否为一个完整的数据包。
-
如果当前读取的数据不足以拼接成一个完整的业务数据包,那就保留该数据,继续从TCP缓冲区中读取,直到得到一个完整的数据包。
-
如果当前读到的数据加上已经读取的数据足够拼接成一个数据包,那就将已经读取的数据拼接上本次读取的数据,构成一个完整的业务数据包传递到业务逻辑,多余的数据仍然保留,以便和下次读到的数据尝试拼接。
使用 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专栏