写这篇文章的初衷是最近被别人问到dubbo的通信协议及底层序列化过程,发现自己比较欠缺这一块的知识。所以这个周末,结合了dubbo源码和一些网络教程,总结了一下。
编码和解码
首先做一下名词解释
编码:序列化,它将对象序列化为字节数组,用于网络传输,数据持久化或者其他用途。
解码:反序列化,把从网络,磁盘等读取的字节数还原成原始对象,以方便后续的业务逻辑操作。
dubbo在IP和TCP协议之上,又封装了自己的一层协议,为了解决TCP出现的粘包和拆包问题。
解决粘包和拆包问题有几个公认的方法:
- 消息定长,例如固定为1000个字节
- 在包尾增加回车或空格等特殊字符作为切割。典型的有FTP协议。
- 将消息分为消息头和消息体。例如dubbo (dubbo的消息头是定长的16个字节)。
dubbo网络通信的编解码过程分为几个步骤:
- consumer请求编码
- provider请求解码
- provider的响应结果编码(如果有返回值的话)
- 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个字节:消息体长度,也就是消息头+请求数据的长度。
以下是编码的入口函数
然后判断编码的类型是针对请求的还是请求响应的
然后是编码的主要方法
第214行是选择序列化组件
第215行HEADER_LENGTH==16,表示消息头是一个16个字节的数组。
第218行是在头2个字节中写入魔数,MAGIC是一个固定的数字
第221行到228行是在第3个字节处设置双向或者单向标记
231行,在第5个字节开始写入请求id,该id也用作请求返回的唯一标识
251行,从第13个字节开始写入消息体长度
最后就是写入消息
provider的请求解码
–>NettyCodecAdapter.InternalDecoder.messageReceived
–>DubboCountCodec.decode
–>ExchangeCodec.decode
–>ExchangeCodec.decodeBody
请求解码的字段意义就是上一步请求编码的字段意义。
以下是请求解码的关键方法:
可以看到这里是一个while循环,主要是为了解决TCP协议拆包的问题,如果没有达到指定的包长度,就循环的接收。如果出现粘包的问题,则根据消息体长度截断。
这里是ExchangeCodec.decode调用,主要是截取消息头。
这里是解析的关键方法。可以看到,首先校验一下头两个字节的魔数是否正确,如果不正确就直接返回。
然后判断头部长度是否是16,如果不是,也是返回异常结果。
然后再取出消息体长度,根据消息体长度来解包
解码内容过程先判断单向请求还是双向请求,然后分别处理
以双向请求为例,通过解码得到数据data的对象,该对象包括了rpc的方法名和参数以及dubbo版本号等一系列数据
至此,请求解码过程结束
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
然后按照字段意义设置byte数组,基本和请求的encode类似,但是需要注意的是第三个字节设置的是序列化组件的类型(比如hessian在dubbo中定义的类型是2),以及第4个字节是response的结果响应码,比如OK==20
在最后的encodeResponseData方法中会根据返回结果,塞入RESPONSE_VALUE或者RESPONSE_NULL_VALUE或者RESPONSE_WITH_EXCEPTION的响应码字节。
最后是放入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的响应码字节做相应的处理。