Protocol Buffers 学习

概述 

一个语言中立,平台无关的、可扩展的用来进行数据结构和序列化的协议栈,相比xml它更小、更快、更简单,一次完成数据结构定义可以使用工具生成多种语言代码,实现轻松的从各种数据流中写入和读取结构化数据.

下述定一个消息体,两个必须的字段name,和id,一个可选字段email.

message Person {

  required string name = 1;

  required int32 id = 2;

  optional string email = 3;

}

目前支持Java、Python、Object C、C++中生成代码,Proto3版本可以使用Go,Buby和C#.

下述代码使用生成的Java代码将一个消息对象进行序列化输出.

Person john =Person.newBuilder()

    .setId(1234)

    .setName("John Doe")

    .setEmail("jdoe@example.com")

    .build();

output = newFileOutputStream(args[0]);

john.writeTo(output);

 下面的代码表示从一个流中读取消息字节并反序列化为消息对象.

Person john;

fstreaminput(argv[1],

    ios::in | ios::binary);

john.ParseFromIstream(&input);

id = john.id();

name =john.name();

email =john.email();

协议目标

         .很容易就引入新的字段而不需要改动服务,

         .协议格式自我描述性

         .自动序列化和反序列化,避免手工操作

         .可用于RPC调用、和数据持久存储

         .支持生成客户端RPC  STUB类[占坑代码,需要实现的被拿到Server端执行的代码]

可参考文档

         官方教程, https://developers.google.com/protocol-buffers/docs/tutorials

         协议编码, https://developers.google.com/protocol-buffers/docs/encoding

         参考文献, https://developers.google.com/protocol-buffers/docs/reference/overview

语法[proto2]

定义消息类型

         protobuf  IDL[接口定义语言]以.proto文件结尾,当然只要内容是一个合法的protobuf语法格式就可以.

         #下面定义一个请求消息[SearchRequest],带有必须的字段query,和两个可选的字段page_number,result_per_page.

         messageRequest{

                requiredstring query = 1;

                 optional int32 page_number = 2;

                optional int32 result_per_page =3;

        }

         消息SearchRequest定义了三个字段,每个字段都有一个名称和类型,一个必须在字段修饰符,和一个必须的编号[编号在序列化和反序列化是标识字段顺序]

指定字段类型

可以使用标量类型也可以指定复合类型如枚举和其它消息类型.

指定字段标签

         消息格式中的每个字段都必须有一个唯一的数字类型的标签,上面的1,2,3.用于在二进制格式中识别消息字段.

         1-15使用1个字节编码,16-2027使用2字节编码,

上面的required,optionel都属于字段的标签,

         标记的范围1-(2^29-1)且 19000 -19999为协议保留标记.

消息体中的每一个字段都有一个唯一的编号标记,用于在消息的二进制格式中识别字段,一旦消息类型被使用就不应该修改.

指定字段规则

         消息字段必须指定一个规则,标识该字段的行为可以是以下之一

                   .required,必须的,格式良好的消息有一个required字段

                  .optional,可选的,0个或者1个

                   .repeated,可重复的,出现0次或者多次,顺序被保留

         repeated字段应该使用[packed=true],获得更有效的编码.

         repeatedint32 samples = 4 [packed=true];

添加更多的消息类型

         messageSearchRequest {

                required string query = 1;

                optional int32 page_number = 2;

                optional int32 result_per_page =3;

          }

    messageSearchResponse {

                 ...

    }

 

一个IDL文件中可以出现多个messge类型,

使用注释

         使用//  /**  doc  **/格式进行注释

保留字段

         在定义消息时可定义保留字段,保留字段可被子消息实现并使用.保留字段可以定义保留的属性和保留的标签.

         messageFoo {

              reserved 2, 15, 9 to 11;

              reserved "foo","bar";

        }

生成代码

         >protoc.exe  --java_out ./  test.proto

