大家好,欢迎来到IT知识分享网。
于是他们意识到,一个好的协议应该具有兼容性和可扩展性。
他们决定制定一个以后每个版本兼容的新协议。方法很简单,就是加一个version字段:
于是他们决定为每个字段增加一个额外信息来作为一个字段的唯一标识——tag,虽然增加内存和带宽,但是可以容许这些冗余,换取易用性。
于是他们决定使用<Tag,Length,Value>三元组编码,简称TLV编码。其中Value字段是可以嵌套的。
TLV具备了很好可扩展性,但是由于其增加了2个额外的冗余信息 Tag 和 Length,特别是如果协议大部分是基本数据类型int ,short, byte,会浪费几倍存储空间。另外Value具体是什么含义,需要通信双方事先得到描述文档,即TLV不具备结构化和自解释特性。
于是我们可以定义一些type值如下:
为了解决这个问题,A重新规划一下协议类型,剥离语言特性,定义一些共性,对使用类型做了强制性约束。虽然带来了约束,但是带来通用性、简洁性和跨语言性,大家表示都很赞同,于是有了一个新的类型(type)规范。
于是后续约定协议,其实就变成了使用中间语言来书写sample.idl 协议文件,然后使用Gencpp.exe等工具,生成对应语言的编解码代码。
JCE是一种类C++语言的IDL,用于生成具体的服务接口文件。
对于结构定义,可以支持扩展字段,即可以增加字段而不影响以前结构的解析。
协议的作用是为了能在网络传输中起到封装数据的作用,是双方对传输数据的一种约定、规则,或者说通信语言。有了共同的语言之后,一方就可以将数据按照协议进行序列化,打包成字节流,底层调用send或者sendto函数发出去,而接收方只需按照约定好的规则对字节流进行反序列化,转换成自己读得懂的数据结构。
其中头信息包括以下几个部分:
Tag由Tag 1和Tag 2一起表示,取值范围是0~255,用来区分不同的字段(编码之后的数据中,只会存储字段对应的tag,而不会传输字段名,通信双方都有JCE文件,通过tag可以找到对应的字段名)。其中 Tag 2 是可选的,当Tag的值不超过14时,只需要用 Tag 1 就可以表示;当Tag的值超过14而小于256时,Tag 1 固定为15,而用 Tag 2 表示Tag的值。Tag不允许大于255。
头信息后紧跟数值数据。char、bool也被看作整型。所有的整型数据之间不做区分,也就是说一个short的值可以赋值给一个int。由于长度固定,因此不需要长度信息。
2.3.2 数字0
2.3.3 字符串(包括String1、String4)
String1跟一个字节的长度(该长度数据不包括头信息)(字符的长度小于等于255),接着紧跟内容。
String4跟四个字节的长度(该长度数据不包括头信息)(字符的长度大于255,注意最大长度为2^32 – 1),接着紧跟内容。
2.3.4 Map
紧跟一个整形数据(包括头信息)表示Map的大小,然后紧跟[Key数据(Tag为0),Value数据(Tag为1)]对列表。
在序列化的时候将key和value分开进行存放,先存放其长度,接着存放所有的key,其中key存放的tag为0,value存放的tag为1:
紧跟一个整形数据(包括头信息)表示List的大小,然后紧跟元素列表(Tag为0)
2.3.6 自定义结构开始
自定义结构开始标志,后面紧跟字段数据,字段按照tag升序顺序排列
2.3.7 自定义结构结束
自定义结构结束标志,Tag为0
比如如下结构定义:
获取头部信息,读取第一个字节的高4位作为tag值,如果大于等于15,则读取下一个字节的值作为真实的tag值;读取第一个字节的低四位作为type值
将指针偏移到数据部分
获取数据真实大小
读取数据部分成功后返回其值
TAF框架层统一使用RequestPacket 和 ResponsePacket 作为请求包结构和响应包结构,这些结构通过 JCE 定义,网络传输时,首先使用 JCE 协议将结构编码成二进制流,然后在前面加上整个数据包的长度(表示长度的字节大小+二进制数据大小),组成最终的数据包。
其中请求、响应包结构中的 sBuffer 成员变量,则是业务自己定义的请求、响应包结构体JCE序列化后的二进制数据。
举个例子,假设有一个接口协议定义如下:
将 EchoRequest JCE序列号成二进制数据 vtIn;
将 vtIn 赋值给 RequestPacket 的 sBuffer 成员变量;
填充 RequestPacket 的其他成员变量信息(比如 requestId 等);
将 RequestPacket JCE序列号成二进制数据 vtBody;
计算 vtBody 的大小 bodySize(假设是1024byte);
bodySize自身的大小(一个int32,4byte) + bodySize(1024byte) = 整个数据包的大小作为header(一个值为1028的int32整数);
header + vtBody 作为最终的数据,通过socket发送到目标ip:port上
如果终端也采用这种方式,则需要接入所有目标服务的本地代理,不方便功能的扩展。
实际上,终端和目标后台服务之间有一层代理服务(即接入层),它内部维护了命令字和目标服务代理的映射关系。
终端通过HTTP协议把数据发给接入层(对外暴露统一的域名供客户端访问),HTTP数据的body,是一个JCE结构体序列化之后加密、压缩得到的数据,接入层收到请求,首先对body解压缩、解密,然后反序列化,得到一个请求结构,结构中包含请求的命令字、以及各命令字协议的JCE序列化数据。
接入层从映射表中找到对应的后台服务后,代替终端去和后台服务通信,得到结果后再把结果以HTTP回包的形式返回给终端。
为了实现统一接入,接入层跟后台服务通信的协议必须统一:
这样一来,接入层就可以实现对RPC参数的统一打包解包:tag 1 2 3 分别是 head, requestData,responseData,tag 0 则为ret。
转载于:https://www.cnblogs.com/ExMan/p/10547497.html
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/131925.html