前言
在最近弄的蓝牙项目中,发现有些平台的内存非常小。如果使用前面介绍的protobuf-c进行数据的序列化,会因为内存限制没有办法跑起来;
对于嵌入式C语音需要进行数据的序列化Nanopb支持是比较好的,使用起来也相对比较简单;Nanopb编解码的接口是统一的(pb_encode
和pb_decode
),但是protobuf-c会为每一个不同的message
生产一个编解码的接口。
Nanopb下载与安装
下载地址:https://jpa.kapsi.fi/nanopb/download/
我这里下载的是Linux版本:nanopb-0.3.9.2-linux-x86.tar.gz
我们在下载解压后,下面的几个文件将会在编解码时会用到:pb_common.c
、pb_common.h
、pb_decode.c
、pb_decode.h
、pb_encode.c
、pb_encode.h
、pb.h
,这些文件放在我们需要移植的平台上。
定义.proto文件
- 我这里随便定义了一个
UserInformation.proto
.
syntax = "proto3";
option optimize_for = LITE_RUNTIME;
enum UserStatus {
UNKNOWN = 0;
IDLE = 1;
BUSY = 2;
}
message UserInformation {
string name = 1;
uint32 age = 2;
string phone = 3;
UserStatus stat = 4;
string email = 5;
}
-
在
.proto
中optimize_for的参数:
option optimize_for = LITE_RUNTIME;
optimize_for是文件级别的选项,Protocol Buffer定义三种优化级别SPEED/CODE_SIZE/LITE_RUNTIME
。缺省情况下是SPEED
。SPEED: 表示生成的代码运行效率高,但是由此生成的代码编译后会占用更多的空间。
CODE_SIZE: 和SPEED
恰恰相反,代码运行效率较低,但是由此生成的代码编译后会占用更少的空间,通常用于资源有限的平台,如Mobile。
LITE_RUNTIME: 生成的代码执行效率高,同时生成代码编译后的所占用的空间也是非常少。这是以牺牲Protocol Buffer提供的反射功能为代价的。因此我们在C++中链接Protocol Buffer库时仅需链接libprotobuf-lite,而非libprotobuf。在Java中仅需包含protobuf-java-2.4.1-lite.jar,而非protobuf-java-2.4.1.jar。注:对于LITE_MESSAGE选项而言,其生成的代码均将继承自MessageLite,而非Message。
-
特别注意:
Nanopb
编译器不支持string
的动态定义,Protobuf-c
编译出来是char *
指针类型,但是Nanopb
编译一定是char
数组类型。所以,这里需要定义一个UserInformation.options
:
// *******************************
// *** UserInformation options ***
// *******************************
//
UserInformation.name max_size:20
UserInformation.phone max_size:16
UserInformation.email max_size:30
编译.proto文件
- 编译C code命令:
./generator-bin/protoc --nanopb_out=./ UserInformation.proto
- 编译C++ code命令:
./generator-bin/protoc --cpp_out=./ UserInformation.proto
测试demo code
对user data进行封包,主要4个步骤:
- 定义并初始化
UserInformation userInfo
变量; - 对
userInfo
进行赋值; - 使用
pb_ostream_from_buffer
生成encode需要的pb_ostream_t
,里面包含encode后输出的buffer addr; - 使用
pb_encode
进行编码;
static int pack_user_data(uint8_t *buffer)
{
//Initialize the UserInformation structure;
UserInformation userInfo = UserInformation_init_zero;
// Initialize buffer
memset(buffer, 0, DATA_BUFFER_SIZE);
strcpy(userInfo.name, "Benjamin");
userInfo.age = 18;
strcpy(userInfo.phone, "0755-12345678");
userInfo.stat = UserStatus_IDLE;
strcpy(userInfo.email, "ZhangSan@123.com");
// encode userInfo data
pb_ostream_t enc_stream;
enc_stream = pb_ostream_from_buffer(buffer, DATA_BUFFER_SIZE);
if (!pb_encode(&enc_stream, UserInformation_fields, &userInfo))
{
//encode error happened
printf("pb encode error in %s [%s]\n", __func__,PB_GET_ERROR(&enc_stream));
return -1;
}
return enc_stream.bytes_written;
}
对user data进行解包,主要3个步骤:
- 定义并初始化
UserInformation userInfo
变量; - 使用
pb_istream_from_buffer
生成decode需要的pb_istream_t
,里面包含将要decode的buffer addr; - 使用
pb_decode
进行解码;
static int unpack_user_data(const uint8_t *buffer, size_t len)
{
UserInformation userInfo;
// decode userInfo data
pb_istream_t dec_stream;
dec_stream = pb_istream_from_buffer(buffer, len);
if (!pb_decode(&dec_stream, UserInformation_fields, &userInfo))
{
printf("pb decode error in %s\n", __func__);
return -1;
}
printf("Unpack: %s %d %s %s\n", userInfo.name, userInfo.age, userInfo.phone, userInfo.email);
return 0;
}
main函数进行调用
int main()
{
uint8_t buffer[DATA_BUFFER_SIZE];
int lenght = pack_user_data(buffer);
if(lenght<0){
printf("main: pack_user_data is fail!!!\n");
return -1;
}
printf("User data len: %d\n",lenght);
unpack_user_data(buffer, lenght);
return 0;
}
运行的结果如下: