二、protocol buffer二进制协议

2018-12-16  本文已影响0人  小manong

一、是什么

  • 在谷歌内部长期使用,产品成熟度高;
  • 跨语言、支持多种语言,包括 C++、Java 和 Python
  • 编码后的消息更小,更加有利于存储和传输
  • 编解码的性能非常高
  • 支持不同协议版本的前向兼容
  • 支持定义可选和必选字段

二、如何工作

通过在.proto文件中定义protocol buffer消息类型来指定您希望如何构建序列化信息。每个protocol buffer消息都是一个小的逻辑信息记录,包含一系列名称 - 值对。以下.proto是定义包含有关人员信息的消息的文件的一个非常基本的示例:

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

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

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }
  repeated PhoneNumber phone = 4;
}

三、proto3

1、定义消息类型

syntax = "proto3";//明确使用版本proto3,不指定的话默认是proto2
//SearchRequest 定义了三个属性,每一个属性都有名称和类型
message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

2、指定字段类型

(2)复杂数据结构

3、分配字段编号

4、指定字段规则

单数:格式良好的消息可以包含该字段中的零个或一个(但不超过一个)。
复数:此字段可以在格式良好的消息中重复任意次数(包括零)。将保留重复值的顺序。

5、添加更多消息类型

message SearchRequest { 
  string query = 1; 
  int32 page_number = 2; 
  int32 result_per_page = 3; 
} 

message SearchResponse { 
 ... 
}

6、添加注释

/* SearchRequest represents a search query, with pagination options to
 * indicate which results to include in the response. */

message SearchRequest {
  string query = 1;
  int32 page_number = 2;  // Which page number do we want?
  int32 result_per_page = 3;  // Number of results to return per page.
}

7、导入其他的proto文件

import "/other.proto"

8、编写proto3文件规范

(1)消息(使用驼峰命名)和字段名称(使用下划线)

message SongServerRequest {
  required string song_name = 1;
}

(2)枚举(名称使用驼峰,字段使用大写的下划线)

enum Foo {
  FIRST_VALUE = 0;
  SECOND_VALUE = 1;
}

(3)RPC 服务(服务名称和任何RPC方法名称全部使用驼峰命名)

service FooService {
  rpc GetSomething(FooRequest) returns (FooResponse);
}

四、java语言实战Proto3

1、定义协议格式(addressbook.proto)

syntax = "proto3";
//1、proto3包名,这有助于防止不同项目之间的命名冲突
package tutorial;
//2、申明java包名
option java_package = "com.example.tutorial";
//3、申明产生的外部java类名,如果不申明将使用驼峰形式产生(AddressBook)
option java_outer_classname = "AddressBookProtos";

message Person {
//4、required 必须提供该字段的值,否则该消息将被视为“未初始化”。
//尝试构建一个未初始化的消息将抛出一个RuntimeException。
//解析未初始化的消息将抛出一个IOException。除此之外,必填字段的行为与可选字段完全相同。
  required string name = 1;
  required int32 id = 2;
//5、optional:该字段可能已设置,也可能未设置。如果未设置可选字段值,则使用默认值。
//对于简单类型,您可以指定自己的默认值,就像我们type在示例中为电话号码所做的那样。
//否则,使用系统默认值:数字类型为0,字符串为空字符串,bools为false。对于嵌入式消息,
//默认值始终是消息的“默认实例”或“原型”,其中没有设置任何字段。调用访问器以获取尚未显
//式设置的可选(或必需)字段的值始终返回该字段的默认值。
  optional string email = 3;

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

  message PhoneNumber {
    required string number = 1;
    optional PhoneType type = 2 [default = HOME];
  }
//6、repeated:该字段可以重复任意次数(包括零)。重复值的顺序将保
//留在协议缓冲区中。将重复字段视为动态大小的数组。
  repeated PhoneNumber phones = 4;
}

message AddressBook {
  repeated Person people = 1;
}

