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

2023-10-29

入门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语言类型 含义
double float64 64位浮点数
float float32 32位浮点数
int32 int32 32位有符号整数
int64 int64 64位有符号整数
uint32 uint32 32位无符号整数
uint64 uint64 64位无符号整数
sint32 int32 32位有符号整数,使用可变长度编码
sint64 int64 64位有符号整数,使用可变长度编码
fixed32 uint32 32位无符号整数,使用固定长度编码
fixed64 uint64 64位无符号整数,使用固定长度编码
sfixed32 int32 32位有符号整数,使用固定长度编码
sfixed64 int64 64位有符号整数,使用固定长度编码
bool bool 布尔类型
string string UTF-8编码的字符串
bytes []byte 字节数组
enum 枚举类型 枚举类型
message 消息类型 消息类型
repeated 切片类型 重复的字段,可以包含多个值
map map类型 映射类型,将一组键值对映射到另一组值

需要注意的是,由于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}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • go中有memset的类似物吗?

    在 C 中 我可以使用某些值初始化数组memset https msdn microsoft com en us library aa246471 28v vs 60 29 aspx const int MAX 1000000 int is
  • 如何将 SQLite 数据库捆绑到 Go 二进制文件中?

    我尝试使用 go bindata 和 packr 但这些包没有显示如何将 SQLite 数据库文件打包到二进制文件中 我不需要以任何方式更新数据库 我只想在启动时从中读取数据 如何将 SQLite 数据库文件嵌入到 Go 二进制文件中 SQ
  • 正确的文件扩展名或缩写是什么。 golang 的文本/模板?

    我正在考虑为其创建语法荧光笔 但我不知道这种特定类型模板的常规缩写 In 例子之一 http golang org pkg text template example Template helpers从文本 模板 godoc 中 它们引用
  • Golang:带有 JSON 负载的 http.NewRequest POST 返回错误 500

    我正在开发一个 API 库 有一个API端点 POST 当您发出curl命令时 它是 curl H X API TOKEN API TOKEN http interest graph getprismatic com text topic
  • 将 time.Time 转换为字符串

    我正在尝试将数据库中的一些值添加到 string在围棋中 其中一些是时间戳 我收到错误 无法在数组元素中使用 U Created date 类型 time Time 作为类型字符串 我可以转换吗time Time to string typ
  • os.Mkdir 和 os.MkdirAll 权限

    我正在尝试在程序开始时创建一个日志文件 我需要检查是否 log如果不创建目录 则目录存在 然后继续创建日志文件 好吧 我尝试使用os Mkdir 也os MkdirAll 但无论我在第二个参数中输入什么值 我都会得到一个没有权限的锁定文件夹
  • 如何同时使用 LoadHTMLGlob 和 LoadHTMLFiles

    我想要来自不同子目录的分隔符逻辑模板templates文件夹 下面是我的templates文件夹 templates authentication login gohtml logout gohtml index gohtml profil
  • 如何仅在测试时允许一个包访问另一个包的未导出数据?

    In Go 编程语言 第 11 2 4 节 有一个外部测试访问的示例fmt isSpace 通过声明IsSpace in fmt s export test go文件 这似乎是完美的解决方案 所以这就是我所做的 a a go package
  • 在 Visual Studio Code 中调试 Go 测试

    在我的 Windows 计算机上 我安装了 Visual Studio Code 要手动运行测试 我进入控制台到项目文件夹并输入 go test main test go 它工作完美 但我遇到一种情况 我需要调试我的测试以了解发生了什么 为
  • 如何将接口转换为接口切片?

    我的输入是interface 而且我知道它可以是任何类型的数组 我想读取我输入的元素之一 所以我尝试将我的interface 进入一个 interface 但是 go 会给我以下错误 恐慌 接口转换 interface 是 map stri
  • 如何将所有GET请求查询参数放入Go中的结构体中?

    你好 我想将 get 查询参数转换为 Go 中的结构 例如我有这样的结构 type Filter struct Offset int64 json offset Limit int64 json limit SortBy string js
  • 如何在 Go 中填写 void* C 指针?

    我正在尝试与 Go 中的一些 C 代码交互 使用 cgo 这一直相对简单 直到我遇到这种 相当常见 的情况 需要将指针传递给本身包含指向某些数据的指针的结构 我似乎无法弄清楚如何从 Go 中做到这一点 而不诉诸于将结构的创建放入 C 代码本
  • 将产生 goroutine 的 golang 方法

    据我所知 如果 goroutine 太忙 它们会阻止其他 goroutine 运行 对我来说 这意味着我的应用程序的性能和响应能力可能取决于我知道哪些库方法将控制其他 goroutine 例如通常是 Read 和 Write 有什么方法可以
  • Facebook服务器端登录、CORS

    我正在实现一个带有 FB 服务器端登录的网站 简化步骤如下 一个简单的按钮触发 JS 脚本 该脚本调用我的后端 APIhttps localhost fblogin function sendFbLoginData get https lo
  • container_memory_working_set_bytes 与 process_resident_memory_bytes 和total_rss 之间的关系

    我希望了解以下关系 容器内存工作集字节 vs 进程驻留内存字节 vs 总计RSS 容器内存 rss 文件映射以便更好地配备OOM可能性警报系统 这似乎违背了我的理解 这让我现在感到困惑 如果容器 pod 运行单个进程 执行用 Go 编写的编
  • 我们如何在 Go 中使用通道来代替互斥锁?

    通道将通信 值的交换 与同步相结合 保证两个计算 goroutine 处于已知状态 如何使用 Google Go 中的通道来执行互斥量的功能 package main import sync var global int 0 var m s
  • 使用泛型:类型参数 T 不能与 == 进行比较

    我正在操场上玩 Go Generics 尝试编写一些通用数组函数 https gotipplay golang org p vS7f Vxxy2j https gotipplay golang org p vS7f Vxxy2j packa
  • pprof 和 ps 之间的内存使用差异

    我一直在尝试分析用 cobra 构建的 cli 工具的堆使用情况 这pprof工具显示如下 Flat Flat Sum Cum Cum Name Inlined 1 58GB 49 98 49 98 1 58GB 49 98 os Read
  • “http:多个response.WriteHeader调用”有什么不好的影响?

    尽管我发现 http 多个响应 WriteHeader 调用 例外 但我的服务器表现良好 此异常不会导致我的服务器出现恐慌或行为异常 我进行了很多搜索 但只找到了如何解决这个问题 没有文档描述异常的不良影响 有人可以帮我找出为什么 http
  • 如何使用 go1.6.2 构建 linux 32 位

    有没有任何组合GOARCH and GOOS我可以设置哪些值来构建 ELF 32 位二进制文 件 GOOS linux and GOARCH 386 更多示例 架构 32 bit gt GOARCH 386 64 bit gt GOARCH

随机推荐