写这篇文章的初衷是最近被别人问到dubbo的通信协议及底层序列化过程,发现自己比较欠缺这一块的知识。所以这个周末,结合了dubbo源码和一些网络教程,总结了一下。

编码和解码

首先做一下名词解释
编码:序列化,它将对象序列化为字节数组,用于网络传输,数据持久化或者其他用途。
解码:反序列化,把从网络,磁盘等读取的字节数还原成原始对象,以方便后续的业务逻辑操作。

dubbo在IP和TCP协议之上,又封装了自己的一层协议,为了解决TCP出现的粘包和拆包问题。
解决粘包和拆包问题有几个公认的方法:

  • 消息定长,例如固定为1000个字节
  • 在包尾增加回车或空格等特殊字符作为切割。典型的有FTP协议。
  • 将消息分为消息头和消息体。例如dubbo (dubbo的消息头是定长的16个字节)。

dubbo网络通信的编解码过程分为几个步骤:

  1. consumer请求编码
  2. provider请求解码
  3. provider的响应结果编码(如果有返回值的话)
  4. consumer的响应结果解码(如果有返回值的话)
consumer请求编码

首先说一下结论,然后对照代码解释一下

consumer请求编码调用栈
–>NettyCodecAdapter.InternalEncoder.encode
–>DubboCountCodec.encode
–>ExchangeCodec.encode
–>ExchangeCodec.encodeRequest
–>DubboCodec.encodeRequestData
第1-2个字节:一个魔数数字(就是一个固定的数字)
第3个字节:双向或单向的标记。双向是指请求有去有回,有返回值。单向是指请求有去无回,没有返回值。
第4个字节:在request请求中空着
第5-12个字节:请求id,long型的8个字节。异步变同步的全局唯一ID,用来做Consumer和Provider的来回通信标记。
第13-16个字节:消息体长度,也就是消息头+请求数据的长度。

以下是编码的入口函数

dubbo通信框架有几种 dubbo通信过程_dubbo


然后判断编码的类型是针对请求的还是请求响应的

dubbo通信框架有几种 dubbo通信过程_dubbo_02


然后是编码的主要方法

dubbo通信框架有几种 dubbo通信过程_ide_03


第214行是选择序列化组件

第215行HEADER_LENGTH==16,表示消息头是一个16个字节的数组。

第218行是在头2个字节中写入魔数,MAGIC是一个固定的数字

第221行到228行是在第3个字节处设置双向或者单向标记

231行,在第5个字节开始写入请求id,该id也用作请求返回的唯一标识

dubbo通信框架有几种 dubbo通信过程_dubbo通信框架有几种_04


251行,从第13个字节开始写入消息体长度

dubbo通信框架有几种 dubbo通信过程_序列化_05


最后就是写入消息

provider的请求解码

–>NettyCodecAdapter.InternalDecoder.messageReceived

–>DubboCountCodec.decode

–>ExchangeCodec.decode

–>ExchangeCodec.decodeBody

请求解码的字段意义就是上一步请求编码的字段意义。

以下是请求解码的关键方法:

dubbo通信框架有几种 dubbo通信过程_拆包_06


可以看到这里是一个while循环,主要是为了解决TCP协议拆包的问题,如果没有达到指定的包长度,就循环的接收。如果出现粘包的问题,则根据消息体长度截断。

dubbo通信框架有几种 dubbo通信过程_拆包_07


这里是ExchangeCodec.decode调用,主要是截取消息头。

dubbo通信框架有几种 dubbo通信过程_dubbo通信框架有几种_08


这里是解析的关键方法。可以看到,首先校验一下头两个字节的魔数是否正确,如果不正确就直接返回。

然后判断头部长度是否是16,如果不是,也是返回异常结果。

dubbo通信框架有几种 dubbo通信过程_dubbo通信框架有几种_09


然后再取出消息体长度,根据消息体长度来解包

dubbo通信框架有几种 dubbo通信过程_ide_10


解码内容过程先判断单向请求还是双向请求,然后分别处理

以双向请求为例,通过解码得到数据data的对象,该对象包括了rpc的方法名和参数以及dubbo版本号等一系列数据

dubbo通信框架有几种 dubbo通信过程_拆包_11


至此,请求解码过程结束

provider的响应结果编码

–> NettyCodecAdapter.InternalEncoder.encode
–>DubboCountCodec.encode
–>ExchangeCodec.encode
–>ExchangeCodec.encodeResponse
–>DubboCodec.encodeResponseData

其中DubboCodec.encodeResponseData是先写入一个字节。这个字节可能是RESPONSE_VALUE或者RESPONSE_NULL_VALUE或者RESPONSE_WITH_EXCEPTION
响应结果编码的消息头还是一个16字节定长的数组
第1-2个字节:魔数数字(固定数字)
第3个字节:序列化组件类型,它用于和客户端约定序列化编码号
第4个字节:response的结果响应码,例如OK=20
第5-12个字节:请求id
第13-16个字节:消息体长度

因为请求以及结果响应的encode都是一套入口,所以首先还是判断encode类型是request还是response

dubbo通信框架有几种 dubbo通信过程_dubbo通信框架有几种_12

然后按照字段意义设置byte数组,基本和请求的encode类似,但是需要注意的是第三个字节设置的是序列化组件的类型(比如hessian在dubbo中定义的类型是2),以及第4个字节是response的结果响应码,比如OK==20

dubbo通信框架有几种 dubbo通信过程_序列化_13

在最后的encodeResponseData方法中会根据返回结果,塞入RESPONSE_VALUE或者RESPONSE_NULL_VALUE或者RESPONSE_WITH_EXCEPTION的响应码字节。

dubbo通信框架有几种 dubbo通信过程_ide_14


最后是放入attachments参数。

consumer的响应结果解码

consumer的响应结果解码和之前request的响应结果解码类似,就不赘述了。需要注意的是响应结果的第三个字节是序列化组件类型,第4个字节是response的结果响应码
–> NettyCodecAdapter.InternalDecoder.messageReceived
–>DubboCountCodec.decode
–>ExchangeCodec.decode
–>DubboCodec.decodeBody
–>DecodeableRPCResult.decode
其中DecodeableRPCResult.decode根据RESPONSE_VALUE或者RESPONSE_NULL_VALUE或者RESPONSE_WITH_EXCEPTION的响应码字节做相应的处理。