一、引言
在C语言中使用protobuf协议时,难免会遇到要使用数组或者传输字符串,但是protobuf给我们编译出来的结构体成员是一个 pb_callback_t 类型的,很多人会疑惑这种类型要怎么处理呢?
我们以一个学生的信息举例。如以下的 string类型,我们其实需要创建一个char类型的数组存放字符串,例如char name[20]里面存放名字"张三";repeated 声明的重复字符表示我们想要一个 subject 的数组,例如subject subjects[3]存放3个科目的科目名字和科目分数。
syntax = "proto3";
package Student;
message subject
{
string name = 1;
uint32 score = 2;
}
message Info
{
string name = 1;
uint32 age = 2;
uint32 height = 3;
repeated subject subjects = 4;
}
直接生成的pb文件。可以看到生成的不是我们想要的结果,而是一个 pb_callback_t ,这个类型又不能直接赋值,所以本文讲述如何处理这种情况。
二、生成固定大小的数组
2.1 配置文件
在本例中,我们假设学生的名字和科目是一个固定大小的数组。也就是让protobuf生成的是 char name[20],subject subjects[3],subject类型里面name为char name[10]。
注:如果你想设置的是个 int 类型或是其它类型的数组,原理和设置结构体是一样的!
我们只需在 .proto 文件相同的目录下创建一个以 .options 结尾名字相同的文件。例如:
这个文件就可以固定配置数组的大小,按照刚刚的要求,我们需要在里面写上几行配置的代码:
Student.subject.name max_size:10
Student.Info.name max_size:20
Student.Info.subjects max_count:3
- 第一个Student:表示包名称,也就是proto文件里面声明的 package Student;
- 第二个subject:表示包里面的subject消息,也就是proto文件里面声明的 message subject
- 第三个name:表示subject里面的成员
- 第四个max_size:10:表示设置 string 类型的最大值为10Byte
- 第五个max_count:3:表示设置 repeated 声明的最大数量为3个
我们看生成的代码:
可见已经按照我们想要的配置好了,其中 pb_size_t subjects_count; 表示实际需要打包几个 subjects 数组,例如 subjects_count = 1 表示只有一个subjects需要打包的,那么protobuf打包的时候只会打包subjects[0]这个数组。
2.2 测试
接下来我们简单写个代码测试一下是否有效。
/*
* @Author: Troubadour 2276791354@qq.com
* @Date: 2024-05-21 14:31:09
* @LastEditors: Troubadour 2276791354@qq.com
* @LastEditTime: 2024-05-21 15:02:55
* @Version:
* @Description:
*/
#include <stdio.h>
#include <string.h>
#include "pb_decode.h"
#include "pb_encode.h"
#include "student.pb.h"
#include "pb.h"
/* 定义一下数据内容 */
Student_Info Student_Info_Raw = {
.name = "Troubadour",
.height = 180,
.age = 18,
.subjects_count = 3,
.subjects = {
{ .name = "C", .score = 100 },
{ .name = "C++", .score = 100 },
{ .name = "Python", .score = 100 },
}
};
/* 创建一个存放编码后的数据 */
uint8_t SendBuff[50] = {0};
/* 编码函数 */
uint32_t pb_endcode_Student_Info(Student_Info *src, uint8_t *dest)
{
pb_ostream_t stream = pb_ostream_from_buffer(dest, sizeof(SendBuff));
bool status = pb_encode(&stream, Student_Info_fields, src);
if (!status)
{
printf("Encode error: %s\n", PB_GET_ERROR(&stream));
}
return stream.bytes_written;
}
/* 解码函数 */
bool pb_decode_Student_Info(uint8_t *buf, uint32_t buf_size, Student_Info *dest)
{
pb_istream_t stream = pb_istream_from_buffer(buf, buf_size);
bool status = pb_decode(&stream, Student_Info_fields, dest);
if (!status)
{
printf("Decode error: %s\n", PB_GET_ERROR(&stream));
}
return status;
}
void main(void)
{
/* 编码后的长度 */
uint32_t len = 0;
/* 解码后数据存放的地方 */
Student_Info Student_Info_recv;
printf("Test protobuf!\n");
len = pb_endcode_Student_Info(&Student_Info_Raw, SendBuff);
printf("Encode len: [%d] \n", len);
if (pb_decode_Student_Info(SendBuff, len, &Student_Info_recv) == false)
{
return;
}
/* 打印解码结果 */
printf("Decode success. \n");
printf("name: %s \n", Student_Info_recv.name);
printf("height: %d \n", Student_Info_recv.height);
printf("age: %d \n", Student_Info_recv.age);
for (int i = 0; i < Student_Info_recv.subjects_count; i++)
{
printf("subjects[%d]: %s, %d \n", i, Student_Info_recv.subjects[i].name, Student_Info_recv.subjects[i].score);
}
}
输出结果:
Test protobuf!
Encode len: [45]
Decode success.
name: Troubadour
height: 180
age: 18
subjects[0]: C, 100
subjects[1]: C++, 100
subjects[2]: Python, 100
可以看见也是成功编码解码了。如果我们改一下subjects_count = 2,让他只打包数组的两个数据,输出结果:
可以看到 len 只有33,证明 subjects_count 是可以控制编码subjects结构体数组的数据量的。
Test protobuf!
Encode len: [33]
Decode success.
name: Troubadour
height: 180
age: 18
subjects[0]: C, 100
subjects[1]: C++, 100
三、生成不固定大小的数组(处理pb_callback_t)
这里主要分四种,一种是结构体、一种是整形、一种是浮点型、一种是字符串。这里我会一一讲解。
对于 pb_callback_t 大概的运行流程是:
具体的编码过程:先根据字段编码该类型的标题 tag,然后再对内容编码。(具体使用方法下面会举例子)
字段标题编码:
bool pb_encode_tag_for_field(pb_ostream_t *stream, const pb_field_iter_t *field)
内容编码:
结构体:(也就是子消息)
bool pb_encode_submessage(pb_ostream_t *stream, const pb_msgdesc_t *fields, const void *src_struct);
整形:(包含 bool, enum, int32, int64, uint32 and uint64)
bool pb_encode_varint(pb_ostream_t *stream, uint64_t value);
浮点型:(单精度用32的,双精度用64)
bool pb_encode_fixed32(pb_ostream_t *stream, const void *value);
bool