gRPC快速入门

gRPC快速入门(一)——Protobuf简介

https://blog.51cto.com/u_9291927/2331980

gRPC快速入门(一)——Protobuf简介

一、Protobuf简介

1、Protobuf简介

Protobuf即Protocol Buffers,是Google公司开发的一种跨语言和平台的序列化数据结构的方式,是一个灵活的、高效的用于序列化数据的协议。
与XML和JSON格式相比,protobuf更小、更快、更便捷。protobuf是跨语言的,并且自带一个编译器(protoc),只需要用protoc进行编译,就可以编译成Java、Python、C++、C#、Go等多种语言代码,然后可以直接使用,不需要再写其它代码,自带有解析的代码。
只需要将要被序列化的结构化数据定义一次(在.proto文件定义),便可以使用特别生成的源代码(使用protobuf提供的生成工具)轻松的使用不同的数据流完成对结构数据的读写操作。甚至可以更新.proto文件中对数据结构的定义而不会破坏依赖旧格式编译出来的程序。
GitHub地址: https://github.com/protocolbuffers/protobuf
不同语言源码版本下载地址:
 https://github.com/protocolbuffers/protobuf/releases/latest

2、Protobuf的优缺点

Protobuf的优点如下:
A、性能号,效率高
序列化后字节占用空间比XML少3-10倍,序列化的时间效率比XML快20-100倍。
B、有代码生成机制
将对结构化数据的操作封装成一个类,便于使用。
C、支持向后和向前兼容
当客户端和服务器同时使用一块协议的时候, 当客户端在协议中增加一个字节,并不会影响客户端的使用
D、支持多种编程语言
Protobuf目前已经支持Java,C++,Python、Go、Ruby等多种语言。

Protobuf的缺点如下:
A、二进制格式导致可读性差
B、缺乏自描述

二、Protobuf编译器安装

1、C++版本Protobuf编译器安装

下载C++版本的Protobuf源码protobuf-cpp-3.6.1.tar.gz
解压Protobuf源码:
tar -zxvf protobuf-cpp-3.6.1.tar.gz
进入protobuf-3.6.1源码目录:
cd protobuf-3.6.1
配置变量:
./configure --prefix=/usr/local/protobuf
编译:
make
检查、测试:
make check
安装:
sudo make install
设置环境变量:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/protobuf/lib
export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/protobuf/lib
export PATH=$PATH:/usr/local/protobuf/bin
 

检查版本号:
protoc --version

2、Protobuf编译器使用

Protobuf提供了protoc编译器,用于通过定义好的.proto文件来生成Java,Python,C++,Ruby,Objective-C,C#,Go等语言代码。
protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --javanano_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
(1)导入目录设置
IMPORT_PATH声明了一个.proto文件所在的解析import具体目录。如果忽略该值,则使用当前目录。如果有多个目录则可以多次调用–proto_path,会顺序的被访问并执行导入。-I=IMPORT_PATH是–proto_path的简化形式。
(2)生成代码指定

--cpp_out :在目标目录DST_DIR中产生C++代码
--java_out :在目标目录DST_DIR中产生Java代码
--python_out :在目标目录 DST_DIR 中产生Python代码
--go_out :在目标目录 DST_DIR 中产生Go代码
--ruby_out:在目标目录 DST_DIR 中产生Ruby代码
--javanano_out:在目标目录DST_DIR中生成JavaNano
--objc_out:在目标目录DST_DIR中产生Object代码
--csharp_out:在目标目录DST_DIR中产生Object代码
 --php_out:在目标目录DST_DIR中产生Object代码 

(3)导入proto消息文件指定
必须指定一个或多个.proto文件作为输入,多个.proto文件可以只指定一次。虽然文件路径是相对于当前目录的,每个文件必须位于其IMPORT_PATH下,以便每个文件可以确定其规范的名称。
(4)生成编程语言相关代码
当用Protobuf编译器来运行.proto文件时,编译器将生成所选择语言的代码,相应语言的代码可以操作在.proto文件中定义的消息类型,包括获取、设置字段值,将消息序列化到一个输出流中以及从一个输入流中解析消息。
对C++语言,编译器会为每个*.proto文件生成一个*.h文件和一个.cc文件,.proto文件中的每一个消息有一个对应的类。
对Java语言,编译器为每一个消息类型生成了一个.java文件以及一个特殊的Builder类(用来创建消息类接口的)。
对Go语言,编译器会为每个消息类型生成了一个.pb.go文件。
对Ruby语言,编译器会为每个消息类型生成了一个.rb文件。

三、Protobuf3语法

1、消息定义

Protobuf中,消息即结构化数据。

message Person {
  string name = 1;
  int32 id = 2;  
  string email = 3;
} 

Person消息格式有3个字段,在消息中承载的数据分别对应于每一个字段,其中每个字段都有一个名字和一种类型。
在一个消息文件.proto中可以定义多个消息类型,在定义多个相关的消息的时候较为有用。

// [START declaration]
syntax = "proto3";
package Company.Person;

import "google/protobuf/timestamp.proto";
// [END declaration]

// [START messages]
message Person {
  string name = 1;
  int32 id = 2;  // Unique ID number for this person.
  string email = 3;
  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }
  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;

  google.protobuf.Timestamp last_updated = 5;
}

// Our address book file is just one of these.
message AddressBook {
  repeated Person people = 1;
}
// [END messages]
 

.proto文件中非注释非空的第一行必须使用Proto版本声明,版本声明如下:
syntax = “proto3”;
如果不使用proto3版本声明,Protobuf编译器默认使用proto2版本。
Proto消息文件的命名如下:
packageName.MessageName.proto
packageName为package声明的包名
MessageName为消息名称

2、添加注释

添加注释可以使用C/C++/java风格的双斜杠(//)语法格式。

3、Package

.proto文件中可以新增一个可选的package声明符,用来防止不同的消息类型有命名冲突。包的声明符会根据使用语言的不同影响生成的代码:
A、对于C语言,产生的类会被包装在C的命名空间中。
B、对于Java语言,包声明符会变为java的一个包,除非在.proto文件中提供了一个明确有java_package。
C、对于Go语言,包可以被用做Go包名称,除非显式的提供一个option go_package在.proto文件中。
Protobuf语法中类型名称的解析与C++是一致的:首先从最内部开始查找,依次向外进行,每个包会被看作是其父类包的内部类。当然对于Company.Person以“.”分隔的是从最外围开始的。
Protobuf编译器会解析.proto文件中定义的所有类型名。 对于不同语言的代码生成器会知道如何来指向每个具体的类型,即使它们使用了不同的规则。

4、字段类型

字段类型包括标量类型和合成类型。
标量类型包括:
gRPC快速入门(一)——Protobuf简介_Protobuf
合成类型包括枚举(enumerations)或其它消息类型。

5、标识符

在消息定义中,每个字段都有唯一的一个数字标识符。标识符用来在消息的二进制格式中识别各个字段,一旦使用就不能够再改变。
最小的标识符可以从1开始,最大到2^29 - 1(536,870,911),不可以使用其中[19000-19999]( Protobuf协议实现中进行了预留,从FieldDescriptor::kFirstReservedNumber 到 FieldDescriptor::kLastReservedNumber)的标识号。如果非要在.proto文件中使用预留标识符,编译时就会报警。
[1,15]内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为频繁出现的消息元素保留[1,15]内的标识号。

6、字段规则

消息的字段修饰符必须是如下之一:
A、singular:一个格式良好的message应该有0个或者1个该字段(但不能超过1个)。
B、repeated:在一个格式良好的消息中,该字段可以重复任意多次(包括0次),重复值的顺序会被保留。
在proto3中,repeated的标量字段默认情况下使用packed。

7、保留标识符

如果通过删除或者注释所有字段,以后的用户在更新消息类型的时候可能重用标识符。如果使用旧版本代码加载相同的.proto文件会导致严重的问题,包括数据损坏、隐私错误等等。为了确保不会发生向前兼容可以为字段tag(reserved name可能会JSON序列化的问题)指定reserved标识符,Protobuf编译器会警告未来尝试使用相应字段标识符的用户。
不要在同一行reserved声明中同时声明字段名字和标识符。

message Foo {
    reserved 2, 15, 9 to 11;
    reserved "foo", "bar";
} 

8、默认值

当一个消息被解析的时候,如果编码消息里不包含一个特定的singular元素,被解析的对象所对应的字段被设置为一个默认值,不同类型默认值如下:
对于string,默认是一个空string
对于bytes,默认是一个空的bytes
对于bool,默认是false
对于数值类型,默认是0
对于枚举,默认是第一个定义的枚举值,必须为0
对于消息类型(message),字段没有被设置,确切的消息是根据语言确定的,通常情况下是对应语言中空列表。
对于标量消息字段,一旦消息被解析,就无法判断字段是被设置为默认值还是根本没有被设置,应该在定义消息类型时注意。

9、枚举

当定义一个消息类型时,需要为消息中的某个字段指定预定义值序列中的一个值,此时可以使用枚举定义预定以序列。如为Person消息添加一个PhoneType类型的字段,PhoneType类型的值可能是MOBILE,HOME,WORK。

 message Person {
  string name = 1;
  int32 id = 2;  // Unique ID number for this person.
  string email = 3;
  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }
  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;

  google.protobuf.Timestamp last_updated = 5;
}
 

每个枚举类型必须将其第一个类型映射为0。
可以通过allow_alias选项为true,将不同的枚举常量指定为相同的值,否则编译器会在别名的地方产生一个错误信息。

enum EnumAllowingAlias {
    option allow_alias = true;
    UNKNOWN = 0;
    STARTED = 1;
    RUNNING = 1;
}
enum EnumNotAllowingAlias {
    UNKNOWN = 0;   //EnumNotAllowingAlias中没有设置allow_alias
    STARTED = 1;
    // RUNNING = 1;//error 
} 

枚举常量必须在32位整型值的范围内。因为enum值是使用可变编码方式的,对负数不够高效,因此不推荐在enum中使用负数。
可以在一个消息定义的内部或外部定义枚举,枚举可以在.proto文件中的任何消息定义里重用。可以在一个消息中声明一个枚举类型,而在另一个不同的消息中使用枚举(采用MessageType.EnumType的语法格式)。
当对一个使用了枚举的.proto文件运行Protobuf编译器的时候,生成的代码中将有一个对应的enum(Java或C++),被用来在运行时生成的类中创建一系列的整型值符号常量(symbolic constants)。
在反序列化的过程中,无法识别的枚举值会被保存在消息中。对支持开放枚举类型超出指定范围外的语言(例如C++和Go),未识别的值会被表示成所支持的整型;对封闭枚举类型的语言中(Java),使用枚举中的一个类型来表示未识别的值,并且可以使用所支持整型来访问;在其它情况下,如果解析的消息被序列号,未识别的值将保持原样。

10、引用其它消息类型

