Protobuf使用规范分享
一、Protobuf 的优点
Protobuf 有如 XML,不过它更小、更快、也更简单。它以高效的二进制方式存储,比 XML 小 3 到 10 倍,快 20 到 100 倍。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构。只需使用 Protobuf 对数据结构进行一次描述,即可利用各种不同语言或从各种不同数据流中对你的结构化数据轻松读写。
有两项技术保证了采用 Protobuf 的程序能获得相对于 XML 极大的性能提高。
第一点,我们可以考察 Protobuf 序列化后的信息内容。您可以看到 Protocol Buffer 信息的表示非常紧凑,这意味着消息的体积减少,自然需要更少的资源。比如网络上传输的字节数更少,需要的 IO 更少等,从而提高性能。
第二点,我们需要理解 Protobuf 封解包的大致过程,从而理解为什么会比 XML 快很多。详细看以下链接 http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/
另外,它有一个非常棒的特性,即“向后”兼容性好,人们不必破坏已部署的、依靠“老”数据格式的程序就可以对数据结构进行升级。这样您的程序就可以不必担心因为消息结构的改变而造成的大规模的代码重构或者迁移的问题。因为添加新的消息中的 field 并不会引起已经发布的程序的任何改变。
Protobuf 语义更清晰,无需类似 XML 解析器的东西(因为 Protobuf 编译器会将 .proto 文件编译生成对应的数据访问类以对 Protobuf 数据进行序列化、反序列化操作)。 使用 Protobuf 无需学习复杂的文档对象模型,Protobuf 的编程模式比较友好,简单易学,同时它拥有良好的文档和示例,对于喜欢简单事物的人们而言,Protobuf 比其他的技术更加有吸引力。
二、Protobuf消息定义
消息由至少一个字段组合而成,类似于C语言中的结构。每个字段都有一定的格式。
字段格式:限定修饰符① | 数据类型② | 字段名称③ | = | 字段编码值④ | 字段默认值⑤
1)限定修饰符包含 required\optional\repeated
Required: 表示是一个必须字段,必须相对于发送方,在发送消息之前必须设置该字段的值,对于接收方,必须能够识别该字段的意思。发送之前没有设置required字段或者无法识别required字段都会引发编解码异常,导致消息被丢弃。至于为什么感兴趣的自己可以到protobuf官网深入研究其编解码原理。http://code.google.com/p/protobuf/
Optional:表示是一个可选字段,可选对于发送方,在发送消息时,可以有选择性的设置或者不设置该字段的值。对于接收方,如果能够识别可选字段就进行相应的处理,如果无法识别,则忽略该字段,消息中的其它字段正常处理。---因为optional字段的特性,很多接口在升级版本中都把后来添加的字段都统一的设置为optional字段,这样老的版本无需升级程序也可以正常的与新的软件进行通信,只不过新的字段无法识别而已,因为并不是每个节点都需要新的功能,因此可以做到按需升级和平滑过渡。
Repeated:表示该字段可以包含0,N个元素。其特性和optional一样,但是每一次可以包含多个值。可以看作是在传递一个数组的值。
2)数据类型
Protobuf定义了一套基本数据类型。几乎都可以映射到C\C++\Java等语言的基础数据类型.
另外,有一点特意强调一下:
关于 fixed32 和int32的区别。fixed32的打包效率比int32的效率高,但是使用的空间一般比int32多。因此一个属于时间效率高,一个属于空间效率高。根据项目的实际情况,一般选择fixed32,如果遇到对传输数据量要求比较苛刻的环境,可以选择int32.
3)字段编码值
有了该值,通信双方才能互相识别对方的字段。当然相同的编码值,其限定修饰符和数据类型必须相同.
编码值的取值范围为 1~2^32(4294967296)。其中 1~15的编码时间和空间效率都是最高的,编码值越大,其编码的时间和空间效率就越低(相对于1-15),当然一般情况下相邻的2个值编码效率的是相同的,除非2个值恰好实在4字节,12字节,20字节等的临界区。比如15和16.
有一点需要强调,消息中的字段的编码值无需连续,只要是合法的,并且不能在同一个消息中有字段包含相同的编码值。
三、protobuf编解码原理
1) ProtoBuf编码基础——Varints, varints是一种将一个整数序列化为一个或者多个Bytes的方法,越小的整数,使用的Bytes越少。
Varints的基本规则是:
(a) 每个Byte的最高位(msb)是标志位,如果该位为1,表示该Byte后面还有其它Byte,如果该位为0,表示该Byte是最后一个Byte。
(b)每个Byte的低7位是用来存数值的位
(c)Varints方法用Litte-Endian(小端)字节序
2)ProtoBuf中消息的编码规则:
(a)每条消息(message)都是有一系列的key-value对组成的, key和value分别采用不同的编码方式。
(b)对某一条件消息(message)进行编码的时候,是把该消息中所有的key-value对序列化成二进制字节流;而解码的时候,解码程序读入二进制的字节流,解析出每一个key-value对,如果解码过程中遇到识别不出来的类型,直接跳过。这样的机制,保证了即使该消息添加了新的字段,也不会影响旧的编/解码程序正常工作。
(c)key由两部分组成,一部分是字段编码值(field_num),另一部分是字段类型(wire_type)。key = field_num << 3 | wire_type
类型 | 含义 | 用于 |
---|---|---|
0 | Varint | int32, int64, uint32, uint64, sint32, sint64, bool, enum |
1 | 64-bit | fixed64, sfixed64, double |
2 | Length-delimited | string, bytes, embedded messages, packed repeated fields |
3 | Start group | groups (deprecated) |
4 | End group | groups (deprecated) |
5 | 32-bit | fixed32, sfixed32, float |
(d)varint类型(wire_type=0)的编码,与第(1)部分中介绍的方法基本一致,但是int32, int64和sint32,sint64有些特别之处:int32和int64就是简单的按varints方法来编码,所以像-1、-2这样负数也会占比较多的Bytes。于是sint32和sint64采用了一种改进的方法:先采用Zigzag方法将所有的整数(正数、0和负数)一一映射到所有的无符号数上,然 后再采用varints编码方法进行编码。Zigzag映射函数为:
Zigzag(n) = (n << 1) ^ (n >> 31), n为sint32时
Zigzag(n) = (n << 1) ^ (n >> 63), n为sint64时
(f)64-bit(wire_type=1)和32-bit(wire_type=5)的编码方式就比较简单了,直接在key后面跟上64bits或32bits,采用Little-Endian(小端)字节序。
(g)length-delimited(wire_type=2)的编码方式:key+length+content, key的编码方式是统一的,length采用varints编码方式,content就是由length指定的长度的Bytes。
(h)wire_type=3和4的现在已经不推荐使用了,因此这里也不再做介绍。
如果希望更深入的理解它,可以从代码上面去进一步研究,主要接口有protobuf_c_message_unpack、protobuf_c_message_pack、protobuf_c_message_free_unpacked等,路径/wns/commonlibs/3party/protobuf-c-0.15/protobuf-c-0.15-low-memory-version/src/google/protobuf-c
实际使用过程当中,常常出现一些使用不当的情况导致了程序异常,下面列举常见的几个情况:
1、新增字段限定修饰符设置为required(项目投入运营以后涉及到版本升级时的新增消息字段如果使用了required,需要全网统一升级)
2、多个版本同时在开发时,往同一个结构里边加入相同字段编码值的新optional字段(要知道字段编码值正是处于这种兼容性的考虑)
第2种情况会导致版本兼容性问题,代码示例如下:
发送端(AP),发送函数:
void send_person_info_to_wac() { Wns__PersonInfo person = WNS__PERSON_INFO__INIT; person.name = "sanzer"; person.gender = 1; person.has_option = 1; person.option = 5; wns_ipc_to_local_direct(WNS__MODID, 0, WNS__MSGID, (ProtobufCMessage *)&person); }
proto定义:
package wns; person_info{ requried string name = 1; required int32 gender = 2; optioned int32 option = 3; }
接收端(WAC),接收函数:
int32_t show_person_info_cb(const void *buf, int32_t len, const ProtobufCMessage *msg, const struct wns_cmd_ipc_hdr_t *proxy_hdr) { assert(msg); Wns__PersonInfo *person = (Wns__PersonInfo *)msg;return 0; } // 注册回调函数: wns_ipc_reg_callback(WNS__MSGID, show_person_info_cb, &wns__person_info__descriptor, AUTO_FREE);
proto定义:
package wns; person_info{require string name = 1;require int32 gender = 2;optioned double option = 3; }
分别编译两种proto,并将动态库拷贝到指定目录。在接收端(wac)和发送端(ap)分别运行各自程序,然后观察接收端解析的数据格式。
结果证明接收端与发送端option字段,都采用了相同的字段编码值(3),但是不同的数据属性(发送端:int32,接收端:double),这种情况下,接收端将会解析失败。
四、protobuf使用规范
为了更好的使用它,现制定以下规范:
1、不要修改已经存在的字段编码值
2、新增字段必须为optional或repeated,否则无法保证新老程序在互相传递消息时的消息兼容性。
3、在原有的消息中,不能移除已经存在的required字段,optional和repeated类型的字段可以被移除,但是他们之前使用的标签号必须被保留,不能被新的字段重用。
4、新增字段标签号可以不连续但不能重复。
转载于:https://www.cnblogs.com/zhaodahai/p/6831453.html
Protobuf使用规范分享相关推荐
- 优雅编程之阿里巴巴开发规范分享及扩展学习(三十八)
开心一笑 [小明的前女友开了家小宾馆,小明进去吃碗牛肉面,吃完就付钱,她前女友说什么都不肯收,于是小明把钱放在桌子上就走了,没想到她前女友追出来把钱塞给了小明.小明就跟他说:"做生意不容易啊 ...
- J2EE项目代码编写规范分享
码编写规范目的:能够在编码过程中实现规范化,为以后的程序开发中养成良好的行为习惯. 代码编写规范使用范围:J2EE项目开发. 包命名规范: 目的:包的命名规范应当体现出项目资源良好的划分 servle ...
- python灰产_python入门之编码风格规范分享
最需要写注释的是代码中那些技巧性的部分. 如果你在下次 代码审查 的时候必须解释一下, 那么你应该现在就给它写注释. 对于复杂的操作, 应该在其操作开始前写上若干行注释. 对于不是一目了然的代码, 应 ...
- 分享GitHub上一位老外的嵌入式C编码规范(收藏细读)
简 介: 本文分析在头条上分享GitHub上一位老外的嵌入式C编码规范(收藏细读):嵌入式大杂烩. 关键词: 嵌入式,C语句,编程规范 分享GitHub上一位老外的嵌入式C编码规范(收藏细读) §01 ...
- 【经验分享】工程开发与Coding规范
今天分享分为两部分 :) PART01 工程开发+代码规范分享/ PART02 关于某易云自动签到听歌分享- 5Mins Dev+Coding Rule: PART 1 了解真实工程开发
- 别人家的团队怎么用RabbitMQ:我总结的5点规范
大概从 2013 年开始,我就开始了自己和 RabbitMQ 的接触,到现在已经有七年多了. 在这七年中,既有一些对 RabbitMQ 的深度体验,更有无数的血泪史. 而根据我这么多年的使用经验,我将 ...
- 转 李开复微博爆谷歌公开C++编码规范 称全球最好
转 李开复微博爆谷歌公开C++编码规范 称全球最好 突然看到一篇很早新闻.MARK一下 3月8日消息,今天早上,创新工场董事长兼CEO李开复在腾讯微博上透露,Google的C++编码规范已经公开. & ...
- JAVA里常见的命名规则和命名规范
目录 1.什么是标识符呢?标识符又可以标识什么呢? 2.命名规则和命名规范的区别 3.Java标识符命名规则 4.包名的命名规范 5.类名的命名规范 6.方法名与变量名的命名规范 俗话说&q ...
- 吐血整理的 Android 性能优化思维导图,让面试官眼前一亮
引言 现如今 Android 开发行业的主要问题是因为初级的 Android 开发者太多了,导致初级开发的市场过于饱和,所以也就进一步导致初级和中级的开发者面临更大的竞争,因此想要脱离这种竞争现状,只 ...
最新文章
- 不忘历史才能开辟未来,善于继承才能善于创新
- 用命令行非交互改密码
- java 连接mysql 8.0,java连接mysql 8.0的问题
- springmvc常用配置
- cacti监控java,Cacti for Oracle监控
- mybatis的union查询
- HDU - 2047
- 吐血分享9个软件下载网站以及那些开发者大佬的传奇人生
- 高数_证明_级数收敛的莱布尼茨判别法
- 单机传奇找不到登陆器服务器列表,如果传奇服务端里面没有带登陆器怎么办?...
- web前端二维码打印模板编辑
- 一起学英语第二季第五期
- java精品入门-0基础第一篇
- [转]JavaScript和html5 canvas生成圆形印章
- Lustre—配置和管理磁盘配额
- python函数实验总结_Python程序设计实验报告:实验六 函数
- 动效icon怎么放到html,归纳一下icon动效的一些设计方法
- OSChina 周三乱弹 —— 有舍才有得
- 流批一体技术框架探索及在袋鼠云数栈中的实践
- MATLAB读RGB888数据显示图片
热门文章
- 智慧交通day04-特定目标车辆追踪03:siamese在目标跟踪中的应用-DaSiamRPN(2018)
- MongoDb 大数据查询优化、 MongoDB 索引、复合索引、唯一索引、 explain 分 析查询速度
- LeetCode 1104. 二叉树寻路(数学位运算)
- LeetCode 101. 对称二叉树(递归循环)
- matlab玫瑰,网上收到的用matlab画玫瑰花的代码怎么不行啊,报告错误,求大神
- python简单实践作业答案_python入门实践四:爬取牛客网面试专项练习题及答案
- 大型网站电商网站架构案例和技术架构的示例
- 项目上线最后工作——布署环境
- 薪资不逊NLP算法岗,边缘AI火了!
- 谁说2021届秋招算法岗一定要灰飞烟灭啦?