protobuf与其在go中的简单使用

以proto3为例,proto2语法:[看这里](Language Guide | Protocol Buffers | Google Developers)

基本语法

一个最简单的Message定义

syntax = "proto3";
package model;
option go_package = "protos/model";

message Student {
    int64 id = 1;
    string name = 2;
    int32 age = 3;
}
  • sytax语句必须在第一个非空非注释行,不写的话默认为"proto2"
  • 每个字段后的唯一不重复数字是在二进制格式中准确辨识各字段所需要的,该字段一经确认就不应该再修改, 这个数字最小为1

字段的规则

  • 默认的规则是singular,不需要显式地指出,它相当于proto2的optional+required,即该字段可以不出现,或者仅出现一次
  • repeated表明该字段可以重复出现容易次数,但是保证有序
  • 在一个.proto文件中可以定义多个Message
  • 注释规则和C/Java相同

Message的更新

  • 需要方法确保后续的更新不会导致旧Message的兼容性问题,这里引入的reserved关键词:

    message Student {
        reserved 1,2,3, 5 to 10;
        reserved "id", "name", "age";
    }
    
  • 这里告诉编译器保留1,2,3,5,6,7,8,9,10"id", "name", "age"这些值虽然现在被移除了,但是不可以被后续的更新再次指定,这确保了老的Message类型的兼容性

  • 比如这里后面再更新添加一个int64 grade = 1编译器就会报错

数据类型

看这里

默认值

如果解析的时候某个字段没有被指定特定的值,则会赋予其特定类型的默认值

  • 对于字符串类型,默认值为空串""
  • 对于bytes类型,在Go中就是字节数组,默认值为空字节数组
  • 对于布尔值,默认值为false
  • 对于数字类型,默认值为0
  • 对于枚举类型,默认值为第一个定义的枚举值
  • 对于内嵌的Message类型,默认值却决于不同的语言实现
  • 对于repeated的字段,默认值一般是一个空的列表

枚举类型的定义

message Student {
    reserved 10 to 15;
    int64 id = 1;
    string name = 2;
    int32 age = 3;
    /**枚举定义**/
    enum Gender {
        MALE = 0;
        FEMALE = 1;
        OTHER = 2;
    }
    Gender gender = 4;
}

枚举的第一项必须也只能是0

若需要对同一个枚举变量定义相同的值,需要在前面加上如下的选项

enum Gender {
	option allow_alias = true;
    MALE = 0;
    BOY = 0;
    FEMALE = 1;
    GIRL = 1;
    OTHER = 2;
}

这里必须有option这一行,要不然编译器会报错

引入另一个Message Type

message Student {
    reserved 10 to 15;
    int64 id = 1;
    string name = 2;
    int32 age = 3;
    enum Gender {
        option allow_alias = true;
        MALE = 0;
        BOY = 0;
        FEMALE = 1;
        GIRL = 1;
        OTHER = 2;
    }
    Gender gender = 4;
    // 内嵌别的Message Type
    repeated Pet pet = 5;
}

message Pet {
    string name = 1;
    enum PetType {
        DOG = 0;
        CAT = 1;
        BUNNY = 2;
    }
    PetType type = 2;
}

在一个.proto文件中引入另一个.proto文件中的定义

这在Java里无效

看这里

内嵌的类型

message Pet {
    string name = 1;
    enum PetType {
        DOG = 0;
        CAT = 1;
        BUNNY = 2;
    }
    PetType type = 2;
    // 内嵌的栖息地类型
    message Habitat {
        string name = 1;
        double area = 2;
    }
    Habitat habitat = 3;
}

这里内嵌类型并不局限于一层的内嵌,可以有多层内嵌

更新一个Message Type的注意事项

  • 不要改变已有字段的数字
  • 移除一个字段时要么指定reserved要么只是在这个字段前面加上OBSOLETE前缀以防出现兼容性问题

未知字段

所谓未知字段,就是更新后新添加的字段对于旧的MessageType而言是未知的

  • 在proto2和proto3.5之后的版本未知字段在序列化过程中是保留的
  • 在proto3 - proto3.5之间的版本,未知字段直接被忽略

Any类型的字段

可以在没有Message定义的情况下加入内嵌的自定义对象,类似于Java中实体包含了一个JSONObject类型的字段

使用前需要导入google/protobuf/any.proto这个.proto文件

import "google/protobuf/any.proto";
message Property {
    int64 id = 1;
    string name = 2;
    google.protobuf.Any detail = 3;
}

Oneof

oneof用来确保多个字段同时只有一个被赋值

message Property {
    oneof oneof_id_name {
        int64 id = 1;
        string name = 2;
    }
    google.protobuf.Any detail = 3;
}

这样只能同时设置id或者name中的一个字段的值

Maps

message Property {
    int64 id = 1;
    string name = 2;
    //Map定义
    map<string, string> detail = 3;
}
  • 键的类型不可以是浮点类型或者bytes类型
  • 值的类型可以是任意类型除了map类型本身

package protos.model;
  • 在C++中表现为命名空间的嵌套
  • 在Java中指定option java_package以生成对应Java包
  • 在Go中与Java类似,指定项为option go_package

package 指定的包是防止多个.proto之间互相引入产生命名冲突问题,option xx_package 则是指定生成特定语言的包结构

定义RPC服务

看这里

在Go中的使用

安装配置

  1. 下载对应的protoc:[链接](Releases · protocolbuffers/protobuf (github.com))

  2. 解压将bin目录配置到环境变量

  3. 在当前模块下获取protoc-gen-go

    go get google.golang.org/protobuf/cmd/protoc-gen-go
    

包的声明

  • 必须指定包名,可以在.proto文件中通过option go_package指定,也可以通过命令行的方式指定

编译.proto

protoc --go_out=$OUT_DIR $INPUT_DIR_OR_FILE
// For example:
protoc --go_out=. protos/model/*.proto

使用生成的结构

  1. 这里有一个简单的.proto文件,位于protobuf模块

    syntax = "proto3";
    package model;
    option go_package = "protos/model";
    
    message Student {
        int64 id = 1;
        string name = 2;
        int32 age = 3;
        enum Gender {
            option allow_alias = true;
            MALE = 0;
            BOY = 0;
            FEMALE = 1;
            GIRL = 1;
            OTHER = 2;
        }
        Gender gender = 4;
        message Phone {
            string model = 1;
            string brand = 2;
        }
        repeated Phone phones = 5;
        map<string,string> detail = 6;
    }
    
    
  2. 编译生成对应go文件后,引入对应的包

    package main
    
    import (
    	pb "protobuf/protos/model" // 生成的文件
    	"google.golang.org/protobuf/proto" // protobuf的工具包
    )
    
  3. 实例化一个定义的Message:Student

    stu := pb.Student{
        Id:     1001,
        Name:   "Alice",
        Age:    22,
        Gender: pb.Student_FEMALE,
        Phones: []*pb.Student_Phone{
            {Model: "iPhone12", Brand: "Apple.Inc"},
            {Model: "Mate40", Brand: "Huawei"},
            {Model: "S21", Brand: "Samsung"},
        },
        Detail: map[string]string{
            "身高": "180CM",
            "体重": "75KG",
            "爱好": "无",
        },
    }
    
  4. stu序列化为字节数组

    bufs, err := proto.Marshal(&stu)
    
  5. 从字节序列中读取Student

    var stu1 pb.Student
    proto.Unmarshal(bufs, &stu1)
    fmt.Println(&stu1)
    
  6. 输出结果

    image-20210721131610539

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值