基础知识

protobuf英文全称为Protocol Buffers,是Google的一种语言中立、平台中立、可扩展的结构化数据序列化机制。它类似于 JSON,但更小更快,并且能生成原生语言绑定。你只需一次性定义好你想要的数据结构,然后就可以使用专门生成的源代码,轻松地从各种数据流中读写你的结构化数据,并支持多种语言。

序列化和反序列化

  • 序列化和反序列化对于学web的可能比较熟悉,但我不是学web的,所以简单介绍一下:

    • 序列化:就是将一个对象转换为字节序的过程;

    • 反序列化:就是将一个字节序转换为一个完整对象的过程;

    • 使用序列化的情况:

      1. 存储数据:当你想把的内存中的对象状态保存到⼀个⽂件中或者存到数据库中时。
      2. 网络传输:网络直接传输数据,但是无法直接传输对象,所以要在传输前序列化,传输完成后反 序列化成对象。例如我们之前学习过socket编程中发送与接收数据。
    • 主流的序列化和反序列化工具有:XML、JSON、ProtoBuf;

protobuf版本

  • 目前protobuf的大版本只有protobuf2protobuf3,这两个大版本。protobuf是开源的,其源代码和发行版都被放置在github仓库中,对应的网址:protocolbuffers/protobuf: Protocol Buffers - Google’s data interchange format
  • 通过翻找github上protobuf的发行版,会发现protobuf的版本号有V x.y.z以及下图所示的V x.y这种。
    • 其中V x.y.zx表示主版本号,y表示次版本号,z表示补丁版本号。而通过翻阅后面会发现主版本号只出现2、3
    • 由于主版本号只出现2、3,所以更新到后面主版本号意义确实不太大了,所以就更换了版本号的命名方案,将版本号3给省略了,变成x.y的形式。这种形式默认是protobuf3。(图中V 21.0其实就是这种形式)
    • 通过观察github仓库的版本,protobuf2的版本范围为:v2.4.1v2.6.1,而protobuf3的版本范围为:v3.0.0v3.20.1。而版本命名后的目前截止至2025年10月11日,protobuf的版本已经是从v21.0 v33.0
  • 为了能用上新版本的一些功能以及向下兼容,所以在安装protobuf的时候最好还是安装最新版本,protobuf3是兼容protobuf2的。

image-20251010025324870

protobuf工作原理

使用步骤如下:

  1. 编写.proto⽂件,⽬的是为了定义结构对象(message)及属性内容。
  2. 使⽤ protoc 编译器编译 .proto ⽂件,⽣成⼀系列接口代码,存放在新⽣成头⽂件和源⽂件中。
  3. 依赖⽣成的接口。将编译⽣成的头⽂件包含进我们的代码中,使用编译器为我们生成的序列化,反序列化方法以及一些对消息字段进行读写的方法。

image-20251011082338701

protobuf环境搭建

所谓搭建protobuf环境,其实就是安装protoc编译器,以及Python相关的库,这样基本就可以在C语言中以及Python中使用protobuf结构体。

  • 下载ProtoBuf前一定要安装依赖库:autoconf automake libtool curl make g++ unzip,否则安装会出现问题。安装命令如下:
1
sudo apt-get install autoconf automake libtool curl make g++ unzip -y
  • 然后去到protobufgithub网站上的releases的网站上选择该选项并复制链接,这个博客的安装方法是24年12月份安装的,所以版本还没到v30甚至以上。Releases · protocolbuffers/protobuf

image-20251011083219938

  • 然后在Linux下使用命令wget下载对应连接的压缩包,并且将压缩包解压出来。
1
wget https://github.com/protocolbuffers/protobuf/releases/download/v29.1/protoc-29.1-linux-x86_64.zip

image-20241214223636850

  • 先创建一个protoc-29.1-linux-x86_64文件夹用来存放,解压后的protbuf文件。
1
mkdir protoc-29.1-linux-x86_64
  • 之后对下载下来的压缩包进行解压,解压到指定文件,使用如下命令