可以将其它消息类型用作字段类型。对于同一个消息文件内部定义的消息,可以在其它消息内部直接引用消息类型;对于在其它消息文件定义的消息类型,可以通过导入其他消息文件中的定义来使用相应的消息类型。如使用google.protobuf.Timestamp消息类型需要导入相应消息文件:
import "google/protobuf/timestamp.proto";
如果要在父消息类型的外部重用消息类型,需要以Parent.Type的形式使用。

11、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
不同语言的实现会支持动态库以线程安全的方式去帮助封装或者解封装Any值。例如在java中,Any类型会有特殊的pack()和unpack()访问器,在C++中会有PackFrom()和UnpackTo()方法。

12、Oneof

Oneof定义用来代表在实现的时候,该组属性中有且只能有一个被定义,不能出现多个。

message SampleMessage {
  oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
  }
} 

上述定义中只能出现name或者sub_message的出现,不能同时出现,同时Oneof不能出现repeated域。重复传递值到Oneof多个域仅仅最后的会生效,其它的将被忽略掉。

13、Map

如果要创建一个关联映射,Protobuf提供了一种快捷的语法:

map<key_type, value_type> map_field = N; 

其中key_type可以是任意Integer或者string类型(除了floating和bytes的任意标量类型都可以),value_type可以是任意类型,但不能是map类型。
例如,创建一个Project的映射,每个Projecct使用一个string作为key:

map<string, Project> projects = 3; 

Map的字段可以是repeated。
序列化后的顺序和map迭代器的顺序是不确定的,所以不要期望以固定顺序处理Map
当为.proto文件产生生成文本格式的时候,map会按照key 的顺序排序,数值化的key会按照数值排序。
从序列化中解析或者融合时,如果有重复的key则后一个key不会被使用,当从文本格式中解析map时,如果存在重复的key。
向后兼容性问题
map语法序列化后等同于如下内容,因此即使是不支持map语法的Protobuf实现也可以处理数据:

message MapFieldEntry {
    key_type key = 1;
    value_type value = 2;
}
repeated MapFieldEntry map_field = N; 

14、定义服务

如果想要将消息类型用在RPC(远程方法调用)系统中,可以在.proto文件中定义一个RPC服务接口,Protobuf编译器将会根据所选择的不同语言生成服务接口代码及stub。如要定义一个RPC服务并具有一个方法Search,Search方法能够接收SearchRequest并返回一个SearchResponse,可以在.proto文件中进行如下定义:

service SearchService {
    rpc Search (SearchRequest) returns (SearchResponse);
} 

最直观的使用Protobuf的RPC系统是gRPC,由谷歌开发的语言和平台中的开源的PRC系统,gRPC在使用Protobuf时非常有效,如果使用特殊的Protobuf插件可以直接从.proto文件中产生相关的RPC代码。
如果不想使用gRPC,可以使用Protobuf用于自己的RPC实现。

15、JSON映射

Proto3支持JSON的编码规范,便于在不同系统之间共享数据。
如果JSON编码的数据丢失或者其本身是null,数据会在解析成Protobuf的时候被表示成默认值。如果一个字段在Protobuf中表示为默认值,会在转化成JSON编码的时候忽略掉以节省空间。
gRPC快速入门(一)——Protobuf简介_Protobuf_02

16、更新消息类型

如果一个已有的消息格式已无法满足新的需求,需要在要息中添加一个额外的字段,但同时旧版本写的代码仍然可用。可以使用更新消息解决,更新消息而不破坏已有代码是非常简单的。更新消息时规则如下:
A、不要更改任何已有字段的标识符。
B、如果增加新的字段,使用旧格式的字段仍然可以被新产生的代码所解析。应该记住元素的默认值,新代码就可以以适当的方式和旧代码产生的数据交互。通过新代码产生的消息也可以被旧代码解析,但新增加的字段会被忽视掉。未被识别的字段会在反序列化的过程中丢弃掉,如果消息再被传递给新的代码,新的字段依然是不可用的。
C、非required的字段可以移除。只要标识符在新的消息类型中不再使用(推荐重命名字段,例如在字段前添加“OBSOLETE_”前缀)。
D、int32, uint32, int64, uint64,和bool是全部兼容的,可以相互转换,而不会破坏向前、 向后的兼容性。
E、sint32和sint64是互相兼容的,但与其它整数类型不兼容。
F、string和bytes是兼容的——只要bytes是有效的UTF-8编码。
G、嵌套消息与bytes是兼容的——只要bytes包含该消息的一个编码过的版本。
H、fixed32与sfixed32是兼容的,fixed64与sfixed64是兼容的。
I、枚举类型与int32,uint32,int64和uint64相兼容(注意如果值不相兼容则会被截断),然而在客户端反序列化后可能会有不同的处理方式,例如,未识别的proto3枚举类型会被保留在消息中,但表示方式会依照语言而定。int类型的字段总会保留他们的
J、可以添加新的optional或repeated的字段, 但必须使用新的标识符(消息中从未使用过的标识符,不能使用已经被删除过的标识符)。

17、选项

在定义.proto文件时能够标注一系列的options。options并不改变整个文件声明的含义,但却能够影响特定环境下处理方式。完整的可用选项可以在google/protobuf/descriptor.proto找到。
一些选项是文件级别的,意味着它可以作用于最外范围,不包含在任何消息内部、enum或服务定义中。一些选项是消息级别的,意味着它可以用在消息定义的内部。当然有些选项可以作用在域、enum类型、enum值、服务类型及服务方法中。到目前为止,并没有一种有效的选项能作用于所有的类型
optimize_for(文件选项): 可以被设置为LITE_RUNTIME,SPEED,CODE_SIZE。这些值将通过如下的方式影响C及Java代码的生成: 
SPEED (default): Protobuf编译器将通过在消息类型上执行序列化、语法分析及其它通用的操作,生成的代码最优。
CODE_SIZE:Protobuf编译器将会产生最少量的类,通过共享或基于反射的代码来实现序列化、语法分析及各种其它操作。采用CODE_SIZE方式产生的代码将比SPEED要少得多,但操作要相对慢些。CODE_SIZE方式生成代码中实现的类及其对外的API与SPEED模式都是一样的,常用在一些包含大量的.proto文件而且并不盲目追求速度的应用中。
LITE_RUNTIME:Protobuf编译器依赖于运行时核心类库来生成代码(即采用libprotobuf-lite替代libprotobuf)。libprotobuf-lite核心类库由于忽略了一些描述符及反射,要比全类库小得多。这种模式经常在移动手机平台应用多一些。编译器采用LITE_RUNTIME模式产生的方法实现与SPEED模式不相上下,产生的类通过实现MessageLite接口,但仅仅是Messager接口的一个子集。
option optimize_for = CODE_SIZE;
cc_enable_arenas(文件选项):对于C
产生的代码启用arena allocation。
objc_class_prefix(文件选项):设置Objective-C类的前缀,添加到所有Objective-C从此.proto文件产生的类和枚举类型。没有默认值,所使用的前缀应该是×××荐的3-5个大写字符,注意2个字节的前缀是苹果所保留的。
deprecated(字段选项):如果设置为true则表示该字段已经被废弃,并且不应该在新的代码中使用。在大多数语言中没有实际的意义。
int32 old_field = 6 [deprecated=true];
java_package (file option):指定生成java类所在的包,如果在.proto文件中没有明确的声明java_package,会使用默认包名。不需要生成java代码时不起作用。
java_outer_classname (file option):指定生成Java类的名称,如果在.proto文件中没有明确声明java_outer_classname,生成的class名称将会根据.proto文件的名称采用驼峰式的命名方式进行生成。如(foo_bar.proto生成的java类名为FooBar.java),不需要生成java代码时不起任何作用
objc_class_prefix (file option): 指定Objective-C类前缀,会前置在所有类和枚举类型名之前。没有默认值,应该使用3-5个大写字母。注意所有2个字母的前缀是Apple保留的。

四、proto文件编码规范

Proto文件编码规范如下:
A、描述文件以.proto做为文件后缀。
B、除结构定义外的语句以分号结尾,结构定义包括:message、service、enum;rpc方法定义结尾的分号可有可无。
C、Message命名采用驼峰命名方式,字段命名采用小写字母加下划线分隔方式。
D、Enums类型名采用驼峰命名方式,字段命名采用大写字母加下划线分隔方式。
E、Service与rpc方法名统一采用驼峰式命名。

gRPC快速入门(二)——Protobuf序列化原理解析

https://blog.51cto.com/u_9291927/2332264

gRPC快速入门(二)——Protobuf序列化原理解析

一、Protobuf序列化原理简介

1、序列化

序列化是将数据结构或对象转换成二进制字节流的过程。
Protobuf对于不同的字段类型采用不同的编码方式和数据存储方式对消息字段进行序列化,以确保得到高效紧凑的数据压缩。
Protobuf序列化过程如下:
(1)判断每个字段是否有设置值,有值才进行编码。
(2)根据字段标识号与数据类型将字段值通过不同的编码方式进行编码。
(3)将编码后的数据块按照字段类型采用不同的数据存储方式封装成二进制数据流。

2、反序列化

反序列化是将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程。
Protobuf反序列化过程如下:
(1)调用消息类的parseFrom(input)解析从输入流读入的二进制字节数据流。
(2)将解析出来的数据按照指定的格式读取到Java、C++、Phyton对应的结构类型中。

二、Protobuf编码方式

1、Varint编码

Varint编码是一种变长的编码方式,编码原理是用字节表示数字,值越小的数字,使用越少的字节数表示。因此,可以通过减少表示数字的字节数进行数据压缩。
对int32类型的数字,一般需要4个字节表示。如果采用Varint编码,对于很小的int32类型数字,则可以用1个字节来表示;虽然大的数字会需要5个字节来表示,但大多数情况下,消息都不会有很大的数字,所以采用Varint编码方式总是可以用更少的字节数来表示数字。
Varint编码后每个字节的最高位都有特殊含义:
A、如果是1,表示后续的字节也是数字的一部分。
B、如果是0,表示本字节是最后一个字节,且剩余7位都用来表示数字。
当使用Varint解码时时,只要读取到最高位为0的字节时,表示本字节是一个值经Varint编码后得到的字节流的最后一个字节。
在计算机内,负数一般会被表示为很大的整数 ,因为计算机定义负数的符号位为数字的最高位,如果采用Varint编码方式表示一个负数,那么一定需要5个byte(因为负数的最高位是1,会被当做很大的整数处理)
Protobuf定义了sint32 / sint64类型表示负数,通过先采用Zigzag编码(将有符号数转换成无符号数),再采用Varint编码,从而用于减少编码后的字节数。
对于一个int32类型的值300的Varint编码如下:
300的二进制编码为:100101100(256+32+8+4)
从字节流末尾取出7bit并在最高位增加1构成一个字节:[1]010 1100
从字节流末尾取出7bit并在最高位增加1构成一个字节,如果是最后一个字节增加0:[0]0000010
两字节为:[0]0000010 [1]010 1100
转换为小端模式:10101100 00000010
编码结果:1010 1100 0000 0010

2、Zigzag编码

