详解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 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

  • 17
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Troubadour~

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

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

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

打赏作者

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

抵扣说明:

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

余额充值