Protocol Buffer

一. Protocol Buffer

生成代码包含一个外部类,文件名字默认为外部类的名字,不能和消息重复;外部类名字可以手动指定;

1.1 定义Protocol Buffer消息

创建扩展名为.proto的文件,如:MyMessage.proto,并将以下内容存入该文件中。

      message LogonReqMessage {
          required int64 acctID = 1;
          required string passwd = 2;
      }

说明:

  1. message是消息定义的关键字
  2. LogonReqMessage为消息的名字
  3. required前缀表示该字段为必要字段,既在序列化和反序列化之前该字段必须已经被赋值。还有optional和repeated字段,其中repeated表示数组;
  4. int64和string分别表示长整型和字符串型的消息字段
  5. acctID和passwd分别表示消息字段名
  6. 标签数字1和2则表示不同的字段在序列化后的二进制数据中的布局位置;1-15能优化,既标签值和类型信息仅占有一个byte;尽可能考虑让repeated类型的字段标签位于1到15之间

1.2 函数枚举字段

	  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开始定义。

1.3 含有嵌套消息字段

	  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;
      }

如果定义在其他消息中,使用import可以包含进来;

import "myproject/CommonMessages.proto"

1.4 限定符的基本规则

限定符:required/optional/repeated

  1. 在每个消息中必须至少留有一个required类型的字段。
  2. 每个消息中可以包含0个或多个optional类型的字段。
  3. repeated表示的字段可以包含0个或多个数据
  4. 如果打算在原有消息协议中添加新的字段,同时还要保证老版本的程序能够正常读取或写入,那么对于新添加的字段必须是optional或repeated;

1.5 类型对照

.proto Notes C++ Java
double double double
float float float
int32 变量长度编码,正数效率高 int32 int
int64 变量长度编码,正数效率高 int64 long
uint32 变量长度编码 uint32 int
uint64 变量长度编码 uint64 long
sint32 变量长度编码,负数效率高 int32 int
sint64 变量长度编码,负数效率高 int64 long
fixed32 固定4byte,228以上比uint32有效 uint32 int
fixed64 固定8byte,256以上比uint64有效 uint64 long
sfixed32 固定4byte int32 int
sfixed64 固定8byte int64 long
bool bool boolean
string 必须为UTF8或者ASCII码 String
bytes 需要包含任意序列字节 string ByteString

1.6 消息升级原则

同时兼容新老版本,规则如下:

  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限定符也是相互兼容的。

1.7 Packages

我们可以在.proto文件中定义包名,如:

   package ourproject.lyphone;

在生成java代码时,会作为包名

1.8 Options

Protocol Buffer内置的选项被分为以下三个级别:

  1. 文件级别,这样的选项将影响当前文件中定义的所有消息和枚举。
  2. 消息级别,这样的选项仅影响某个消息及其包含的所有字段。
  3. 字段级别,这样的选项仅仅响应与其相关的字段。

选项例子:
例子1:option java_package = "com.companyname.projectname"
java_package文件级别,值作为java的包名,生成的java包会存到相应的路径下;
如果没有指定该选项,java的包名为package关键字指定的名称;

例子2:option java_outer_classname = "LYPhoneMessage"
java_outer_classname文件级别,显示的指定生成Java代码的外部类名称,如果没有指定该选项,Java代码的外部类名称为当前文件的文件名部分,my_project.proto会转为MyProject;
因此在.proto文件中定义的消息均为指定外部类的内部类,这样才能将这些消息生成到同一个Java文件中。
在实际的使用中,为了避免总是输入该外部类限定符,可以将该外部类静态引入到当前Java文件中,如:import static com.company.project.LYPhoneMessage.*。

例子3:option optimize_for = LITE_RUNTIME
optimize_for是文件级别的选项,Protocol Buffer定义三种优化级别
SPEED: 缺省,表示生成的代码运行效率高,但是由此生成的代码编译后会占用更多的空间。
CODE_SIZE:效率低,但是占用空间少
LITE_RUNTIME:效率高, 空间也少;在java中包含protobuf-java-2.4.1-lite.jar‘

例子4:[pack = true]
以通知Protocol Buffer在为该类型的消息对象编码时更加高效。如:

  repeated int32 samples = 4 [packed=true]

例子5:[default = default_value]
optional类型的字段,如果在序列化时没有被设置,或者是老版本的消息中根本不存在该字段,那么在反序列化该类型的消息是,optional的字段将被赋予类型相关的缺省值,如bool被设置为false,int32被设置为0。Protocol Buffer也支持自定义的缺省值,如:

  optional int32 result_per_page = 3 [default = 10]

1.9 工具protoc

protoc --proto_path=IMPORT_PATH --java_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。

## 二. java例子 ### 2.1 生成目标语言代码
protoc -I=./message --java_out=./src ./MyMessage.proto

从上面的命令行参数中可以看出,待编译的文件为MyMessage.proto,他存放在当前目录的message子目录下。--java_out参数则指示编译工具我们需要生成目标语言是java,输出目录是当前目录的src子目录。这里需要补充说明的是,因为在MyMessage.proto文件中定义了option java_package = "com.lsk.lyphone"的文件级选项,所以输出的目前是src/com/lsk/lyphone,生成的目标代码文件名是MyMessage.java。
定义一个Message

	option java_package = "com.lsk.lyphone";
	option java_outer_classname = "LYPhoneMessage";
	option optimize_for = LITE_RUNTIME;
	message LogonReqMessage {
	required int64 acctID = 1;
	required string passwd = 2;
	}

optimize_for字段,指定了LITE_RUNTIME,文件的父类为com.google.protobuf.GeneratedMessageLite,而不是com.google.protobuf.GeneratedMessage;
Builder类继承自com.google.protobuf.MessageLiteOrBuilder,而非com.google.protobuf.MessageOrBuilder,MessageLite缺少Protocol Buffer反射的支持;
每个消息均会生成一个Builder接口和一个与消息对应的实现类,该实现类又将同时实现生成的Builder接口和扩展Protocol Buffer内置的GeneratedMessageLite;

测试代码:

    //构造并初始化Builder
    LYPhoneMessage.LogonReqMessage.Builder logonReqBuilder
                        = LYPhoneMessage.LogonReqMessage.newBuilder();
    logonReqBuilder.setAcctID(20);
    logonReqBuilder.setPasswd("123456");

    //builder对象初始化完毕后,再通过build方法生成与之对应的消息类对象。
    LYPhoneMessage.LogonReqMessage logonReq = logonReqBuilder.build();
    int length = logonReq.getSerializedSize();
    System.out.println("The result length is : "+length);

    //直接序列化到内存中,之后可对该内存进行二次加工后再写到本地文件或发送到远端,如加密。
    byte[] buf = logonReq.toByteArray();
    LYPhoneMessage.LogonReqMessage logonReq2 = LYPhoneMessage.LogonReqMessage.parseFrom(buf);
    System.out.println("accId = " + logonReq2.getAcctID() + "\tpassword = " + logonReq2.getPasswd());

序列化与反序列化可以看Protocol Buffer的源码实现,比较通俗易懂;

http://www.cnblogs.com/stephen-liu74/archive/2013/01/06/2842972.html

posted @ 2016-08-22 20:42  zhangshihai1232  阅读(195)  评论(0)    收藏  举报