1.Protocol Buffer 概念

  Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准,目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件。他们用于 RPC 系统和持续数据存储系统。

  Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。

2.Protocol Buffer 使用

  使用ProtocolBuffer,要先编写一个.proto文件,用这个文件来描述你希望保存的数据结构。然后用ProtocolBuffer编译器创建一个类,这个类用高效的二进制的格式实现了ProtocolBuffer数据的自动编解码。生成的类提供了组成ProtocolBuffer字段的getter和setter方法,以及提供了负责读写一个ProtocolBuffer单位的方法。重要的是ProtocolBuffer格式支持向后的兼容性,新的代码依然可以读取用旧格式编码的数据。

2.1定义第一个Protocol Buffer消息 

 创建扩展名为.proto的文件,如:MyMessage.proto,并将以下内容存入该文件中。
      message LogonReqMessage {
          required int64 acctID = 1;
          required string passwd = 2;
      }
      这里将给出以上消息定义的关键性说明。
      1. message是消息定义的关键字,等同于C++中的struct/class,或是Java中的class。
      2. LogonReqMessage为消息的名字,等同于结构体名或类名。
      3. required前缀表示该字段为必要字段,既在序列化和反序列化之前该字段必须已经被赋值。与此同时,在Protocol Buffer中还存在另外两个类似的关键字,optional和repeated,带有这两种限定符的消息字段则没有required字段这样的限制。相比于optional,repeated主要用于表示数组字段。具体的使用方式在后面的用例中均会一一列出。
      4. int64和string分别表示长整型和字符串型的消息字段,在Protocol Buffer中存在一张类型对照表,既Protocol Buffer中的数据类型与其他编程语言(C++/Java)中所用类型的对照。该对照表中还将给出在不同的数据场景下,哪种类型更为高效。该对照表将在后面给出。
      5. acctID和passwd分别表示消息字段名,等同于Java中的域变量名,或是C++中的成员变量名。
      6. 标签数字12则表示不同的字段在序列化后的二进制数据中的布局位置。在该例中,passwd字段编码后的数据一定位于acctID之后。需要注意的是该值在同一message中不能重复。另外,对于Protocol Buffer而言,标签值为1到15的字段在编码时可以得到优化,既标签值和类型信息仅占有一个byte,标签范围是16到2047的将占有两个bytes,而Protocol Buffer可以支持的字段数量则为2的29次方减一。有鉴于此,我们在设计消息结构时,可以尽可能考虑让repeated类型的字段标签位于1到15之间,这样便可以有效的节省编码后的字节数量。

2.2定义第二个(含有枚举字段)Protocol Buffer消息
      //在定义Protocol Buffer的消息时,可以使用和C++/Java代码同样的方式添加注释。
      enum UserStatus {
          OFFLINE = 0;  //表示处于离线状态的用户
          ONLINE = 1;   //表示处于在线状态的用户
      }
      message UserInfo {
          required int64 acctID = 1;
          required string name = 2;
          required UserStatus status = 3;
      }
      这里将给出以上消息定义的关键性说明(仅包括上一小节中没有描述的)。
      1. enum是枚举类型定义的关键字,等同于C++/Java中的enum。
      2. UserStatus为枚举的名字。
      3. 和C++/Java中的枚举不同的是,枚举值之间的分隔符是分号,而不是逗号。
      4. OFFLINE/ONLINE为枚举值。
      5. 0和1表示枚举值所对应的实际整型值,和C/C++一样,可以为枚举值指定任意整型值,而无需总是从0开始定义。如:
      enum OperationCode {
          LOGON_REQ_CODE = 101;
          LOGOUT_REQ_CODE = 102;
          RETRIEVE_BUDDIES_REQ_CODE = 103;
    
          LOGON_RESP_CODE = 1001;
          LOGOUT_RESP_CODE = 1002;
          RETRIEVE_BUDDIES_RESP_CODE = 1003;
      }