数据类型对照

         下面是支持的字段类型和对应关系.

        

可选字段和默认值

         被标记为optional的字段可以指定默认值,如果一个optional字段没有被设置则使用默认值,

optional int32result_per_page = 3 [default = 10];

内部定义了一些特殊的默认值,

string ->””,Boolean->false,number->0,emums ->第一个值,

枚举

         do`tlike this.

使用其它消息类型

         消息类型可以是一个已经被定义的消息,类似于面向对象语言的引用关系.

        

         messageSearchResponse {

              repeated Result result = 1;

        }

        message Result {

          required string url = 1;

          optional string title = 2;

          repeated string snippets =3;

        }

导入IDL文件

         可以使用import语法导入一个已经存在的.proto文件到当前.proto文件中来使用,

         import"myproject/other_protos.proto";

         默认只能使用由import直接导入的元素,如果存在间接的import关系可以使用import public 语法,

         import public "new.proto";

         import "other.proto";

嵌套类型

         允许定义像内部类一样的语法,protobuf message允许嵌套定义,

         message SearchResponse {

             message Result {

               required string url = 1;

               optional string title = 2;

               repeated string snippets = 3;

              }

                repeated Result result = 1;

        }

更新消息字段

         慎重,允许更新单不能破坏现有消息规则!

         .不改变任何现有字段的标记

         .添加的任何新字段都应该是optinal或者repeated修饰的,设置合理的默认值,

         .被删除的字段的标记号不应该被再次使用

         .非 required字段可以被转为扩展字段,反过来也是

         .int32, uint32, int64, uint64, and bool是兼容的

         .sint32 and sint64弧线兼容,与其它整数类型不兼容

         .utf-8格式的字符串和字节是兼容的

         …….

扩展

         允许实现类似于继承的语意,使用extensions定义保留的字段标记,通过继承让子消息来实现

        message Foo {

          // ...

          extensions 100 to 199;

        }

        extend Foo {

          optional int32 bar = 126;

        }

 

生成的代码对扩展字段有专门的处理代码,以和标准字段进行区分.

嵌套扩展

         扩展允许定义嵌套的行为

         messageBaz {

              extend Foo {

            optional int32 bar = 126;

          }

          ...

        }

 

选择扩展的标签

         一定要避免扩展消息不能使用原消息中已经使用的字段标签,为此可使用语法extensions指定扩展标记的范围.

         messageFoo {

              extensions 1000 to max;

        }

        max is 2^29 - 1, or 536,870,911.

One of

        指定一组属性其中只能有一个有效,当设置其中一个时,其它的属性被擦除,oneof中不能使用required,optional和repeated进行修饰.

         messageSampleMessage {

              oneof test_oneof {

                 string name = 4;

                 SubMessage sub_message =9;

              }

        }

和optional修饰的字段一样也有对饮的get和set方法.

Maps

         类型允许是Map类型,使用以下语法:

map<key_type,value_type> map_field = N;

         key必须是整数或者String类型,value可以是除了map之外的其它类型,map不支持repeated,optional,required修饰,

         map<string,Project> projects = 3;

          .map不支持扩展,

         .不能被repeated,optional,required修饰

         .不保证顺序

向后兼容

         map可以被模拟

         messageMapFieldEntry {

              optional key_type key = 1;

              optional value_type value =2;

        }

repeatedMapFieldEntry map_field = N; 

Package

         允许使用包概念来区分命名空间,

         package foo.bar;

        message Open {... }     

         messageFoo {

              ...

              required foo.bar.Open open =1;

              ...

         Java中的包生成规则,package默认被用作为Java包,除非在proto文件中使用了optionjava_package语意.

         建议使用package来区分命名空间!

定义服务

         Protobuf本身是一个序列化协议,允许将序列化后的数据通过RPC方式进行传输.Protobuf提供了定义RPC服务的接口,Protobuf可帮助生成接口文件和客户端STUB类.

         serviceSearchService {

              rpc Search (SearchRequest)returns (SearchResponse);

          }

Stub转发所有的调用到一个RpcChannel, RpcChannel是一个抽象的接口,可自定义实现。

服务端类应该实现Service接口.

 grpc,google提供的一种开源RPC系统,

其它可选操作

         可通过一些可选的配置,来影响整体的处理方式,

          .java_package,生成的javal类所在的包

         .java_outer_classname,输出的Java类名,如果没有则使用proto的文件名作为类名,

         .optimize_for,代码优化[SPEED,CODE_SIZE,LITE_RUNTIME]

         .java_generic_services,是否生成服务接口[true,false]

         .packed,使用该字段描述没有任何缺点,可能是用更紧凑的编码方式.

         .deprecated,过时的,true.

生成代码

         protoc指令用于将IDL文件转为对应开发语言的实现

         protoc--proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR--python_out=DST_DIR path/to/file.proto

         .--java_out,生成的java代码的存储路径

        

技巧

使用流式程序

         Protobuf的格式是固有的,不能确定消息的开始和结束,在流式写入过程中需要记录消息的开始和结束,最简单的写入消息的时候先写入消息的大小.读取消息时先读取消息长度在获取消息体.

大数集

         Protobuf不是为大数据的场景设计的,消息大与1M可以考虑使用其它的方案,分而治之将大数据集中的小数据使用Protobuf进行编码.

        

自描述

         定义的属性应该自我描述.表述其用意.

Java开发

Why Use Protocol Buffers?

         Java序列化已知的性能问题和兼容性,序列化后的大小,Protobuf可以表现出很好性能.            

定义一个消息对象

         packagecom.xiaofen;

        option java_package="com.xiaofen.bean";

        option java_outer_classname="MessageProtos";

         message Message{

                 required string id=1;

                 optional string msg=2;

                 optional User from=3;

                 optional User to=4;

        }

        message User{

             required string id=1;

             optional stringname=2;

        }

生成Java Class

         protoc--java_out=./   message.proto  

序列化一个消息

@Test
public void testSerialize() {

    MessageProtos.User from = MessageProtos.User.newBuilder().setId("1001").setName("client").build();
    MessageProtos.User to = MessageProtos.User.newBuilder().setId("1000").setName("server").build();
    MessageProtos.Message message = MessageProtos.Message.newBuilder().setId("0").setFrom(from).setTo(to).setMsg("hai server i`m client").build();
    OutputStream outputStream = null;
    try {
        Path path = Paths.get("./message.stream");
        if (!path.toFile().exists()) {
            path.toFile().createNewFile();
        }
        outputStream = Files.newOutputStream(Paths.get("./message.stream"), StandardOpenOption.WRITE);
        message.writeTo(outputStream);
        outputStream.flush();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (outputStream != null) {
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

从序列化文件读取消息

@Test
public void testUSerialize() {
    {
        InputStream inputStream = null;
        try {
            inputStream = Files.newInputStream(Paths.get("./message.stream"), StandardOpenOption.READ);
            MessageProtos.Message message = MessageProtos.Message.parseFrom(inputStream);
            System.out.println(message);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

 

其它方法

@Test
public void testUseFull() {
   
MessageProtos.Message message =MessageProtos.Message.newBuilder().setMsg("msg").setId("1").build();
   
//是否所有必须的字段已经被设置
   
System.out.println(message.isInitialized());
   
//toString
   
System.out.println(message.toString());
   
//序列化为字节数组
   
System.out.println(message.toByteArray());
   
//序列化到输出流
   
//System.out.println(message.writeTo(null));
    //....
}

还可以做什么?

         序列化框架和RPC框架是分布式系统的基础,在掌握Hessian、Avro、Protobuf、Protostuff等序列化[编码]技术后还需要掌握高性能IO框架,这在设计高性能应用程序中是必不可少的.

        

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值