(1)应该非常小心地将字段标记为required。谷歌的一些工程师得出的结论是,使用required弊大于利; 他们更喜欢只使用optional和repeated。但是,这种观点并不普遍。
(2)可以在Protocol Buffer Language Guide中中找到需要的所有格式形式

2、编译Proto3文件

参考网址进行:https://my.oschina.net/u/573325/blog/1617416
(1)proto文件(由于插件的原因上面所讲的optional 和required 属性描述全部会报错误,因此全部删除)

syntax = "proto3";
option java_package = "com.qiu.proto";
option java_outer_classname = "AddressBookProtos";

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

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

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

message AddressBook {
    repeated Person people = 1;
}

(2)生成java文件解析

3、测试

maven添加如下的依赖,用于序列化操作

<dependency>
            <groupId>com.google.protobuf</groupId>
            <artifactId>protobuf-java-util</artifactId>
            <version>3.5.1</version>
        </dependency>

测试类

public class ProtoFileTest {
    public static void main(String[] args) throws InvalidProtocolBufferException {
        //1、构建person类的build
        AddressBookProtos.Person.Builder person= AddressBookProtos.Person.newBuilder();
        person.setEmail("2222@a");
        person.setId(1);
        person.setName("test");
        //2、构建phonenumer内部类的build
        AddressBookProtos.Person.PhoneNumber.Builder phoneNumer= AddressBookProtos.Person.PhoneNumber.newBuilder();
        phoneNumer.setNumber("123456789");
        phoneNumer.setType(AddressBookProtos.Person.PhoneType.MOBILE);
        AddressBookProtos.Person.PhoneNumber.Builder phoneNumer2= AddressBookProtos.Person.PhoneNumber.newBuilder();
        phoneNumer2.setNumber("987654321");
        phoneNumer2.setType(AddressBookProtos.Person.PhoneType.MOBILE);
        //person类添加phonenumbers列表
        person.addPhones(phoneNumer);
        person.addPhones(phoneNumer2);
    //创建person类
        AddressBookProtos.Person build = person.build();
        byte[] bytes = build.toByteArray();
        AddressBookProtos.Person addressBook1 = AddressBookProtos.Person.parseFrom(bytes);

        List<AddressBookProtos.Person.PhoneNumber> phonesList = person.getPhonesList();
        phonesList.forEach(p->{
            System.out.println("type:"+p.getType()+" number:"+p.getNumber());
        });
        //进行person json序列化
        String personJsonString = JsonFormat.printer().print(person);
        System.out.println("person序列化:"+personJsonString);
        //3、构建AddressBook通讯录
        AddressBookProtos.AddressBook.Builder addressBook= AddressBookProtos.AddressBook.newBuilder();
        addressBook.addPeople(person);
        //进行地址序列化
        String addressJsonStr = JsonFormat.printer().print(addressBook);
        System.out.println("通讯录序列化:"+addressJsonStr);

    }
}

测试结果:

type:MOBILE number:123456789
type:MOBILE number:987654321
person序列化:{
  "name": "test",
  "id": 1,
  "email": "2222@a",
  "phones": [{
    "number": "123456789"
  }, {
    "number": "987654321"
  }]
}
通讯录序列化:{
  "people": [{
    "name": "test",
    "id": 1,
    "email": "2222@a",
    "phones": [{
      "number": "123456789"
    }, {
      "number": "987654321"
    }]
  }]
}

4、总结

(1)使用步骤总结

(2)使用protoBuffer原生api总结

AddressBookProtos.Person person1= AddressBookProtos.Person.newBuilder()
                                                .setId(1).setName("test")
                                                .build();
toByteArray()
toByteString()
writeTo()
....
AddressBookProtos.Person build = person.build();
byte[] bytes = build.toByteArray();
AddressBookProtos.Person addressBook1 = AddressBookProtos.Person.parseFrom(bytes);

(3)使用第三方工具进行序列化和反序列化(比如netty)

上一篇下一篇

猜你喜欢

热点阅读