目录
- [1 什么是Proto3?](#1 什么是Proto3?)
- [2 Proto3基础语法](#2 Proto3基础语法)
-
- [2.1 语法声明](#2.1 语法声明)
- [2.2 包名(Package)](#2.2 包名(Package))
- [2.3 导入语句(Import)](#2.3 导入语句(Import))
- [2.4 注释](#2.4 注释)
- [2.5 数据类型](#2.5 数据类型)
-
- [2.5.1 标量类型(Scalar Types)](#2.5.1 标量类型(Scalar Types))
- [2.5.2 复合类型](#2.5.2 复合类型)
- [2.6 消息定义](#2.6 消息定义)
- [3 单例设计模式](#3 单例设计模式)
-
- [3.1 什么是单例模式?](#3.1 什么是单例模式?)
- [3.2 饿汉单例模式](#3.2 饿汉单例模式)
- [3.3 懒汉单例模式](#3.3 懒汉单例模式)
- [4 案例](#4 案例)
1 什么是Proto3?
在分布式系统与微服务架构中,服务间数据通信的效率直接影响系统吞吐量、延迟和资源消耗。当 JSON、XML 等文本协议因冗余字符和低解析效率成为性能瓶颈时,Protocol Buffers(简称 Protobuf)作为 Google 开源的一种跨语言、平台无关的二进制序列化协议,核心功能是将结构化数据转换为紧凑的二进制流(序列化),以及从二进制流还原数据(反序列化),其工作流程基于预定义的 Schema(通过 .proto文件描述数据结构),并由 Protobuf 编译器(protoc)生成目标语言的数据访问类,屏蔽底层编码细节。Protobuf通过紧凑编码、强类型约束和跨语言支持,成为高吞吐、低延迟场景的主流选择。

2 Proto3基础语法
Proto3 文件以 .proto为后缀,核心结构包括语法声明、包名、导入语句、消息/枚举/服务定义和选项
2.1 语法声明
文件开头必须声明使用的 Proto 语法版本(目前主流为 proto3):
proto
syntax = "proto3"; // 声明使用 Proto3 语法
2.2 包名(Package)
用于避免命名冲突,类似编程语言的命名空间:
proto
package user.v1; // 包名为 user.v1,生成代码时可映射为命名空间(如 Java 的 package)
2.3 导入语句(Import)
引入其他 .proto文件的定义(如标准库的 any.proto):
protobuf
import "google/protobuf/any.proto"; // 导入 Any 类型定义
import "common/types.proto"; // 导入自定义公共类型
2.4 注释
支持单行(//)和多行(/* ... */)注释,与 C++ 一致:
protobuf
// 单行注释:定义用户信息消息
/*
多行注释:
包含用户 ID、姓名、年龄等基础字段
*/
2.5 数据类型
2.5.1 标量类型(Scalar Types)
标量类型是 Proto3 的基础数据类型,对应不同编程语言的原生类型(见下表):
| Proto3 类型 | 描述 | 对应 C++ 类型 | 对应 Python 类型 | 取值范围/说明 |
|---|---|---|---|---|
| double | 双精度浮点数 | double | float | 64 位 IEEE 754 |
| float | 单精度浮点数 | float | float | 32 位 IEEE 754 |
| int32 | 有符号 32 位整数 | int32 | int | 变长编码(负数效率低,建议用 sint32) |
| int64 | 有符号 64 位整数 | int64 | int/long | 变长编码(负数效率低,建议用 sint64) |
| uint32 | 无符号 32 位整数 | uint32 | int | 变长编码 |
| uint64 | 无符号 64 位整数 | uint64 | int/long | 变长编码 |
| sint32 | 有符号 32 位整数(优化) | int32 | int | 变长编码(负数效率高) |
| sint64 | 有符号 64 位整数(优化) | int64 | int/long | 变长编码(负数效率高) |
| fixed32 | 固定 32 位无符号整数 | uint32 | int | 4 字节,值大时比 uint32 高效 |
| fixed64 | 固定 64 位无符号整数 | uint64 | int/long | 8 字节,值大时比 uint64 高效 |
| sfixed32 | 固定 32 位有符号整数 | int32 | int | 4 字节 |
| sfixed64 | 固定 64 位有符号整数 | int64 | int/long | 8 字节 |
| bool | 布尔值 | bool | bool | true/false |
| string | UTF-8 字符串 | std::string | str | 长度 ≤ 2^32-1 字节 |
| bytes | 任意字节序列 | std::string | bytes | 长度 ≤ 2^32-1 字节 |
2.5.2 复合类型
- 消息(Message):自定义结构化数据类型(核心),可嵌套。
- 枚举(Enum):定义命名的常量集合。
- 键值对集合(Map):类似哈希表。
2.6 消息定义
消息是 Proto3 的核心,用于定义结构化数据。语法为:
protobuf
message 消息名 {
// 字段定义(类型 字段名 = 字段编号;)
类型 字段名 = 字段编号;
// ... 更多字段
}
-
字段规则与编号
- 字段规则:Proto3 仅支持 singular(默认,可省略)和 repeated(重复字段,类似数组),无 required/optional(所有字段默认 optional)。
- 字段编号:1-15(占 1 字节)、16-2047(占 2 字节),不可重复,不可使用 19000-19999(Google 保留)。编号越小,编码效率越高。
示例:定义用户信息消息
protobufmessage User { int32 id = 1; // singular 字段(默认),用户 ID string name = 2; // 用户名(UTF-8 字符串) bool is_active = 3; // 是否激活 repeated string emails = 4; // repeated 字段,邮箱列表(可重复) bytes avatar = 5; // 头像字节流 } -
嵌套消息
消息可嵌套定义(内部消息),支持多层嵌套
示例
protobufmessage Address { string street = 1; // 街道 string city = 2; // 城市 string zipcode = 3; // 邮编 } message User { int32 id = 1; string name = 2; Address address = 3; // 嵌套 Address 消息 repeated Address addresses = 4; // 多个地址(repeated 嵌套消息) } -
Repeated 字段
repeated修饰的字段表示"可重复",类似数组。Proto3 中默认使用 packed 编码(连续存储元素,提升效率):
示例
protobufmessage Order { repeated int32 item_ids = 1; // 商品 ID 列表(packed 编码) repeated Item items = 2; // 商品详情列表(嵌套消息 repeated) } -
Oneof 字段
oneof定义一组互斥字段(同一时间仅一个字段有效),节省存储空间:
示例
protobufmessage SearchRequest { string query = 1; oneof result_type { int32 page_number = 2; // 页码(与下面字段互斥) int32 offset = 3; // 偏移量(与上面字段互斥) } } -
Map 类型
用形如
map<key_type, value_type> map_name = 字段编号;定义键值对,key_type类型只能是标量类型(除 float/double/bytes),value_type类型任意:示例
protobufmessage UserProfile { map<string, string> attributes = 1; // 属性键值对(如 {"age": "30", "gender": "male"}) map<int32, Address> region_addresses = 2; // key 为区域 ID,value 为地址 } -
枚举Enum
枚举定义命名的常量集合,语法为
protobufenum 枚举名 { 枚举值1 = 0; // 必须包含 0 值(作为默认值) 枚举值2 = 1; // ... 更多枚举值 }protobuf示例:定义用户角色枚举 enum UserRole { USER_ROLE_UNSPECIFIED = 0; // 默认值(必须存在) USER_ROLE_ADMIN = 1; // 管理员 USER_ROLE_MEMBER = 2; // 普通成员 USER_ROLE_GUEST = 3; // 访客 } message User { int32 id = 1; UserRole role = 2; // 使用枚举类型 }
3 单例设计模式
3.1 什么是单例模式?
单例模式(Singleton Pattern)是一种设计模式,旨在确保一个类在整个应用程序中只有一个实例,并提供全局访问点。这种模式在需要唯一实例的场景中非常有用,比如配置管理器 、日志记录 、线程池 、数据库连接等
实现单例模式的核心在于:
- 私有构造函数且禁止拷贝
- 静态的唯一实例
单例模式需要考虑线程安全问题,防止多个线程实例化多个单例,
3.2 饿汉单例模式
饿汉单例模式的核心特征是:在类加载时就创建唯一的实例(静态加载)。这种实现方式简单且线程安全------运行时的多线程还未产生,但可能会在类加载时浪费资源,因为实例即使在未被使用时也会被创建。
cpp
template<typename TSingleton>
class Singleton {
public:
using TSingletonPtr = std::unique_ptr<TSingleton>;
private:
Singleton() = default;
virtual ~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton(Singleton&&) = delete;
Singleton& operator=(const Singleton&) = delete;
static TSingletonPtr instance;
public:
static TSingletonPtr& Instance() {
if (instance == nullptr) {
instance.reset(new TSingleton());
}
return instance;
}
};
template<typename TSingleton>
std::unique_ptr<TSingleton> Singleton<TSingleton>::instance = std::make_unique<TSingleton>();
应用案例:
cpp
class Counter{
public:
Counter() : cnt_(0) {};
Counter(int cnt) : cnt_(cnt) {};
~Counter() = default;
int counter() const { return cnt_; }
private:
int cnt_;
};
int main() {
std::cout << Singleton<Counter>::Instance()->counter();
return 0;
}
3.3 懒汉单例模式
懒汉单例模式的核心特征是:在首次运行时需要时才创建该实例(动态加载),避免了不必要的资源消耗。它的实现相对简单,但需要处理线程安全问题以避免并发环境下的问题
在饿汉单例模式中,实例是静态的成员变量,所以需要在类加载时初始化;而在懒汉单例模式中,实例为局部静态变量,而局部静态变量具有延迟构造且线程安全(C++11保证)的特性,即运行时第一次调用Instance()时触发单例构造且只构造一次
cpp
template<typename TSingleton>
class Singleton {
public:
using TSingletonPtr = std::unique_ptr<TSingleton>;
private:
Singleton() = default;
virtual ~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton(Singleton&&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static TSingletonPtr& Instance() {
static TSingletonPtr instance = std::make_unique<TSingleton>();
return instance;
}
};
应用案例:
cpp
class Counter{
public:
Counter() : cnt_(0) {};
Counter(int cnt) : cnt_(cnt) {};
~Counter() = default;
int counter() const { return cnt_; }
private:
int cnt_;
};
int main() {
std::cout << Singleton<Counter>::Instance()->counter();
return 0;
}
4 案例
首先,定义一个系统配置类
cpp
class SystemConfig {
public:
void initialize() {
std::string file_path(__FILE__);
size_t base_pos = file_path.rfind("/src/");
std::string base_dir = file_path.substr(0, base_pos);
std::string config_path = base_dir + "/system_config/system_config.pb.txt";
if (readTextProtoFile(config_path, &config_)) {
is_initialized_ = true;
} else {
R_WARN << "Read System configure file " << config_path << " failed.";
}
}
const pb::SystemConfig& configure();
private:
bool is_initialized_{ false };
pb::SystemConfig config_;
};
该类会读取基于proto定义的配置并初始化;用单例模板包装以保证系统配置在生命周期内只有一个实例
cpp
using SystemConfigPtr = common::structure::Singleton<SystemConfig>;
接着,编写CMakeLists.txt来进行proto编译
cmake
set(PROTO_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/protos)
file(MAKE_DIRECTORY ${PROTO_OUTPUT_DIR})
set(PROTO_SRCS "")
set(PROTO_HDRS "")
# system configure related protos
file(GLOB SYSTEM_CONFIG_PROTOS "${CMAKE_CURRENT_SOURCE_DIR}/system_config/*.proto")
protobuf_generate(
TARGET ${PROJECT_NAME}
LANGUAGE cpp
OUT_VAR SYSTEM_CONFIG_PROTO_SRCS
PROTOS ${SYSTEM_CONFIG_PROTOS}
)
list(APPEND PROTO_SRCS ${SYSTEM_CONFIG_PROTO_SRCS})
include_directories(
include
${catkin_INCLUDE_DIRS}
)
target_link_libraries(${PROJECT_NAME}
${PROTOBUF_LIBRARIES}
${catkin_LIBRARIES}
)
target_include_directories(${PROJECT_NAME} PUBLIC
${CMAKE_CURRENT_BINARY_DIR}
)
这种写法可以使生成的.pb.cc以及.pb.h文件保持相对目录结构,在大型项目管理中比较方便。
🔥 更多精彩专栏:
👇源码获取 · 技术交流 · 抱团学习 · 咨询分享 请联系👇