自己学习Protobuf的整理
自己学习Protobuf的整理
前言
本篇文章是我自己在学习Protobuf时做的整理。如有错误,还望告知,谢谢。
环境
Windows10
Unity2022.3.8f1c1 il2Cpp .Net FrameWork
Google.Protobuf 3.27.1
Protobuf文件写法的简单说明
下列说明以protobuf csharp教程中的内容为例子说明。
我们创建一个以.proto
为后缀名的文件。并填入以下示例内容:
1 | syntax = "proto3"; |
所有的Protobuf
文件都以syntax
作为开头。syntax
关键字用于指定.proto
文件所使用的Protobuf
语法版本。现在支持proto2
和proto3
。proto2
和proto3
之间有一些区别,但是因为我项目使用的是proto3
,所以下文大多以proto3
为主。特别注意的是在.proto
文件中如果你不注明所用的syntax
是哪个,那么第一行必须是非空或非注释行。这是在语言指导中明确指出的,下面是文章原文:
1 | The first line of the file specifies that you’re using proto3 syntax: if you don’t do this the protocol buffer compiler will assume you are using proto2. This must be the first non-empty, non-comment line of the file. |
比如你的文件是下面这样的写法
1 | // 我就是要注释 |
那么你会得到FirstLine.proto:5:5: Expected "required", "optional", or "repeated".
(FirstLine.proto
是我的.proto
文件名)的提示。既然这里展现出了message
,那么这里我就将message
和package
一起讲述package
等同于CSharp中的namespace
,使用工具生成CSharp代码后,package
会变成下面这样:
1 | namespace tutorial |
而message
就像是CSharp中Class
的作用。而我们使用工具生成的代码就会像下面这样:
1 | [ ] |
而message FirstLine
中的string
则是Protobuf
支持的数据类型。当然Protobuf
支持的数据类型也是很多的。
Protobuf文件支持的数据类型
Protobuf
所有支持的类型都在其官方文档——Language Guide
(proto 3)中存在说明。(如果你是用proto 2,则其文档为:Language Guide
(proto 2))
PS:以下英文部分翻译均为机翻,如果你觉得不好请查看上面的文档
- Scalar Value Types(标量值类型)
proto的类型 | 注释 | c++类型 | Java/Kotlin类型 | Python类型 | Go类型 | Ruby类型 | C#类型 | Dart类型 |
---|---|---|---|---|---|---|---|---|
double | double | double | float | *float64 | Float | double | double | |
float | float | float | float | *float32 | Float | float | float | |
int32 | 其使用变长编码,对负数进行编码效率低下。如果您的字段可能有负值,请使用sint32类型。 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | *int32 |
int64 | 其使用变长编码,对负数进行编码效率低下。如果您的字段可能有负值,请使用sint32类型。 | int64 | long | int/long | *int64 | Bignum | long | Int64 |
uint32 | 其使用变长编码。 | uint32 | int | int/long | *uint32 | Fixnum or Bignum (as required) | uint | int |
uint64 | 其使用变长编码。 | uint64 | long | int/long | *uint64 | Bignum | ulong | Int64 |
sint32 | 其使用变长编码,是带符号整型值。它们比普通int32更有效地编码负数。 | int32 | int | int | int32 | Fixnum or Bignum (as required) | int | *int32 |
sint64 | 其使用变长编码,是带符号整型值。它们比普通int64更有效地编码负数。 | int64 | long | int/long | *int64 | Bignum | long | Int64 |
fixed32 | 其总是四个字节。如果值经常大于\(2^{28}\),则比uint32更有效。 | uint32 | int | int/long | *uint32 | Fixnum or Bignum (as required) | uint | int |
fixed64 | 其总是八个字节。如果值通常大于\(2^{56}\),则比uint64更有效。 | uint64 | long | int/long | *uint64 | Bignum | ulong | Int64 |
sfixed32 | 其总是四个字节。 | int32 | int | int | *int32 | Fixnum or Bignum (as required) | int | int |
sfixed64 | 其总是八个字节。 | int64 | long | int/long | *int64 | Bignum | long | Int64 |
bool | bool | boolean | bool | *bool | TrueClass/FalseClass | bool | bool | |
string | 字符串必须始终包含UTF-8编码或7位ASCII文本,且长度不能超过\(2^{32}\)。 | string | String | unicode (Python 2) or str (Python 3) | *string | String (UTF-8) | string | String |
bytes | 可以包含不超过\(2^{32}\)的任意字节序列。 | string | ByteString | bytes | []byte | String(ASCII-8BIT) | ByteString | List |
上述表格上还有一些注解,但是我就不继续搬运了。有兴趣的朋友可以去看一下官方的文档。
除了上述的基本类型外,我们还可以通过其他的方式来使用一些数据结构。下面的示例就是使用repeated创建了一个字符串数组。
1 | message Show |
我们还可以使用map生成类似字典一样的数据结构。示例如下
1 | message Show |
但是map
是不能够被repeated
,即你不能创建一个List<Dictionary<T,T>>
这样的类型,对于Protobuf来说则是RepeatedField<MapField<T,T>>
。
1 | syntax = "proto3"; |
上面的例子使用工具命令生成代码后会得到这样的提示:MapRepeat.proto:4:17: Field labels (required/optional/repeated) are not allowed on map fields.
(MapRepeat.proto
是我的.proto
文件名)。而如果是下面这个例子:
1 | syntax = "proto3"; |
那么你会得到下面的这样的提示:MapRepeat.proto:4:25: Expected ">".
。除了这些之外map
还有其他需要注意的地方:
1 | 原文: |
Protobuf
也支持Enumerations(枚举)。比如下面这样例子
1 | syntax = "proto3"; |
我们需要注意的是枚举的开始可以从0开始,且必须要有0值。除此之外0值对应的元素必须是第一个(这个是为了和Proto2协议兼容)。官方文档中也有特别的描述
1 | 原文: |
没有0值如:
1 | enum TestEnum |
则你会得到这样的提示:Enumerations.proto:4:15: The first enum value must be zero for open enums.
。有0值但0值所对应的元素不是第一个,如:
1 | enum TestEnum |
我们得到的提示和上面一样。Protobuf关于枚举还有些其他的规定,比如其值必须在32比特的整型范围内。虽然我觉得枚举使用负值很奇怪,但是Protobuf官方对于枚举其实并没有说不能为负值只是不推荐。
1 | 原文: |
我这里也展示一下负值枚举的生成:
1 | syntax = "proto3"; |
生成代码的部分:
1 | public enum TestEnum { |
不过我还是觉得枚举是负数还是很奇怪啊。
关于枚举,官方还提到一个重要的点,Protobuf生成的代码可能受到特定于语言的枚举数限制(一种语言的枚举数较低)。查看您计划使用的语言的限制。这也就是说明Protobuf生成的代码本身还是会受到语言的约束。而有关其更多信息,你若感兴趣则可以查阅Enum Behavior,Enumerations等文档。
既然介绍完了Protobuf支持的数据类型,那么我们再回到第一个示例中。其中的import
等同于CSharp中引入名空间,这样你就可以在本文件中使用其他文件里的消息定义。~~我觉得这里的package
和import
和Java的语法及其类似。虽然我在网上搜索的时候是有查到import
可以用绝对路径,但是我个人在使用的时候发现不行。这也许是我操作的问题。不过一般而言项目本身的东西本来就要放在一起。所以我觉得就算没掌握如何使用绝对路径也就没啥关系了。例子如下:
1 | syntax = "proto3"; |
具体ImportTestOther.proto
中的内容我就不展示了,反正这是在ImportTestOther.proto
下的定义。我设定的ImportTest
所在文件路径为D:\Test
,而ImportTestOther.proto
是在D:\Test\TestOutput
下。值得注意的是在Protobuf
中地址文件的分隔符是使用'/'而Windows下的分隔符则为'\'。如果你的proto
文件在其他的路径下,你也可以生成工具的命令中使用-I--proto_path--proto_path
来添加查找的地址。
如果你只是想写一个简单的Protobuf文件,那我觉得到这里就可以了。如果你想要了解关于Protobuf更多的特性和信息的话,我还是推荐你们去官方文档上查看具体的说明。
Google.Protobuf工具下载
如果我们需要将.proto
文件转换为我们想要的代码,那么我们必须要先下载Google.Protobuf工具。Google.Protobuf工具下载的GitHub地址如下:https://github.com/protocolbuffers/protobuf/releases。大家按照自己的需求下载对应的工具就好。
使用Google.Protobuf工具生成CSharp代码
我个人下载的是protoc-27.1-win64
,你可以在下载文件中找到bin
文件夹,并在其下面有这protoc.exe
文件。虽然我个人很希望Google能提供一个图形界面给我们使用,但是它并没有。所以我们只能使用命令窗口通过命令调用protoc.exe
来转换我们的.proto
文件。无论你喜欢使用cmd
命令窗口还是PowerShell
命令窗口,命令都是一样的。下面是生成.proto
文件转换为CSharp
文件的命令格式和对应的例子
1 | 格式: |
上面的格式是官方文档中提供的格式说明,addressbook.proto
是官方提供的一个文件。其中protoc
就是protoc.exe
的位置,正如我例子中的C:\protoc-27.1-win64\bin\protoc.exe
。如果你命令窗口的路径指到protoc.exe
的位置,那么就只需要protoc
(PowerShell窗口需要是.\\protoc
)。$SRC_DIR
表示的是你.proto
文件所在的上层目录位置。就像例子中Enumerations.proto
文件的位置在C:\Users\Administrator\Desktop\TestInput\Enumerations.proto
,所以$SRC_DIR
即为:C:\Users\Administrator\Desktop\TestInput
。--csharp_out=
表示生成CSharp
文件输出的位置为,即我们所需要填入的$DST_DIR
。这里要特别注意一下$DST_DIR
后面是有存在空格的。
在Windows下使用时需注意的点
- 不支持路径中带有中文
无论是$SRC_DIR
还是$DST_DIR
,他们都不支持内有中文。或许是我电脑的问题,不过我还是希望后面Google能解决这个问题。现在我电脑运行下面的命令后
1 | protoc.exe -I=C:\Users\Administrator\Desktop\TestInput --csharp_out=C:\Users\Administrator\Desktop\Protobuf各类文件\TestOutput C:\Users\Administrator\Desktop\TestInput\Enumerations.proto |
便会出现这样的提示:
1 | C:\Users\Administrator\Desktop\Protobuf各类文件\TestOutput/: No such file or directory |
但是在我电脑上C:\Users\Administrator\Desktop\Protobuf各类文件\TestOutput
文件夹是存在的。虽然$SRC_DIR
和$DST_DIR
,他们都不支持内有中文。但是protoc.exe
的路径中还是支持的。
- 路径中不能带有"()"
这个也是刚好我自己在做测试项目的时候发现了,那时候我的文件名里有使用"(xxx)"来做同项目同名文件的区分。然后我就发现了这个错误。运行的命令如下:
1 | C:\Users\Administrator\Desktop\protoc-27.1-win64\bin()\protoc.exe -I=C:\Users\Administrator\Desktop\TestInput --csharp_out=C:\Users\Administrator\Desktop\\TestOutput C:\Users\Administrator\Desktop\TestInput\Enumerations.proto |
提示如下:
1 | 所在位置 行:1 字符: 72 |
包括$SRC_DIR
和$DST_DIR
的路径中也不能存在"()"。
Unity中使用Google.Protobuf
在我们将生成的代码拉入Unity前,我们要先将运行Google.Protobuf所需的dll导入进Unity。
Google.Protobuf的dll文件下载方案
方法一:
protobuf项目有给出对应的工程,你可以用这工程自己构建出dll文件。
方法二:
直接使用NuGet包管理器下载Google.Protobuf
。在Vistual
Studio中则在:项目->管理NuGet程序包。在NuGet界面搜索并下载Google.Protobuf
。但是这样Unity中是不会加载由NuGet下载的dll。我个人在操作的时候,其包的位置在Unity的Asset文件同层次下的Packages文件夹中。当然你也可以在NuGet中找到包的位置。因为Google.Protobuf
包的依赖关系,我们需要导入下面这些dll:
1 | System.Runtime.CompilerServices.Unsafe.dll |
我们使用包管理器下载下来的包是包含许多版本的dll。我是选择了使用了netstand2.0
版本下的dll文件。特别要注意这些文件导入的版本要尽量一致。如果你不知道你自己要导入哪个版本的dll文件,你可以选择一个要导入的dll并将其所有的版本的dll依次导入一遍。那个dll在导入的时候不报错就用哪个,然后其他要导入的dll文件选择一样的版本就好。我个人建议先导入System.Runtime.CompilerServices.Unsafe.dll
试试看,因为它不需要任何的依赖。
Google.Protobuf工具整合到Unity中
一般而言,我们只会将.proto
文件转换为一种语言。而Google自身的工具要使用命令窗口,我个人又觉得麻烦。所以我想着能否将Google.Protobuf工具整合到Unity中。当然这是可以的,我的想法比较简单。首先我们根据用户的输入来进行命令的拼接,然后我们使用CSharp
代码来调用Windows下的命令窗口执行这个命令。具体代码的实现如下(这里为了防止调用命令窗口时Unity卡死,我这里导入了官方一个编辑器协程包——Editor Coroutines
。大家在黏贴代码的时候,记得先使用Unity自带的Package
Manager下载Editor Coroutines
。):
1 | using System; |
我有同事说不用编辑器协程也是可以的,但是我懒不想再去实验了。上面的代码一定会输出一个错误信息,当出现了错误信息一般就表示这个程序运行完了。但是这个错误信息并不是没有用处,如果你发现你没有正确生成代码,那么你再去看看误信息里面输出了啥。
碎碎念
这又是一篇写了快一个月的文章,不过这次到不是我偷懒。而是我最近工作真的忙,本来就是在工作中遇到然后想着一边学习一边写文章记录的。我没想到他们压工作时间,压得这么狠。紧迫的项目时间根本没法让我有这样的“闲情雅致”。所以我只能记录一些我想写的东西,然后一点点去补上去。可是时间间隔太久了,有些我有想法写的东西最后也不得了之了。后面我就想懒一下写得差不多就好了,毕竟过了这么久现在也没什么激情想写它了。而且工作上关于Protobuf的知识也够用了,那我就更加没动力了。话说我六月份就更新了2篇技术文,不过我想7月份也没啥技术文好写了。毕竟我最近的确没遇到啥新玩意了。
资源地址
protobuf
项目地址: https://github.com/protocolbuffers/protobuf/releases
参考文章
protobuf
教程: https://protobuf.dev/getting-started/protobuf csharp
教程: https://protobuf.dev/getting-started/csharptutorial/- 通过一个完整例子彻底学会protobuf序列化原理: https://cloud.tencent.com/developer/article/1520442
- 【保姆级】Protobuf详解及入门指南: https://blog.csdn.net/aqin1012/article/details/136628117
- Google Protocol Buffers(Protobuf):入门指南、介绍和应用场景: https://blog.csdn.net/hj1993/article/details/130714731
- unity 中使用Google Protobuf的使用: https://blog.csdn.net/weixin_43298513/article/details/135462197