Zigazg编码是一种变长的编码方式,其编码原理是使用无符号数来表示有符号数字,使得绝对值小的数字都可以采用较少字节来表示,特别对表示负数的数据能更好地进行数据压缩。
Zigzag编码对Varint编码在表示负数时不足的补充,从而更好的帮助Protobuf进行数据的压缩。因此,如果提前预知字段值是可能取负数的时候,需要采用sint32/sint64数据类型。
Protobuf通过Varint和Zigzag编码后,大大减少了字段值占用字节数。
-2的Zigzag过程如下:
gRPC快速入门(二)——Protobuf序列化原理解析_序列化原理

三、Protobuf数据存储方式

1、T-L-V数据存储方式

T-L-V(Tag - Length - Value),即标识符-长度-字段值的存储方式,其原理是以标识符-长度-字段值表示单个数据,最终将所有数据拼接成一个字节流,从而实现数据存储的功能。
其中Length可选存储,如储存Varint编码数据就不需要存储Length,此时为T-V存储方式。
gRPC快速入门(二)——Protobuf序列化原理解析_序列化原理_02
T-L-V 存储方式的优点:
A、不需要分隔符就能分隔开字段,减少了分隔符的使用。
B、各字段存储得非常紧凑,存储空间利用率非常高。
C、如果某个字段没有被设置字段值,那么该字段在序列化时的数据中是完全不存在的,即不需要编码,相应字段在解码时才会被设置为默认值。

2、T-V数据存储方式

消息字段的标识号、数据类型、字段值经过Protobuf采用Varint和Zigzag编码后,以T-V(Tag-Value)方式进行数据存储。
对于Varint与Zigzag编码方式编码的数据,省略了T-L-V中的字节长度Length。
gRPC快速入门(二)——Protobuf序列化原理解析_序列化原理_03
Tag是消息字段标识符和数据类型经Varint与Zigzag编码后的值,因此Tag存储了字段的标识符(field_number)和数据类型(wire_type),即Tag = 字段数据类型(wire_type) + 标识号(field_number)。
Tag占用一个字节的长度(如果标识符大于15,则占用多一个字节的位置),字段数据类型(wire_type)占用3个bit,字段标识符(field_number)占用4个bit,最高位用于Varint编码保留。

Tag = (field_number << 3) | wire_type
enum WireType { 
      WIRETYPE_VARINT = 0, 
      WIRETYPE_FIXED64 = 1, 
      WIRETYPE_LENGTH_DELIMITED = 2, 
      WIRETYPE_START_GROUP = 3, 
      WIRETYPE_END_GROUP = 4, 
      WIRETYPE_FIXED32 = 5
   }; 

解码时,Protobuf根据Tag将Value对应于消息中的字段。

message person
{ 
   required int32     id = 1;  
   // wire type = 0,field_number =1 
   required string    name = 2;  
   // wire type = 2,field_number =2 
 } 

对于Person消息的name字段的Tag编码如下:

nameTag = 2 << 3 | 2
nameTag = 0001 0010 

根据Tag解码得到filed_number、wire_type:

nameTag = 0001 0010
field_number = nameTag >> 3
field_number = 0010
wire_type = nameTag & 3
wire_type = 010 

四、Protobuf序列化原理解析

1、Protobuf序列化简介

Protobuf对于数据存储的三大原则:
(1)Protocol Buffer将消息中的每个字段进行编码后,利用T - L - V 存储方式进行数据的存储,最终得到一个二进制字节流。
(2)ProtoBuf对于不同数据类型采用不同的序列化方式(数据编码方式与数据存储方式)
Protobuf对于不同的字段类型采用不同的编码和数据存储方式对消息字段进行序列化,以确保得到高效紧凑的数据压缩。不同类型的数据采用的编码方式和存储方式如下:
gRPC快速入门(二)——Protobuf序列化原理解析_序列化原理_04
对于Varint编码数据的存储,不需要存储字节长度Length,使用T-V存储方式进行存储;对于采用其它编码方式(如LENGTH_DELIMITED)的数据,使用T-L-V存储方式进行存储。
(3)ProtoBuf对于数据字段值的独特编码方式与T-L-V数据存储方式,使得 ProtoBuf序列化后数据量体积极小。

2、WireType=0的序列化

WireType=0的类型包括int32,int64,uint32,unint64,bool,enum以及sint32和sint64。
编码方式采用Varint编码(如果为负数,采用Zigzag辅助编码),数据存储方式使用T-V方式存储二进制字节流。

3、WireType=1的序列化

WireType=1的类型包括fixed64,sfixed64,double。
编码方式采用64bit编码(编码后数据大小为64bit,高位在后,低位在前),数据存储方式使用T-V方式存储二进制字节流。

4、WireType=2的序列化

WireType=2的类型包括string,bytes,嵌套消息,packed repeated字段。
对于编码方式,标识符Tag采用Varint编码,字节长度Length采用Varint编码,string类型字段值采用UTF-8编码,嵌套消息类型的字段值根据嵌套消息内部的字段数据类型进行选择,
数据存储方式使用T-L-V方式存储二进制字节流。

5、WireType=5的序列化

WireType=5的类型包括fixed32,sfixed32,float。
编码方式采用32bit编码(编码后数据大小为32bit,高位在后,低位在前),数据存储方式使用T-V方式存储二进制字节流。

五、Protobuf序列化示例

1、String类型

String类型字段的值使用UTF-8编码。消息数据流如下:
gRPC快速入门(二)——Protobuf序列化原理解析_Protobuf_05

message Test
{
    required string str = 2;
}

// 将str设置为:testing
Test.setStr(“testing”)

// 经过protobuf编码序列化后的数据以二进制的方式输出
// 输出为:18, 7, 116, 101, 115, 116, 105, 110, 103 

gRPC快速入门(二)——Protobuf序列化原理解析_Protobuf_06

2、嵌套消息类型

嵌套消息类型采用T-L-V的存储方式,外部消息的V即为嵌套消息的字段 
,在T-L-V的V中嵌套了一系列的T-L-V。
编码方式:字段值(即V)根据字段的数据类型采用不同编码方式。
gRPC快速入门(二)——Protobuf序列化原理解析_Protobuf_07

message Test2
{
    required string str = 1;
    required int32 id1 = 2;
}

message Test3 {
  required Test2 c = 1;
}

// 将Test2中的字段str设置为:testing
// 将Test2中的字段id1设置为:296
// 编码后的字节为:10 ,12 ,18,7,116, 101, 115, 116, 105, 110, 103,16,-88,2 

gRPC快速入门(二)——Protobuf序列化原理解析_序列化原理_08

3、通过packed修饰的 repeat 字段

message Test
{
    repeated int32 Car = 4 ;
    // 表达方式1:不带packed=true
    repeated int32 Car = 4 [packed=true];
    // 表达方式2:带packed=true
}

Test.setCar(3);
Test.setCar(270);
Test.setCar(86942); 

如果序列化时对多个 T - V对存储(不带packed=true),则会导致Tag的冗余,即相同的Tag存储多次。
gRPC快速入门(二)——Protobuf序列化原理解析_序列化原理_09
为了解决Tag数据冗余,采用带packed=true的repeated字段存储方式,即将相同的Tag只存储一次、添加repeated字段下所有字段值的长度Length、连续存储repeated字段值,组成一个大的Tag - Length - Value -Value -Value对,即T - L - V - V - V对。
gRPC快速入门(二)——Protobuf序列化原理解析_序列化原理_10
通过采用带packed=true 的 repeated字段存储方式,从而更好地压缩序列化后的数据长度。

六、Protobuf使用建议

基于Protobuf序列化原理分析,为了有效降低序列化后数据量的大小,可以采用以下措施:
(1)多用 optional或 repeated修饰符 
若optional 或 repeated 字段没有被设置字段值,那么该字段在序列化时的数据中是完全不存在的,即不需要进行编码,但相应的字段在解码时会被设置为默认值。
(2)字段标识号(Field_Number)尽量只使用1-15,且不要跳动使用
Tag是需要占字节空间的。如果Field_Number>16时,Field_Number的编码就会占用2个字节,那么Tag在编码时就会占用更多的字节;如果将字段标识号定义为连续递增的数值,将获得更好的编码和解码性能。
(3)若需要使用的字段值出现负数,请使用sint32/sint64,不要使用int32/int64。
采用sint32/sint64数据类型表示负数时,会先采用Zigzag编码再采用Varint编码,从而更加有效压缩数据。
(4)对于repeated字段,尽量增加packed=true修饰
增加packed=true修饰,repeated字段会采用连续数据存储方式,即T - L - V - V -V方式。

参考文献:
Carson_Ho:Protocol Buffer序列化原理大揭秘

gRPC快速入门(三)——Protobuf应用示例

 https://blog.51cto.com/u_9291927/2332269

gRPC快速入门(三)——Protobuf应用示例

一、Protobuf使用流程

在工程开发中使用Protobuf流程如下:
(1)定义proto描述文件,以proto作为后缀名。
(2)使用Protobuf编译器protoc来生成编程语言代码文件,对消息格式以特定的语言方式描述。
(3)使用Protobuf库提供的API来编写应用程序 。

二、Protobuf C++示例

1、环境变量设置

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/protobuf/lib
export LIBRARY_PATH=$LIBRARY_PATH:/usr/local/protobuf/lib
export PATH=$PATH:/usr/local/protobuf/bin
export PKG_CONFIG_PATH=/usr/local/protobuf/lib/pkgconfig/
 
 

2、定义proto文件

addressbook.proto文件如下:

// [START declaration]
syntax = "proto3";
package Book;
import "google/protobuf/timestamp.proto";
// [END declaration]

// [START messages]
message Person {
  string name = 1;
  int32 id = 2;  // Unique ID number for this person.
  string email = 3;

  enum PhoneType {
    MOBILE = 0;
    HOME = 1;
    WORK = 2;
  }

  message PhoneNumber {
    string number = 1;
    PhoneType type = 2;
  }

  repeated PhoneNumber phones = 4;

  google.protobuf.Timestamp last_updated = 5;
}

// Our address book file is just one of these.
message AddressBook {
  repeated Person people = 1;
}
// [END messages]
 
 

3、生成C++代码

protoc --cpp_out=. addressbook.proto
生成C++代码addressbook.pb.h和addressbook.pb.cc。
addressbook.pb.h文件:

// Generated by the protocol buffer compiler.  DO NOT EDIT!
// source: addressbook.proto

#ifndef PROTOBUF_INCLUDED_addressbook_2eproto
#define PROTOBUF_INCLUDED_addressbook_2eproto

#include <string>

#include <google/protobuf/stubs/common.h>

#if GOOGLE_PROTOBUF_VERSION < 3006001
#error This file was generated by a newer version of protoc which is
#error incompatible with your Protocol Buffer headers.  Please update
#error your headers.
#endif
#if 3006001 < GOOGLE_PROTOBUF_MIN_PROTOC_VERSION
#error This file was generated by an older version of protoc which is
#error incompatible with your Protocol Buffer headers.  Please
#error regenerate this file with a newer version of protoc.
#endif

