Google Protocol Buffer的安装与.proto文件的定义(转)
转自(https://www.cnblogs.com/yinheyi/p/6080244.html)
什么是protocol Buffer呢?
Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准.
我理解的就是:它是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。方便文件的存储与网络传输.
我们自己就不用定义它们的存储与传输协议了.
怎么使用protobuf呢?
第一步, 写一个proto的文件 .定义你需要的数据结构.
每二步, 使用你想要用的语言的proto文件编译器把写的proto文件编译为目标语言的相关类. (目前google提供了 C++、Java、Python 三种语言的 API).
第三步, 把第二步生成的类包含到你写的程序中, 就可以使用它了.
看个基于C++主语言的例子,下面是一个.proto文件,
package information // 定义了package的名字 message Person { required string name = 1; required int32 id = 2; optional string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { required string number = 1; optional PhoneType type = 2 [default = HOME]; } repeated PhoneNumber phone = 4; }
当我们命名这个文件时,用一个良好的命名习惯: 以这个的格式,packagename.MessageName.proto,所以这里的话,我们可以把这个文件命名为 information.Person.proto
然后,我们把.proto文件用编译器编译成C++的代码以后生成了两个文件:information.Person.h和information.Person.cc文件.
现在我们就可以用它了.
如定义一个Person的类对象,然后设置它里面的值(通过类的方法),populate,
Person person; person.set_name("John Doe"); person.set_id(1234); person.set_email("jdoe@example.com"); fstream output("myfile", ios::out | ios::binary); person.SerializeToOstream(&output); //把它序列化到输入输出流中,这里就是写文件中了.然后呢,我们可以从文件中再次retrieve出来 .
fstream input("myfile", ios::in | ios::binary); //读出文件 Person person; person.ParseFromIstream(&input); //反序列化它 cout << "Name: " << person.name() << endl; cout << "E-mail: " << person.email() << endl;
protobuf的安装:
方法一: 如果你用的ubuntu或debian的话,可以运行: sudo apt-get install protobuf-complier 命令直接安装.
方法二: 可以参考这里,https://github.com/google/protobuf,里面是什么我没有怎么仔细看哦,如果想简单安装的话,可以来这里下载protobuf-2.5.0tar.gz, 链接为:http://pan.baidu.com/s/1i5jsGiL,密码:50fo,下载完以后执行下面操作:
1,执行 tar –zxf protobuf-2.5.0.tar.gz 命令,解压文件;
2,执行 cd protobuf-2.5.0命令,进入目录。
3,执行 ./configure --prefix=加自己想安装到的绝对目录, 设置安装目录;
4,执行:make 进行编译
5, 执行: make check
6,执行: make install 进行安装;
最后呢, 加入它的环境变量,可以上系统找到它: export PATH= /home/work /protobuf/bin:$PATH
然后,你在shell里执行: protoc –version 命令,就会显示 libprotoc 2.5.0.
安装成功;
.proto文件的格式:
首先,我们定义一个.proto文件,下面的消息SearchRequest定义了3个字段.并以此为例:
message SearchRequest { required string query = 1; optional int32 page_number = 2; optional int32 result_per_page = 3; }
字段的类型: 可以为标量形式,如 string, int32等 ,也可以为复合形式,如枚举类型或其它的message类型,常见的类型如下(能看清哈):
分配的标记: 如上面所示,它们后面都有一个数字,干什么用的呢?它们的作用就是在以二进制形式的时候,可能通过这个分配的数字标记标识对应的字段,数字的范围可以从1至2次方-1. 1-15占一个字节(包括这个数字与字段类型),16-2047占两个字节.另个19000-19999是保留的数字标记. 在使用标记时,尽可能让经常出现的字段表示为1-15. 还有,我们要小的标记为将来使用.
字段的rules:
required: 在一个well-formed mmessage里,一定 要在这么一个字段.
optional: 表示可以存在可以不存在的字段.
repeated: 表示可以重复的字段,重复的次数可以为0哦. 另外,这些重复的values的顺序也会被保留下来.由于历史原因,为了让repeated的字段可以更好的encode,现在的新代码都会使一个选项[packed = true],如: repeated int32 samples = 4 [packed=true];
注意:当使用required字段的时候,要特征特别特别的小心哦,为什么呢?当我们在一个meassage里面定义了required字段的时候,如果我们有时候不想写它或着发送requied字段的时候,这时候就会出现问题,old readers会认为这个message不完整,它们就会把拒绝或着丢掉它. 所以,google的很多工程师认为,required的good 小于 harm, 所以他们选择只用optional 和repeated.
我们可以在一个.proto文件里面定义多个message.在.proto文件里面,我们用 // 来增加注释.
保留字段:在我们在删除或者注释了一个字段后,如果后来有别人使用了我们的.proto文件,为了防止别人再用我们删除或着注释过的字段的标记或名字时出现访问时的数据错误,我们应该保留我们使用过的字段的标记或名字.方法如下:
message Foo { reserved 2, 15, 9 to 11; reserved "foo", "bar"; } //注意:我们不能把标记与名字和放到一个 reserved里.
对于optional 字段, 我们可以设置它的默认值.当optional字段没有一个value的时候,就用默认的value代替.格式比如:
optional int32 result_per_page = 3 [default = 10];//就就是默认10了.另外,如果我们没有设置认字段的时候,它会根据类型,设置系统的默认value. 对于 string, 默认为空字符, 对于bool类型, 默认为 false , 对于 数字类型,默认为0, 对于枚举类型,默认列表里的第一个值(所以啊,设置枚举类型时,一定特别注意啦)
message SearchRequest { required string query = 1; optional int32 page_number = 2; optional int32 result_per_page = 3 [default = 10]; enum Corpus { UNIVERSAL = 0; WEB = 1; IMAGES = 2; LOCAL = 3; NEWS = 4; PRODUCTS = 5; VIDEO = 6; } optional Corpus corpus = 4 [default = UNIVERSAL]; }
1. 我们可以在枚举变量里面定义一个变量的别名,方法是我们把不同变量名的变量值定义成一样的,并且设置 变量 allow_alias 变true.如:
enum EnumAllowingAlias { option allow_alias = true; //一定变忘了,要不会出错的. UNKNOWN = 0; STARTED = 1; //下面的变量的值都为1 RUNNING = 1; }2. 枚举类型的值应该为一个32位的整数, 由于枚举类型的值在encoding时,为变长整数的编码方式,对于 负数来说效率是很低的,所以不建议用负数作为值.
3. 我们可以把枚举类型定义在一个message里面,也可以定义在外面,这样的话,在一个.proro文件里,所有的message都可以使用它. 另外,也可以把一个枚举类型定义在其它的.proto文件里,使用的时候的syntax为:
MessageType.EnumType
.
我们可以使用一个message类型作为一个字段的type.
message SearchResponse { repeated Result result = 1; } message Result { required string url = 1; optional string title = 2; repeated string snippets = 3; }
Importy语句:
当我们想使用别的 .proto文件里定义的message的时候怎么办呢? 我们可以像在C/C++里导入头文件一样,使用import语句,可以把别的.proto文件导入进来. 如:
import "myproject/other_protos.proto";默认地,我们只能使用直接导入的.proto文件,(即,如果我们import的一个文件里又import了其它.proto文件,但是我们不能使用间接使用哦). 不过有一个方法, 可以让我们做到传递式的引用.即, import pulic notion.
// 第一个 .proto文件的位置; //各种定义;// 第二个.proto文件 import public "第一个文件" import "其它文件"// 各种定义// 第三个.proto文件 import "第二个文件" //这是,我们可以使用第一个文件里的定义,第二个文件里的定义,但是不能使用其它文件里的定义import文件时的搜索路经: 可以通过 –I/ –proto_path指定搜索路经, 或着it looks in the directory in which the compiler was invoked.
怎么更新一个 .proto文件
(对于这部分现在不先看,因为用不着,要不看了也没有用.)
Oneof 的使用:
当我们有很多可选的字段的时候,并且在很多情况下最多有一个字段被使用时,这时我们可以作用Oneof.如:
message SampleMessage { oneof test_oneof { string name = 4; SubMessage sub_message = 9; } }这时,在Oneof 里的所有字段不会包含 required, optional ,或repeated 类型说明符. 它们共用一个memory,所示可以节约内存.我们可以使用case()或WhichOneof()方法来查看哪一个value被使用(使用哪一个方法决定我们使用的语言).
注意:当我们为Oneof里的多个字段设置值时,只有最后一个被设置的字段被保留下来了.
注意它的后向兼容性。
Map的使用:
可以增加一个map:
map<key_type, value_type> map_field = N;其中,key_type,可以为任意的整数或者string类型, value_type,可以为任意的类型. 如下为一个例子,我们把一个 Project的 Message类型与一个 string的value值相綁定了。
map<string, Project> projects = 3;
Packages:
它存在的目的是为了防止在.proto文件里的名字冲突. 就像C++里的命名空间差不多.如下所示:
首先我们定义了一个这个:
package foo.bar; message Open { ... }然后再我们使用它的时候,我们要这么用:
message Foo { ... required foo.bar.Open open = 1; //看到没,我们要加上它的package 的名字. ... }
另外,还有很多可以选的选项,见:https://developers.google.com/protocol-buffers/docs/proto.