2.3定义第三个(含有嵌套消息字段)Protocol Buffer消息
      我们可以在同一个.proto文件中定义多个message,这样便可以很容易的实现嵌套消息的定义。如:
      enum UserStatus {
          OFFLINE = 0;
          ONLINE = 1;
      }
      message UserInfo {
          required int64 acctID = 1;
          required string name = 2;
          required UserStatus status = 3;
      }
      message LogonRespMessage {
          required LoginResult logonResult = 1;
          required UserInfo userInfo = 2;
      }
      这里将给出以上消息定义的关键性说明(仅包括上两小节中没有描述的)。
      1. LogonRespMessage消息的定义中包含另外一个消息类型作为其字段,如UserInfo userInfo。
      2. 上例中的UserInfo和LogonRespMessage被定义在同一个.proto文件中,那么我们是否可以包含在其他.proto文件中定义的message呢?Protocol Buffer提供了另外一个关键字import,这样我们便可以将很多通用的message定义在同一个.proto文件中,而其他消息定义文件可以通过import的方式将该文件中定义的消息包含进来,如:
      import "myproject/CommonMessages.proto"

2.4限定符(required/optional/repeated)的基本规则
      1. 在每个消息中必须至少留有一个required类型的字段。
      2. 每个消息中可以包含0个或多个optional类型的字段。
      3. repeated表示的字段可以包含0个或多个数据。需要说明的是,这一点有别于C++/Java中的数组,因为后两者中的数组必须包含至少一个元素。
      4. 如果打算在原有消息协议中添加新的字段,同时还要保证老版本的程序能够正常读取或写入,那么对于新添加的字段必须是optional或repeated。道理非常简单,老版本程序无法读取或写入新增的required限定符的字段。
2.5类型对照表

.proto Type Notes C++ Type Java Type
double    double  double
float    float  float
int32 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint32 instead.  int32  int
int64 Uses variable-length encoding. Inefficient for encoding negative numbers – if your field is likely to have negative values, use sint64 instead.  int64  long
uint32 Uses variable-length encoding.  uint32  int
uint64 Uses variable-length encoding.  uint64  long
sint32 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int32s.  int32  int
sint64 Uses variable-length encoding. Signed int value. These more efficiently encode negative numbers than regular int64s.   int64  long
fixed32 Always four bytes. More efficient than uint32 if values are often greater than 228  uint32  int
fixed64 Always eight bytes. More efficient than uint64 if values are often greater than 256.  uint64  long
sfixed32 Always four bytes.  int32  int
sfixed64 Always eight bytes.  int64  long
bool    bool  boolean
string A string must always contain UTF-8 encoded or 7-bit ASCII text.  string  String
bytes May contain any arbitrary sequence of bytes. string ByteString

2.6 Protocol Buffer消息升级原则
      在实际的开发中会存在这样一种应用场景,既消息格式因为某些需求的变化而不得不进行必要的升级,但是有些使用原有消息格式的应用程序暂时又不能被立刻升级,这便要求我们在升级消息格式时要遵守一定的规则,从而可以保证基于新老消息格式的新老程序同时运行。规则如下:
      1. 不要修改已经存在字段的标签号。
      2. 任何新添加的字段必须是optional和repeated限定符,否则无法保证新老程序在互相传递消息时的消息兼容性。
      3. 在原有的消息中,不能移除已经存在的required字段,optional和repeated类型的字段可以被移除,但是他们之前使用的标签号必须被保留,不能被新的字段重用。
      4. int32、uint32、int64、uint64和bool等类型之间是兼容的,sint32和sint64是兼容的,string和bytes是兼容的,fixed32和sfixed32,以及fixed64和sfixed64之间是兼容的,这意味着如果想修改原有字段的类型时,为了保证兼容性,只能将其修改为与其原有类型兼容的类型,否则就将打破新老消息格式的兼容性。
      5. optional和repeated限定符也是相互兼容的。

