阅读须知
- Netty版本:4.1.14.Final
- 文章中使用/* */注释的方法会做深入分析
正文
之前我们介绍了ChannelHandler,这篇文章我们来介绍一个具体的子类实现ByteToMessageDecoder,它是一个抽象类,继承了ChannelInboundHandlerAdapter,所以它处理入站事件,从命名上可以看出,它是一个解码器,用于将ByteBuf解码成POJO对象,我们来看实现:
ByteToMessageDecoder:
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 只处理ByteBuf类型的msg,其他透传
if (msg instanceof ByteBuf) {
CodecOutputList out = CodecOutputList.newInstance();
try {
ByteBuf data = (ByteBuf) msg;
// 通过cumulation是否为空判断解码器是否缓存了没有解码完成的半包消息
// 如果为空说明是首次解码或者最近一次已经处理完了半包消息
first = cumulation == null;
if (first) {
cumulation = data; // cumulation为空直接赋值
} else {
/* cumulation不为空需要累积本次消息 */
cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
}
/* 调用解码方法 */
callDecode(ctx, cumulation, out);
} catch (DecoderException e) {
throw e;
} catch (Throwable t) {
throw new DecoderException(t);
} finally {
// 判断如果cumulation不为空并且已经读取完毕,则释放cumulation
if (cumulation != null && !cumulation.isReadable()) {
numReads = 0;
cumulation.release();
cumulation = null;
} else if (++ numReads >= discardAfterReads) {
numReads = 0;
// 读取了足够的数据,尝试丢弃一些字节,避免OOM风险
discardSomeReadBytes();
}
int size = out.size();
decodeWasNull = !out.insertSinceRecycled();
// 通过管道转发CodecOutputList中的内容
fireChannelRead(ctx, out, size);
// 回收数组,清除它并清空内部存储中的所有entry
out.recycle();
}
} else {
ctx.fireChannelRead(msg);
}
}
ByteToMessageDecoder.COMPOSITE_CUMULATOR:
public ByteBuf cumulate(ByteBufAllocator alloc, ByteBuf cumulation, ByteBuf in) {
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); // 写入本次的消息
in.release(); // 写入后释放
return buffer;
}
ByteToMessageDecoder:
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;
}
ByteToMessageDecoder:
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();
// 如果ChannelHandlerContext已移除,直接退出循环,继续操作缓冲区是不安全的
if (ctx.isRemoved()) {
break;
}
outSize = 0;
}
int oldInputLength = in.readableBytes();
/* 解码 */
decodeRemovalReentryProtection(ctx, in, out);
// 同样的检查操作
if (ctx.isRemoved()) {
break;
}
// 判断out长度是否变化
if (outSize == out.size()) {
if (oldInputLength == in.readableBytes()) {
// 没有消费ByteBuf,说明是个半包消息,需要继续读取后续的数据报,退出循环
break;
} else {
// 消费了ByteBuf,继续执行
continue;
}
}
// 没有消费ByteBuf,out的长度却变了(解码出了一个或多个对象),这种情况认为是非法的
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 (Throwable cause) {
throw new DecoderException(cause);
}
}
ByteToMessageDecoder:
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);
}
}
}
到这里ByteToMessageDecoder的源码分析就完成了。