24. Dubbo原理解析-编码解码之编码解码流程

这里把ExchangeCodec和DubboCodec放一起来讲解dubbo传输的底层协议组成以及它的编码解码过程。

 

传输协议

协议格式<header><bodydata>

协议头 :header 是16个字节的定长数据

   =  2 //short类型的MAGIC = (short) 0xdabb

  • 1 //一个字节的消息标志位,用来表示消息是request还是//response,twoway还是oneway,是心跳还是正常请求以及采用//的序列化反序列化协议

 + 1 //状态位, 消息类型为response时,设置请求响应状态

 + 8 //设置消息的id long类型

 + 4 //设置消息体body长度 int类型

 

 

Body data:body是消息传递的真正内容,body的占用的字节大小由协议头后四位保存。Body的内容:

Request.getData()得到dubbo请求消息传输的body对象RpcInvocation,对于请求对象dubbo其实是知道需要传输哪些信息的,所以并没有把整个RpcInvocation对象序列化传输,而是序列化传输必要字段信息,下面列举出具体信息:

  1. dubbo的版本信息

  2. 服务接口名如:com.zhanqiu.DemoService

  3. 服务的版本号

  4. 调服务的方法名

  5. 调服务的方法的参数描述符如:[int.class, boolean[].class,Object.class] => "I[ZLjava/lang/Object;"

  6. 遍历传输的参数值逐个序列化

  7. 将整个附属信息map对象attachments序列化

下图是序列化Request的body的具体代码:

 

Response.getResult()获取Result对象。根据Result对象是否有异常对象序列里化异常对象, 如果没有获取result.getValue()此对象为真正返回的业务对象消息的的body数据,整体序列化result.getValue()返回的对象。

 

 

编码整体流程:

  1. 判断消息类型是Request, Resonse如果不是调父类(可能是string telnet类型)

  2. 获取序列化方式, 可以同URL指定,没有默认为hessian方式

  3. 构建存储header的字节数组,大小是16

  4. Header数组前两位写入dubbo协议魔数(short)0xdabb

  5. Header数组第三位, 一个字节4位与或方式存储,

1)     哪种序列化方式

2)     请求还是响应消息

3)     请求时twoway还是oneway

4)     是心跳,还是正常消息

5)     如果是response, 响应的状态

  1. 获取buffer的写入位置writerIndex, 加上消息头长度16,重新设置buffer的写入位置,这里是消息body的写入位置, 因为后面是先写body,要把header的位置空出来

  2. 序列化消息body, (request, response参考前面的)写入buffer

  3. 计算消息体大小writerIndex – startIndex

  4. 检查消息体是否超过限制大小

10.      重置writeIndex就是第6点获取的值

11.      给消息头数组最后四位写入消息消body长度值int类型

12.      向buffer写入消息头数据

13.      Buffer设置writerIndex=savedWriteIndex+ HEADER_LENGTH + len

 

 

解码整体流程:

  1. 从channle获取可读取的字节数readable

  2. readable跟header_length取小构建字节数组header[]

readable < header_length说明不是一个全的dubbo协议消息(所以后面要判断消息头魔数),或者只是一个telnet消息

  1. 如果判断header[]的第一个和第二个字节不是dubbo协议魔数

a)  如果可读取字节readable大于header_length, 重新构建header[], 读取全部可读取readable数据到header

b)  遍历header中的字节判断中间是否有dubbo协议的魔数0xdabb, 如果有说明魔数开始的是dubbo协议的消息。

重新设置buffer的读取位置从魔数开始

截取header[]中从头开始到魔术位置的数据

c)  调父类解码,可能就是telnet字符串解码

  1. 如果是dubbo协议的消息 readable < header_length 说明读取header[]数据不全, 返回NEED_MORE_INPUT说明需要读取更多数据

  2. 读取header[]最后四位一个int的数据,bodydata的长度len

  3. 计算总消息大小tt = len + body_length

  4. 如果可读取数据readable < tt, 数据不够返回NEED_MORE_INPUT

  5. 由buffer和len构建ChannelBufferInputStream, 后面序列化工具会使用

  6. 下面是解码消息体body data过程

9.1 header[2] 第三位获取标志位

9.2 从标志位判断采用哪种序列化方式,获取具体的序列化工具

9.3 读取header[4]开始的8位, 获取消息的id

9.4 根据标志位判读消息为响应类型

a)构建resonse对象 new Response(id)

b)根据标志位如果是心跳,给response对象设置Event类型

c)从header[3]获取消息响应状态,给 response对象设置消息状态

d) 如果是事件消息直接利用反序列化工具读取对象

e) 如果不是构建消息接口DecodeableRpcResult result 序列化工具读取请求结果并设置到result的value属性上

f)如果响应状态不是ok, 反序列化errMessage并设置给response

9.5 根据标志位判断消息为请求类型

a) 根据id构建Request(id)

b) 根据状态位设置请求模式 twoway还是oneway

c) 根据状态位设置请求是否是事件类型

d) 如果是事件类型直接通过饭序列化工具读取

e) 如果不是事件请求,构建DecodeableRpcInvocation

      通过反序列化工具

      读取dubbo版本,设置到invocation的map中

      读取path服务名,设置到invocation的map中

      读取服务版本,设置到invocation的map中

      读取调用服务的方法, 设置到invocation的methodName属性上

      读取方法描述符, 得到入参类型

      遍历读取入参参数值       读取map对象不为空添加到invocation的map中

代码交流 2021