Netty源码解析-解码器(Decoder)是如何工作(图文并茂)

 2023-02-04
原文作者:蚂蚁背大象 原文地址:https://juejin.cn/post/7077097959480885262

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

1. 解码器(Decoder)概述

以类似流的方式将Bytes从一个ByteBuf中通过解码器转换成另一种消息类型

202212302149126961.png

Netty中的解码器就是ChannelInboundHandlerAdapter的一个实现。主要是将网络中的bytes数据解码成用户需要的消息类型。

TIps: 解码器不能使用@Sharable修饰,解码器只能单独为一个Channel进行解码,如果为多个进行解码会导致数据混乱

2.解码器(Decoder)如何工作

解码器的重要一个类就是ByteToMessageDecoder ,该类继承了ChannelInboundHandlerAdapter 。所以从本质上来说解码器其实就是 ChannelInboundHandler 。既然是ChannelInboundHandler 数据读取就在ChannelInboundHandler#channelRead方法里面:

202212302149132562.png

Tips: 这里为什么会有一个if else的分支,原因在于如果开发者ByteToMessageDecoder设置在ServerBootstrap的ServerSocketChannel的handler,那么Object的对象类型就是SocketChannel,走的就是else分支,如果是设置在ServerBootstrap的childHandler那么那么Object的对象类型就是ByteBuf,走的就是if分支。 这里我只需要关注if分支即可

202212302149141783.png

2.1 累加器Cumulator

ByteToMessageDecoder 类中有一个Cumulator接口:

    public abstract class ByteToMessageDecoder extends ChannelInboundHandlerAdapter {
            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);
        }
    }

作用:累加传入的Byte数据,同时也是为了解决粘包和半包的问题。

假设我们每次解码成的数据都要是:ABCD,由于存在粘包和半包的情况,累加器会进行如下操作(以半包为例):

202212302149147114.png

第一次传入并不会解码出需要的Message,会将数据存入ByteToMessageDecoder的cumulation变量,第二次传入数据会将解码器的之前数据和传入的数据进行合并,cumulation中的数据就变成了如上图所示,然后进行解码就解码出了所需要的Message。然后清空cumulation中已经解码了的数据。

2.2 解码器工作解析

202212302149155485.png

从网络中读取的bytes数组,首先经过ByteToMessageDecoder累加器处理:

202212302149163056.png

如上图1所示:累加器处理传入的数据和前一次处理剩下的数据合并到一起,然后调用解码:

202212302149170227.png

①变量是用来保存解码后的消息,②就是调用解码方法**ByteToMessageDecoder#decode** :

202212302149178318.png

ByteToMessageDecoder#decode 是一个抽象方法,所以具体看解码器的实现,例如:

  • RedisDecoder
  • XmlDecoder

等等一些Netty实现的解码器。解码出来的结构化对象,保存在**List<Object>**中。当前的ChannelHandler解码完成后触发后续的ChannelHandler

202212302149184169.png

后续ChannelHandler的channelRead方法中传入的值就是解码后的Message类型,如下图ByteToMessageDecoder#fireChannelRead静态方法:

2022123021491917910.png

Tips:

  • 传入的Bytefuf如何不够解码成需要类型的Message,这些bytes会被累加器保存在ByteToMessageDecoder的私有变量ByteBuf cumulation中
  • 解码多出来的同样也会被bytes会被累加器保存在ByteToMessageDecoder的私有变量ByteBuf cumulation中
  • 累加器的作用就是用来解决粘包和半包的问题。

3.总结

解码器的关键就是累加器,累加器的作用就是解决了数据的粘包和半包问题。数据不够解析成对应的Message将数据保存下来,等待后续的数据传入进行合并再次进行解码,当一次传入的数据过多但是又不能够完整的解析成多个(大于1)消息,将解析后的剩下的数据保存下来,等待后续的数据传入然后进行累加。这样就实现了整个解码的过程。

我是蚂蚁背大象,文章对你有帮助点赞关注我,文章有不正确的地方请您斧正留言评论~谢谢