当你第一次学习ProtoBuf时,这是你需要知道的一切

入门ProtoBuf:从零开始的序列化与反序列化

0.简介

protobuf

ProtoBuf是由Google开发的一种轻量级的数据序列化协议,其全称为Protocol Buffers。ProtoBuf是一种跨语言跨平台的数据交换格式,可以用于数据的存储和网络传输。ProtoBuf的主要特点包括:

  1. 简洁高效:ProtoBuf使用二进制编码,序列化后的数据非常紧凑,占用空间小,传输速度快。同时,ProtoBuf支持对数据进行压缩,进一步减小数据传输的开销。
  2. 可扩展:ProtoBuf支持对数据结构进行版本控制和扩展,可以方便地向已有的数据结构中添加新的字段和数据类型。
  3. 跨平台:ProtoBuf定义的数据结构可以被多种编程语言所解析和生成,例如Java、C++、Python、Go等,实现了不同语言之间的数据交换。
  4. 代码自动生成:通过ProtoBuf提供的编译器工具,可以根据ProtoBuf定义的数据结构自动生成对应的代码,大大简化了开发人员的工作。

总之,ProtoBuf是一种高效、可扩展、跨平台的数据序列化协议,广泛应用于分布式系统、网络通信、数据存储等场景。

1.安装

官方下载地址

注意,不同的电脑系统安装包是不一样的:

