【QML】.proto文件的编写与编译

使用 Protobuf 的流程基本就是:先创建 .proto 文件定义消息格式,然后用内嵌的 protoc 编译。创建 .proto 文件,其实就相当于定义数据结构,规定一下我们发消息的格式和内容是什么。

一、编写.proto文件

1、文件规范:

1 创建.proto文件时,文件命名应该使用全小写字母命名,多个字母之间用 _ 连接。例如:lower_snake_case.proto。
2 书写.proto文件代码时,应使用2个空格的缩进

2、注释方式:

使用 // 或者 /**/ 进行注释

3、指定proto3语法:

在文件首行中指定该proto文件所采用的语法

syntax = "proto3";

【说明:】ProtocolBuffers 语言版本3,简称 proto3,是 .proto 文件最新的语法版本。相较于proto2,语法更加简化,使用更加简单。

4、package申明符

package xxx;  // 可选

【说明:】声明的package在项目中应该具有唯一性,它的作用是避免我们定义的消息发生冲突。它就类似于C++的命名空间,实际上进过编译器编译后就会变成相同名字的 命名空间。

5、定义message

消息(message)就是要定义的结构化对象,我们可以给这个结构化对象中定义其对应的消息字段。message经编译器编译后转变成对应的类(C++)。

1 // message 格式
2 message MessageName{   
3 
4 }
5 // 消息类型命名规范:使用驼峰命名法,首字母大写

6、编写消息字段

消息字段的格式定义为:类型 字段名 = 唯一编号

• 字段名称命名规范:全小写字母,多个字母之间用 _ 连接。
• 字段类型分为:标量数据类型和特殊类型(包括枚举、其他消息类型等)。
• 字段唯一编号:用来标识字段,一旦开始使用就不能够再改变

数据类型说明示例
int32 / int64 整数,32位或64位 int32 age = 1;
uint32 / uint64 无符号整数,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;
  • 枚举类型:用于定义一组命名常量。
  • 消息类型:类似于结构体,用于定义复杂的数据结构。

唯一编号用于唯一的标识每一个字段,在解析和序列化的时候可以准确快速的定位到每个字段的位置和大小。

 1 // 指定采用 proto3 语法
 2 syntax = "proto3";
 3 
 4 // 相当于命名空间
 5 package contacts;
 6 
 7 message PeopleInfo{
 8     string name = 1;    //不是赋值,而是指定唯一编号
 9     int32 age = 2;
10 }

7、导入其他文件

可以通过 import 导入其他 .proto 文件中的定义。

import "common.proto";

8、嵌套消息

消息可以包含其他消息作为字段。

 1 message Address {
 2     string street = 1;
 3     string city = 2;
 4 }
 5 
 6 message User {
 7     int32 id = 1;
 8     string name = 2;
 9     Address address = 3; // 嵌套消息
10 }

9、枚举类型

 1 enum UserType {
 2     UNKNOWN = 0;
 3     ADMIN = 1;
 4     USER = 2;
 5 }
 6 
 7 message User {
 8     int32 id = 1;
 9     string name = 2;
10     UserType type = 3;  // 使用枚举类型
11 }

enum 是定义枚举类型的关键字,相当于 C++ 中的 enum,但不同点在于枚举值之间的分隔符是分号,不是逗号

 1 enum Time {
 2     Monday = 0;
 3     Friday = 1;
 4 }
 5 message Invite {
 6     required string host = 1;
 7     required string name = 2;
 8     required string address = 3;
 9     required Time time = 4;
10     optional string info = 5;
11 }

Time 为枚举变量的名字,Monday 和 Friday 都是枚举值,0 和 1 表示枚举值所对应的实际整型值,可以为枚举值指定任意的整型数值,不是必须从 0 开始定义。

10、重复字段

使用 repeated 定义列表字段。

1 message User {
2     int32 id = 1;
3     repeated string roles = 2; // 角色列表
4 }

11、字段规则

[字段规则][类型][名称] = [字段编号];

 

1 message Invite {
2     required string host = 1;
3     required string name = 2;
4     required string address = 3;
5     optional string info = 4;
6 }

required:消息体中必填字段,不设置会导致编解码异常。一般不填就认为是必填字段了,字段必须出现且仅能出现一次。
optional:消息体中可选字段。生成的是对应的指针,字段可出现 0 次或 1 次。
repeated:消息体中可重复字段,重复的值的顺序会被保留,在go中重复的会被定义为切片,字段可出现任意次(包括 0 次)。

  1. 在每个 message 中至少要有一个 required 类型的字段
  2. 每个 message 中可以有任意个 optional 类型的字段
  3. 如果想要在原有的消息协议中添加新的字段,同时还要保证老版本的程序能够正常读取或写入,那么对于新添加的字段必须是optional或repeated。道理非常简单,老版本程序无法读取或写入新增的required限定符的字段。

12、服务定义

用于定义 RPC 服务的接口。

1 service UserService {
2     rpc GetUser (UserRequest) returns (UserResponse);
3     rpc ListUsers (Empty) returns (stream User);
4 }

二、编译.proto文件

1、编译指令

protoc [--proto_path=IMPORT_PATH] --cpp_out=DST_DIR  file.proto
  • protoc:编译工具
  • –proto_path:指定 .proto 文件的检索路径,可以多次指定指定。不指定默认在当前文件夹下检索。可以简写为 -I.
  • –cpp_out:指定编译后的文件类型为C++,同理 --java_out 和 --python_out 分别表示生成 Java 和 Python 代码,其后的路径是生成的代码所存放的目录
  • DST_DIR:指定文件的生成路径
  • file.proto:指定要编译的 .proto 文件(–proto_path 路径下的)

如果我们在 contact.proto 文件所在路径下,则可以使用下面的指令直接生成

protoc --cpp_out=. contact.proto 

编译后自动为我们生成了 .cc 和 .h 文件。这也印证了我们前面所说的Protobuf使用特点:ProtoBuf是通过Protoc编辑器编译生成的头文件和源文件来使用的

三、protobuf文件示例

 1 syntax = "proto3";
 2 
 3 package example;
 4 
 5 import "google/protobuf/empty.proto";
 6 
 7 enum UserType {
 8     UNKNOWN = 0;
 9     ADMIN = 1;
10     USER = 2;
11 }
12 
13 message User {
14     int32 id = 1;
15     string name = 2;
16     UserType type = 3;
17     repeated string roles = 4; // 用户角色
18 }
19 
20 message UserRequest {
21     int32 id = 1;
22 }
23 
24 message UserResponse {
25     User user = 1;
26 }
27 
28 service UserService {
29     rpc GetUser (UserRequest) returns (UserResponse);
30     rpc ListUsers (google.protobuf.Empty) returns (stream User);
31 }

 

posted @ 2025-08-28 17:22  taohuaxiaochunfeng  阅读(117)  评论(0)    收藏  举报