一、简介ByteToMessageDecoder类
浅谈Netty的源码学习之ByteToMessageDecoder,这个解码器是Netty诸多解码的父类解码器,下图就是ByteToMessageDecoder类的子类和父类以及实现的接口类关系。
通过上面两张图片我们可以清晰看到有诸多子类是实现了ByteToMessageDecoder类的。
下面我们说说ByteToMessageDecoder的主要功能,它主要就是实现入站操作,它是一个不能共享的解码器,因为需要存还没有解码完的数据,还有各种状态,是独立的,不能进行共享。
下面直接放源码的分析和浅谈:(我的Netty版本是4.1.30版本,不同版本源码会有不同,但是大同小异)
/*
* Copyright 2012 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.socket.ChannelInputShutdownEvent;
import io.netty.util.internal.StringUtil;
import java.util.List;
/**
* 字节到消息的编码器,因此应该是实现入站操作的,这类解码器处理器都不是共享的,因为需要存还没有
* 解码完的数据,还有各种状态,是独立的,不能进行共享。
* 所以在pipeline上需要每次创建新的对象,不是能够进行对象复用。
*/
public abstract class ByteToMessageDecoder extends ChannelInboundHandlerAdapter {
/**
* 初始化合并累加器(默认是累加模式)
*
*/
public static final Cumulator MERGE_CUMULATOR = new Cumulator() {
/**
* 主要是做一般缓冲区的合并,直接将新的缓冲区拷贝到累加缓冲区中。
* cumulation 累加的缓冲区
* in 新读取的数据
*/
@Override
public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
try {
final ByteBuf buffer;
//判断累加缓冲区是否达到阈值,达到阈值进行扩展
if (cumulation.writerIndex() > cumulation.maxCapacity() - in.readableBytes()
|| cumulation.refCnt() > 1 || cumulation.isReadOnly()) {
//达到阈值进行扩展
buffer = expandCumulation(alloc, cumulation, in.readableBytes());
} else {
//赋值
buffer = cumulation;
}
buffer.writeBytes(in);
return buffer;
} finally {
// for whatever release (for example because of OutOfMemoryError)
//因为in不需要在进行往下的传递,所以应该直接释放
in.release();
}
}
};
/**
* Cumulate {@link ByteBuf}s by add them to a {@link CompositeByteBuf} and so do no memory copy whenever possible.
* Be aware that {@link CompositeByteBuf} use a more complex indexing implementation so depending on your use-case
* and the decoder implementation this may be slower then just use the {@link #MERGE_CUMULATOR}.
*/
public static final Cumulator COMPOSITE_CUMULATOR = new Cumulator() {
@Override
public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
ByteBuf buffer;
try {
if (cumulation.refCnt() > 1) {
// Expand cumulation (by replace it) when the refCnt is greater then 1 which may happen when the
// user use slice().retain() or duplicate().retain().
//
// See:
// - https://github.com/netty/netty/issues/2327
// - https://github.com/netty/netty/issues/1764
buffer = expandCumulation(alloc, cumulation, in.readableBytes());
buffer.writeBytes(in);
} else {
CompositeByteBuf composite;
if (cumulation instanceof CompositeByteBuf) {
composite = (CompositeByteBuf) cumulation;
} else {
composite = alloc.compositeBuffer(Integer.MAX_VALUE);
composite.addComponent(true, cumulation);
}
composite.addComponent(true, in);
in = null;
buffer = composite;
}
return buffer;
} finally {
if (in != null) {
// We must release if the ownership was not transfered as otherwise it may produce a leak if
// writeBytes(...) throw for whatever release (for example because of OutOfMemoryError).
in.release();
}
}
}
};
//状态码
private static final byte STATE_INIT = 0; //表示初始化状态
private static final byte STATE_CALLING_CHILD_DECODE = 1; //表示正在调用子类解码器
private static final byte STATE_HANDLER_REMOVED_PENDING = 2; //表示正在删除
ByteBuf cumulation; //核心重点 累加的缓冲区
private Cumulator cumulator = MERGE_CUMULATOR; //累加器的默认状(默认是合并的累加器)
private boolean singleDecode; //是否解码一次
private boolean decodeWasNull; //
private boolean first; //是否是第一次累加缓冲区,true表示第一次累加缓存区,false表示不是第一次累加缓冲区
/**
* A bitmask where the bits are defined as
* <ul>
* <li>{@link #STATE_INIT}</li>
* <li>{@link #STATE_CALLING_CHILD_DECODE}</li>
* <li>{@link #STATE_HANDLER_REMOVED_PENDING}</li>
* </ul>
*/
private byte decodeState = STATE_INIT;
private int discardAfterReads = 16; //读取16个字节后丢弃已读的
private int numReads; //累加器(cumulation)读取数据的次数
protected ByteToMessageDecoder() {
ensureNotSharable();
}
/**
* If set then only one message is decoded on each {@link #channelRead(ChannelHandlerContext, Object)}
* call. This may be useful if you need to do some protocol upgrade and want to make sure nothing is mixed up.
*
* Default is {@code false} as this has performance impacts.
*/
public void setSingleDecode(boolean singleDecode) {
this.singleDecode = singleDecode;
}
/**
* If {@code true} then only one message is decoded on each
* {@link #channelRead(ChannelHandlerContext, Object)} call.
*
* Default is {@code false} as this has performance impacts.
*/
public boolean isSingleDecode() {
return singleDecode;
}
/**
* Set the {@link Cumulator} to use for cumulate the received {@link ByteBuf}s.
*/
public void setCumulator(Cumulator cumulator) {
if (cumulator == null) {
throw new NullPointerException("cumulator");
}
this.cumulator = cumulator;
}
/**
* Set the number of reads after which {@link ByteBuf#discardSomeReadBytes()} are called and so free up memory.
* The default is {@code 16}.
*/
public void setDiscardAfterReads(int discardAfterReads) {
if (discardAfterReads <= 0) {
throw new IllegalArgumentException("discardAfterReads must be > 0");
}
this.discardAfterReads = discardAfterReads;
}
/**
* Returns the actual number of readable bytes in the internal cumulative
* buffer of this decoder. You usually do not need to rely on this value
* to write a decoder. Use it only when you must use it at your own risk.
* This method is a shortcut to {@link #internalBuffer() internalBuffer().readableBytes()}.
*/
protected int actualReadableBytes() {
return internalBuffer().readableBytes();
}
/**
* Returns the internal cumulative buffer of this decoder. You usually
* do not need to access the internal buffer directly to write a decoder.
* Use it only when you must use it at your own risk.
*/
protected ByteBuf internalBuffer() {
if (cumulation != null) {
return cumulation;
} else {
return Unpooled.EMPTY_BUFFER;
}
}
@Override
public final void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
if (decodeState == STATE_CALLING_CHILD_DECODE) {
decodeState = STATE_HANDLER_REMOVED_PENDING;
return;
}
ByteBuf buf = cumulation;
if (buf != null) {
// Directly set this to null so we are sure we not access it in any other method here anymore.
cumulation = null;
int readable = buf.readableBytes();
if (readable > 0) {
ByteBuf bytes = buf.readBytes(readable);
buf.release();
ctx.fireChannelRead(bytes);
} else {
buf.release();
}
numReads = 0;
ctx.fireChannelReadComplete();
}
handlerRemoved0(ctx);
}
/**
* Gets called after the {@link ByteToMessageDecoder} was removed from the actual context and it doesn't handle
* events anymore.
*
*/
protected void handlerRemoved0(ChannelHandlerContext ctx) throws Exception { }
/**
* 读写方法
* 只是在业务的处理前进行解码和缓存的操作。主要将读取的数据(msg)叠加到累加缓冲区(cumulation)中,并且调用解码方法(callDecode)
*
*
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//只对ByteBuf的消息进行处理
if (msg instanceof ByteBuf) {
//CodecOutputList就是一个列表
CodecOutputList out = CodecOutputList.newInstance();
try {
//消息转换成ByteBuf
ByteBuf data = (ByteBuf) msg;
//第一次读取数据时cumulation是null,所以first是ture
first = cumulation == null;
//判断是否是第一次解码
if (first) {
//第一解码,直接将读取的ByteBuf赋值给累加缓冲区
cumulation = data;
} else {
//不是第一解码,需要将读取的ByteBuf叠加到累加缓冲区
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) {
// 读取数据的次数大于阈值,则尝试丢弃已读的,避免占着内存
numReads = 0;
discardSomeReadBytes();
}
int size = out.size();
//有被添加或者设置,表是有读过了
decodeWasNull = !out.insertSinceRecycled();
//尝试向下传递,但是out为空,长度为空,向下传递无效
fireChannelRead(ctx, out, size);
out.recycle();
}
} else {
//不是ByteBuf类型的消息直接向下传递
ctx.fireChannelRead(msg);
}
}
/**
* 在pipeline上向下传递消息
*/
static void fireChannelRead(ChannelHandlerContext ctx, List<Object> msgs, int numElements) {
//判断list的类型是否是CodecOutputList
if (msgs instanceof CodecOutputList) {
//
fireChannelRead(ctx, (CodecOutputList) msgs, numElements);
} else {
for (int i = 0; i < numElements; i++) {
ctx.fireChannelRead(msgs.get(i));
}
}
}
/**
* 在pipeline上向下传递消息
*/
static void fireChannelRead(ChannelHandlerContext ctx, CodecOutputList msgs, int numElements) {
for (int i = 0; i < numElements; i ++) {
ctx.fireChannelRead(msgs.getUnsafe(i));
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
numReads = 0;
discardSomeReadBytes();
if (decodeWasNull) {
decodeWasNull = false;
if (!ctx.channel().config().isAutoRead()) {
ctx.read();
}
}
ctx.fireChannelReadComplete();
}
protected final void discardSomeReadBytes() {
if (cumulation != null && !first && cumulation.refCnt() == 1) {
// discard some bytes if possible to make more room in the
// buffer but only if the refCnt == 1 as otherwise the user may have
// used slice().retain() or duplicate().retain().
//
// See:
// - https://github.com/netty/netty/issues/2327
// - https://github.com/netty/netty/issues/1764
cumulation.discardSomeReadBytes();
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
channelInputClosed(ctx, true);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof ChannelInputShutdownEvent) {
// The decodeLast method is invoked when a channelInactive event is encountered.
// This method is responsible for ending requests in some situations and must be called
// when the input has been shutdown.
channelInputClosed(ctx, false);
}
super.userEventTriggered(ctx, evt);
}
private void channelInputClosed(ChannelHandlerContext ctx, boolean callChannelInactive) throws Exception {
CodecOutputList out = CodecOutputList.newInstance();
try {
channelInputClosed(ctx, out);
} catch (DecoderException e) {
throw e;
} catch (Exception e) {
throw new DecoderException(e);
} finally {
try {
if (cumulation != null) {
cumulation.release();
cumulation = null;
}
int size = out.size();
fireChannelRead(ctx, out, size);
if (size > 0) {
// Something was read, call fireChannelReadComplete()
ctx.fireChannelReadComplete();
}
if (callChannelInactive) {
ctx.fireChannelInactive();
}
} finally {
// Recycle in all cases
out.recycle();
}
}
}
/**
* Called when the input of the channel was closed which may be because it changed to inactive or because of
* {@link ChannelInputShutdownEvent}.
*/
void channelInputClosed(ChannelHandlerContext ctx, List<Object> out) throws Exception {
if (cumulation != null) {
callDecode(ctx, cumulation, out);
decodeLast(ctx, cumulation, out);
} else {
decodeLast(ctx, Unpooled.EMPTY_BUFFER, out);
}
}
/**
* 解码方法
* 判断list列表是否有消息解码出来了,如果有需要向下传递,如果没有就读取累加缓冲区的可读取的数据长度,并且调用解码方法(decodeRemovalReentryProtection())
* 之后判断是否有读取数据内容,有就继续执行循环,没有跳出循环
* @param ctx 当前的channel上下文
* @param in 累加缓冲区
* @param out list的列表(开始列表是空的)
*/
protected void callDecode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
try {
//循环直到累加缓冲区没有可读数据,或者直接跳出循环为止
while (in.isReadable()) {
//获取list的长度
int outSize = out.size();
//判断是否有消息解码出来,有就向下传递
if (outSize > 0) {
//向下传递解码器
fireChannelRead(ctx, out, outSize);
//将list列表清空
out.clear();
//判断handler的上下文是否被删除,被删除就不在处理,直接跳出循环
if (ctx.isRemoved()) {
break;
}
//设置outSize为0,重点当向下传递一次之后需要将outSize重新设置为0
outSize = 0;
}
//继续读取数据操作,累加缓冲区中可以读取的长度(写入的长度-已读取的长度)
int oldInputLength = in.readableBytes();
//继续解码操作,通过decodeRemovalReentryProtection()方法调用子类解码器进行解码,如果满足子类解码要求,子类会修改list列表的长度这样下次循环就能执行向下床底解码的方法
decodeRemovalReentryProtection(ctx, in, out);
//判断handler的上下文是否被删除,被删除就不在处理,直接跳出循环
if (ctx.isRemoved()) {
break;
}
//判断当前的outSize是否等于list列表的长度,等于表示没有生成新的消息,可能要求不够无法解码出一个消息,不相等等于生成了一个新的消息
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);
}
}
/**
* decode抽象的解码方法需要子类进行实现
* in 累加缓冲区
* out list的列表
*/
protected abstract void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception;
/**
* 解码方法,这里不做数据的读取处理,
*
* @param ctx handlercontext的上下文
* @param in 累加缓冲区
* @param out list的列表
* @throws Exception is thrown if an error occurs
*/
final void decodeRemovalReentryProtection(ChannelHandlerContext ctx, ByteBuf in, List<Object> out)
throws Exception {
//切换ByteToMessageDecoder的状态为子类解码状态
decodeState = STATE_CALLING_CHILD_DECODE;
try {
//调用子类解码器
decode(ctx, in, out);
} finally {
//判断decodeState状态是否为待删除,如果不是返回ture,如果是返回false
boolean removePending = decodeState == STATE_HANDLER_REMOVED_PENDING;
//重置decodeState为初始化状态
decodeState = STATE_INIT;
//如果当前ByetToMessageDecoder为待删除状态
if (removePending) {
//删除handlercontext的上下文
handlerRemoved(ctx);
}
}
}
/**
* Is called one last time when the {@link ChannelHandlerContext} goes in-active. Which means the
* {@link #channelInactive(ChannelHandlerContext)} was triggered.
*
* By default this will just call {@link #decode(ChannelHandlerContext, ByteBuf, List)} but sub-classes may
* override this for some special cleanup operation.
*/
protected void decodeLast(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
if (in.isReadable()) {
// Only call decode() if there is something left in the buffer to decode.
// See https://github.com/netty/netty/issues/4386
decodeRemovalReentryProtection(ctx, in, out);
}
}
static ByteBuf expandCumulation(ByteBufAllocator alloc, ByteBuf cumulation, int readable) {
ByteBuf oldCumulation = cumulation;
cumulation = alloc.buffer(oldCumulation.readableBytes() + readable);
cumulation.writeBytes(oldCumulation);
oldCumulation.release();
return cumulation;
}
/**
* Cumulate {@link ByteBuf}s.
*/
public interface Cumulator {
/**
* Cumulate the given {@link ByteBuf}s and return the {@link ByteBuf} that holds the cumulated bytes.
* The implementation is responsible to correctly handle the life-cycle of the given {@link ByteBuf}s and so
* call {@link ByteBuf#release()} if a {@link ByteBuf} is fully consumed.
*/
ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in);
}
}