2.7Packages
      我们可以在.proto文件中定义包名,如:
      package ourproject.lyphone;
      该包名在生成对应的C++文件时,将被替换为名字空间名称,既namespace ourproject { namespace lyphone。而在生成的Java代码文件中将成为包名。

2.8 Options
      Protocol Buffer允许我们在.proto文件中定义一些常用的选项,这样可以指示Protocol Buffer编译器帮助我们生成更为匹配的目标语言代码。Protocol Buffer内置的选项被分为以下三个级别:
      1. 文件级别,这样的选项将影响当前文件中定义的所有消息和枚举。
      2. 消息级别,这样的选项仅影响某个消息及其包含的所有字段。
      3. 字段级别,这样的选项仅仅响应与其相关的字段。
      下面将给出一些常用的Protocol Buffer选项。
      1. option java_package = "com.companyname.projectname";
      java_package是文件级别的选项,通过指定该选项可以让生成Java代码的包名为该选项值,如上例中的Java代码包名为com.companyname.projectname。与此同时,生成的Java文件也将会自动存放到指定输出目录下的com/companyname/projectname子目录中。如果没有指定该选项,Java的包名则为package关键字指定的名称。该选项对于生成C++代码毫无影响。
      2. option java_outer_classname = "LYPhoneMessage";
      java_outer_classname是文件级别的选项,主要功能是显示的指定生成Java代码的外部类名称。如果没有指定该选项,Java代码的外部类名称为当前文件的文件名部分,同时还要将文件名转换为驼峰格式,如:my_project.proto,那么该文件的默认外部类名称将为MyProject。该选项对于生成C++代码毫无影响。
      注:主要是因为Java中要求同一个.java文件中只能包含一个Java外部类或外部接口,而C++则不存在此限制。因此在.proto文件中定义的消息均为指定外部类的内部类,这样才能将这些消息生成到同一个Java文件中。在实际的使用中,为了避免总是输入该外部类限定符,可以将该外部类静态引入到当前Java文件中,如:import static com.company.project.LYPhoneMessage.*
      3. option optimize_for = LITE_RUNTIME;
      optimize_for是文件级别的选项,Protocol Buffer定义三种优化级别SPEED/CODE_SIZE/LITE_RUNTIME。缺省情况下是SPEED。
      SPEED: 表示生成的代码运行效率高,但是由此生成的代码编译后会占用更多的空间。
      CODE_SIZE: 和SPEED恰恰相反,代码运行效率较低,但是由此生成的代码编译后会占用更少的空间,通常用于资源有限的平台,如Mobile。
      LITE_RUNTIME: 生成的代码执行效率高,同时生成代码编译后的所占用的空间也是非常少。这是以牺牲Protocol Buffer提供的反射功能为代价的。因此我们在C++中链接Protocol Buffer库时仅需链接libprotobuf-lite,而非libprotobuf。在Java中仅需包含protobuf-java-2.4.1-lite.jar,而非protobuf-java-2.4.1.jar。
      注:对于LITE_MESSAGE选项而言,其生成的代码均将继承自MessageLite,而非Message。    
      4. [pack = true]: 因为历史原因,对于数值型的repeated字段,如int32、int64等,在编码时并没有得到很好的优化,然而在新近版本的Protocol Buffer中,可通过添加[pack=true]的字段选项,以通知Protocol Buffer在为该类型的消息对象编码时更加高效。如:
      repeated int32 samples = 4 [packed=true]。
      注:该选项仅适用于2.3.0以上的Protocol Buffer。
      5. [default = default_value]: optional类型的字段,如果在序列化时没有被设置,或者是老版本的消息中根本不存在该字段,那么在反序列化该类型的消息是,optional的字段将被赋予类型相关的缺省值,如bool被设置为false,int32被设置为0。Protocol Buffer也支持自定义的缺省值,如:
      optional int32 result_per_page = 3 [default = 10]。
2.9 命令行编译工具
      protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR path/to/file.proto
      这里将给出上述命令的参数解释。
      1. protoc为Protocol Buffer提供的命令行编译工具。
      2. --proto_path等同于-I选项,主要用于指定待编译的.proto消息定义文件所在的目录,该选项可以被同时指定多个。
      3. --cpp_out选项表示生成C++代码,--java_out表示生成Java代码,--python_out则表示生成Python代码,其后的目录为生成后的代码所存放的目录。
      4. path/to/file.proto表示待编译的消息定义文件。
      注:对于C++而言,通过Protocol Buffer编译工具,可以将每个.proto文件生成出一对.h和.cc的C++代码文件。生成后的文件可以直接加载到应用程序所在的工程项目中。如:MyMessage.proto生成的文件为MyMessage.pb.h和MyMessage.pb.cc。

参考资料:http://i.cnblogs.com/PostDone.aspx?postid=4458913&actiontip=%e5%8f%91%e5%b8%83%e6%88%90%e5%8a%9f

Protocol Buffer详解的更多相关文章

  1. 前端后台以及游戏中使用Google Protocol Buffer详解

    前端后台以及游戏中使用Google Protocol Buffer详解 0.什么是protoBuf protoBuf是一种灵活高效的独立于语言平台的结构化数据表示方法,与XML相比,protoBuf更 ...

  2. Potocol Buffer详解

    protocol安装及使用 上一篇博文介绍了一个综合案例,这篇将详细介绍protocol buffer. 为什么使用protocol buffer? java默认序列化效率较低. apache的thr ...

  3. java NIO Buffer 详解(1)

    1.java.io  最为核心的概念是流(stream),面向流的编程,要么输入流要么输出流,二者不可兼具: 2.java.nio 中拥有3个核心概念: Selector Channel, Buffe ...

  4. 网卡的 Ring Buffer 详解

    1. 网卡处理数据包流程 网卡处理网络数据流程图: 图片来自参考链接1 上图中虚线步骤的解释: DMA 将 NIC 接收的数据包逐个写入 sk_buff ,一个数据包可能占用多个 sk_buff , ...

  5. Java NIO中的Buffer 详解

    Java NIO中的Buffer用于和NIO通道进行交互.如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的.缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存.这块内存被包装成NIO ...

  6. mysql-5.7 innodb change buffer 详解

    一.innodb change buffer 介绍: 1.innodb change buffer 是针对oltp场景下磁盘IO的一种优化(我也感觉这个不太像人话,但是它又非常的准确的说明 innod ...

  7. node new Buffer()详解

    new Buffer(size) size {Number} 分配一个 size 字节大小的新 Buffer.size 必须小于等于 require('buffer').kMaxLength(在64位 ...

  8. Java NIO Buffer详解

    一.ByteBuffer类型化的put与get方法 /** * ByteBuffer类型化的put与get方法 */ public class NioTest5 { public static voi ...

  9. Golang bytes.buffer详解

    原文:https://www.jianshu.com/p/e53083132a25 Buffer 介绍 Buffer 是 bytes 包中的一个 type Buffer struct{…} A buf ...

随机推荐

  1. SQL Server 数据库初始化准备脚本

    通常我们在项目部署前都会写一份数据库初始化脚本.由于数据库外键的限制,我们需要按照数据引用顺序添加初始记录,这个整理过程相当麻烦. 因此写了以下脚本,原理是先去掉所有外键,然后执行一次清空,然后添加数 ...

  2. android:screenOrientation的说明 固定屏幕显示方向

    Activity在屏幕当中显示的方向.属性值可以是下表中列出的一个值: "unspecified" 默认值,由系统来选择方向.它的使用策略,以及由于选择时特定的上下文环境,可能会因 ...

  3. openwrt(路由器)的源码地址

    https://dev.openwrt.org/wiki/GetSource 路由器的源码地址

  4. centos 升级GCC/G++

    #get rep yum install centos-release-scl-rh #yum install centos-release-scl # install g++ 5.2.1 yum - ...

  5. Mac AppStore下载文件的获取

    有时候希望把在mac AppStore下载的App共享给其他人,但是application里面的都是已经安装的应用,那么如何找到pkg安装文件呢? (后附:注意事项!) 方法一: 1.首先下载一个Ap ...

  6. R语言常用基础知识

    seq(from = 1, to = 1, by = ((to - from)/(length.out - 1)),     length.out = NULL, along.with = NULL, ...

  7. ACM - ICPC World Finals 2013 I Pirate Chest

    原题下载:http://icpc.baylor.edu/download/worldfinals/problems/icpc2013.pdf 题目翻译: 问题描述 海盗Dick受够了在公海上厮杀.抢劫 ...

  8. JAVA使用DES加密算法加密解密

    程序中使用了.properties文件作为参数配置文档,好处是灵活配置各项参数 一旦对数据库的一些参数进行了配置,势必涉及数据库的IP,端口,用户名和密码 properties文件全是unicode编 ...

  9. laravel重要概念和知识点

    Service Provider: 一个laravel service provider就是一个注册IoC container binding的类.实际上,laravel本身就自包含了一堆管理核心框架 ...

  10. vc判断文件是否存在

    #include <io.h> #include <stdio.h> #include <stdlib.h> void main( void ) { /* Chec ...