详解protobuf-c之在C语言中如何使用repeated生成数组和字符串(包含配置pb_callback_t)
在C语言中使用protobuf协议时,难免会遇到要使用数组或者传输字符串,但是protobuf给我们编译出来的结构体成员是一个 pb_callback_t 类型的,很多人会疑惑这种类型要怎么处理呢?我们以一个学生的信息举例。如以下的 string类型,我们其实需要创建一个char类型的数组存放字符串,例如char name[20]里面存放名字"张三";repeated 声明的重复字符表示我们想要一
一、引言
在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 pb_encode_fixed64(pb_ostream_t *stream, const void *value);
四、生成不固定大小的数组结构体(pb_callback_t)
4.1 配置文件
.proto文件不用动,还说刚刚那个。
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;
}
.options文件我们暂且配置一下字符串的,也就是说只有subject数组会是pb_callback_t类型,方便大家理解,后面我会一次性全部使用 pb_callback_t 类型。
OK,接下来我们看生成的代码:
可见里面 pb_callback_t 类型了。
4.2 处理 pb_callback_t
pb_callback_t 这个个类型主要是需要用户自己对数据进行打包和解包的处理。该结构体原型:
typedef struct pb_callback_s pb_callback_t;
struct pb_callback_s {
/* Callback functions receive a pointer to the arg field.
* You can access the value of the field as *arg, and modify it if needed.
*/
union {
bool (*decode)(pb_istream_t *stream, const pb_field_t *field, void **arg);
bool (*encode)(pb_ostream_t *stream, const pb_field_t *field, void * const *arg);
} funcs;
/* Free arg for use by callback */
void *arg;
};
可以看到有两个成员,一个是回调函数(编码/解码),一个是参数。
接下来我们先写搞定编码过程,如下:(讲解都在注释里!)
/* 定义一下数据内容. */
Student_Info Student_Info_Raw = {
.name = "Troubadour",
.height = 180,
.age = 18,
};
/* 建议额外定义一个结构体,存放数据打包的长度和数据. */
typedef struct
{
uint8_t subjects_count;
Student_subject *subjects;
} pbcb_subject_t;
/* 填充数据. */
Student_subject std_subject[] = {
{.name = "C", .score = 100,},
{.name = "C++", .score = 100, },
{.name = "Python", .score = 100,},
{.name = "Java", .score = 100,},
{.name = "JavaScrip", .score = 100,}
};
/* 定义好发送的数据和数组长度 */
pbcb_subject_t pbcb_subject = {
.subjects_count = sizeof(std_subject) / sizeof(std_subject[0]),
.subjects = std_subject,
};
/* 数组编码回调函数 */
bool write_subject(pb_ostream_t *stream, const pb_field_iter_t *field, void * const *arg)
{
/* 获取输入参数,作为编码的数据 */
pbcb_subject_t *encode_subject = (pbcb_subject_t*)*arg;
/* 不定长度数组采用以下方式编码,根据数组的长度进行循环编码 */
for (int i = 0; i < encode_subject->subjects_count; ++i)
{
if (!pb_encode_tag_for_field(stream, field))
{
printf("write_subject error 1. i = [%d] \n", i);
return false;
}
if (!pb_encode_submessage(stream, Student_subject_fields, &encode_subject->subjects[i]))
{
printf("write_subject error 2. i = [%d] \n", i);
return false;
}
}
printf("write_subject OK \r\n");
return true;
}
/* 开始打包Student数据 */
uint32_t Student_Info_Encode(Student_Info *Student_Info, uint8_t *buff, uint32_t len)
{
/* 传入编码回调函数和参数 */
Student_Info->subjects.funcs.encode = write_subject;
Student_Info->subjects.arg = &pbcb_subject;
/* 创建输出流 */
pb_ostream_t stream = pb_ostream_from_buffer(buff, len);
/* 根据字段列表和数据进行编码 */
if (!pb_encode(&stream, Student_Info_fields, Student_Info))
{
printf("Encoding failed: %s\n", PB_GET_ERROR(&stream));
return 0;
}
return stream.bytes_written;
}
接下来开始解包的过程:
/* 定义接收数据的变量 */
Student_subject recv_subjects[READ_SUBJECT_SIZE] = {0};
pbcb_subject_t pb_decoded_subject = {
.subjects_count = 0,
.subjects = recv_subjects,
};
/* 数组解码回调函数 */
bool read_subject(pb_istream_t *stream, const pb_field_iter_t *field, void **arg)
{
/* 传入保存解包后的结构体 */
pbcb_subject_t *decode_subject = (pbcb_subject_t *)(*arg);
/* 判断数组是否已满 */
if (decode_subject->subjects_count >= READ_SUBJECT_SIZE)
{
printf("subjects array exceeds size. \n");
return false;
}
/* 按照字段列表进行解包,解包后保存到数组中,并且计数值加一 */
if(!pb_decode(stream, Student_subject_fields, &decode_subject->subjects[decode_subject->subjects_count++]))
{
printf("read subject error. \n");
return false;
}
return true;
}
/* 开始解码Student数据 */
void Student_Info_Decode(Student_Info *decoded_Student_Info, uint8_t *buff, uint32_t len)
{
/* 创建输入流 */
pb_istream_t istream = pb_istream_from_buffer(buff, len);
/* 计数值清零 */
pb_decoded_subject.subjects_count = 0;
/* 传入解码回调函数和参数 */
decoded_Student_Info->subjects.arg = &pb_decoded_subject;
decoded_Student_Info->subjects.funcs.decode = read_subject;
/* 开始解码 */
if (!pb_decode(&istream, Student_Info_fields, decoded_Student_Info))
{
printf("Decoding failed: %s\n", PB_GET_ERROR(&istream));
return;
}
printf("Decoding successful.\n");
// 输出解码后的基本信息
printf("Name: %s\n", decoded_Student_Info->name);
printf("Age: %d\n", decoded_Student_Info->age);
printf("Height: %d\n", decoded_Student_Info->height);
/* 打印解码后的subject信息 */
for (int i = 0; i < pb_decoded_subject.subjects_count; ++i)
{
printf("subjects name: %s, score: %d\n", pb_decoded_subject.subjects[i].name, pb_decoded_subject.subjects[i].score);
}
}
完整代码+测试:
/*
* @Author: Troubadour 2276791354@qq.com
* @Date: 2024-05-21 14:31:09
* @LastEditors: Troubadour 2276791354@qq.com
* @LastEditTime: 2024-05-21 19:12:48
* @Version:
* @Description:
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pb_decode.h"
#include "pb_encode.h"
#include "student.pb.h"
#include "pb.h"
#define READ_SUBJECT_SIZE (10)
/* 定义一下数据内容. */
Student_Info Student_Info_Raw = {
.name = "Troubadour",
.height = 180,
.age = 18,
};
/* 建议额外定义一个结构体,存放数据打包的长度和数据. */
typedef struct
{
uint8_t subjects_count;
Student_subject *subjects;
} pbcb_subject_t;
/* 填充数据. */
Student_subject std_subject[] = {
{.name = "C", .score = 100,},
{.name = "C++", .score = 100, },
{.name = "Python", .score = 100,},
{.name = "Java", .score = 100,},
{.name = "JavaScrip", .score = 100,}
};
/* 定义好发送的数据和数组长度 */
pbcb_subject_t pbcb_subject = {
.subjects_count = sizeof(std_subject) / sizeof(std_subject[0]),
.subjects = std_subject,
};
/* 数组编码回调函数 */
bool write_subject(pb_ostream_t *stream, const pb_field_iter_t *field, void * const *arg)
{
/* 获取输入参数,作为编码的数据 */
pbcb_subject_t *encode_subject = (pbcb_subject_t*)*arg;
/* 不定长度数组采用以下方式编码,根据数组的长度进行循环编码 */
for (int i = 0; i < encode_subject->subjects_count; ++i)
{
if (!pb_encode_tag_for_field(stream, field))
{
printf("write_subject error 1. i = [%d] \n", i);
return false;
}
if (!pb_encode_submessage(stream, Student_subject_fields, &encode_subject->subjects[i]))
{
printf("write_subject error 2. i = [%d] \n", i);
return false;
}
}
printf("write_subject OK \r\n");
return true;
}
/* 开始打包Student数据 */
uint32_t Student_Info_Encode(Student_Info *Student_Info, uint8_t *buff, uint32_t len)
{
/* 传入编码回调函数和参数 */
Student_Info->subjects.funcs.encode = write_subject;
Student_Info->subjects.arg = &pbcb_subject;
/* 创建输出流 */
pb_ostream_t stream = pb_ostream_from_buffer(buff, len);
/* 根据字段列表和数据进行编码 */
if (!pb_encode(&stream, Student_Info_fields, Student_Info))
{
printf("Encoding failed: %s\n", PB_GET_ERROR(&stream));
return 0;
}
return stream.bytes_written;
}
/* 定义接收数据的变量 */
Student_subject recv_subjects[READ_SUBJECT_SIZE] = {0};
pbcb_subject_t pb_decoded_subject = {
.subjects_count = 0,
.subjects = recv_subjects,
};
/* 数组解码回调函数 */
bool read_subject(pb_istream_t *stream, const pb_field_iter_t *field, void **arg)
{
/* 传入保存解包后的结构体 */
pbcb_subject_t *decode_subject = (pbcb_subject_t *)(*arg);
/* 判断数组是否已满 */
if (decode_subject->subjects_count >= READ_SUBJECT_SIZE)
{
printf("subjects array exceeds size. \n");
return false;
}
/* 按照字段列表进行解包,解包后保存到数组中,并且计数值加一 */
if(!pb_decode(stream, Student_subject_fields, &decode_subject->subjects[decode_subject->subjects_count++]))
{
printf("read subject error. \n");
return false;
}
return true;
}
/* 开始解码Student数据 */
void Student_Info_Decode(Student_Info *decoded_Student_Info, uint8_t *buff, uint32_t len)
{
/* 创建输入流 */
pb_istream_t istream = pb_istream_from_buffer(buff, len);
/* 计数值清零 */
pb_decoded_subject.subjects_count = 0;
/* 传入解码回调函数和参数 */
decoded_Student_Info->subjects.arg = &pb_decoded_subject;
decoded_Student_Info->subjects.funcs.decode = read_subject;
/* 开始解码 */
if (!pb_decode(&istream, Student_Info_fields, decoded_Student_Info))
{
printf("Decoding failed: %s\n", PB_GET_ERROR(&istream));
return;
}
printf("Decoding successful.\n");
// 输出解码后的基本信息
printf("Name: %s\n", decoded_Student_Info->name);
printf("Age: %d\n", decoded_Student_Info->age);
printf("Height: %d\n", decoded_Student_Info->height);
/* 打印解码后的subject信息 */
for (int i = 0; i < pb_decoded_subject.subjects_count; ++i)
{
printf("subjects name: %s, score: %d\n", pb_decoded_subject.subjects[i].name, pb_decoded_subject.subjects[i].score);
}
}
int main(void)
{
/* 创建一个存放编码后的数据 */
uint8_t SendBuff[200] = {0};
uint32_t len = 0;
Student_Info decode_Student;
printf("test protobuf. \n", len);
/* 编码并获取长度 */
len = Student_Info_Encode(&Student_Info_Raw, SendBuff, sizeof(SendBuff));
if (len <= 0)
{
printf("Encoding failed.\n");
}
printf("Encoding successful, length: %d bytes.\n", len);
/* 根据长度解码 */
Student_Info_Decode(&decode_Student, SendBuff, len);
return 0;
}
运行结果,解码后数据正确!
test protobuf.
write_subject OK
Encoding successful, length: 70 bytes.
Decoding successful.
Name: Troubadour
Age: 18
Height: 180
subjects name: C, score: 100
subjects name: C++, score: 100
subjects name: Python, score: 100
subjects name: Java, score: 100
subjects name: JavaScrip, score: 100
五、生成不固定大小的数组(整形、枚举或者Bool类型)
前面说了结构体的数组,这里说一下整形的数组。区别还是在于编码和解码的回调函数。
先看 .proto 文件和生成的结构体,int32和uint32编码和解码方法是一样的,这里以int32为例。
完整编码解码代码+测试结果:
/*
* @Author: Troubadour 2276791354@qq.com
* @Date: 2024-05-21 14:31:09
* @LastEditors: Troubadour 2276791354@qq.com
* @LastEditTime: 2024-05-22 10:20:12
* @Version:
* @Description:
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pb_decode.h"
#include "pb_encode.h"
#include "student.pb.h"
#include "pb.h"
#define READ_MAX_SIZE (30)
/* 定义一下数据内容. */
Student_Info Student_Info_Raw = {
.name = "Troubadour",
.height = 180,
.age = 18,
};
/* 建议额外定义一个结构体,存放数据打包的长度和数据. */
typedef struct
{
uint16_t number_count;
int32_t *numbers;
} pb_cb_intType;
/* 填充数据. */
int32_t number[] = {-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
/* 定义好要编码的数据和数组长度 */
pb_cb_intType pb_encode_number = {
.number_count = sizeof(number) / sizeof(number[0]),
.numbers = number,
};
/* 数组编码回调函数 */
bool write_number_cb(pb_ostream_t *stream, const pb_field_iter_t *field, void * const *arg)
{
/* 获取输入参数,作为编码的数据 */
pb_cb_intType *encode_number = (pb_cb_intType*)*arg;
/* 不定长度数组采用以下方式编码,根据数组的长度进行循环编码 */
for (int i = 0; i < encode_number->number_count; ++i)
{
if (!pb_encode_tag_for_field(stream, field))
{
printf("pb_encode_varint error 1. i = [%d] \n", i);
return false;
}
/* 该编码函数适用: bool, enum, int32, int64, uint32 和 uint64 字段类型. */
if (!pb_encode_varint(stream, (uint64_t)encode_number->numbers[i]))
{
printf("pb_encode_varint error 2. i = [%d] \n", i);
return false;
}
}
printf("pb_encode_varint OK \r\n");
return true;
}
/* 开始打包Student数据 */
uint32_t Student_Info_Encode(Student_Info *Student_Info, uint8_t *buff, uint32_t len)
{
/* 传入编码回调函数和参数 */
Student_Info->number.funcs.encode = write_number_cb;
Student_Info->number.arg = &pb_encode_number;
/* 创建输出流 */
pb_ostream_t stream = pb_ostream_from_buffer(buff, len);
/* 根据字段列表和数据进行编码 */
if (!pb_encode(&stream, Student_Info_fields, Student_Info))
{
printf("Encoding failed: %s\n", PB_GET_ERROR(&stream));
return 0;
}
return stream.bytes_written;
}
/* 定义接收数据的变量,或者你也可以选择采用申请动态内存的方式. */
int32_t recv_numbers[READ_MAX_SIZE] = {0};
pb_cb_intType pb_decode_number = {
.number_count = 0,
.numbers = recv_numbers,
};
/* 数组解码回调函数 */
bool read_number_cb(pb_istream_t *stream, const pb_field_iter_t *field, void **arg)
{
/* 传入保存解包后的结构体 */
pb_cb_intType *decode_number = (pb_cb_intType*)*arg;
/* 判断数组是否已满 */
if (decode_number->number_count >= READ_MAX_SIZE)
{
printf("subjects array exceeds size. \n");
return false;
}
/* 该解码函数适用: bool, enum, int32, int64, uint32 和 uint64 字段类型. */
if (!pb_decode_varint(stream, (uint64_t *)&decode_number->numbers[decode_number->number_count++]))
{
printf("pb_decode_varint32 error. \n");
return false;
}
return true;
}
/* 开始解码Student数据 */
void Student_Info_Decode(Student_Info *decoded_Student_Info, uint8_t *buff, uint32_t len)
{
/* 创建输入流 */
pb_istream_t istream = pb_istream_from_buffer(buff, len);
/* 计数值清零 */
pb_decode_number.number_count = 0;
/* 传入解码回调函数和参数 */
decoded_Student_Info->number.funcs.decode = read_number_cb;
decoded_Student_Info->number.arg = &pb_decode_number;
/* 开始解码 */
if (!pb_decode(&istream, Student_Info_fields, decoded_Student_Info))
{
printf("Decoding failed: %s\n", PB_GET_ERROR(&istream));
return;
}
printf("Decoding successful.\n");
// 输出解码后的基本信息
printf("Name: %s\n", decoded_Student_Info->name);
printf("Age: %d\n", decoded_Student_Info->age);
printf("Height: %d\n", decoded_Student_Info->height);
/* 打印解码后的subject信息 */
printf("numbers: ");
for (int i = 0; i < pb_decode_number.number_count; ++i)
{
printf("%d ", pb_decode_number.numbers[i]);
}
printf("\n");
}
int main(void)
{
/* 创建一个存放编码后的数据 */
uint8_t SendBuff[200] = {0};
uint32_t len = 0;
Student_Info decode_Student;
printf("test protobuf. \n", len);
/* 编码并获取长度 */
len = Student_Info_Encode(&Student_Info_Raw, SendBuff, sizeof(SendBuff));
if (len <= 0)
{
printf("Encoding failed.\n");
}
printf("Encoding successful, length: %d bytes.\n", len);
/* 根据长度解码 */
Student_Info_Decode(&decode_Student, SendBuff, len);
return 0;
}
test protobuf.
pb_encode_varint OK
Encoding successful, length: 94 bytes.
Decoding successful.
Name: Troubadour
Age: 18
Height: 180
numbers: -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7 8 9 10
六、生成不固定大小的数组(浮点型)
浮点型分 float 和 double 两种类型,分别对应两个不同的编码和解码函数。这里以double类型为例(代码中已注明 float 的使用方法)
配置文件:
完整编码解码代码+测试结果:
/*
* @Author: Troubadour 2276791354@qq.com
* @Date: 2024-05-21 14:31:09
* @LastEditors: Troubadour 2276791354@qq.com
* @LastEditTime: 2024-05-22 10:29:25
* @Version:
* @Description:
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pb_decode.h"
#include "pb_encode.h"
#include "student.pb.h"
#include "pb.h"
#define READ_MAX_SIZE (30)
/* 定义一下数据内容. */
Student_Info Student_Info_Raw = {
.name = "Troubadour",
.height = 180,
.age = 18,
};
/* 建议额外定义一个结构体,存放数据打包的长度和数据. */
typedef struct
{
uint16_t number_count;
double *numbers;
} pb_cb_intType;
/* 填充数据. */
double number[] = {-5.9, -4.9, -3.9, -2.8, -1.8, 0, 1.2, 2.5, 3.5, 4.6, 5.5, 6.6, 7.7, 8.8, 9.9, 10.10, 999999.123456};
/* 定义好要编码的数据和数组长度 */
pb_cb_intType pb_encode_number = {
.number_count = sizeof(number) / sizeof(number[0]),
.numbers = number,
};
/* 数组编码回调函数 */
bool write_double_cb(pb_ostream_t *stream, const pb_field_iter_t *field, void * const *arg)
{
/* 获取输入参数,作为编码的数据 */
pb_cb_intType *encode_number = (pb_cb_intType*)*arg;
/* 不定长度数组采用以下方式编码,根据数组的长度进行循环编码 */
for (int i = 0; i < encode_number->number_count; ++i)
{
if (!pb_encode_tag_for_field(stream, field))
{
printf("pb_encode_varint error 1. i = [%d] \n", i);
return false;
}
/* 该编码函数可用于双精度浮点型字段类型. 单精度浮点型请使用 pb_encode_fixed32 函数。 */
if (!pb_encode_fixed64(stream, &encode_number->numbers[i]))
{
printf("pb_encode_varint error 2. i = [%d] \n", i);
return false;
}
}
printf("pb_encode_varint OK \r\n");
return true;
}
/* 开始编码数据 */
uint32_t Student_Info_Encode(Student_Info *Student_Info, uint8_t *buff, uint32_t len)
{
/* 传入编码回调函数和参数 */
Student_Info->float_number.funcs.encode = write_double_cb;
Student_Info->float_number.arg = &pb_encode_number;
/* 创建输出流 */
pb_ostream_t stream = pb_ostream_from_buffer(buff, len);
/* 根据字段列表和数据进行编码 */
if (!pb_encode(&stream, Student_Info_fields, Student_Info))
{
printf("Encoding failed: %s\n", PB_GET_ERROR(&stream));
return 0;
}
return stream.bytes_written;
}
/* 定义接收数据的变量,或者你也可以选择采用申请动态内存的方式. */
double recv_numbers[READ_MAX_SIZE] = {0};
pb_cb_intType pb_decode_number = {
.number_count = 0,
.numbers = recv_numbers,
};
/* 数组解码回调函数 */
bool read_double_cb(pb_istream_t *stream, const pb_field_iter_t *field, void **arg)
{
/* 传入保存解包后的结构体 */
pb_cb_intType *decode_number = (pb_cb_intType*)*arg;
/* 判断数组是否已满 */
if (decode_number->number_count >= READ_MAX_SIZE)
{
printf("subjects array exceeds size. \n");
return false;
}
/* 该解码函数用于双精度浮点型字段类型. 单精度浮点型请使用 pb_decode_fixed32 函数。 */
if (!pb_decode_fixed64(stream, &decode_number->numbers[decode_number->number_count++]))
{
printf("pb_decode_varint32 error. \n");
return false;
}
return true;
}
/* 开始解码Student数据 */
void Student_Info_Decode(Student_Info *decoded_Student_Info, uint8_t *buff, uint32_t len)
{
/* 创建输入流 */
pb_istream_t istream = pb_istream_from_buffer(buff, len);
/* 计数值清零 */
pb_decode_number.number_count = 0;
/* 传入解码回调函数和参数 */
decoded_Student_Info->float_number.funcs.decode = read_double_cb;
decoded_Student_Info->float_number.arg = &pb_decode_number;
/* 开始解码 */
if (!pb_decode(&istream, Student_Info_fields, decoded_Student_Info))
{
printf("Decoding failed: %s\n", PB_GET_ERROR(&istream));
return;
}
printf("Decoding successful.\n");
// 输出解码后的基本信息
printf("Name: %s\n", decoded_Student_Info->name);
printf("Age: %d\n", decoded_Student_Info->age);
printf("Height: %d\n", decoded_Student_Info->height);
/* 打印解码后的subject信息 */
printf("doubles: ");
for (int i = 0; i < pb_decode_number.number_count; ++i)
{
printf("%f ", pb_decode_number.numbers[i]);
}
printf("\n");
}
int main(void)
{
/* 创建一个存放编码后的数据 */
uint8_t SendBuff[200] = {0};
uint32_t len = 0;
Student_Info decode_Student;
printf("test protobuf. \n", len);
/* 编码并获取长度 */
len = Student_Info_Encode(&Student_Info_Raw, SendBuff, sizeof(SendBuff));
if (len <= 0)
{
printf("Encoding failed.\n");
}
printf("Encoding successful, length: %d bytes.\n", len);
/* 根据长度解码 */
Student_Info_Decode(&decode_Student, SendBuff, len);
return 0;
}
七、生成不固定大小的字符串数组
配置和生成的文件。
完整编码解码代码+测试结果:
/*
* @Author: Troubadour 2276791354@qq.com
* @Date: 2024-05-21 14:31:09
* @LastEditors: Troubadour 2276791354@qq.com
* @LastEditTime: 2024-05-22 11:35:50
* @Version:
* @Description:
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pb_decode.h"
#include "pb_encode.h"
#include "student.pb.h"
#include "pb.h"
/* 定义一下数据内容. */
Student_Info Student_Info_Raw = {
.height = 180,
.age = 18,
};
#define WRITE_STRING "TroubadourCSDN"
/* 数组编码回调函数 */
bool write_string_cb(pb_ostream_t *stream, const pb_field_iter_t *field, void * const *arg)
{
/* 获取输入参数,作为编码的数据 */
const pb_byte_t *str = (const pb_byte_t *)*arg;
/* 编码标题 */
if (!pb_encode_tag_for_field(stream, field))
{
printf("write_string_cb error 1. \n");
return false;
}
/* 编码字符串 */
if (!pb_encode_string(stream, str, strlen((const char *)str)))
{
printf("write_string_cb error 2. \n");
return false;
}
printf("write_string_cb OK \r\n");
return true;
}
/* 开始打包Student数据 */
uint32_t Student_Info_Encode(Student_Info *Student_Info, uint8_t *buff, uint32_t len)
{
/* 传入编码回调函数和参数 */
Student_Info->name.funcs.encode = write_string_cb;
Student_Info->name.arg = WRITE_STRING;
/* 创建输出流 */
pb_ostream_t stream = pb_ostream_from_buffer(buff, len);
/* 根据字段列表和数据进行编码 */
if (!pb_encode(&stream, Student_Info_fields, Student_Info))
{
printf("Encoding failed: %s\n", PB_GET_ERROR(&stream));
return 0;
}
return stream.bytes_written;
}
/* 数组解码回调函数 */
bool read_string_cb(pb_istream_t *stream, const pb_field_iter_t *field, void **arg)
{
/* 传入保存解码后的数据*/
pb_byte_t *str = (pb_byte_t *)*arg;
/* 获取字符串字段的长度 */
uint32_t stream_len = stream->bytes_left;
if (str == NULL)
{
printf("malloc error. \n");
return false;
}
/* 读取字符串 */
if (!pb_read(stream, str, stream_len))
{
printf("pb_decode_varint32 error. \n");
free(str);
return false;
}
return true;
}
/* 开始解码Student数据 */
void Student_Info_Decode(Student_Info *decoded_Student_Info, uint8_t *buff, uint32_t len)
{
/* 保存字符串的地址 */
char *student_name[64] = {0};
/* 创建输入流 */
pb_istream_t istream = pb_istream_from_buffer(buff, len);
/* 传入解码回调函数和参数 */
decoded_Student_Info->name.funcs.decode = read_string_cb;
decoded_Student_Info->name.arg = student_name;
/* 开始解码 */
if (!pb_decode(&istream, Student_Info_fields, decoded_Student_Info))
{
printf("Decoding failed: %s\n", PB_GET_ERROR(&istream));
return;
}
printf("Decoding successful.\n");
// 输出解码后的基本信息
printf("naem: %s \n", student_name);
printf("Age: %d\n", decoded_Student_Info->age);
printf("Height: %d\n", decoded_Student_Info->height);
}
int main(void)
{
/* 创建一个存放编码后的数据 */
uint8_t SendBuff[200] = {0};
uint32_t len = 0;
Student_Info decode_Student;
printf("test protobuf. \n");
/* 编码并获取长度 */
len = Student_Info_Encode(&Student_Info_Raw, SendBuff, sizeof(SendBuff));
if (len <= 0)
{
printf("Encoding failed.\n");
}
printf("Encoding successful, length: %d bytes.\n", len);
/* 根据长度解码 */
Student_Info_Decode(&decode_Student, SendBuff, len);
return 0;
}
test protobuf.
write_string_cb OK
Encoding successful, length: 21 bytes.
Decoding successful.
naem: TroubadourCSDN
Age: 18
Height: 180
更多推荐
所有评论(0)