在上一篇文章中,我们讲解了netty中将一条消息转换为另一条消息的框架称为MessageToMessage编码器。但是messagetomessage在处理的时候只考虑了channel中消息的转换,但是我们知道channel中最终传输的数据一定是ByteBuf,所以我们还需要一个message和ByteBuf相互转换的框架。这个框架叫做MessageToByte。注意这里的byte指的是ByteBuf,而不是字节类型。MessageToByte框架介绍为了方便扩展和用户定制,netty封装了一套MessageToByte框架。这个框架中共有三个核心类,分别是MessageToByteEncoder、ByteToMessageDecoder和ByteToMessageCodec。我们分别看下这三个核心类的定义:publicabstractclassMessageToByteEncoderextendsChannelOutboundHandlerAdapterpublicabstractclassByteToMessageDecoderextendsChannelInboundHandlerAdapterpublicabstractclassByteToMessageCodecextendsChannelDuplexHandler这三种分别继承自ChannelOutboundHandlerAdapter,ChannelInboundHandlerAdapter和ChannelDuplexHandler,分别表示向通道写入消息、从通道读取消息、向通道读写消息的双向操作。这三个类都是抽象类,接下来我们将详细分析这三个类的具体实现逻辑。MessageToByteEncoder首先看编码器。如果对比一下MessageToByteEncoder和MessageToMessageEncoder的源码实现,可以发现它们有很多相似之处。首先,在MessageToByteEncoder中定义了一个用于消息类型匹配的TypeParameterMatcher。该匹配器用于匹配接收到的消息类型。如果类型匹配,则进行消息转换操作,否则直接将消息写入通道。与MessageToMessageEncoder不同的是,MessageToByteEncoder多了一个preferDirect字段,表示在将消息转为ByteBuf时是使用diretBuf还是heapBuf。该字段的用法如下:}else{返回ctx.alloc().heapBuffer();最后我们看一下它的核心方法write:publicvoidwrite(ChannelHandlerContextctx,Objectmsg,ChannelPromisepromise)throwsException{ByteBufbuf=null;try{if(acceptOutboundMessage(msg)){@SuppressWarnings("unchecked")我施放=(I)msg??;buf=allocateBuffer(ctx,cast,preferDirect);尝试{编码(ctx,cast,buf);}最后{ReferenceCountUtil.release(cast);}if(buf.isReadable()){ctx.写(buf,承诺);}别的{buf.release();ctx.write(Unpooled.EMPTY_BUFFER,promise);}缓冲区=空;}else{ctx.write(msg,promise);}}catch(EncoderExceptione){抛出e;}catch(Throwablee){thrownewEncoderException(e);}finally{if(buf!=null){buf.release();}}}上面我们提到,write方法首先通过matcher来判断是否是要接受的消息类型。如果是,则调用encode方法将消息对象转换为ByteBuf。如果没有,直接将消息写入频道。与MessageToMessageEncoder不同,encode方法需要传入一个ByteBuf对象,而不是一个CodecOutputList。MessageToByteEncoder有一个抽象方法encode需要实现如下,protectedabstractvoidencode(ChannelHandlerContextctx,Imsg,ByteBufout)throwsException;ByteToMessageDecoderByteToMessageDecoder用于将通道中的ByteBuf消息转换为具体的消息类型,其中最重要的是Decoder方法,即channelRead方法,如下:publicvoidchannelRead(ChannelHandlerContextctx,Objectmsg)throwsException{if(msginstanceofByteBuf){CodecOutputListout=CodecOutputList.newInstance();try{first=accumulation==null;accumulation=cumulator.cumulate(ctx.alloc(),first?Unpooled.EMPTY_BUFFER:accumulation,(ByteBuf)msg??);callDecode(ctx,accumulation,out);}catch(DecoderExceptione){抛出e;}catch(Exceptione){thrownewDecoderException(e);}finally{try{if(cumulation!=null&&!cumulation.isReadable()){读数=0;积累.释放();积累=空;}elseif(++numReads>=discardAfterReads){numReads=0;discardSomeReadBytes();}intsize=out.size();firedChannelRead|=输出。insertSinceRecycled();fireChannelRead(ctx,输出,大小);}最后{out.recycle();}}}else{ctx.fireChannelRead(msg);}}}channelRead接收Object对象进行消息读取,因为这里只接受ByteBuf消息,所以方法内部会调用msginstanceofByteBuf来判断消息的类型。如果不是ByteBuf类型的消息,则不会对消息进行转换。输出对象是CodecOutputList。将ByteBuf转换为CodecOutputList后,调用fireChannelRead方法将output对象传递过去。这里的关键是如何将接收到的ByteBuf转换成CodecOutputList。转换方法称为callDecode,它接收一个称为cumulation的参数。在上面的方法中,我们还看到了一个和cumulation很像的名字叫cumulator。那么他们两个有什么区别呢?在ByteToMessageDecoder中,cumulation是一个ByteBuf对象,Cumulator是一个接口,定义了一个cumulate方法:}Cumulator用于合并传入的ByteBuf成为一个新的ByteBuf。ByteToMessageDecoder中定义了两种Cumulators,分别是MERGE_CUMULATOR和COMPOSITE_CUMULATOR。MERGE_CUMULATOR是通过内存拷贝将传入的ByteBuf复制到目标ByteBuf的累加。而COMPOSITE_CUMULATOR是将ByteBuf添加到一个CompositeByteBuf结构中,而不做内存拷贝,因为目标的结构比较复杂,所以速度会比直接内存拷贝慢。用户想要扩展的方法是decode方法,用于将一个ByteBuf转换成其他对象:protectedabstractvoiddecode(ChannelHandlerContextctx,ByteBufin,List