1
unzip ./protoc-29.1-linux-x86_64.zip -d ./protoc-29.1-linux-x86_64
  • 然后进入该文件夹,将bin/protoc可执行文件和include/google目录分别放到usr/local/binusr/local/include目录下
1
sudo cp ./protoc /usr/local/bin/
  • 移动完之后执行protoc --version就会出现如下信息,这就代表着protoc成功安装完成了。

image-20251011083522423

protobuf编译

  • 给出如下的protobuf代码,看看如何编译它,编译它后什么文件会输出出来,先不用理解代码中的内容。创建一个.proto文件。
1
2
3
4
5
6
7
syntax = "proto3";

message Test{
int32 id = 1;
string name = 2;
string email = 3;
}

image-20251011090435753

  • 接下来使用protoc --python_out=. protobuf1.proto,将该protobuf1文件给编译一下,发现编译后出现了一个.py文件。

image-20251011090542395

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# NO CHECKED-IN PROTOBUF GENCODE
# source: protobuf1.proto
# Protobuf Python Version: 5.29.1
"""Generated protocol buffer code."""
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import runtime_version as _runtime_version
from google.protobuf import symbol_database as _symbol_database
from google.protobuf.internal import builder as _builder
_runtime_version.ValidateProtobufRuntimeVersion(
_runtime_version.Domain.PUBLIC,
5,
29,
1,
'',
'protobuf1.proto'
)
# @@protoc_insertion_point(imports)

_sym_db = _symbol_database.Default()




DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fprotobuf1.proto\"/\n\x04Test\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\r\n\x05\x65mail\x18\x03 \x01(\tb\x06proto3')

_globals = globals()
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'protobuf1_pb2', _globals)
if not _descriptor._USE_C_DESCRIPTORS:
DESCRIPTOR._loaded_options = None
_globals['_TEST']._serialized_start=19
_globals['_TEST']._serialized_end=66
# @@protoc_insertion_point(module_scope)

  • 还可以编译成.C文件,以及.CPP文件,使用protoc --c_out=. protobuf1.proto将生成.c文件和.h文件。然后使用protoc --cpp_out=. protobuf1.proto就可以生成.cc文件和.h文件。

image-20251011091044219

image-20251011091336142

编译指令 结果
protoc --c_out=. protobuf1.proto 生成.c文件以及.h文件
protoc --python_out=. protobuf1.proto 生成.py文件
protoc --cpp_out=. protobuf1.proto 生成.cc文件以及.h文件

protobuf使用

  • 现在已经将protobuf进行编译,编译成了其他语言的文件,那如何在编写其他语言的情况下使用这个protobuf文件。接下来的例子就是做一个实例了解一下如何使用。
1
2
3
4
5
6
7
8
# 注意如果第一次编写的时候应该需要下载protobuf1_pb2.py文件所包含的库文件
import protobuf1_pb2
msg = protobuf1_pb2.Test()
msg.id = 123
msg.name = "张三"
msg.email = "111@qq.com"
print(msg) # 结构化输出msg
print(msg.SerializeToString()) # 序列化转成二进制字节形式

image-20251011093240496

数据类型

标量类型

Protobuf支持多种标量类型,具体如下表:

数据类型 说明 示例
int32int64 32位或64位有符号整数 int age=1;
uint32uint64 32位或64位无符号整数 uint32 id=2;
float 单精度浮点数 float score = 3;
double 双精度浮点数 double ratio = 4;
bool 布尔值 bool is_active = 5;
string 字符串 string name=6;
bytes 二进制数据 bytes data=7;

复合类型

  • 枚举类型:用于定义一组命名常量。使用关键字enum进行定义。
  • 消息类型:类似于结构体,用于定义复杂的数据结构。使用message关键字进行定义。

例子1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 下面创建一个包含枚举类型的消息
syntax = "proto3";
package my_package;

message Person{
// 声明一个枚举类型,注意等号
enum Gender {
GENDER_UNKNOWN = 0;
MALE = 1;
FEMALE = 2;
}
// 消息类型开始定义
string name = 1;
// 定义一个Gender变量
Gender gender = 2;
}

例子2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
syntax = "proto3";