#include <google/protobuf/io/coded_stream.h>
#include <google/protobuf/arena.h>
#include <google/protobuf/arenastring.h>
#include <google/protobuf/generated_message_table_driven.h>
#include <google/protobuf/generated_message_util.h>
#include <google/protobuf/inlined_string_field.h>
#include <google/protobuf/metadata_lite.h>
#include <google/protobuf/message_lite.h>
#include <google/protobuf/repeated_field.h>  // IWYU pragma: export
#include <google/protobuf/extension_set.h>  // IWYU pragma: export
#include <google/protobuf/generated_enum_util.h>
#include <google/protobuf/timestamp.pb.h>
// @@protoc_insertion_point(includes)
#define PROTOBUF_INTERNAL_EXPORT_protobuf_addressbook_2eproto 

namespace protobuf_addressbook_2eproto {
// Internal implementation detail -- do not use these members.
struct TableStruct {
  static const ::google::protobuf::internal::ParseTableField entries[];
  static const ::google::protobuf::internal::AuxillaryParseTableField aux[];
  static const ::google::protobuf::internal::ParseTable schema[3];
  static const ::google::protobuf::internal::FieldMetadata field_metadata[];
  static const ::google::protobuf::internal::SerializationTable serialization_table[];
  static const ::google::protobuf::uint32 offsets[];
};
}  // namespace protobuf_addressbook_2eproto
namespace book {
class AddressBook;
class AddressBookDefaultTypeInternal;
extern AddressBookDefaultTypeInternal _AddressBook_default_instance_;
class Person;
class PersonDefaultTypeInternal;
extern PersonDefaultTypeInternal _Person_default_instance_;
class Person_PhoneNumber;
class Person_PhoneNumberDefaultTypeInternal;
extern Person_PhoneNumberDefaultTypeInternal _Person_PhoneNumber_default_instance_;
}  // namespace book
namespace google {
namespace protobuf {
template<> ::book::AddressBook* Arena::CreateMaybeMessage<::book::AddressBook>(Arena*);
template<> ::book::Person* Arena::CreateMaybeMessage<::book::Person>(Arena*);
template<> ::book::Person_PhoneNumber* Arena::CreateMaybeMessage<::book::Person_PhoneNumber>(Arena*);
}  // namespace protobuf
}  // namespace google
namespace book {

enum Person_PhoneType {
  Person_PhoneType_MOBILE = 0,
  Person_PhoneType_HOME = 1,
  Person_PhoneType_WORK = 2,
  Person_PhoneType_Person_PhoneType_INT_MIN_SENTINEL_DO_NOT_USE_ = ::google::protobuf::kint32min,
  Person_PhoneType_Person_PhoneType_INT_MAX_SENTINEL_DO_NOT_USE_ = ::google::protobuf::kint32max
};
bool Person_PhoneType_IsValid(int value);
const Person_PhoneType Person_PhoneType_PhoneType_MIN = Person_PhoneType_MOBILE;
const Person_PhoneType Person_PhoneType_PhoneType_MAX = Person_PhoneType_WORK;
const int Person_PhoneType_PhoneType_ARRAYSIZE = Person_PhoneType_PhoneType_MAX + 1;

// ===================================================================

class Person_PhoneNumber : public ::google::protobuf::MessageLite /* @@protoc_insertion_point(class_definition:book.Person.PhoneNumber) */ {
 public:
  Person_PhoneNumber();
  virtual ~Person_PhoneNumber();

  Person_PhoneNumber(const Person_PhoneNumber& from);

  inline Person_PhoneNumber& operator=(const Person_PhoneNumber& from) {
    CopyFrom(from);
    return *this;
  }
  #if LANG_CXX11
  Person_PhoneNumber(Person_PhoneNumber&& from) noexcept
    : Person_PhoneNumber() {
    *this = ::std::move(from);
  }

  inline Person_PhoneNumber& operator=(Person_PhoneNumber&& from) noexcept {
    if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) {
      if (this != &from) InternalSwap(&from);
    } else {
      CopyFrom(from);
    }
    return *this;
  }
  #endif
  static const Person_PhoneNumber& default_instance();

  static void InitAsDefaultInstance();  // FOR INTERNAL USE ONLY
  static inline const Person_PhoneNumber* internal_default_instance() {
    return reinterpret_cast<const Person_PhoneNumber*>(
               &_Person_PhoneNumber_default_instance_);
  }
  static constexpr int kIndexInFileMessages =
    0;

  void Swap(Person_PhoneNumber* other);
  friend void swap(Person_PhoneNumber& a, Person_PhoneNumber& b) {
    a.Swap(&b);
  }

  // implements Message ----------------------------------------------

  inline Person_PhoneNumber* New() const final {
    return CreateMaybeMessage<Person_PhoneNumber>(NULL);
  }

  Person_PhoneNumber* New(::google::protobuf::Arena* arena) const final {
    return CreateMaybeMessage<Person_PhoneNumber>(arena);
  }
  void CheckTypeAndMergeFrom(const ::google::protobuf::MessageLite& from)
    final;
  void CopyFrom(const Person_PhoneNumber& from);
  void MergeFrom(const Person_PhoneNumber& from);
  void Clear() final;
  bool IsInitialized() const final;

  size_t ByteSizeLong() const final;
  bool MergePartialFromCodedStream(
      ::google::protobuf::io::CodedInputStream* input) final;
  void SerializeWithCachedSizes(
      ::google::protobuf::io::CodedOutputStream* output) const final;
  void DiscardUnknownFields();
  int GetCachedSize() const final { return _cached_size_.Get(); }

  private:
  void SharedCtor();
  void SharedDtor();
  void SetCachedSize(int size) const;
  void InternalSwap(Person_PhoneNumber* other);
  private:
  inline ::google::protobuf::Arena* GetArenaNoVirtual() const {
    return NULL;
  }
  inline void* MaybeArenaPtr() const {
    return NULL;
  }
  public:

  ::std::string GetTypeName() const final;

  // nested types ----------------------------------------------------

  // accessors -------------------------------------------------------

  // string number = 1;
  void clear_number();
  static const int kNumberFieldNumber = 1;
  const ::std::string& number() const;
  void set_number(const ::std::string& value);
  #if LANG_CXX11
  void set_number(::std::string&& value);
  #endif
  void set_number(const char* value);
  void set_number(const char* value, size_t size);
  ::std::string* mutable_number();
  ::std::string* release_number();
  void set_allocated_number(::std::string* number);

  // .book.Person.PhoneType type = 2;
  void clear_type();
  static const int kTypeFieldNumber = 2;
  ::book::Person_PhoneType type() const;
  void set_type(::book::Person_PhoneType value);

  // @@protoc_insertion_point(class_scope:book.Person.PhoneNumber)
 private:

  ::google::protobuf::internal::InternalMetadataWithArenaLite _internal_metadata_;
  ::google::protobuf::internal::ArenaStringPtr number_;
  int type_;
  mutable ::google::protobuf::internal::CachedSize _cached_size_;
  friend struct ::protobuf_addressbook_2eproto::TableStruct;
};
// -------------------------------------------------------------------

class Person : public ::google::protobuf::MessageLite /* @@protoc_insertion_point(class_definition:book.Person) */ {
 public:
  Person();
  virtual ~Person();

  Person(const Person& from);

  inline Person& operator=(const Person& from) {
    CopyFrom(from);
    return *this;
  }
  #if LANG_CXX11
  Person(Person&& from) noexcept
    : Person() {
    *this = ::std::move(from);
  }

  inline Person& operator=(Person&& from) noexcept {
    if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) {
      if (this != &from) InternalSwap(&from);
    } else {
      CopyFrom(from);
    }
    return *this;
  }
  #endif
  static const Person& default_instance();

  static void InitAsDefaultInstance();  // FOR INTERNAL USE ONLY
  static inline const Person* internal_default_instance() {
    return reinterpret_cast<const Person*>(
               &_Person_default_instance_);
  }
  static constexpr int kIndexInFileMessages =
    1;

  void Swap(Person* other);
  friend void swap(Person& a, Person& b) {
    a.Swap(&b);
  }

  // implements Message ----------------------------------------------

  inline Person* New() const final {
    return CreateMaybeMessage<Person>(NULL);
  }

  Person* New(::google::protobuf::Arena* arena) const final {
    return CreateMaybeMessage<Person>(arena);
  }
  void CheckTypeAndMergeFrom(const ::google::protobuf::MessageLite& from)
    final;
  void CopyFrom(const Person& from);
  void MergeFrom(const Person& from);
  void Clear() final;
  bool IsInitialized() const final;

  size_t ByteSizeLong() const final;
  bool MergePartialFromCodedStream(
      ::google::protobuf::io::CodedInputStream* input) final;
  void SerializeWithCachedSizes(
      ::google::protobuf::io::CodedOutputStream* output) const final;
  void DiscardUnknownFields();
  int GetCachedSize() const final { return _cached_size_.Get(); }

  private:
  void SharedCtor();
  void SharedDtor();
  void SetCachedSize(int size) const;
  void InternalSwap(Person* other);
  private:
  inline ::google::protobuf::Arena* GetArenaNoVirtual() const {
    return NULL;
  }
  inline void* MaybeArenaPtr() const {
    return NULL;
  }
  public:

  ::std::string GetTypeName() const final;

  // nested types ----------------------------------------------------

  typedef Person_PhoneNumber PhoneNumber;

  typedef Person_PhoneType PhoneType;
  static const PhoneType MOBILE =
    Person_PhoneType_MOBILE;
  static const PhoneType HOME =
    Person_PhoneType_HOME;
  static const PhoneType WORK =
    Person_PhoneType_WORK;
  static inline bool PhoneType_IsValid(int value) {
    return Person_PhoneType_IsValid(value);
  }
  static const PhoneType PhoneType_MIN =
    Person_PhoneType_PhoneType_MIN;
  static const PhoneType PhoneType_MAX =
    Person_PhoneType_PhoneType_MAX;
  static const int PhoneType_ARRAYSIZE =
    Person_PhoneType_PhoneType_ARRAYSIZE;

  // accessors -------------------------------------------------------

  // repeated .book.Person.PhoneNumber phones = 4;
  int phones_size() const;
  void clear_phones();
  static const int kPhonesFieldNumber = 4;
  ::book::Person_PhoneNumber* mutable_phones(int index);
  ::google::protobuf::RepeatedPtrField< ::book::Person_PhoneNumber >*
      mutable_phones();
  const ::book::Person_PhoneNumber& phones(int index) const;
  ::book::Person_PhoneNumber* add_phones();
  const ::google::protobuf::RepeatedPtrField< ::book::Person_PhoneNumber >&
      phones() const;

  // string name = 1;
  void clear_name();
  static const int kNameFieldNumber = 1;
  const ::std::string& name() const;
  void set_name(const ::std::string& value);
  #if LANG_CXX11
  void set_name(::std::string&& value);
  #endif
  void set_name(const char* value);
  void set_name(const char* value, size_t size);
  ::std::string* mutable_name();
  ::std::string* release_name();
  void set_allocated_name(::std::string* name);

  // string email = 3;
  void clear_email();
  static const int kEmailFieldNumber = 3;
  const ::std::string& email() const;
  void set_email(const ::std::string& value);
  #if LANG_CXX11
  void set_email(::std::string&& value);
  #endif
  void set_email(const char* value);
  void set_email(const char* value, size_t size);
  ::std::string* mutable_email();
  ::std::string* release_email();
  void set_allocated_email(::std::string* email);

  // .google.protobuf.Timestamp last_updated = 5;
  bool has_last_updated() const;
  void clear_last_updated();
  static const int kLastUpdatedFieldNumber = 5;
  private:
  const ::google::protobuf::Timestamp& _internal_last_updated() const;
  public:
  const ::google::protobuf::Timestamp& last_updated() const;
  ::google::protobuf::Timestamp* release_last_updated();
  ::google::protobuf::Timestamp* mutable_last_updated();
  void set_allocated_last_updated(::google::protobuf::Timestamp* last_updated);

  // int32 id = 2;
  void clear_id();
  static const int kIdFieldNumber = 2;
  ::google::protobuf::int32 id() const;
  void set_id(::google::protobuf::int32 value);

  // @@protoc_insertion_point(class_scope:book.Person)
 private:

  ::google::protobuf::internal::InternalMetadataWithArenaLite _internal_metadata_;
  ::google::protobuf::RepeatedPtrField< ::book::Person_PhoneNumber > phones_;
  ::google::protobuf::internal::ArenaStringPtr name_;
  ::google::protobuf::internal::ArenaStringPtr email_;
  ::google::protobuf::Timestamp* last_updated_;
  ::google::protobuf::int32 id_;
  mutable ::google::protobuf::internal::CachedSize _cached_size_;
  friend struct ::protobuf_addressbook_2eproto::TableStruct;
};
// -------------------------------------------------------------------

