Apache Dubbo编解码原理

RPC 框架客户端在发送消息前会对 POJO 的消息内容编码为二进制流,然后通过 TCP 通道发送到服务端,服务端接受到二进制流后需要解码为 POJO 对象,那么这中间过程真的是那么简单吗?其实不然,本 Chat 旨在揭开这神秘的面纱。

本 Chat 内容如下:

什么是 TCP 半包与粘包问题,如何解决?

Apache Dubbo 自定义的协议帧是怎样的?

Apache Dubbo 客户端发送消息时是如何对消息进行编码的?

Apache Dubbo 服务端接受消息时是如何对消息进行解码的?如何处理半包、粘包问题的?

TCP 半包与粘包

大家都知道在客户端与服务端进行网络通信时候,客户端会通过 socket 把需要发送的内容进行序列化为二进制流后发送出去,然后二进制流通过网络流向服务器端,服务端接受到该请求后会解析该请求包,然后反序列化后对请求进行处理。这看似是一个很简单的过程,但是细细想来却会发现没有那么简单。如下图是客户端与服务端交互流程:

在这里插入图片描述

如上图首先在客户端发送数据时,实际是把数据写入到了 TCP 发送缓存里面的,如果发送的包的大小比 TCP 发送缓存的容量大,那么这个数据包就会被分成多个包,通过 socket 多次发送到服务端。而服务端获取数据是从接受缓存里面获取的,假设服务端第一次从接受缓存里面获取的数据是整个包的一部分,这时候就产生了半包现象,半包不是说只收到了全包的一半,是说收到了全包的一部分。

服务器读取到半包数据后,会对读取的二进制流进行解析,一般的会把二进制流反序列化为对象,这里服务器由于只读取了客户端序列化对象后的一部分,所以反序列会报错。

同理如果发送的数据包大小比 TCP 发送缓存容量小,并且假设 TCP 缓存可以存放多个包,那么客户端和服务端的一次通信就可能传递了多个包,这时候服务端从接受缓存就可能一下读取了多个包,这时候就出现了粘包现象,由于服务端从接受缓存获取的二进制流是多个对象转换来的,所以在后续的反序列化时候肯定也会出错。

常见的解决半包粘包的方法有下面 3 种。

比较常见方案是在应用层设计协议时候把协议包分为 header 和 body 两部分,header 里面记录 body 长度。当服务端从接受缓冲区读取数据后,如果发现数据大小小于包的长度则说明出现了半包,这时候就回退读取缓存的指针,等待下次读事件到来的时候再次测试。如果发现包长度大于了包长度则看如果长度是包大小整数倍则说明了出现了粘包,则循环读取多个包,否者就是出现了多个整包+半包,这时候读取整数个包然后回退半包的指针,可知 header 记录 body 长度。

在这里插入图片描述

还有一种方式是在多个包之间添加分隔符,使用分隔符来判断一个包的结束。

在这里插入图片描述

可知这种方式时候每个包大小可以不固定,当服务器端读取时候每次遇到分隔符就知道当前包结束了。

还有一种是包定长,就是每个包大小固定长度:

在这里插入图片描述

可知这种方式每个包的大小必须一致。

Apache Dubbo 自定义的协议帧

在 TCP 协议栈中,每层协议都有自己的协议报文格式,比如 TCP 协议是网络七层模型中的传输层,有 TCP 协议报文格式;在 TCP 上层是应用层,应用层协议常见的有 HTTP 协议等,Dubbo 协议作为建立在 TCP 协议之上的一种应用层协议,自然也有自己的协议包格式,Dubbo 协议也是参考 TCP 协议栈中的协议,协议内容由 header 和 body 两部分组成,其结构如下图:

在这里插入图片描述

其中协议头 header 格式如下图:

在这里插入图片描述

如上图图可知 header 总包含了 16 个字节的数据:

其中前两个字节为魔数,类似 Class 类文件里面的魔数,这里用来标识一个帧的开始,固定为 0xdabb,其中第一个字节固定为 0xda,第二个字节固定为 0xbb。

后面的一个字节是请求类型和序列化标记 id 的组合结果:requst flag|serializationId。

其中高四位标示请求类型,枚举值如下:

    protected static final byte FLAG_REQUEST = (byte) 0x80;//1000

    protected static final byte FLAG_TWOWAY = (byte) 0x40;//0100

    protected static final byte FLAG_EVENT = (byte) 0x20;//0010

其中低四位标示序列化方式,枚举值如下:

DubboSerialization:0001

Hessian2Serialization:0010

JavaSerialization:0011

CompactedJavaSerialization :0100

FastJsonSerialization:0110 

NativeJavaSerialization:0111

KryoSerialization:1000

FstSerialization: 1001

ProtostuffSerialization:1010

后面一个字节是响应报文里面才设置(请求报文里面不设置),用来标示响应的结果码

猜你喜欢

转载自www.cnblogs.com/ibdibd/p/12940948.html