详解protobuf-c之在C语言中如何使用repeated生成数组和字符串(包含配置pb_callback_t)

一、引言

        在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 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Troubadour~

觉得不错,打赏支持一下!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值