class AddressBook : public ::google::protobuf::MessageLite /* @@protoc_insertion_point(class_definition:book.AddressBook) */ {
 public:
  AddressBook();
  virtual ~AddressBook();

  AddressBook(const AddressBook& from);

  inline AddressBook& operator=(const AddressBook& from) {
    CopyFrom(from);
    return *this;
  }
  #if LANG_CXX11
  AddressBook(AddressBook&& from) noexcept
    : AddressBook() {
    *this = ::std::move(from);
  }

  inline AddressBook& operator=(AddressBook&& from) noexcept {
    if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) {
      if (this != &from) InternalSwap(&from);
    } else {
      CopyFrom(from);
    }
    return *this;
  }
  #endif
  static const AddressBook& default_instance();

  static void InitAsDefaultInstance();  // FOR INTERNAL USE ONLY
  static inline const AddressBook* internal_default_instance() {
    return reinterpret_cast<const AddressBook*>(
               &_AddressBook_default_instance_);
  }
  static constexpr int kIndexInFileMessages =
    2;

  void Swap(AddressBook* other);
  friend void swap(AddressBook& a, AddressBook& b) {
    a.Swap(&b);
  }

  // implements Message ----------------------------------------------

  inline AddressBook* New() const final {
    return CreateMaybeMessage<AddressBook>(NULL);
  }

  AddressBook* New(::google::protobuf::Arena* arena) const final {
    return CreateMaybeMessage<AddressBook>(arena);
  }
  void CheckTypeAndMergeFrom(const ::google::protobuf::MessageLite& from)
    final;
  void CopyFrom(const AddressBook& from);
  void MergeFrom(const AddressBook& from);
  void Clear() final;
  bool IsInitialized() const final;

  size_t ByteSizeLong() const final;
  bool MergePartialFromCodedStream(
      ::google::protobuf::io::CodedInputStream* input) final;
  void SerializeWithCachedSizes(
      ::google::protobuf::io::CodedOutputStream* output) const final;
  void DiscardUnknownFields();
  int GetCachedSize() const final { return _cached_size_.Get(); }

  private:
  void SharedCtor();
  void SharedDtor();
  void SetCachedSize(int size) const;
  void InternalSwap(AddressBook* other);
  private:
  inline ::google::protobuf::Arena* GetArenaNoVirtual() const {
    return NULL;
  }
  inline void* MaybeArenaPtr() const {
    return NULL;
  }
  public:

  ::std::string GetTypeName() const final;

  // nested types ----------------------------------------------------

  // accessors -------------------------------------------------------

  // repeated .book.Person people = 1;
  int people_size() const;
  void clear_people();
  static const int kPeopleFieldNumber = 1;
  ::book::Person* mutable_people(int index);
  ::google::protobuf::RepeatedPtrField< ::book::Person >*
      mutable_people();
  const ::book::Person& people(int index) const;
  ::book::Person* add_people();
  const ::google::protobuf::RepeatedPtrField< ::book::Person >&
      people() const;

  // @@protoc_insertion_point(class_scope:book.AddressBook)
 private:

  ::google::protobuf::internal::InternalMetadataWithArenaLite _internal_metadata_;
  ::google::protobuf::RepeatedPtrField< ::book::Person > people_;
  mutable ::google::protobuf::internal::CachedSize _cached_size_;
  friend struct ::protobuf_addressbook_2eproto::TableStruct;
};
// ===================================================================


// ===================================================================

#ifdef __GNUC__
  #pragma GCC diagnostic push
  #pragma GCC diagnostic ignored "-Wstrict-aliasing"
#endif  // __GNUC__
// Person_PhoneNumber

// string number = 1;
inline void Person_PhoneNumber::clear_number() {
  number_.ClearToEmptyNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
}
inline const ::std::string& Person_PhoneNumber::number() const {
  // @@protoc_insertion_point(field_get:book.Person.PhoneNumber.number)
  return number_.GetNoArena();
}
inline void Person_PhoneNumber::set_number(const ::std::string& value) {
  
  number_.SetNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited(), value);
  // @@protoc_insertion_point(field_set:book.Person.PhoneNumber.number)
}
#if LANG_CXX11
inline void Person_PhoneNumber::set_number(::std::string&& value) {
  
  number_.SetNoArena(
    &::google::protobuf::internal::GetEmptyStringAlreadyInited(), ::std::move(value));
  // @@protoc_insertion_point(field_set_rvalue:book.Person.PhoneNumber.number)
}
#endif
inline void Person_PhoneNumber::set_number(const char* value) {
  GOOGLE_DCHECK(value != NULL);
  
  number_.SetNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited(), ::std::string(value));
  // @@protoc_insertion_point(field_set_char:book.Person.PhoneNumber.number)
}
inline void Person_PhoneNumber::set_number(const char* value, size_t size) {
  
  number_.SetNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited(),
      ::std::string(reinterpret_cast<const char*>(value), size));
  // @@protoc_insertion_point(field_set_pointer:book.Person.PhoneNumber.number)
}
inline ::std::string* Person_PhoneNumber::mutable_number() {
  
  // @@protoc_insertion_point(field_mutable:book.Person.PhoneNumber.number)
  return number_.MutableNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
}
inline ::std::string* Person_PhoneNumber::release_number() {
  // @@protoc_insertion_point(field_release:book.Person.PhoneNumber.number)
  
  return number_.ReleaseNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
}
inline void Person_PhoneNumber::set_allocated_number(::std::string* number) {
  if (number != NULL) {
    
  } else {
    
  }
  number_.SetAllocatedNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited(), number);
  // @@protoc_insertion_point(field_set_allocated:book.Person.PhoneNumber.number)
}

// .book.Person.PhoneType type = 2;
inline void Person_PhoneNumber::clear_type() {
  type_ = 0;
}
inline ::book::Person_PhoneType Person_PhoneNumber::type() const {
  // @@protoc_insertion_point(field_get:book.Person.PhoneNumber.type)
  return static_cast< ::book::Person_PhoneType >(type_);
}
inline void Person_PhoneNumber::set_type(::book::Person_PhoneType value) {
  
  type_ = value;
  // @@protoc_insertion_point(field_set:book.Person.PhoneNumber.type)
}

// -------------------------------------------------------------------

// Person

// string name = 1;
inline void Person::clear_name() {
  name_.ClearToEmptyNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
}
inline const ::std::string& Person::name() const {
  // @@protoc_insertion_point(field_get:book.Person.name)
  return name_.GetNoArena();
}
inline void Person::set_name(const ::std::string& value) {
  
  name_.SetNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited(), value);
  // @@protoc_insertion_point(field_set:book.Person.name)
}
#if LANG_CXX11
inline void Person::set_name(::std::string&& value) {
  
  name_.SetNoArena(
    &::google::protobuf::internal::GetEmptyStringAlreadyInited(), ::std::move(value));
  // @@protoc_insertion_point(field_set_rvalue:book.Person.name)
}
#endif
inline void Person::set_name(const char* value) {
  GOOGLE_DCHECK(value != NULL);
  
  name_.SetNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited(), ::std::string(value));
  // @@protoc_insertion_point(field_set_char:book.Person.name)
}
inline void Person::set_name(const char* value, size_t size) {
  
  name_.SetNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited(),
      ::std::string(reinterpret_cast<const char*>(value), size));
  // @@protoc_insertion_point(field_set_pointer:book.Person.name)
}
inline ::std::string* Person::mutable_name() {
  
  // @@protoc_insertion_point(field_mutable:book.Person.name)
  return name_.MutableNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
}
inline ::std::string* Person::release_name() {
  // @@protoc_insertion_point(field_release:book.Person.name)
  
  return name_.ReleaseNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
}
inline void Person::set_allocated_name(::std::string* name) {
  if (name != NULL) {
    
  } else {
    
  }
  name_.SetAllocatedNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited(), name);
  // @@protoc_insertion_point(field_set_allocated:book.Person.name)
}

// int32 id = 2;
inline void Person::clear_id() {
  id_ = 0;
}
inline ::google::protobuf::int32 Person::id() const {
  // @@protoc_insertion_point(field_get:book.Person.id)
  return id_;
}
inline void Person::set_id(::google::protobuf::int32 value) {
  
  id_ = value;
  // @@protoc_insertion_point(field_set:book.Person.id)
}

// string email = 3;
inline void Person::clear_email() {
  email_.ClearToEmptyNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
}
inline const ::std::string& Person::email() const {
  // @@protoc_insertion_point(field_get:book.Person.email)
  return email_.GetNoArena();
}
inline void Person::set_email(const ::std::string& value) {
  
  email_.SetNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited(), value);
  // @@protoc_insertion_point(field_set:book.Person.email)
}
#if LANG_CXX11
inline void Person::set_email(::std::string&& value) {
  
  email_.SetNoArena(
    &::google::protobuf::internal::GetEmptyStringAlreadyInited(), ::std::move(value));
  // @@protoc_insertion_point(field_set_rvalue:book.Person.email)
}
#endif
inline void Person::set_email(const char* value) {
  GOOGLE_DCHECK(value != NULL);
  
  email_.SetNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited(), ::std::string(value));
  // @@protoc_insertion_point(field_set_char:book.Person.email)
}
inline void Person::set_email(const char* value, size_t size) {
  
  email_.SetNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited(),
      ::std::string(reinterpret_cast<const char*>(value), size));
  // @@protoc_insertion_point(field_set_pointer:book.Person.email)
}
inline ::std::string* Person::mutable_email() {
  
  // @@protoc_insertion_point(field_mutable:book.Person.email)
  return email_.MutableNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
}
inline ::std::string* Person::release_email() {
  // @@protoc_insertion_point(field_release:book.Person.email)
  
  return email_.ReleaseNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited());
}
inline void Person::set_allocated_email(::std::string* email) {
  if (email != NULL) {
    
  } else {
    
  }
  email_.SetAllocatedNoArena(&::google::protobuf::internal::GetEmptyStringAlreadyInited(), email);
  // @@protoc_insertion_point(field_set_allocated:book.Person.email)
}

