写点什么

protobuf 详解

  • 2023-07-06
    福建
  • 本文字数:5401 字

    阅读完需:约 18 分钟

protobuf 概述


protobuf 简介


Protobuf 是 Protocol Buffers 的简称,它是 Google 公司开发的一种数据描述语言,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化 。它很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。目前提供了 C++、Java、Python 三种语言的 API。获取地址:http://www.jnpfsoft.com/?from=infoq


  1. protobuf 是类似与 json 一样的数据描述语言(数据格式)

  2. protobuf 非常适合于 RPC 数据交换格式


注意:protobuf本身并不是和gRPC绑定的。它也可以被用于非 RPC 场景,如存储等


protobuf 的优劣势


1)优势:


  1. 序列化后体积相比 Json 和 XML 很小,适合网络传输

  2. 序列化反序列化速度很快,快于 Json 的处理速度

  3. 消息格式升级和兼容性还不错

  4. 支持跨平台多语言


2)劣势:


  1. 应用不够广(相比 xml 和 json)

  2. 二进制格式导致可读性差

  3. 缺乏自描述


protoc 安装(windows)


protoc 就是 protobuf 的编译器,它把 proto 文件编译成不同的语言


下载安装 protoc 编译器(protoc)


下载 protobuf:https://github.com/protocolbuffers/protobuf/releases/download/v3.20.1/protoc-3.20.1-win64.zip


解压后,将目录中的 bin 目录的路径添加到系统环境变量,然后打开 cmd 输入protoc查看输出信息,此时则安装成功


安装 protocbuf 的 go 插件(protoc-gen-go)


由于 protobuf 并没直接支持 go 语言需要我们手动安装相关插件


protocol buffer 编译器需要一个插件来根据提供的 proto 文件生成 Go 代码,Go1.16+要使用下面的命令安装插件:


go install google.golang.org/protobuf/cmd/protoc-gen-go@latest  // 目前最新版是v1.3.0
复制代码


安装 grpc(grpc)


 go get -u -v google.golang.org/grpc@latest    // 目前最新版是v1.53.0
复制代码


安装 grpc 的 go 插件(protoc-gen-go-grpc)


说明:在google.golang.org/protobuf中,protoc-gen-go纯粹用来生成 pb 序列化相关的文件,不再承载 gRPC 代码生成功能,所以如果要生成 grpc 相关的代码需要安装 grpc-go 相关的插件:protoc-gen-go-grpc


 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest  // 目前最新版是v1.3.0
复制代码


protobuf 语法



protobuf 语法


  • 类型:类型不仅可以是标量类型(intstring等),也可以是复合类型(enum等),也可以是其他message

  • 字段名:字段名比较推荐的是使用下划线/分隔名称

  • 字段编号:一个 message 内每一个字段编号都必须唯一的,在编码后其实传递的是这个编号而不是字段名

  • 字段规则:消息字段可以是以下字段之一

singular:格式正确的消息可以有零个或一个字段(但不能超过一个)。使用 proto3 语法时,如果未为给定字段指定其他字段规则,则这是默认字段规则

optional:与 singular 相同,不过可以检查该值是否明确设置

repeated:在格式正确的消息中,此字段类型可以重复零次或多次。系统会保留重复值的顺序

map:这是一个成对的键值对字段


  • 保留字段:为了避免再次使用到已移除的字段可以设定保留字段。如果任何未来用户尝试使用这些字段标识符,编译器就会报错


简单语法


proto 文件基本语法


 syntax = "proto3";              // 指定版本信息,不指定会报错 package pb;                     // 后期生成go文件的包名 // message为关键字,作用为定义一种消息类型 message Person{     string name = 1;   // 名字     int32  age = 2 ;   // 年龄 } ​ enum test{     int32 age = 0; }
复制代码


protobuf 消息的定义(或者称为描述)通常都写在一个以 .proto 结尾的文件中:


  1. 第一行指定正在使用proto3语法:如果不这样做,协议缓冲区编译器将假定正在使用 proto2(这也必须是文件的第一个非空的非注释行)

  2. 第二行 package 指明当前是 pb 包(生成 go 文件之后和 Go 的包名保持一致)

  3. message 关键字定义一个 Person 消息体,类似于 go 语言中的结构体,是包含一系列类型数据的集合。

    许多标准的简单数据类型都可以作为字段类型,包括boolint32, floatdouble,和string

    也可以使用其他 message 类型作为字段类型。


在 message 中有一个字符串类型的 value 成员,该成员编码时用 1 代替名字。在 json 中是通过成员的名字来绑定对应的数据,但是 Protobuf 编码却是通过成员的唯一编号来绑定对应的数据,因此 Protobuf 编码后数据的体积会比较小,能够快速传输,缺点是不利于阅读。


