Protocol Buffer
一. Protocol Buffer
生成代码包含一个外部类,文件名字默认为外部类的名字,不能和消息重复;外部类名字可以手动指定;
1.1 定义Protocol Buffer消息
创建扩展名为.proto的文件,如:MyMessage.proto,并将以下内容存入该文件中。
      message LogonReqMessage {
          required int64 acctID = 1;
          required string passwd = 2;
      }
说明:
- message是消息定义的关键字
- LogonReqMessage为消息的名字
- required前缀表示该字段为必要字段,既在序列化和反序列化之前该字段必须已经被赋值。还有optional和repeated字段,其中repeated表示数组;
- int64和string分别表示长整型和字符串型的消息字段
- acctID和passwd分别表示消息字段名
- 标签数字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;
      }
说明
- enum是枚举类型定义的关键字,等同于C++/Java中的enum。
- UserStatus为枚举的名字。
- 和C++/Java中的枚举不同的是,枚举值之间的分隔符是分号,而不是逗号。
- OFFLINE/ONLINE为枚举值。
- 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
- 在每个消息中必须至少留有一个required类型的字段。
- 每个消息中可以包含0个或多个optional类型的字段。
- repeated表示的字段可以包含0个或多个数据
- 如果打算在原有消息协议中添加新的字段,同时还要保证老版本的程序能够正常读取或写入,那么对于新添加的字段必须是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 消息升级原则
同时兼容新老版本,规则如下:
- 不要修改已经存在字段的标签号。
- 任何新添加的字段必须是optional和repeated限定符,否则无法保证新老程序在互相传递消息时的消息兼容性。
- 在原有的消息中,不能移除已经存在的required字段,optional和repeated类型的字段可以被移除,但是他们之前使用的标签号必须被保留,不能被新的字段重用。
- int32、uint32、int64、uint64和bool等类型之间是兼容的,sint32和sint64是兼容的,string和bytes是兼容的,fixed32和sfixed32,以及fixed64和sfixed64之间是兼容的,这意味着如果想修改原有字段的类型时,为了保证兼容性,只能将其修改为与其原有类型兼容的类型,否则就将打破新老消息格式的兼容性。
- optional和repeated限定符也是相互兼容的。
1.7 Packages
我们可以在.proto文件中定义包名,如:
   package ourproject.lyphone;
在生成java代码时,会作为包名
1.8 Options
Protocol Buffer内置的选项被分为以下三个级别:
- 文件级别,这样的选项将影响当前文件中定义的所有消息和枚举。
- 消息级别,这样的选项仅影响某个消息及其包含的所有字段。
- 字段级别,这样的选项仅仅响应与其相关的字段。
选项例子:
例子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
参数解释。
- protoc为Protocol Buffer提供的命令行编译工具。
- --proto_path等同于-I选项,主要用于指定待编译的.proto消息定义文件所在的目录,该选项可以被同时指定多个。
- --cpp_out选项表示生成C++代码,--java_out表示生成Java代码,--python_out则表示生成Python代码,其后的目录为生成后的代码所存放的目录。
- 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
 
                     
                    
                 
                    
                
 
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号