// repeated .book.Person.PhoneNumber phones = 4;
inline int Person::phones_size() const {
  return phones_.size();
}
inline void Person::clear_phones() {
  phones_.Clear();
}
inline ::book::Person_PhoneNumber* Person::mutable_phones(int index) {
  // @@protoc_insertion_point(field_mutable:book.Person.phones)
  return phones_.Mutable(index);
}
inline ::google::protobuf::RepeatedPtrField< ::book::Person_PhoneNumber >*
Person::mutable_phones() {
  // @@protoc_insertion_point(field_mutable_list:book.Person.phones)
  return &phones_;
}
inline const ::book::Person_PhoneNumber& Person::phones(int index) const {
  // @@protoc_insertion_point(field_get:book.Person.phones)
  return phones_.Get(index);
}
inline ::book::Person_PhoneNumber* Person::add_phones() {
  // @@protoc_insertion_point(field_add:book.Person.phones)
  return phones_.Add();
}
inline const ::google::protobuf::RepeatedPtrField< ::book::Person_PhoneNumber >&
Person::phones() const {
  // @@protoc_insertion_point(field_list:book.Person.phones)
  return phones_;
}

// .google.protobuf.Timestamp last_updated = 5;
inline bool Person::has_last_updated() const {
  return this != internal_default_instance() && last_updated_ != NULL;
}
inline const ::google::protobuf::Timestamp& Person::_internal_last_updated() const {
  return *last_updated_;
}
inline const ::google::protobuf::Timestamp& Person::last_updated() const {
  const ::google::protobuf::Timestamp* p = last_updated_;
  // @@protoc_insertion_point(field_get:book.Person.last_updated)
  return p != NULL ? *p : *reinterpret_cast<const ::google::protobuf::Timestamp*>(
      &::google::protobuf::_Timestamp_default_instance_);
}
inline ::google::protobuf::Timestamp* Person::release_last_updated() {
  // @@protoc_insertion_point(field_release:book.Person.last_updated)
  
  ::google::protobuf::Timestamp* temp = last_updated_;
  last_updated_ = NULL;
  return temp;
}
inline ::google::protobuf::Timestamp* Person::mutable_last_updated() {
  
  if (last_updated_ == NULL) {
    auto* p = CreateMaybeMessage<::google::protobuf::Timestamp>(GetArenaNoVirtual());
    last_updated_ = p;
  }
  // @@protoc_insertion_point(field_mutable:book.Person.last_updated)
  return last_updated_;
}
inline void Person::set_allocated_last_updated(::google::protobuf::Timestamp* last_updated) {
  ::google::protobuf::Arena* message_arena = GetArenaNoVirtual();
  if (message_arena == NULL) {
    delete reinterpret_cast< ::google::protobuf::MessageLite*>(last_updated_);
  }
  if (last_updated) {
    ::google::protobuf::Arena* submessage_arena =
      reinterpret_cast<::google::protobuf::MessageLite*>(last_updated)->GetArena();
    if (message_arena != submessage_arena) {
      last_updated = ::google::protobuf::internal::GetOwnedMessage(
          message_arena, last_updated, submessage_arena);
    }
    
  } else {
    
  }
  last_updated_ = last_updated;
  // @@protoc_insertion_point(field_set_allocated:book.Person.last_updated)
}

// -------------------------------------------------------------------

// AddressBook

// repeated .book.Person people = 1;
inline int AddressBook::people_size() const {
  return people_.size();
}
inline void AddressBook::clear_people() {
  people_.Clear();
}
inline ::book::Person* AddressBook::mutable_people(int index) {
  // @@protoc_insertion_point(field_mutable:book.AddressBook.people)
  return people_.Mutable(index);
}
inline ::google::protobuf::RepeatedPtrField< ::book::Person >*
AddressBook::mutable_people() {
  // @@protoc_insertion_point(field_mutable_list:book.AddressBook.people)
  return &people_;
}
inline const ::book::Person& AddressBook::people(int index) const {
  // @@protoc_insertion_point(field_get:book.AddressBook.people)
  return people_.Get(index);
}
inline ::book::Person* AddressBook::add_people() {
  // @@protoc_insertion_point(field_add:book.AddressBook.people)
  return people_.Add();
}
inline const ::google::protobuf::RepeatedPtrField< ::book::Person >&
AddressBook::people() const {
  // @@protoc_insertion_point(field_list:book.AddressBook.people)
  return people_;
}

#ifdef __GNUC__
  #pragma GCC diagnostic pop
#endif  // __GNUC__
// -------------------------------------------------------------------

// -------------------------------------------------------------------


// @@protoc_insertion_point(namespace_scope)

}  // namespace book

namespace google {
namespace protobuf {

template <> struct is_proto_enum< ::book::Person_PhoneType> : ::std::true_type {};

}  // namespace protobuf
}  // namespace google

// @@protoc_insertion_point(global_scope)

#endif  // PROTOBUF_INCLUDED_addressbook_2eproto
 
 

4、序列化接口

bool SerializeToString(string* output) const
序列化消息,将消息对象以string方式输出。
bool ParseFromString(const string& data)
反序列化消息,解析给定的string为消息对象     
bool SerializeToOstream(ostream* output) const
序列化消息,将消息对象写入给定的c++  ostream中
bool ParseFromIstream(istream* input)
反序列化消息,从给定的c++ istream中解析出消息对象

5、使用示例

main.cpp文件:

#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace std;

// 需要输入地址簿文件作为参数
int main(int argc, char* argv[])
{
    // Verify that the version of the library that we linked against is
    // compatible with the version of the headers we compiled against.
    GOOGLE_PROTOBUF_VERIFY_VERSION;

    if (argc != 2)
    {
        cerr << "Usage:  " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
        return -1;
    }
    /********************************************
     * 反序列化
     * 从地址簿文件读取数据
     * *******************************************/
    Book::AddressBook address_book;
    // 从地址簿文件读取地址簿信息
    fstream input(argv[1], ios::in | ios::binary);
    if (!input)
    {
        cout << argv[1] << ": File not found.  Creating a new file." << endl;
    }
    else if (!address_book.ParseFromIstream(&input))
    {
        cerr << "Failed to parse address book." << endl;
        return -1;
    }
    cout << "*********print people**************" << endl;
    for (int i = 0; i < address_book.people_size(); i++)
    {
        const Book::Person& person = address_book.people(i);
        cout << "ID: " << person.id() << endl;
        cout << "Name: " << person.name() << endl;
        cout << "EMail: " << person.email() << endl;
        const Book::Person_PhoneNumber& phone = address_book.people(i).phones(0);
        cout << "PhoneNumber: " << phone.number() << endl;
    }

    /************************************
     * 序列化
     * 地址簿增加一个人并写入到地址簿文件
     * *********************************/
    // 地址簿增加一个人
    cout << "*****************add a people**************" << endl;
    Book::Person* person = address_book.add_people();
    person->set_id(10001);
    person->set_name("scorpio");
    person->set_email("scorpio@hotmail.com");
    Book::Person::PhoneNumber* phone = person->add_phones();
    phone->set_number("139xxxxxxxx");
    phone->set_type(Book::Person_PhoneType_HOME);
    // 写入地址簿文件
    fstream output(argv[1], ios::out | ios::trunc | ios::binary);
    if (!address_book.SerializeToOstream(&output))
    {
        cerr << "Failed to write address book." << endl;
        return -1;
    }

    /***************************************
     * 清理libprotobuf分配的所有全局对象
     * ************************************/
    // Optional:  Delete all global objects allocated by libprotobuf.
    google::protobuf::ShutdownProtobufLibrary();

    return 0;
}
// output:
//*********print people**************
//ID: 10001
//Name: scorpio
//EMail: scorpio@hotmail.com
//PhoneNumber: 139xxxxxxxx
//ID: 10001
//Name: scorpio
//EMail: scorpio@hotmail.com
//PhoneNumber: 139xxxxxxxx
//*****************add a people**************
 
 

6、运行结果测试

编译程序:
g++ addressbook.pb.cc main.cpp -o readwrite pkg-config --cflags --libs protobuf -std=c++11
运行程序:
./readwrite book
结果如下:

*********print people**************
ID: 10001
Name: scorpio
EMail: scorpio@hotmail.com
PhoneNumber: 139xxxxxxxx
ID: 10001
Name: scorpio
EMail: scorpio@hotmail.com
PhoneNumber: 139xxxxxxxx
*****************add a people**************
 
 

三、Protobuf Go示例

1、定义proto文件

book/book.proto文件如下:

syntax="proto3";
package book;

// 出版社
message Publisher{
    string name = 1;
}  
// 书籍信息
message Book {
     string name = 1;
    message Author {
        string name = 1;
        string address = 2;
    }
    Author author = 3;

    enum BookType{
        SCIENCE = 0;
        LITERATURE = 1;
    }

    BookType type = 4;
    Publisher publisher = 5;
}
 
 

2、生成Go代码

protoc --go_out=. book.proto
生成book/book.pb.go文件,内容如下:

package proto

import (
   fmt "fmt"
   proto "github.com/golang/protobuf/proto"
   math "math"
)

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package

type Book_BookType int32

const (
   Book_SCIENCE    Book_BookType = 0
   Book_LITERATURE Book_BookType = 1
)

var Book_BookType_name = map[int32]string{
   0: "SCIENCE",
   1: "LITERATURE",
}

var Book_BookType_value = map[string]int32{
   "SCIENCE":    0,
   "LITERATURE": 1,
}

func (x Book_BookType) String() string {
   return proto.EnumName(Book_BookType_name, int32(x))
}

func (Book_BookType) EnumDescriptor() ([]byte, []int) {
   return fileDescriptor_1e89d0eaa98dc5d8, []int{1, 0}
}