message 常见的数据类型与 go 中类型对比



protobuff 语法进阶


message 嵌套


messsage 除了能放简单数据类型外,还能存放另外的 message 类型:


 syntax = "proto3";          // 指定版本信息,不指定会报错 package pb;                 // 后期生成go文件的包名 // message为关键字,作用为定义一种消息类型 message Person{     string name = 1;  // 名字     int32  age = 2 ;  // 年龄     // 定义一个message     message PhoneNumber {         string number = 1;         int64 type = 2;     }     PhoneNumber phone = 3; }
复制代码


message 成员编号,可以不从 1 开始,但是不能重复,不能使用 19000 - 19999


repeated 关键字


repeadted 关键字类似与 go 中的切片,编译之后对应的也是 go 的切片,用法如下:


 syntax = "proto3";              // 指定版本信息,不指定会报错 package pb;                     // 后期生成go文件的包名 // message为关键字,作用为定义一种消息类型 message Person{     string name = 1;   // 名字     int32  age = 2 ;   // 年龄     // 定义一个message     message PhoneNumber {         string number = 1;         int64 type = 2;     }     repeated PhoneNumber phone = 3; }
复制代码


默认值


解析数据时,如果编码的消息不包含特定的单数元素,则解析对象对象中的相应字段将设置为该字段的默认值


不同类型的默认值不同,具体如下:


  • 对于字符串,默认值为空字符串

  • 对于字节,默认值为空字节

  • 对于 bools,默认值为 false

  • 对于数字类型,默认值为零

  • 对于枚举,默认值是第一个定义的枚举值,该值必须为 0。

  • repeated 字段默认值是空列表

  • message 字段的默认值为空对象


enum 关键字


在定义消息类型时,可能会希望其中一个字段有一个预定义的值列表


比如说,电话号码字段有个类型,这个类型可以是,home,work,mobile


我们可以通过 enum 在消息定义中添加每个可能值的常量来非常简单的执行此操作。示例如下:


 syntax = "proto3";              // 指定版本信息,不指定会报错 package pb;                     // 后期生成go文件的包名 // message为关键字,作用为定义一种消息类型 message Person{     string name = 1;   // 名字     int32  age = 2 ;   // 年龄     // 定义一个message     message PhoneNumber {         string number = 1;         PhoneType type = 2;     }          repeated PhoneNumber phone = 3; } // enum为关键字,作用为定义一种枚举类型 enum PhoneType {     MOBILE = 0;     HOME = 1;     WORK = 2; }
复制代码


如上,enum 的第一个常量映射为 0,每个枚举定义必须包含一个映射到零的常量作为其第一个元素。这是因为:

  • 必须有一个零值,以便我们可以使用 0 作为数字默认值。

  • 零值必须是第一个元素,以便与 proto2 语义兼容,其中第一个枚举值始终是默认值。


enum 还可以为不同的枚举常量指定相同的值来定义别名。如果想要使用这个功能必须将allow_alias选项设置为 true,负责编译器将报错。示例如下:


 syntax = "proto3";              // 指定版本信息,不指定会报错 package pb;                     // 后期生成go文件的包名 // message为关键字,作用为定义一种消息类型 message Person{     string name = 1;   // 名字     int32  age = 2 ;   // 年龄     // 定义一个message     message PhoneNumber {         string number = 1;         PhoneType type = 2;     }     repeated PhoneNumber phone = 3; } // enum为关键字,作用为定义一种枚举类型 enum PhoneType {     // 如果不设置将报错     option allow_alias = true;     MOBILE = 0;     HOME = 1;     WORK = 2;     Personal = 2; }
复制代码


oneof 关键字