注意:我们需要将下载得到的可执行文件protoc所在的 bin 目录加到我们电脑的环境变量中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mFFpTrND-1678628859826)(https://gitee.com/xzxwbb/cloud-image/raw/master/img/image-20230312210450307.png)]

编译go语言的工具包

这个protoc可以将proto文件编译为任何语言的文件,想要编译为go语言的,还需要下载另外一个可执行文件

命令是这样的:

go install google.golang.org/ProtoBuf/cmd/protoc-gen-go@latest

或者

go get github.com/golang/protobuf/protoc-gen-go

win10下protoc-gen-go不是内外部命令的方案

  • 克隆protobuf的源码
git clone https://github.com/golang/protobuf

image-20230312210450307

  • 进入protobuf/protoc-gen-go目录并进入到cmd中

  • go build -o protoc-gen-go.exe main.go
    
  • 将protoc-gen-go.exe拷贝到windows/System32目录

2.简单入门

  1. 创建hello.proto

    syntax = "proto3";
    
    package hello;
    
    option go_package = "./;hello";
    
    message Say{
      int64           id    = 1;
      string          hello = 2;
      repeated string word  = 3;
    }
    
  2. 在hello.proto所在目录下运行protoc命令

    protoc --go_out=. hello.proto
    
  3. 生成hello.pb.goimage-20230312211353997

3.进阶

关键字

  1. message:定义一个消息类型,即一组相关的字段。
  2. field:定义消息类型中的一个字段,包括字段的名称、编号和类型。
  3. enum:定义一个枚举类型,用于在一组有限的值中进行选择。
  4. option:为ProtoBuf编译器提供指令,用于控制如何生成代码。
  5. package:定义一组相关消息类型的命名空间。
  6. import:引用其他ProtoBuf文件中定义的消息类型。
  7. oneof:定义一组互斥的字段,只能设置其中的一个字段。
  8. repeated:定义一个字段可以包含多个值。
  9. map:定义一个映射类型,可以将一组键值对映射到另一组值。
  10. service:定义一个RPC服务,包括一组RPC方法和其参数。

以下是一个简单的ProtoBuf消息类型定义示例,展示了上述关键字的用法:

syntax = "proto3";

package example;

// 定义一个枚举类型
enum Fruit {
  APPLE = 0;
  BANANA = 1;
  ORANGE = 2;
}

// 定义一个消息类型
message FruitBasket {
  string owner = 1;
  repeated Fruit fruits = 2;
  map<string, int32> fruit_counts = 3;
  oneof favorite_fruit {
    Fruit favorite = 4;
    string favorite_other = 5;
  }
}

// 定义一个RPC服务
service FruitService {
  rpc GetFruitBasket (FruitBasketRequest) returns (FruitBasket);
}

message FruitBasketRequest {
  string owner = 1;
}

上面的代码定义了一个名为“FruitBasket”的消息类型,它包含了一些字段:

  • owner:一个字符串类型的字段,编号为1。
  • fruits:一个枚举类型的重复字段,编号为2。
  • fruit_counts:一个映射类型的字段,将字符串键映射到32位整数值,编号为3。
  • favoritefavorite_other:一组互斥的字段,只能设置其中一个,分别为枚举类型Fruit的值或字符串类型的值,编号分别为4和5。

该代码还定义了一个名为“FruitService”的RPC服务,其中包括一个名为“GetFruitBasket”的方法,该方法接受一个“FruitBasketRequest”消息类型的参数,并返回一个“FruitBasket”消息类型的响应。

分配标识号

在ProtoBuf中,每个字段都需要分配一个唯一的编号(tag),用于标识该字段。该编号用于在消息二进制编码期间标识字段,以便解析器可以识别和处理消息中的字段。

这些编号的使用可以使ProtoBuf消息具有更小的尺寸、更快的解析速度和更好的向后兼容性。由于ProtoBuf编码是紧凑的二进制格式,字段的编号将直接存储在编码数据流中,这意味着即使字段顺序发生变化,解析器仍然可以正确识别和解析数据。

在ProtoBuf中,字段的编号必须为正整数,且不能重复或跳过。通常,推荐将较小的编号分配给消息中经常使用的字段,以减小消息的尺寸和解析时间。编号的范围为1到536,870,911(在proto3语法中)或1到2^29-1(在proto2语法中)。

示例代码中,owner字段的编号为1,fruits字段的编号为2,fruit_counts字段的编号为3,favorite字段的编号为4,favorite_other字段的编号为5。在消息编码过程中,这些编号将用于标识和定位这些字段,以便能够准确地解析和构造ProtoBuf消息。

字段类型

在ProtoBuf中,每个字段都必须指定一种类型。

在Go语言中,可以使用proto包来生成和解析ProtoBuf消息。该包将ProtoBuf消息的字段类型映射到Go语言中的数据类型。

以下是常见的ProtoBuf字段类型和它们在Go语言中的映射,同时附有每种类型的含义:

ProtoBuf类型Go语言类型含义
doublefloat6464位浮点数
floatfloat3232位浮点数
int32int3232位有符号整数
int64int6464位有符号整数
uint32uint3232位无符号整数
uint64uint6464位无符号整数
sint32int3232位有符号整数,使用可变长度编码
sint64int6464位有符号整数,使用可变长度编码
fixed32uint3232位无符号整数,使用固定长度编码
fixed64uint6464位无符号整数,使用固定长度编码
sfixed32int3232位有符号整数,使用固定长度编码
sfixed64int6464位有符号整数,使用固定长度编码
boolbool布尔类型
stringstringUTF-8编码的字符串
bytes[]byte字节数组
enum枚举类型枚举类型
message消息类型消息类型
repeated切片类型重复的字段,可以包含多个值
mapmap类型映射类型,将一组键值对映射到另一组值

需要注意的是,由于ProtoBuf和Go语言中的数据类型有些细微差别,因此在处理某些类型时可能需要进行一些类型转换。

protoc的常用命令

  • --proto_path:指定ProtoBuf文件的搜索路径,可以指定多个路径。
  • --proto_path=:清空默认的ProtoBuf文件搜索路径。
  • --go_out:将ProtoBuf文件编译成Go语言的源代码文件。该选项后面需要指定生成的源代码文件的输出目录。
  • --java_out:将ProtoBuf文件编译成Java语言的源代码文件。该选项后面需要指定生成的源代码文件的输出目录。
  • --python_out:将ProtoBuf文件编译成Python语言的源代码文件。该选项后面需要指定生成的源代码文件的输出目录。
  • --cpp_out:将ProtoBuf文件编译成C++语言的源代码文件。该选项后面需要指定生成的源代码文件的输出目录。
  • --csharp_out:将ProtoBuf文件编译成C#语言的源代码文件。该选项后面需要指定生成的源代码文件的输出目录。
  • --objc_out:将ProtoBuf文件编译成Objective-C语言的源代码文件。该选项后面需要指定生成的源代码文件的输出目录。
  • --grpc_out:将ProtoBuf文件编译成支持gRPC的源代码文件。该选项后面需要指定生成的源代码文件的输出目录。
  • --plugin:指定一个插件来处理生成的源代码文件。该选项后面需要指定插件的路径和文件名。
  • --descriptor_set_out:将ProtoBuf文件编译成二进制的ProtoBuf描述符文件。该选项后面需要指定输出的文件名和路径。

这些选项中,最常用的是--proto_path--[language]_out。使用这些选项,可以将ProtoBuf文件编译成多种不同语言的源代码文件,并且可以通过--proto_path选项来指定ProtoBuf文件的搜索路径,以便protoc能够找到需要编译的文件。

枚举类型

创建enum.proto

syntax = "proto3";//指定版本信息,非注释的第一行

enum SexType //枚举消息类型,使用enum关键词定义,一个性别类型的枚举类型
{
    UNKONW = 0; //proto3版本中,首成员必须为0,成员不应有相同的值
    MALE = 1;  //1男
    FEMALE = 2; //2女  0未知
}

// 定义一个用户消息
message UserInfo
{
    string name = 1; // 姓名字段
    SexType sex = 2; // 性别字段,使用SexType枚举类型
}

生成.pb.go

protoc --go_out=. enum.proto

消息嵌套

外部引用

// 定义Article消息
message Article {
  string url = 1;
  string title = 2;
  repeated string tags = 3; // 字符串数组类型
}

// 定义ListArticle消息
message ListArticle {
  // 引用上面定义的Article消息类型,作为results字段的类型
  repeated Article articles = 1; // repeated关键词标记,说明articles字段是一个数组
}

内部嵌套

message ListArticle {
  // 嵌套消息定义
  message Article {
    string url = 1;
    string title = 2;
    repeated string tags = 3;
  }
  // 引用嵌套的消息定义
  repeated Article articles = 1;
}

import导入其他proto文件定义的消息

创建article.proto

syntax = "proto3";

package nesting;

option go_package = "./;article";

message Article {
  string          url   = 1;
  string          title = 2;
  repeated string tags  = 3; // 字符串数组类型
}

创建list_article.proto

syntax = "proto3";
// 导入Article消息定义
import "article.proto";

package nesting;

option go_package = "./;article";

// 定义ListArticle消息
message ListArticle {
  // 使用导入的Result消息
  repeated Article articles = 1;
}

生成.pb.go

protoc --go_out=. article.proto
protoc --go_out=. list_article.proto

map类型

创建score.proto

syntax = "proto3";

package map;

option go_package = "./;score";

message Student{
  int64              id    = 1; //id
  string             name  = 2; //学生姓名
  map<string, int32> score = 3;  //学科 分数的map
}

生成.pb.go

protoc --go_out=. score.proto

实战

  1. 创建study_info.proto
syntax = "proto3";

package demo;

option go_package = "./;study";

message StudyInfo {
  int64              id       = 1; //id
  string             name     = 2; //学习的科目名称
  int32              duration = 3; //学习的时长 单位秒
  map<string, int32> score    = 4; //学习的分数
}
  1. 生成.pb.go
protoc --go_out=. study_info.proto 
  1. 编写go文件,读取ProtoBuf中定义的字段,进行赋值,取值,转成结构体等操作

proto编码和解码的操作和json是非常像的,都使用“Marshal”和“Unmarshal”关键字

package main

import (
   "fmt"
   "google.golang.org/ProtoBuf/proto"
   study "juejin/ProtoBuf/proto/demo"
)

func main() {
   // 初始化proto中的消息
   studyInfo := &study.StudyInfo{}

   //常规赋值
   studyInfo.Id = 1
   studyInfo.Name = "学习ProtoBuf"
   studyInfo.Duration = 180

   //在go中声明实例化map赋值给ProtoBuf消息中定义的map
   score := make(map[string]int32)
   score["实战"] = 100
   studyInfo.Score = score

   //用字符串的方式:打印ProtoBuf消息
   fmt.Printf("字符串输出结果:%v\n", studyInfo.String())

   //转成二进制文件
   marshal, err := proto.Marshal(studyInfo)
   if err != nil {
      return
   }
   fmt.Printf("Marshal转成二进制结果:%v\n", marshal)

   //将二进制文件转成结构体
   newStudyInfo := study.StudyInfo{}
   err = proto.Unmarshal(marshal, &newStudyInfo)
   if err != nil {
      return
   }
   fmt.Printf("二进制转成结构体的结果:%v\n", &newStudyInfo)
}

控制台输出

字符串输出结果: id:1 name:"学习ProtoBuf" duration:180 score:{key:"实战" value:100}
Marshal转化二进制结果:[8 1 18 14 229 173 166 228 185 160 80 114 111 116 111 66 117 102 24 180 1 34 10 10 6 229 174 158 230 136 152 16 100]
二进制转化为结构体的结果:id:1 name:"学习ProtoBuf" duration:180 score:{key:"实战" value:100}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奥库甘道夫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值