// 出版社
type Publisher struct {
   Name                 string   `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
   XXX_NoUnkeyedLiteral struct{} `json:"-"`
   XXX_unrecognized     []byte   `json:"-"`
   XXX_sizecache        int32    `json:"-"`
}

func (m *Publisher) Reset()         { *m = Publisher{} }
func (m *Publisher) String() string { return proto.CompactTextString(m) }
func (*Publisher) ProtoMessage()    {}
func (*Publisher) Descriptor() ([]byte, []int) {
   return fileDescriptor_1e89d0eaa98dc5d8, []int{0}
}

func (m *Publisher) XXX_Unmarshal(b []byte) error {
   return xxx_messageInfo_Publisher.Unmarshal(m, b)
}
func (m *Publisher) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
   return xxx_messageInfo_Publisher.Marshal(b, m, deterministic)
}
func (m *Publisher) XXX_Merge(src proto.Message) {
   xxx_messageInfo_Publisher.Merge(m, src)
}
func (m *Publisher) XXX_Size() int {
   return xxx_messageInfo_Publisher.Size(m)
}
func (m *Publisher) XXX_DiscardUnknown() {
   xxx_messageInfo_Publisher.DiscardUnknown(m)
}

var xxx_messageInfo_Publisher proto.InternalMessageInfo

func (m *Publisher) GetName() string {
   if m != nil {
      return m.Name
   }
   return ""
}

// 书籍信息
type Book struct {
   Name                 string        `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
   Author               *Book_Author  `protobuf:"bytes,3,opt,name=author,proto3" json:"author,omitempty"`
   Type                 Book_BookType `protobuf:"varint,4,opt,name=type,proto3,enum=book.Book_BookType" json:"type,omitempty"`
   Publisher            *Publisher    `protobuf:"bytes,5,opt,name=publisher,proto3" json:"publisher,omitempty"`
   XXX_NoUnkeyedLiteral struct{}      `json:"-"`
   XXX_unrecognized     []byte        `json:"-"`
   XXX_sizecache        int32         `json:"-"`
}

func (m *Book) Reset()         { *m = Book{} }
func (m *Book) String() string { return proto.CompactTextString(m) }
func (*Book) ProtoMessage()    {}
func (*Book) Descriptor() ([]byte, []int) {
   return fileDescriptor_1e89d0eaa98dc5d8, []int{1}
}

func (m *Book) XXX_Unmarshal(b []byte) error {
   return xxx_messageInfo_Book.Unmarshal(m, b)
}
func (m *Book) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
   return xxx_messageInfo_Book.Marshal(b, m, deterministic)
}
func (m *Book) XXX_Merge(src proto.Message) {
   xxx_messageInfo_Book.Merge(m, src)
}
func (m *Book) XXX_Size() int {
   return xxx_messageInfo_Book.Size(m)
}
func (m *Book) XXX_DiscardUnknown() {
   xxx_messageInfo_Book.DiscardUnknown(m)
}

var xxx_messageInfo_Book proto.InternalMessageInfo

func (m *Book) GetName() string {
   if m != nil {
      return m.Name
   }
   return ""
}

func (m *Book) GetAuthor() *Book_Author {
   if m != nil {
      return m.Author
   }
   return nil
}

func (m *Book) GetType() Book_BookType {
   if m != nil {
      return m.Type
   }
   return Book_SCIENCE
}

func (m *Book) GetPublisher() *Publisher {
   if m != nil {
      return m.Publisher
   }
   return nil
}

type Book_Author struct {
   Name                 string   `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
   Address              string   `protobuf:"bytes,2,opt,name=address,proto3" json:"address,omitempty"`
   XXX_NoUnkeyedLiteral struct{} `json:"-"`
   XXX_unrecognized     []byte   `json:"-"`
   XXX_sizecache        int32    `json:"-"`
}

func (m *Book_Author) Reset()         { *m = Book_Author{} }
func (m *Book_Author) String() string { return proto.CompactTextString(m) }
func (*Book_Author) ProtoMessage()    {}
func (*Book_Author) Descriptor() ([]byte, []int) {
   return fileDescriptor_1e89d0eaa98dc5d8, []int{1, 0}
}

func (m *Book_Author) XXX_Unmarshal(b []byte) error {
   return xxx_messageInfo_Book_Author.Unmarshal(m, b)
}
func (m *Book_Author) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
   return xxx_messageInfo_Book_Author.Marshal(b, m, deterministic)
}
func (m *Book_Author) XXX_Merge(src proto.Message) {
   xxx_messageInfo_Book_Author.Merge(m, src)
}
func (m *Book_Author) XXX_Size() int {
   return xxx_messageInfo_Book_Author.Size(m)
}
func (m *Book_Author) XXX_DiscardUnknown() {
   xxx_messageInfo_Book_Author.DiscardUnknown(m)
}

var xxx_messageInfo_Book_Author proto.InternalMessageInfo

func (m *Book_Author) GetName() string {
   if m != nil {
      return m.Name
   }
   return ""
}

func (m *Book_Author) GetAddress() string {
   if m != nil {
      return m.Address
   }
   return ""
}

func init() {
   proto.RegisterEnum("book.Book_BookType", Book_BookType_name, Book_BookType_value)
   proto.RegisterType((*Publisher)(nil), "book.Publisher")
   proto.RegisterType((*Book)(nil), "book.Book")
   proto.RegisterType((*Book_Author)(nil), "book.Book.Author")
}

func init() { proto.RegisterFile("book.proto", fileDescriptor_1e89d0eaa98dc5d8) }

var fileDescriptor_1e89d0eaa98dc5d8 = []byte{
   // 222 bytes of a gzipped FileDescriptorProto
   0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4a, 0xca, 0xcf, 0xcf,
   0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x01, 0xb1, 0x95, 0xe4, 0xb9, 0x38, 0x03, 0x4a,
   0x93, 0x72, 0x32, 0x8b, 0x33, 0x52, 0x8b, 0x84, 0x84, 0xb8, 0x58, 0xf2, 0x12, 0x73, 0x53, 0x25,
   0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0xc0, 0x6c, 0xa5, 0x7f, 0x8c, 0x5c, 0x2c, 0x4e, 0xf9, 0xf9,
   0xd9, 0xd8, 0x24, 0x85, 0x34, 0xb9, 0xd8, 0x12, 0x4b, 0x4b, 0x32, 0xf2, 0x8b, 0x24, 0x98, 0x15,
   0x18, 0x35, 0xb8, 0x8d, 0x04, 0xf5, 0xc0, 0x16, 0x80, 0xd4, 0xeb, 0x39, 0x82, 0x25, 0x82, 0xa0,
   0x0a, 0x84, 0xd4, 0xb9, 0x58, 0x4a, 0x2a, 0x0b, 0x52, 0x25, 0x58, 0x14, 0x18, 0x35, 0xf8, 0x8c,
   0x84, 0x91, 0x14, 0x82, 0x88, 0x90, 0xca, 0x82, 0xd4, 0x20, 0xb0, 0x02, 0x21, 0x5d, 0x2e, 0xce,
   0x02, 0x98, 0x8b, 0x24, 0x58, 0xc1, 0xc6, 0xf2, 0x43, 0x54, 0xc3, 0x1d, 0x1a, 0x84, 0x50, 0x21,
   0x65, 0xc6, 0xc5, 0x06, 0xb1, 0x09, 0xab, 0x03, 0x25, 0xb8, 0xd8, 0x13, 0x53, 0x52, 0x8a, 0x52,
   0x8b, 0x8b, 0x25, 0x98, 0xc0, 0xc2, 0x30, 0xae, 0x92, 0x3a, 0x17, 0x07, 0xcc, 0x62, 0x21, 0x6e,
   0x2e, 0xf6, 0x60, 0x67, 0x4f, 0x57, 0x3f, 0x67, 0x57, 0x01, 0x06, 0x21, 0x3e, 0x2e, 0x2e, 0x1f,
   0xcf, 0x10, 0xd7, 0x20, 0xc7, 0x90, 0xd0, 0x20, 0x57, 0x01, 0xc6, 0x24, 0x36, 0x70, 0x70, 0x19,
   0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xca, 0xa2, 0x4d, 0x21, 0x3c, 0x01, 0x00, 0x00,
}
 
 

3、使用示例

main.go文件如下:

package main

import (
   pb "book/proto"
   "github.com/golang/protobuf/proto"
   "fmt"
)

func main(){
   // 将对象转为proto编码
   var b = &pb.Book{Name:"HyperLedger Fabric", Author:&pb.Book_Author{Name:"scorpio"}}
   protoBook, err := proto.Marshal(b)
   if err != nil{
      fmt.Println(err.Error())
   }
   fmt.Println(protoBook)

   // proto编码转化为对象
   var b2 pb.Book
   err = proto.Unmarshal(protoBook, &b2)
   if err != nil{
      fmt.Println(err.Error())
   }
   fmt.Println(b2.Name,b2.Author)
}
// output:
//[10 18 72 121 112 101 114 76 101 100 103 101 114 32 70 97 98 114 105 99 26 9 10 7 115 99 111 114 112 105 111]
//HyperLedger Fabric name:"scorpio" https://blog.csdn.net/weixin_33989058/article/details/92651786

gRPC快速入门(四)——gRPC快速入门

https://blog.51cto.com/u_9291927/2332271

gRPC快速入门(四)——gRPC快速入门

一、gRPC简介

1、gRPC简介

gRPC是一个高性能、通用的开源RPC框架,基于ProtoBuf(Protocol Buffers)序列化协议开发,且支持众多开发语言,目前提供C、Java和Go语言版本,分别是grpc、grpc-java、grpc-go。gRPC提供了一种简单的方法来精确地定义服务和为iOS、Android和后台支持服务自动生成可靠性很强的客户端功能库。gRPC基于HTTP/2标准设计,带来诸如双向流、流控、头部压缩、单TCP连接上的多复用请求等特性,使得gRPC在移动设备上表现更好、更省电和节省空间占用。

2、gRPC的优点

gRPC优点如下:
(1)平台无关,语言无关,可扩展;
(2)提供了友好的动态库,使用简单;
(3)解析速度快,比对应的XML快约20-100倍;
(4)序列化数据非常简洁、紧凑,与XML相比,其序列化之后的数据量约为1/3到1/10。

3、gRPC安装

官方推荐安装命令:
go get google.golang.org/grpc
国内网络环境由于GFW原因,不能直接访问google服务器,报错如下:
package google.golang.org/grpc: unrecognized import path "google.golang.org/grpc" (https fetch: Get https://google.golang.org/grpc?go-get=1: dial tcp 216.239.37.1:443: i/o timeout)
因此,在国内网络环境下推荐直接从GitHub下载相应依赖进行安装。

git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc
git clone https://github.com/golang/net.git $GOPATH/src/golang.org/x/net
git clone https://github.com/golang/text.git $GOPATH/src/golang.org/x/text
go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
git clone https://github.com/google/go-genproto.git $GOPATH/src/google.golang.org/genproto
git clone https://github.com/golang/sys $GOPATH/src/golang.org/x/sys
 
 

安装gRPC:
go install google.golang.org/grpc

二、gRPC使用

1、gRPC使用流程

gRPC使用流程如下:
(1)编写.proto描述文件。
(2)编译生成.pb.go文件。
(3)服务端实现约定的接口并提供服务。
(4)客户端按照约定调用方法请求服务。

2、proto service定义

一个RPC service是一个能够通过参数和返回值进行远程调用的方法。由于gRPC通过将数据编码成Protocal Buffer来实现传输,因此,使用者可以通过Protocal Buffers interface definitioin language(IDL)来定义service method,同时将参数和返回值也定义成Protocal Buffer message类型。gRPC源码目录提供了多个使用示例,本文使用helloworld示例作为模板,helloworld示例目录位于google.golang.org/grpc/examples/helloworld
在$GOPATH/src目录下创建helloworld目录,包含proto、server、client子目录。proto目录下创建helloworld.proto文件。
helloworld.proto:

syntax = "proto3";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}
 
 

helloworld.proto文件中定义了一个Greeter Service,服务包含一个SayHello方法,同时声明了HelloRequest和HelloReply消息结构用于请求和响应。客户端使用HelloRequest参数调用SayHello方法请求服务端,服务端响应HelloReply消息。

3、生成Go代码

根据定义的RPC service,利用protocal buffer compiler,即protoc生成相应的服务器端和客户端的Go代码,生成的代码中包含了客户端能够进行RPC的方法以及服务器端需要进行实现的接口。
在$GOPATH/src/helloworld/proto目录下生成gRPC对应的Go代码的命令如下:
protoc --go_out=plugins=grpc:. helloworld.proto
将在目录下生成helloworld.pb.go文件,内容如下:

// Code generated by protoc-gen-go. DO NOT EDIT.
// source: helloworld.proto

package helloworld

import (
	context "context"
	fmt "fmt"
	proto "github.com/golang/protobuf/proto"
	grpc "google.golang.org/grpc"
	math "math"
)

// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf

// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package

// The request message containing the user's name.
type HelloRequest struct {
	Name                 string   `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

func (m *HelloRequest) Reset()         { *m = HelloRequest{} }
func (m *HelloRequest) String() string { return proto.CompactTextString(m) }
func (*HelloRequest) ProtoMessage()    {}
func (*HelloRequest) Descriptor() ([]byte, []int) {
	return fileDescriptor_17b8c58d586b62f2, []int{0}
}

func (m *HelloRequest) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_HelloRequest.Unmarshal(m, b)
}
func (m *HelloRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_HelloRequest.Marshal(b, m, deterministic)
}
func (m *HelloRequest) XXX_Merge(src proto.Message) {
	xxx_messageInfo_HelloRequest.Merge(m, src)
}
func (m *HelloRequest) XXX_Size() int {
	return xxx_messageInfo_HelloRequest.Size(m)
}
func (m *HelloRequest) XXX_DiscardUnknown() {
	xxx_messageInfo_HelloRequest.DiscardUnknown(m)
}

