• Protocol Buffers学习指南


    一、什么是Protocol Buffers

      Protocol Buffers是谷歌的语言无关、平台无关、可扩展的机制,用于序列化结构化数据(比如XML),但更小、更快、更简单。您只需定义数据的结构化方式,然后就可以使用特殊生成的源代码轻松地向各种数据流写入和读取结构化数据,并使用各种语言。目前支持Java、Python、Objective-C和c++中生成的代码。在我们新的proto3语言版本中,你也可以使用Kotlin, Dart, Go, Ruby和c#,以及更多的语言。

    比如使用Protocol Buffers来定义一个文件:

    • protobuf_test.proto
    syntax = "proto3"; // 说明使用的是proto3版本
    
    message Person {
      string name = 1; // 定义name字段,类型是string,数字1表明指定的编号
      int32 age = 2; // 定义age字段,类型是2,数字2表明指定的编号
    }

    上面就是定义一个简单的proto文件,可以利用该文件生成对应语言的源码,然后进行操作,但是在生成对应源码之前需要先安装编译工具,因为后期使用grpc所以这里可以一起安装:

    python -m pip install grpcio #安装grpc
    python -m pip install grpcio-tools #安装grpc tools
    • 生成Python源码

    在该文件下执行:

    python -m grpc_tools.protoc --python_out=. -I. protobuf_test.proto

    生成protobuf_test_pb2.py文件:

    # -*- coding: utf-8 -*-
    # Generated by the protocol buffer compiler.  DO NOT EDIT!
    # source: protobuf_test.proto
    """Generated protocol buffer code."""
    from google.protobuf import descriptor as _descriptor
    from google.protobuf import message as _message
    from google.protobuf import reflection as _reflection
    from google.protobuf import symbol_database as _symbol_database
    # @@protoc_insertion_point(imports)
    
    _sym_db = _symbol_database.Default()
    
    
    
    
    DESCRIPTOR = _descriptor.FileDescriptor(
      name='protobuf_test.proto',
      package='',
      syntax='proto3',
      serialized_options=None,
      create_key=_descriptor._internal_create_key,
      serialized_pb=b'\n\x13protobuf_test.proto\"#\n\x06Person\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0b\n\x03\x61ge\x18\x02 \x01(\x05\x62\x06proto3'
    )
    
    
    
    
    _PERSON = _descriptor.Descriptor(
      name='Person',
      full_name='Person',
      filename=None,
      file=DESCRIPTOR,
      containing_type=None,
      create_key=_descriptor._internal_create_key,
      fields=[
        _descriptor.FieldDescriptor(
          name='name', full_name='Person.name', index=0,
          number=1, type=9, cpp_type=9, label=1,
          has_default_value=False, default_value=b"".decode('utf-8'),
          message_type=None, enum_type=None, containing_type=None,
          is_extension=False, extension_scope=None,
          serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
        _descriptor.FieldDescriptor(
          name='age', full_name='Person.age', index=1,
          number=2, type=5, cpp_type=1, label=1,
          has_default_value=False, default_value=0,
          message_type=None, enum_type=None, containing_type=None,
          is_extension=False, extension_scope=None,
          serialized_options=None, file=DESCRIPTOR,  create_key=_descriptor._internal_create_key),
      ],
      extensions=[
      ],
      nested_types=[],
      enum_types=[
      ],
      serialized_options=None,
      is_extendable=False,
      syntax='proto3',
      extension_ranges=[],
      oneofs=[
      ],
      serialized_start=23,
      serialized_end=58,
    )
    
    DESCRIPTOR.message_types_by_name['Person'] = _PERSON
    _sym_db.RegisterFileDescriptor(DESCRIPTOR)
    
    Person = _reflection.GeneratedProtocolMessageType('Person', (_message.Message,), {
      'DESCRIPTOR' : _PERSON,
      '__module__' : 'protobuf_test_pb2'
      # @@protoc_insertion_point(class_scope:Person)
      })
    _sym_db.RegisterMessage(Person)
    
    
    # @@protoc_insertion_point(module_scope)
    View Code

    此时可以使用生成的源码文件进行序列化和反序列化数据:

    import protobuf_test_pb2
    
    # 序列化
    request = protobuf_test_pb2.Person()
    request.name = "barry"
    request.age = 20
    req_str = request.SerializeToString()
    print(req_str)
    
    # 反序列化
    res = protobuf_test_pb2.Person()
    res.ParseFromString(req_str)
    print(res)

    二、数据类型

    1、基本数据类型

    proto类型

    说明

    Python类型

    Go类型

    double

     

    float

    float64

    float

     

    float

    float32

    int32

    使用变长编码,对于负值的效率很低,如果你的域有可能有负值,请使用sint64替代

    int

    int32

    uint32

    使用变长编码

    int

    uint32

    uint64

    使用变长编码

    int

    uint64

    sint32

    使用变长编码,这些编码在负值时比int32高效的多

    int

    int32

    sint64

    使用变长编码,有符号的整型值。编码时比通常的int64高效。

    int

    int64

    fixed32

    总是4个字节,如果数值总是比总是比228大的话,这个类型会比uint32高效。

    int

    uint32

    fixed64

    总是8个字节,如果数值总是比总是比256大的话,这个类型会比uint64高效。

    int

    uint64

    sfixed32

    总是4个字节

    int

    int32

    sfixed64

    总是8个字节

    int

    int64

    bool

     

    bool

    bool

    string

    一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。

    str

    string

    bytes

    可能包含任意顺序的字节数据。

    str

    []byte

     上面是proto中的类型以及通过proto生成的Python或者Go语言源码对应的数据类型,那么如果不给对应的数据类型默认值,就会有一个默认值:

    • string 默认为空
    • bytes默认是一个空的bytes
    • bool默认是false
    • int系列默认是0
    • 枚举默认是第一个定义的枚举值,必须是0

    2、 其它数据类型

    2.1 枚举类型

    syntax = "proto3"; // 说明使用的是proto3版本
    
    enum Gender {
      MALE = 0;
      FEMALE = 1;
    }
    message Person {
      string name = 1; // 定义name字段,类型是string,数字1表明指定的编号
      int32 age = 2; // 定义age字段,类型是2,数字2表明指定的编号
      Gender gender = 3; // 使用枚举类型
    }

    枚举的第一个常量映射为0:每个枚举类型必须将其第一个类型映射为0,这是因为:

    • 必须有有一个0值,我们可以用这个0值作为默认值。
    • 这个零值必须为第一个元素,为了兼容proto2语义,枚举类的第一个值总是默认值。

    通过命令行生成Python源码:

    python -m grpc_tools.protoc --python_out=. -I. protobuf_test.proto

    然后进行使用:

    import protobuf_test_pb2
    
    # 序列化
    request = protobuf_test_pb2.Person()
    ...
    request.gender = protobuf_test_pb2.MALE
    req_str = request.SerializeToString()
    print(req_str)

    2.2 map类型

    syntax = "proto3"; // 说明使用的是proto3版本
    
    enum Gender {
      MALE = 0;
      FEMALE = 1;
    }
    message Person {
      string name = 1; // 定义name字段,类型是string,数字1表明指定的编号
      int32 age = 2; // 定义age字段,类型是2,数字2表明指定的编号
      Gender gender = 3; // 使用枚举类型
      map<string, string> hobby = 4; // 使用map类型
    }

    map类型map<string, string> map_field,第一个string是key类型,第二个string是value类型,map_field是字段名称。

    通过命令行生成Python源码:

    python -m grpc_tools.protoc --python_out=. -I. protobuf_test.proto

    然后进行使用:

    import protobuf_test_pb2
    
    # 序列化
    request = protobuf_test_pb2.Person()
    ...
    request.hobby["hobby1"] = "music"
    request.hobby["hobby2"] = "book"
    req_str = request.SerializeToString()
    print(req_str)

    三、进阶使用

    1、message嵌套使用

    syntax = "proto3"; // 说明使用的是proto3版本
    
    message ShopCar {
      int32 id = 1;
    
      message Goods {
        int32 id = 1;
        string name = 2;
      }
    
      repeated Goods data = 2; // 说明嵌套的Good对象可以有多个
    }

    通过命令行生成Python源码:

    python -m grpc_tools.protoc --python_out=. -I. protobuf_test.proto

    然后进行使用:

    import protobuf_test_pb2
    
    shop_car = protobuf_test_pb2.ShopCar()
    
    # 序列化
    shop_car.id = 1
    for i in range(2):
        goods = shop_car.Goods()
        goods.id = 1
        goods.name = f"苹果{i}"
        shop_car.data.extend([goods])
    req_str = shop_car.SerializeToString()
    print(req_str)
    
    # 反序列化
    res = protobuf_test_pb2.ShopCar()
    res.ParseFromString(req_str)
    print(res)

    2、go_package的作用

    对于go_package对于Go语言来说是起作用的,指明了当前proto文件生成的go源码在什么包下:

    syntax = "proto3"; // 说明使用的是proto3版本
    option go_package = "common/v1"; // 表示当前输出的go代码放到的包的位置,注意这个地方需要至少带”/“,否则报错
    
    message Person {
      string name = 1; // 定义name字段,类型是string,数字1表明指定的编号
      int32 age = 2; // 定义age字段,类型是2,数字2表明指定的编号
    }

    如果需要转成go语言源码需要先安装对应的工具:

    • protoc工具

    下载对应平台的protoc工具,这里我选择下载windows系统64位protoc-3.19.4-win64.zip,然后将其解压后并将其配置在系统环境变量中:

    C:\Program Files\protoc-3.19.4-win64\bin
    • go依赖包
    go get github.com/golang/protobuf/protoc-gen-go

    此时可以将protobuf转成Go语言代码:

    protoc -I . protobuf_test.proto --go_out=.

    进入到proto文件所在目录,其中protoc命令就是我们安装的protoc工具,-I是import的意思,将后面 "."当前目录下的proto文件导入,后面的--go_out将proto文件转成go源码导出到"."当期目录下,此时会调用protoc-gen-go.exe命令,这就是我们安装的go依赖,这也就意味着protoc-gen-go下载的可执行文件protoc-gen-go.exe配置到环境变量中,或者放到go sdk的bin目录下即可。

    3、proto文件的互相引入

     如果一个proto文件被多个proto文件共用,那么可以通过导入的方式被其它文件使用,比如:

    • base.proto
    syntax = "proto3";
    
    option go_package="common/v1";
    
    message Empty {
    
    }
    • protobuf_test.proto
    syntax = "proto3"; // 说明使用的是proto3版本
    import "base.proto"; // 引入其它proto文件
    option go_package = "common/v1"; // 表示当前目录下proto类型的文件
    
    message Person {
      string name = 1; // 定义name字段,类型是string,数字1表明指定的编号
      int32 age = 2; // 定义age字段,类型是2,数字2表明指定的编号
      repeated Empty empty = 3; //使用其它proto文件中定义的message
    }
  • 相关阅读:
    浅析如何保证vuex中的state动态添加属性的响应式及解决deep watch / computed监听vuex state对象属性变化不生效的问题
    浅析security遇到java.lang.IllegalArgumentException:Cannot pass null or empty values to constructor问题处理
    Java AES加解密报错javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher的问题原因及2种解决方式
    浅析Vue3中如何通过vmodel实现父子组件的双向数据绑定及利用computed简化父子组件双向绑定
    浅析pdfbox将pdf文件转图片报错Cannot read JPEG2000 image的问题及JPEG与JPEG2000介绍了解
    浅析Vue3中vuex的基本使用、模块化及如何使用mapState/mapGetters和mapActions
    浅析Object.assign()基本用法(对象合并、同名属性覆盖、仅1个参数时直接返回、target不是对象会转成对象、源对象位置为非对象时不同的处理规则字符串的特殊情况、拷贝的属性限制)及需要注意的点(浅拷贝、同名属性替换、数组的处理把索引当属性替换、取值函数先取值再拷贝)和常见应用(给对象添加属性、合并多个对象、给属性设置默认值)
    浅析setup如何通过ref获取子组件实例中的DOM结构/数据/方法及获取子组件实例数据都是空的处理(defineExpose API 的使用)、Vue3模板引用refs、在组合式API中使用template refs、for循环中如何获取及重置refs、如何监听模板引用
    解决uniapp ios播放本地视频不显示controls的问题、uniapp video开始播放如何设置默认全屏
    Deep learning:三十四(用NN实现数据的降维)
  • 原文地址:https://www.cnblogs.com/shenjianping/p/15864315.html
Copyright © 2020-2023  润新知