前言
在前面,我们介绍了Netty的三大组件:
在这篇文章中,我们将介绍Netty中的通用基类的解码器ByteToMessageDecoder
,在大部分的协议解码中,大量的使用了此基类来构造特定的解码器,可以说是大部分的解码器的基石。
同时在后面会介绍读半包的问题,并且揭示ByteToMessageDecoder
是如何解决半包问题的。
解码器基石
故名思义,ByteToMessageDecoder这个解码器其作用是将Byte数据转换为一个Message可读的消息对象,而具体解码转换成什么对象,由子类决定。
首先,我们来看一下ByteToMessageDecoder这个类的类结构是怎样的
很简单,其不过是一个ChannelInboundHandler,从上一篇文章 pipeline事件传播机制源码分析 中,我们可以知道,一个Inbound会关心一个read事件(一般来说),那么看看ByteToMessageDecoder中是否有相应方法,验证我们的猜想
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
...
}
那么,就以此方法为入口,来分析一下都做了什么工作
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 其只关心ByteBuf的消息的解码
if (msg instanceof ByteBuf) {
// 存放解码出来的结果的容器
CodecOutputList out = CodecOutputList.newInstance();
try {
ByteBuf data = (ByteBuf) msg;
// cumulation变量是一个可以累加ByteBuf的ByteBuf
first = cumulation == null;
// 如果是第一次解码
if (first) {
// cumulation直接赋值
cumulation = data;
} else {
// 如果是第n次,将data加入cumulation
// 此时cumulation中有没解码完的旧数据和到来的新数据
cumulation = cumulator.cumulate(ctx.alloc(), cumulation, data);
}
// 子类实现具体的解码逻辑
callDecode(ctx, cumulation, out);
}
// ...
finally {
// cumulation中的数据若被读完,需要被释放
if (cumulation != null && !cumulation.isReadable()) {
numReads = 0;
cumulation.release();
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();
decodeWasNull = !out.insertSinceRecycled()