如果有一个包含许多字段的消息,并且最多只能同时设置其中的一个字段,则可以使用 oneof 功能,示例如下:


 message Person{     string name = 1; // 名字     int32  age = 2 ; // 年龄     //定义一个message     message PhoneNumber {         string number = 1;         PhoneType type = 2;     } ​     repeated PhoneNumber phone = 3;     oneof data{         string school = 5;         int32 score = 6;     } }
复制代码


定义 RPC 服务


如果需要将 message 与 RPC 一起使用,则可以在.proto文件中定义 RPC 服务接口,protobuf 编译器将根据你选择的语言生成 RPC 接口代码。示例如下:


 //定义RPC服务 service HelloService {     rpc Hello (Person)returns (Person); }
复制代码


注意:默认 protobuf 编译期间,不编译服务,如果要想让其编译,需要使用 gRPC


protobuf 编译


编译器调用


protobuf 编译是通过编译器 protoc 进行的,通过这个编译器,我们可以把 .proto 文件生成 go,Java,Python,C++, Ruby 或者 C# 代码


可以使用以下命令来通过 .proto 文件生成 go 代码(以及 grpc 代码)


 // 将当前目录中的所有 .proto文件进行编译生成go代码 protoc --go_out=./ --go_opt=paths=source_relative *.proto
复制代码


protobuf 编译器会把 .proto 文件编译成 .pd.go 文件


--go_out 参数


作用:指定 go 代码生成的基本路径


  1. protocol buffer 编译器会将生成的 Go 代码输出到命令行参数go_out指定的位置

  2. go_out标志的参数是你希望编译器编写 Go 输出的目录

  3. 编译器会为每个.proto 文件输入创建一个源文件

  4. 输出文件的名称是通过将.proto 扩展名替换为.pb.go 而创建的


--go_opt 参数


protoc-gen-go提供了--go_opt参数来为其指定参数,可以设置多个:


  1. paths=import:生成的文件会按go_package路径来生成,当然是在--go_out目录

  • 例如,go_out/$go_package/pb_filename.pb.go

  • 如果未指定路径标志,这就是默认输出模式


  1. paths=source_relative:输出文件与输入文件放在相同的目录中

  • 例如,一个protos/buzz.proto输入文件会产生一个位于protos/buzz.pb.go的输出文件。


  1. module=$PREFIX:输出文件放在以 Go 包的导入路径命名的目录中,但是从输出文件名中删除了指定的目录前缀。

  • 例如,输入文件 pros/buzz.proto,其导入路径为 example.com/project/protos/fizz 并指定example.com/projectmodule前缀,结果会产生一个名为 pros/fizz/buzz.pb.go 的输出文件。

  • 在 module 路径之外生成任何 Go 包都会导致错误,此模式对于将生成的文件直接输出到 Go 模块非常有用。

--proto_path 参数


--proto_path=IMPORT_PATH


  • IMPORT_PATH 是 .proto 文件所在的路径,如果忽略则默认当前目录。

  • 如果有多个目录则可以多次调用--proto_path,它们将会顺序的被访问并执行导入。


使用示例:


 protoc --proto_path=src --go_out=out --go_opt=paths=source_relative foo.proto bar/baz.proto // 编译器将从 `src` 目录中读取输入文件 `foo.proto` 和 `bar/baz.proto`,并将输出文件 `foo.pb.go` 和 `bar/baz.pb.go` 写入 `out` 目录。如果需要,编译器会自动创建嵌套的输出子目录,但不会创建输出目录本身
复制代码


使用 grpc 的 go 插件


安装 proto-gen-go-grpc


google.golang.org/protobuf中,protoc-gen-go纯粹用来生成 pb 序列化相关的文件,不再承载 gRPC 代码生成功能。生成 gRPC 相关代码需要安装 grpc-go 相关的插件protoc-gen-go-grpc


 // 安装protoc-gen-go-grpc go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest  // 目前最新版是v1.3.0
复制代码


生成 grpc 的 go 代码:


 // 主要是--go_grpc_out参数会生成go代码 protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative  *.proto
复制代码


--go-grpc_out 参数


作用:指定 grpc go 代码生成的基本路径


命令会产生的 go 文件:


  1. protoc-gen-go:包含所有类型的序列化和反序列化的 go 代码

  2. protoc-gen-go-grpc:包含 service 中的用来给 client 调用的接口定义以及 service 中的用来给服务端实现的接口定义


--go-grpc_opt 参数


protoc-gen-go类似,protoc-gen-go-grpc提供 --go-grpc_opt 来指定参数,并可以设置多个

github.com/golang/protobuf 和 google.golang.org/protobuf

github.com/golang/protobuf


  1. github.com/golang/protobuf 现在已经废弃

  2. 它可以同时生成 pb 和 gRPC 相关代码的


用法:

 // 它在--go_out加了plugin关键字,paths参数有两个选项,分别是 import 和 source_relative --go_out=plugins=grpc,paths=import:.  *.proto
复制代码

google.golang.org/protobuf


  1. github.com/golang/protobuf的升级版本,v1.4.0之后github.com/golang/protobuf仅是google.golang.org/protobuf的包装

  2. 它纯粹用来生成 pb 序列化相关的文件,不再承载 gRPC 代码生成功能,生成 gRPC 相关代码需要安装 grpc-go 相关的插件protoc-gen-go-grpc


用法:


 // 它额外添加了参数--go-grpc_out以调用protoc-gen-go-grpc插件生成grpc代码 protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative  *.proto
复制代码


文章转载自:Praywu

原文链接:https://www.cnblogs.com/hgzero/p/17240848.html

用户头像

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
protobuf 详解_protobuf_快乐非自愿限量之名_InfoQ写作社区