ProtoBuf应用 protobuf 工具
liebian365 2024-10-24 14:38 32 浏览 0 评论
1.设计协议目标
(1)解析效率
解析效率决定了使用协议的CPU成本。编码长度,即信息编码的长度,决定了使用协议的网络带宽及存储成本。
(2)易实现
需要轻量级协议,而非大而全
(3)可读性
编码后的数据的可读性决定了使用协议的调试及维护成本,不同序列化协议具有不同应用场景。
(4)兼容性
使用协议双方是否能够独立升级协议,增减协议中的字段是非常重要。
(5)跨平台
不同操作系统,不同开发语言,比如Windows用C++,Android用Java,Web用Js,IOS用object-c。
(6)安全
协议安全加密,意味着信息的安全。
2.协议涉及最核心问题
(1)序列化/反序列化
序列化:把对象转换为字节序列的过程称为对象序列化。
反序列化:把字节序列转换为对象的过程称为对象的反序列化。
TLV(TLV是tag, length和value的缩写)编码及其变体,如protobuf。
文本流编码,如XML/JSON。
固定结构编码,协议约定了传输字段类型和字段含义,没有tag,len,只有value,比如TCP/IP。
内存dump,把内存中的数据直接输出,不做任何序列化操作,反序列化,直接还原内存。
(2)数据包的完整性
包与包之间是有边界,一般有一些方案去分界,如以下方法:
以固定大小字节数目来分界,如每个包50个字节,对端每收齐50个字节,就当成一个包来解析;
以特定符号分界,每个包以特定字符来结尾(如\r\n),如果读到这个分界线符,表面上一个包到此为止。
固定包头+包体结构,包头部分是一个固定字节长度的结构,会有一个特定的字段指定包体的大小。接收包,先接收固定字节数的头部,解析出这个包的完整度,按照此长度接收包体。这也是目前应用最多的一种方案。
在序列化的buffer前面增加一个字符流的头部,其中有个字段存储包长度,根据特殊字符(比如根据\n 或者\0)判断头部的完整性。如HTTP和REDIS采用的就是这种方式,收包,先判断已收到的数据中是否包含结束符,收到结束符后解析包头,解出这个包完整长度,按此长度接收包体。
如下协议设计参考:
报文头结构:
协议头字段说明:
(3)协议升级
通过版本号指明协议版本,即通过版本号辨别不同类型协议。支持协议头部可扩展,在设计协议头部时用一个字段来指明头部长度。
(4)协议安全
xxtea 固定key。
AES 固定key。
openssl。
Signal protocol 端到端的通讯加密协议。
(5)数据压缩
参考这篇文章,聊聊字符集编码与数据压缩
deflate
gzip
lzw
主流序列化协议:xml、json、protobuf,其中XML与json前面有文章讲过了,可以参考前面的文章。
XML主要是以文本方式存储,JSON以文本结构进行存储。
protobuf是Google的一种独立和轻量级的数据交换格式,以二进制结构进行存储。
对比如下图:
序列化、反序列化效率对比
测试10万次序列化
从下图可以看出,protobuf的效率,在这些方案中,还是最高。
测试10万次反序列化
常用协议设计,HTTP协议
HTTP不适合后台协议,主要有以下原因:
(1)HTTP协议只是一个框架,没有指定包体的序列化方式,必须配合其它序列化方式才能传递业务逻辑数据。
(2)解析效率低,协议本身复杂。
HTTP适合的场景:
(1)有些公网用户api,协议穿透性好,所以最适合。
(2)效率要求没那么高的场景
3.protobuf的编译安装
开源工具:https://github.com/protocolbuffers/protobuf
1. 解压
tar zxvf protobuf-cpp-3.8.0.tar.gz
2.编译
cd protobuf-3.8.0/
./configure
make
sudo make install
3. 显示版本信息
protoc –version
4. 编写proto文件
5. 将proto文件生成对应的.cc和.h文件
定义一个消息类型
定义一个“搜索请求”的消息格式,每一个请求含有一个查询字符串、你感兴趣的查询结果所在的页数,以及每一页多少条查询结果。如下格式:
syntax = "proto3";
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
第一行指定了你正在使用proto3语法:如果你没有指定这个,编译器会使用proto2。这个指定语法行必须是文件的非空非注释的第一个行。SearchRequest消息格式有3个字段,在消息中承载的数据分别对应于每一个字段。其中每个字段都有一个名字和一种类型。
分配标识号
在消息定义中,每个字段都有唯一的一个数字标识符。这些标识符用来识别各个字段,一旦开始使用,就不再改变。[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。频繁出现的消息元素保留在[1,15]之内的标识号,一定要为这些频繁出现的标识号预留一些标识号。
最小的标识号可以从1开始,最大到2^29 - 1, or 536,870,911。不可以使用其中的[19000-19999](FieldDescriptor::kFirstReservedNumber 到 FieldDescriptor::kLastReservedNumber)的标识号,Protobuf协议实现中对这些进行了预留。如果使用了这些预留标识号,编译时就会报警。
指定字段规则
singular:一个格式良好的消息应该有0个或者1个这种字段(但是不能超过1个)
repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括0次)。重复的值的顺序会被保留。
添加更多消息类型
定义与SearchResponse消息类型对应的回复消息格式的话,你可以将它添加到相同的.proto文件中,如:
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
message SearchResponse {
...
}
//添加注释
message SearchRequest {
string query = 1;
int32 page_number = 2; // Which page number do we want?
int32 result_per_page = 3; // Number of results to return per page.
}
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
保留标识符(Reserved)
当删除或注释所有域,以后的用户重用标记号,当重新更新类型的时候,如果使用旧版本加载相同的.proto文件这会导致严重的问题,包括数据损坏、隐私错误等等。这就要通过指定保留标识符,protocol buffer的编译器会警告未来尝试使用这些域标识符的用户。
注意:不要在同一行reserved声明中同时声明域名字和标识号
message Foo {
reserved 2, 15, 9 to 11;
reserved "foo", "bar";
}
1
2
3
4
5
6
7
8
9
1
2
3
4
5
1
2
3
4
从.proto文件生成了什么?
当protocol buffer编译器来运行.proto文件时,编译器生成所选择语言代码,可以在.proto文件中定义消息类型,包括获取、设置字段值,把消息序列化到一个输出流中,以及从一个输入流中解析消息。
对C++来说,编译器会为每个.proto文件生成一个.h文件和一个.cc文件,.proto文件中的每一个消息有一个对应的类。
对Java来说,编译器为每一个消息类型生成了一个.java文件,以及一个特殊的Builder类(该类是用来创建消息类接口的)。
对Python来说,有点不太一样——Python编译器为.proto文件中的每个消息类型生成一个含有静态描述符的模块,,该模块与一个元类(metaclass)在运行时(runtime)被用来创建所需的Python数据访问类。
默认值
当一个消息被解析的时候,如果被编码的信息不包含一个特定的singular元素,被解析的对象锁对应的域被设置位一个默认值,对于不同类型指定如下:
对于strings,默认是一个空string。
对于bytes,默认是一个空的bytes。
对于bools,默认是false。
对于数值类型,默认是0。
对于枚举,默认是第一个定义的枚举值,必须为0?
对于消息类型(message),域没有被设置,确切的消息是根据语言确定的。
对于可重复域的默认值是空。
枚举
通过向消息定义中添加一个枚举(enum)并且为每个可能的值定义一个常量就可以了。在下面的例子中,在消息格式中添加了一个叫做Corpus的枚举类型——它含有所有可能的值 ——以及一个类型为Corpus的字段。
message SearchRequest {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
enum Corpus {
UNIVERSAL = 0;
WEB = 1;
IMAGES = 2;
LOCAL = 3;
NEWS = 4;
PRODUCTS = 5;
VIDEO = 6;
}
Corpus corpus = 4;
}
Corpus枚举的第一个常量映射为0:每个枚举类型必须将其第一个类型映射为0,这是因为:必须有有一个0值,我们可以用这个0值作为默认值。这个零值必须为第一个元素,为了兼容proto2语义,枚举类的第一个值总是默认值。因为enum值是使用可变编码方式的,对负数不够高效,因此不推荐在enum中使用负数。
在反序列化的过程中,无法识别的枚举值会被保存在消息中,虽然这种表示方式需要依据所使用语言而定。在那些支持开放枚举类型超出指定范围之外的语言中(例如C++和Go),为识别的值会被表示成所支持的整型。
嵌套类型
可以在其他消息类型中定义、使用消息类型,在下面的例子中,Result消息就定义在SearchResponse消息内,如:
message SearchResponse {
message Result {
string url = 1;
string title = 2;
repeated string snippets = 3;
}
repeated Result results = 1;
}
在它的父消息类型的外部重用这个消息类型,你需要以Parent.Type的形式使用它,如:
message SomeOtherMessage {
SearchResponse.Result result = 1;
}
更新一个消息类型
更新规则
(1)不要更改任何已有的字段的数值标识。
(2)记住元素默认值,这样新代码就可以和旧代码产生数据交互。通过新代码产生的消息也可以被旧代码解析:只不过新的字段会被忽视掉,如果再传递,新的字段还是不可用。
(3)非required的字段可以移除——只要它们的标识号在新的消息类型中不再使用。
(4)int32, uint32, int64, uint64,和bool是全部兼容的,这意味着可以将这些类型中的一个转换为另外一个,而不会破坏向前、 向后的兼容性。如果解析出来的数字与对应的类型不相符,那么结果就像在C++中对它进行了强制类型转换一样,可能数据精度就会丢失。
(5)sint32和sint64是互相兼容的,但是它们与其他整数类型不兼容。
(6)string和bytes是兼容的——只要bytes是有效的UTF-8编码。
(7)fixed32与sfixed32是兼容的,fixed64与sfixed64是兼容的。
(8)枚举类型与int32,uint32,int64和uint64相兼容(注意如果值不相兼容则会被截断),然而在客户端反序列化之后他们可能会有不同的处理方式,例如,未识别的proto3枚举类型会被保留在消息中,但是他的表示方式会依照语言而定。int类型的字段总会保留。
Any
Any类型消息允许你在没有指定proto定义的情况下使用消息作为一个嵌套类型。。一个Any类型包括一个可以被序列化bytes类型的任意消息,以及一个URL作为一个全局标识符和解析消息类型。相当于是有个自动型。为了使用Any类型,你需要导入import google/protobuf/any.proto
import "google/protobuf/any.proto";
message ErrorStatus {
string message = 1;
repeated google.protobuf.Any details = 2;
}
给定的消息类型的默认类型URL是type.googleapis.com/packagename.messagename。
在java中,Any类型会有特殊的pack()和unpack()访问器,在C++中会有PackFrom()和UnpackTo()方法。
// Storing an arbitrary message type in Any.
NetworkErrorDetails details = ...;
ErrorStatus status;
status.add_details()‐>PackFrom(details);
// Reading an arbitrary message from Any.
ErrorStatus status = ...;
for (const Any& detail : status.details()) {
if (detail.Is<NetworkErrorDetails>()) {
NetworkErrorDetails network_error;
detail.UnpackTo(&network_error);
... processing network_error ...
}
}
Oneof
消息中有很多可选字段, 并且同时至多一个字段会被设置,可以使用这个属性,使用oneof特性可以节省内存。可以使用case()或者WhichOneof() 方法检查哪个oneof字段被设置, 看使用什么语言。可以增加任意类型的字段, 但是不能使用repeated 关键字。
message SampleMessage {
oneof test_oneof {
string name = 4;
SubMessage sub_message = 9;
}
}
设置多次后,只有最后一次设置的字段有值。
SampleMessage message;
message.set_name("name");
CHECK(message.has_name());
message.mutable_sub_message(); // Will clear name field.
CHECK(!message.has_name());
如果解析器遇到同一个oneof中有多个成员,只有最后一个会被解析成消息。
oneof不支持repeated;
反射API对oneof 字段有效;
使用C++,需确保代码不会导致内存泄漏. 下面的代码会崩溃, 因为sub_message 已经通过set_name()删除了
SampleMessage message;
SubMessage* sub_message = message.mutable_sub_message();
message.set_name("name"); // Will delete sub_message
sub_message‐>set_... // Crashes here
使用Swap()两个oneof消息,每个消息,两个消息将拥有对方的值,例如在下面的例子中,msg1会拥有sub_message并且msg2会有name。
SampleMessage msg1;
msg1.set_name("name");
SampleMessage msg2;
msg2.mutable_sub_message();
msg1.swap(&msg2);
CHECK(msg1.has_sub_message());
CHECK(msg2.has_name());
兼容性
增加或者删除oneof字段时一定要小心. 如果检查oneof的值返回None/NOT_SET,表示oneof没有被复制或者在一个不同的版本中赋值。没有办法判断如果未识别的字段是一个oneof字段
Map
protocol buffer提供了一种快捷的语法:
map<key_type, value_type> map_field = N?
key_type可以是任意Integer或者string类型(所以,除了floating和bytes的任意标量类型都是可以的)value_type可以是任意类型。
创建一个project的映射,每个Projecct使用一个string作为key,可以像下面这样定义:
map<string, Project> projects = 3?
Map的字段可以是repeated。
序列化后的顺序和map迭代器的顺序是不确定的,所以不要期望以固定顺序处理Map。
当为.proto文件产生生成文本格式的时候,map会按照key 的顺序排序,数值化的key会按照数值排序
从序列化中解析或者融合时,如果有重复的key则后一个key不会被使用,当从文本格式中解析map时,如果存在重复的key,生成map的API现在对于所有proto3支持的语言都可用。
即使是不支持map语法的protocol buffer实现也是可以处理你的数据。
message MapFieldEntry {
key_type key = 1?
value_type value = 2?
}
repeated MapFieldEntry map_field = N?
包
为.proto文件新增一个可选的package声明符,用来防止不同的消息类型有命名冲突。如:
package foo.bar;
message Open { ... }
其他的消息格式定义中可以使用包名+消息名的方式来定义域的类型,如:
message Foo {
。。。
required foo.bar.Open open = 1;
...
}
包的声明符会根据使用语言的不同影响生成的代码。
对于C++,产生的类会被包装在C++的命名空间中,如上例中的Open会被封装在 foo::bar空间中; - 对于Java,包声明符会变为java的一个包,除非在.proto文件中提供了一个明确有java_package;
对于 Python,这个包声明符是被忽略的,因为Python模块是按照其在文件系统中的位置进行组织的。
对于Go,包可以被用做Go包名称,除非你显式的提供一个option go_package在你的.proto文件中。
服务
将消息类型用在RPC(远程方法调用)系统中,可以在.proto文件中定义一个RPC服务接口,protocol buffer编译器将会根据所选择的不同语言生成服务接口代码及存根。
service SearchService {
rpc Search (SearchRequest) returns (SearchResponse);
}
今天这篇文章就分享到这里,欢迎关注,点赞,评论,转发。
相关推荐
- 4万多吨豪华游轮遇险 竟是因为这个原因……
-
(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...
- “菜鸟黑客”必用兵器之“渗透测试篇二”
-
"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...
- 科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白
-
作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...
- 麦子陪你做作业(二):KEGG通路数据库的正确打开姿势
-
作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...
- 知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势
-
智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...
- 每日新闻播报(September 14)_每日新闻播报英文
-
AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...
- 香港新巴城巴开放实时到站数据 供科技界研发使用
-
中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...
- 5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper
-
本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...
- Qt动画效果展示_qt显示图片
-
今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...
- 如何从0到1设计实现一门自己的脚本语言
-
作者:dong...
- 三年级语文上册 仿写句子 需要的直接下载打印吧
-
描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...
- C++|那些一看就很简洁、优雅、经典的小代码段
-
目录0等概率随机洗牌:1大小写转换2字符串复制...
- 二年级上册语文必考句子仿写,家长打印,孩子照着练
-
二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)