一、目的
本文档记录PB协议使用的过程。
二、工具准备
- nanopb工具:nanopb-0.3.8-windows-x86
三、应用说明
3.1 PB协议语法说明
1)google官网PB协议指导:
https://developers.google.cn/protocol-buffers/docs/proto?hl=zh-cn#options
2)”看,未来”博主的文档:
https://blog.51cto.com/u_15197573/2772508#import_99
3)PB协议相关函数说明:
https://jpa.kapsi.fi/nanopb/docs/reference.html#pb_encode_tag_for_field
3.2 编译
编写proto文件和options文件后,进入nanopb-0.3.8-windows-x86\generator-bin,打开命令行,输入以下命令。
protoc --nanopb_out=. test.proto
其中test.proto改成所需的文件名称。
输入该命令后,会生成对应的c和h文件,如图 1。
3.3 应用
3.3.1 搭建工程
添加中图 2的文件和编译生成的文件到工程中。
添加图 3的头文件以及编译生产的头文件。
图 3 PB协议相关头文件
3.3.2 字段赋值
1)字段规则为”required”或”optional”,并且字段长度固定时,相应字段直接赋值,参考以下示例。
syntax = "proto2"; message sub { required int32 a = 1; optional int32 b = 2; } message TreadReq { required sub dat1 = 1; required int32 dat2 = 2; optional string dat3 = 3; } |
代码 2 options文件配置
TreadReq.dat3 max_size:5 //设置长度 |
代码 3 生成的代码
/* Struct definitions */ typedef struct _sub { int32_t a; bool has_b; int32_t b; /* @@protoc_insertion_point(struct:sub) */ } sub; typedef struct _TreadReq { sub dat1; int32_t dat2; bool has_dat3; char dat3[5]; /* @@protoc_insertion_point(struct:TreadReq) */ } TreadReq; |
TreadReq tread; tread.dat1.a = 10; tread.dat1.has_b = true; tread.dat1.b = 10; tread.dat2 = 10; tread.has_dat3 = true; memcpy(tread.dat3,"12345",5); |
- 字段规则为”repeated”或不固定长度,使用回调函数赋值,参考以下示例。
代码 5 proto文件配置
syntax = "proto2"; message TreadReq { required string dat1 = 1; repeated int32 dat2 = 2; } |
代码 6 生成的代码
/* Struct definitions */ typedef struct _TreadReq { pb_callback_t dat1; pb_callback_t dat2; /* @@protoc_insertion_point(struct:TreadReq) */ } TreadReq; |
代码 7 字段赋值
bool dat1_callback(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) { if (!pb_encode_tag_for_field(stream, field)) //开始一个字段的赋值 { return false; } if(!pb_encode_string(stream, (const pb_byte_t *)str, 12 )) //写入12个字节 { return false; } } bool dat2_callback(pb_ostream_t *stream, const pb_field_t *field, void * const *arg) { uint8_t i = 0; for(i = 0; i < 20; i++) //repeated字段,重复赋值 { if (!pb_encode_tag_for_field(stream, field)) //开始一个字段的赋值 { return false; } if(!pb_encode_varint(stream, dat[i])) //赋值 { return false; } } } int main() { TreadReq tread; tread.dat1.funcs.encode = &dat1_callback; //使用回调函数赋值 tread.dat1.arg = NULL; tread.dat2.funcs.encode = &dat1_callback; //使用回调函数赋值 tread.dat2.arg = NULL; } |
3.3.3 数据编码encode
数据编码要使用pb_ostream_from_buffer()和pb_encode()。pb_ostream_from_buffer()用于指定缓存区,pb_encode()用于数据编码,具体说明参考PB协议相关函数说明。以代码 4为基础,进行数据编码,如代码 8。其中TreadReq_fields与TreadReq对应。
uint8_t buffer[100]; //协议数据缓存区,使用该缓存区发送协议数据 pb_ostream_t stream TreadReq tread; tread.dat1.a = 10; tread.dat1.has_b = true; tread.dat1.b = 10; tread.dat2 = 10; tread.has_dat3 = true; memcpy(tread.dat3,"12345",5); stream = pb_ostream_from_buffer(buffer, sizeof(buffer)); //指定缓存区 pb_encode(&stream, TreadReq_fields, &tread); //编码 |
3.3.4 数据解码decode
数据解码需要使用pb_istream_from_buffer()和pb_decode()。pb_istream_from_buffer()用于指定解码缓存区,pb_decode()用于数据解码。
- 字段规则为”required”或”optional”,并且字段长度固定时,可以直接使用对应字段的值。以3.3.2 1)为基础,进行数据解码,如代码 9。
void my_pb_decode(uint8_t *buf,uint32_t len) { TreadReq tread; pb_istream_t istream; istream = pb_istream_from_buffer(buf, len); //指定解码缓存区 pb_decode(&istream, TreadReq_fields, &tread); //解码 //规则为"required"时,直接使用字段的值 printf("required:%d,%d\r\n",tread.dat1.a,tread.dat2); if(tread.dat1.has_b) { //规则为"optional"时,先判断是否存在该字段,再使用该值 printf("optional:%d\r\n",tread.dat1.b); } if(tread.has_dat3) { for(int i = 0; i < 5; i++) { //规则为"optional"且长度固定时,先判断是否存在该字段,再使用该值 printf("%d ",tread.dat3[i]); } } } |
- 字段规则为”repeated”或者不定长度时,使用回调函数进行解码。以3.3.2 2)为基础,进行解码,如代码 10。
static bool read_repeated_string(pb_istream_t* stream, const pb_field_t* field, void** arg) { uint8_t*** expected = (uint8_t***)arg; size_t len = stream->bytes_left; if (!pb_read(stream, (uint8_t *)(*expected)++, len)) return false; return true; } static bool read_repeated_varint(pb_istream_t* stream, const pb_field_t* field, void** arg) { int32_t** expected = (int32_t**)arg; uint64_t value; if (!pb_decode_varint(stream, &value)) return false; *(*expected)++ = value; return true; } void my_pb_decode(uint8_t *buf,uint32_t len) { TreadReq tread; pb_istream_t istream; char str[10][100] = {0};//repeated规则,string类型,需要二维数组保存解码数据 int32_t dat[10] = {0};//repeated规则,int32类型,需要一维数组保存解码数据 tread.dat1.funcs.decode = read_repeated_string; //使用回调函数解码 tread.dat1.arg = str; tread.dat2.funcs.decode = &read_repeated_varint; //使用回调函数解码 tread.dat2.arg = dat; istream = pb_istream_from_buffer(buffer, stream.bytes_written); //指定缓存区 pb_decode(&istream, TreadReq_fields, &tread); //解码 //使用解码数据 printf("str:%s\r\n", str[0]); printf("buf:%d,%d,%d,%d,%d\r\n", dat[0], dat[1], dat[2], dat[3], dat[4]); } |