//枚举类型也能定义在消息类型的外面,可以作为多个消息类型中的一个枚举类型
// 系统角色的定义
enum UserRole{
USER_ROLE_UNKNOWN = 0; // 默认值,未知角色
USER_ROLE_ADMIN = 1; // 管理员
USER_ROLE_EDITOR = 2; // 编辑者
USER_ROLE_VIEWER = 3; // 仅查看者
}

// 定义消息结构
message User{
string name = 1;
UserRole role = 2;
}

基本语法

语法版本

使用syntax指定protobuf的版本(proto2proto3),目前推荐使用proto3。例子如下:

1
2
syntax = "proto3"; // 使用protobuf3的版本
syntax = "proto2"; // 使用protobuf2的版本

包声明

使用package定义命名空间,避免不同模块间的命名冲突。例子如下:

1
package my_package;

导入其他文件

使用import 导入其他的.proto文件中的定义。例子如下:

1
import "common.proto";

注释

对于protobuf的注释和C语言是一样的,都是通过// 进行单行注释。以及通过/**/进行多行注释。

1
2
3
4
5
// 这是protobuf的单行注释

/*
这是protobuf的多行注释
*/

定义与声明数据

对于proto3,可以使用数据类型 变量名 = 字段编号来定义和声明数据类型。例子如下:

1
2
3
4
5
6
7
syntax = "proto3";
package my_package;
// 数据类型 变量名 = 字段编号;
// 定义一个age变量,字段编号;
// 字段编号必须是1~2^31-1
// 19000 ~ 19999(系统保留)
uint32 age = 1;

注意:字段编号是字段编号,而不是这些变量的默认值,在protbuf中会自动给变量赋予一个默认值。各种类型的默认值如下:

字段类型 默认值
数值类型int32 uint32 0
字符串 ""(空字符串)
bool false
枚举 枚举中值为0的项
message nil/None(未设置)

对于proto2来说,可以使用标记词 变量名 = 字段编号;来定义和声明数据类型。例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
syntax = "proto2";

package tutorial;

//下面需要使用标记词来,定义消息里面的变量

message Person{
required string name = 1; // 必须字段
optional int32 id = 2; // 可选字段
optional string email = 3; // 可选字段
repeated string phone_number = 4; //重复字段(数组)
}

标记词

  • 标记词在proto2中定义变量是必须要修饰的,而proto3在定义变量的时候默认使用optional修饰,不必要求用户显式的定义声明。接下来介绍一下这些标记词。
标记词 含义 说明
required(proto3已经被移除) 必须字段 必须赋值,如果不赋值序列化会失败,反序列化可能也会出错
optional 可选字段 可以设置;不设置时会使用默认值
repeated 可重复字段 类似数组或列表,可出现多次
  • 对于repeated 字段,表示重复字段,可以出现多次,等价于数组或者列表
1
2
3
4
5
message Student {
repeated string courses = 1;
}
// 序列化后的course字段可能是
courser = ["Math","Englist","Physics"]

proto3语法详解

消息——基本结构

  • 消息,也就是类似于C语言结构体的东西,是protobuf的核心,用于定义数据结构。
1
2
3
4
5
6
7
8
9
syntax = "proto3"; 
package my_package;

message User{
int32 id = 1; // 用户ID
string name = 2;// 用户名
bool is_active = 3; // 是否活跃
}

嵌套消息

  • 一个消息结构中可以嵌套另一个消息结构。
1
2
3
4
5
6
7
8
9
10
11
12
13
syntax = "proto3";
package my_package;

message Address{
string street = 1;
string city = 2;
}

message User{
int32 id = 1;
string name = 2;
Address address = 3; // 嵌套消息
}

枚举类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
syntax = "proto3";
package my_package;

enum UserType {
UNKNOWN = 0;
ADMIN = 1;
USER = 2;
}

message User {
int32 id = 1;
string name = 2;
UserType type =3;
}

服务定义

  • 用于定义RPC服务的接口。
1
2
3
4
service UserSerice {
rpc GetUser (UserRequest) return (UserResponse);
rpc ListUsers (Empty) returns (stream User);
}