var xxx_messageInfo_HelloRequest proto.InternalMessageInfo

func (m *HelloRequest) GetName() string {
	if m != nil {
		return m.Name
	}
	return ""
}

// The response message containing the greetings
type HelloReply struct {
	Message              string   `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}

func (m *HelloReply) Reset()         { *m = HelloReply{} }
func (m *HelloReply) String() string { return proto.CompactTextString(m) }
func (*HelloReply) ProtoMessage()    {}
func (*HelloReply) Descriptor() ([]byte, []int) {
	return fileDescriptor_17b8c58d586b62f2, []int{1}
}

func (m *HelloReply) XXX_Unmarshal(b []byte) error {
	return xxx_messageInfo_HelloReply.Unmarshal(m, b)
}
func (m *HelloReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
	return xxx_messageInfo_HelloReply.Marshal(b, m, deterministic)
}
func (m *HelloReply) XXX_Merge(src proto.Message) {
	xxx_messageInfo_HelloReply.Merge(m, src)
}
func (m *HelloReply) XXX_Size() int {
	return xxx_messageInfo_HelloReply.Size(m)
}
func (m *HelloReply) XXX_DiscardUnknown() {
	xxx_messageInfo_HelloReply.DiscardUnknown(m)
}

var xxx_messageInfo_HelloReply proto.InternalMessageInfo

func (m *HelloReply) GetMessage() string {
	if m != nil {
		return m.Message
	}
	return ""
}

func init() {
	proto.RegisterType((*HelloRequest)(nil), "helloworld.HelloRequest")
	proto.RegisterType((*HelloReply)(nil), "helloworld.HelloReply")
}

func init() { proto.RegisterFile("helloworld.proto", fileDescriptor_17b8c58d586b62f2) }

var fileDescriptor_17b8c58d586b62f2 = []byte{
	// 175 bytes of a gzipped FileDescriptorProto
	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0xc8, 0x48, 0xcd, 0xc9,
	0xc9, 0x2f, 0xcf, 0x2f, 0xca, 0x49, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x42, 0x88,
	0x28, 0x29, 0x71, 0xf1, 0x78, 0x80, 0x78, 0x41, 0xa9, 0x85, 0xa5, 0xa9, 0xc5, 0x25, 0x42, 0x42,
	0x5c, 0x2c, 0x79, 0x89, 0xb9, 0xa9, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x60, 0xb6, 0x92,
	0x1a, 0x17, 0x17, 0x54, 0x4d, 0x41, 0x4e, 0xa5, 0x90, 0x04, 0x17, 0x7b, 0x6e, 0x6a, 0x71, 0x71,
	0x62, 0x3a, 0x4c, 0x11, 0x8c, 0x6b, 0xe4, 0xc9, 0xc5, 0xee, 0x5e, 0x94, 0x9a, 0x5a, 0x92, 0x5a,
	0x24, 0x64, 0xc7, 0xc5, 0x11, 0x9c, 0x58, 0x09, 0xd6, 0x25, 0x24, 0xa1, 0x87, 0xe4, 0x02, 0x64,
	0xcb, 0xa4, 0xc4, 0xb0, 0xc8, 0x14, 0xe4, 0x54, 0x2a, 0x31, 0x38, 0x19, 0x70, 0x49, 0x67, 0xe6,
	0xeb, 0xa5, 0x17, 0x15, 0x24, 0xeb, 0xa5, 0x56, 0x24, 0xe6, 0x16, 0xe4, 0xa4, 0x16, 0x23, 0xa9,
	0x75, 0xe2, 0x07, 0x2b, 0x0e, 0x07, 0xb1, 0x03, 0x40, 0x5e, 0x0a, 0x60, 0x4c, 0x62, 0x03, 0xfb,
	0xcd, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x0f, 0xb7, 0xcd, 0xf2, 0xef, 0x00, 0x00, 0x00,
}

// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn

// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4

// GreeterClient is the client API for Greeter service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type GreeterClient interface {
	// Sends a greeting
	SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
}

type greeterClient struct {
	cc *grpc.ClientConn
}

func NewGreeterClient(cc *grpc.ClientConn) GreeterClient {
	return &greeterClient{cc}
}

func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
	out := new(HelloReply)
	err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, opts...)
	if err != nil {
		return nil, err
	}
	return out, nil
}

// GreeterServer is the server API for Greeter service.
type GreeterServer interface {
	// Sends a greeting
	SayHello(context.Context, *HelloRequest) (*HelloReply, error)
}

func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
	s.RegisterService(&_Greeter_serviceDesc, srv)
}

func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
	in := new(HelloRequest)
	if err := dec(in); err != nil {
		return nil, err
	}
	if interceptor == nil {
		return srv.(GreeterServer).SayHello(ctx, in)
	}
	info := &grpc.UnaryServerInfo{
		Server:     srv,
		FullMethod: "/helloworld.Greeter/SayHello",
	}
	handler := func(ctx context.Context, req interface{}) (interface{}, error) {
		return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest))
	}
	return interceptor(ctx, in, info, handler)
}

var _Greeter_serviceDesc = grpc.ServiceDesc{
	ServiceName: "helloworld.Greeter",
	HandlerType: (*GreeterServer)(nil),
	Methods: []grpc.MethodDesc{
		{
			MethodName: "SayHello",
			Handler:    _Greeter_SayHello_Handler,
		},
	},
	Streams:  []grpc.StreamDesc{},
	Metadata: "helloworld.proto",
}
 
 

生成文件包含服务端接口GreeterServer描述,客户端greeterClient接口及实现,以及HelloRequest、HelloResponse结构体。

4、服务端

在server子目录下创建server.go文件:

package main

import (
   "context"
   "net"

   "google.golang.org/grpc"
   pb "helloworld/proto"
   "google.golang.org/grpc/reflection"
   "google.golang.org/grpc/grpclog"
)

const (
   // Address gRPC服务地址
   port = ":50051"
)

// server is used to implement helloworld.GreeterServer.
type server struct{}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
   return &pb.HelloReply{Message: "Hello " + in.Name}, nil
}

func main() {
   lis, err := net.Listen("tcp", port)
   if err != nil {
      grpclog.Fatalf("failed to listen: %v", err)
   }
   // 实例化gRPC Server
   s := grpc.NewServer()
   // 注册服务
   pb.RegisterGreeterServer(s, &server{})
   // Register reflection service on gRPC server.
   reflection.Register(s)
   if err := s.Serve(lis); err != nil {
      grpclog.Fatalf("failed to serve: %v", err)
   }
}
 
 

服务端引入编译后的proto包,实现约定的SayHello接口方法,接口描述可以查看helloworld.pb.go文件中的GreeterServer接口描述。实例化grpc Server并注册GreeterService,开始提供服务。

5、客户端

客户端使用gRPC访问服务端:

package main

import (
   "context"
   "os"
   "time"

   "google.golang.org/grpc"
   pb "helloworld/proto"
   "log"
)

const (
   address     = "localhost:50051"
   defaultName = "world"
)

func main() {
   // Set up a connection to the server.
   conn, err := grpc.Dial(address, grpc.WithInsecure())
   if err != nil {
      log.Fatalf("did not connect: %v", err)
   }
   defer conn.Close()
   c := pb.NewGreeterClient(conn)

   // Contact the server and print out its response.
   name := defaultName
   if len(os.Args) > 1 {
      name = os.Args[1]
   }
   ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   defer cancel()
   r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
   if err != nil {
      log.Fatalf("could not greet: %v", err)
   }
   log.Printf("Greeting: %s", r.Message)
}
// output:
// 2018/12/17 19:57:47 Greeting: Hello world
 
 

客户端初始化连接后直接调用声明的方法,即可向服务端发起请求。

6、运行测试

在server目录下启动Server:
go run main.go
在client目录下启动客户端:
go run main.go
客户端接收到服务端的响应:
2018/12/17 20:03:44 Greeting: Hello world

gRPC快速入门
gRPC快速入门(一)——Protobuf简介
 http://blog.51cto.com/9291927/2331980
gRPC快速入门(二)——Protobuf序列化原理解析
 http://blog.51cto.com/9291927/2332264
gRPC快速入门(三)——Protobuf应用示例
 http://blog.51cto.com/9291927/2332269
gRPC快速入门(四)——gRPC快速入门
 http://blog.51cto.com/9291927/2332271
###
GRPC localhost:8796/Euipm.GetDataService/getCustomListData

{
  "method": "getHeavilyQuotedEventBondMarketInfos"
}
posted @ 2021-11-03 16:03  CharyGao  阅读(518)  评论(0编辑  收藏  举报