protocolbuffer(以下简称PB)是google 的一种数据交换的格式,它独立于语言,独立于平台。google 提供了多种语言的实现:java、c#、c++、go 和 python,每一种实现都包含了相应语言的编译器以及库文件。由于它是一种二进制的格式,比使用xml进行数据交换快许多。可以把它用于分布式应用之间的数据通信或者异构环境下的数据交换。作为一种效率和兼容性都很优秀的二进制数据传输格式,可以用于诸如网络传输、配置文件、数据存储等诸多领域。
最近有个项目在c语言中用到了protobuf,所以在这里记录一下protobuf的安装和使用方法,以备后用。
1.下载和编译安装包
要使用c语言版的protobuf需要下载两个安装包protobuf-2.6.1.tar.gz(protobuf库)和protobuf-c-1.1.1.tar.gz(可生成c语言格式的protobuf安装包)。
protobuf库现在已更新到protobuf-3.1.0.tar.gz,其中支持C++, Java, Python, C#, JavaNano, JavaScript, Ruby, Go, Objective-c。可以发现原生库并不支持C语言,所以需要安装生成protobuf-c的安装包。
(1)下载
protobuf库下载地址:
protobuf-c下载地址:
(2)编译(以protobuf-2.6.1.tar.gz和protobuf-c-1.1.1.tar.gz为例)
编译protobuf-2.6.1
-
[root@192 ]# tar zxvf protobuf-2.6.1.tar.gz
-
[root@192 ]# cd protobuf-2.6.1 --prefix=/usr
-
[root@192 ]# ./configure
-
[root@192 ]# make
编译protobuf-c-1.1.1
-
[root@192 ]# tar zxvf protobuf-c-1.1.1.tar.gz
-
[root@192 ]# cd protobuf-c-1.1.1
-
[root@192]# ./configure --prefix=/usr protobuf_CFLAGS=-I/home/aaron/Study/protobuf/protobuf-2.6.1/src LDFLAGS=-L/home/aaron/Study/protobuf/protobuf-2.6.1/src/.libs protobuf_LIBS=-lprotobuf PROTOC=/home/aaron/Study/protobuf/protobuf-2.6.1/src/protoc
-
[root@192 ]# make
编译完成后会在protobuf-c-1.1.1/protoc-c/目录下生成protoc-c可执行程序,这个程序是用来通过.proto文件生成pb-c.c和pb-c.h文件的。注意,由于以上两个包没有安装,执行protoc-c时,应该执行protobuf-c-1.1.1/protoc-c/目录下的protoc-c;如果要安装的话两个包都安装,make install即可,这时可以直接运行protoc-c命令的;执行protoc-c命令时需要加载libprotobuf.so.9动态库,该库在protobuf-2.6.1/src/.libs目录下,执行失败证明没有正确地加载该动态库路径。
2 .proto格式文件
.proto文件是用来交换数据的格式,你需要使用protobuf打包的数据结构都应该先写在.proto文件中。.proto文件中的数据结构和c语言中的数据结构有点不一样,具体来看一下下边这个例子。
文件名:game.proto
-
/**
-
* 角色的个人信息
-
*/
-
message personal_info {
-
required string name = 1; /* 角色名字 */
-
required int32 year = 2; /* 角色年龄 */
-
required string weapon = 3; /* 武器 */
-
repeated string armor = 4; /* 防具 */
-
optional int32 money = 5; /* 携带的钱 */
-
}
-
-
/**
-
* 团队信息
-
*/
-
message team_info {
-
required personal_info leader = 1; /* leader */
-
optional personal_info aaron = 2; /* 队员 */
-
}
比如我现在要使用protobuf传输一个游戏团队的基本信息,于是我在game.proto文件中写了两个message结构体。message team_info表示团队成员,leader是必须的,所以他的类型是required。而队员aaron可能不存在,所以他的类型是optional的。message personal_info表示每个成员的基本信息,名字name必须且是字符串,所以name的类型是required string,string表示字符串,而不是c语言中使用char*表示。年龄是int整型,用int32表示。再看一下armor,它的类型是repeated,表示是一个数组,每个数组成员是string类型,这的意思是说防具可能有多个。而money可能没有,所以用optional表示,当然money的值为0,也可以表示没有, 这里设置成optional只是做的一个例子,并不是很合理。
更多参考:
1.3 生成并使用.pb-c.c和.pb-c.h文件
(1)通过使用protoc-c命令使game.proto文件生成相应的c文件。
-
[root@192 ]# ../protobuf-c-1.1.1/protoc-c/protoc-c --c_out=./ game.proto
执行完上边的命令后,可以在game.proto所在的文件夹中看见game.pb-c.c和game.pb-c.h两个文件,编写代码时就会用到这两个文件中的结构体。
(2)使用.pb-c.c和.pb-c.h文件
除了生成的game.pb-c.c和game.pb-c.h会用到,还需要用到protobuf-c-1.1.1/protobuf-c路径下的 protobuf-c.c和protobuf-c.h中的库函数。因此整个工程用了main.c, protobuf-c.c, protobuf-c.h, game.pb-c.c,和game.pb-c.h共5个文件。
首先需要将game.pb-c.h中的#include 修改成#include “protobuf-c.h”。接下来直接看main.c
文件名:main.c
-
#include <stdio.h>
-
#include <string.h>
-
#include <stdlib.h>
-
#include "game.pb-c.h"
-
-
static void proto_store_string(
-
char **addr,
-
const char * str,
-
int max_str_len)
-
{
-
*addr = (char *)malloc(max_str_len);
-
strncpy(*addr, str, max_str_len);
-
(*addr)[max_str_len - 1] = '\0';
-
}
-
-
int protobuf_game_pack(
-
unsigned char * out_buff,
-
int buff_len)
-
{
-
/** 1.Assembly and packaging data */
-
#define STR_LEN 20
-
-
/**
-
* 1.初始化方式有两种,一种是定义变量时,如下边的TEAM_INFO__INIT;
-
* 另一种是变量已经定义好了,则需要使用函数初始化,如personal_info__init()。
-
* 2.每一个结构都必须初始化之后才能使用,所有的初始化宏或函数都在相应的.pc-c.h中。
-
* 3.如果结构体成员是指向结构体的指针,则需要为其动态分配空间。原因是栈上的空间有可能不足,
-
* 堆上分配空间更安全。如下边的teaminfo.leader。
-
*/
-
TeamInfo teaminfo = TEAM_INFO__INIT;
-
teaminfo.leader = (PersonalInfo *)malloc(sizeof(PersonalInfo));
-
teaminfo.aaron = (PersonalInfo *)malloc(sizeof(PersonalInfo));
-
personal_info__init(teaminfo.leader);
-
personal_info__init(teaminfo.aaron);
-
-
/**
-
* 1.对于string成员也需要动态分配空间,如teaminfo.leader->name
-
* 2.对于数组成员,常常是二重指针,也需要分配空间,如teaminfo.leader->armor;
-
* 在结构体成员中也多出了一个n_armor成员,该成员需要填入数组的长度,方便打包时确认。
-
* 3.对于整型成员,如teaminfo.leader->money,发现它也多出了一个has_money成员,因为
-
* 在game.proto中我们对money定义的属性是optional。如果has_money=0,表示money成员没有数据;
-
* 反之,表示money成员带有数据。
-
* 4.对于string和结构体等成员,不管他们的类型是否是optional的,都不需要增加多余的成员。原因是这些成员
-
* 在结构体中是由指针表示的,可以直接检查该指针是否为空来判断该成员是否有数据。例如teaminfo.aaron。
-
*/
-
/** Assemble leader */
-
proto_store_string(&teaminfo.leader->name, "leader", STR_LEN);
-
teaminfo.leader->year = 20;
-
proto_store_string(&teaminfo.leader->weapon, "Lightning sword", STR_LEN);
-
teaminfo.leader->n_armor = 3;
-
teaminfo.leader->armor =
-
(char **)malloc(sizeof(char *) * teaminfo.leader->n_armor);
-
proto_store_string(&teaminfo.leader->armor[0], "Lightning armor", STR_LEN);
-
proto_store_string(&teaminfo.leader->armor[1], "Lightning pants", STR_LEN);
-
proto_store_string(&teaminfo.leader->armor[2], "Lightning boots", STR_LEN);
-
teaminfo.leader->has_money = 1;
-
teaminfo.leader->money = 1000;
-
-
/** Assemble aaron */
-
proto_store_string(&teaminfo.aaron->name, "aaron", STR_LEN);
-
teaminfo.aaron->year = 18;
-
proto_store_string(&teaminfo.aaron->weapon, "Fire sword", STR_LEN);
-
teaminfo.aaron->n_armor = 3;
-
teaminfo.aaron->armor =
-
(char **)malloc(sizeof(char *) * teaminfo.aaron->n_armor);
-
proto_store_string(&teaminfo.aaron->armor[0], "Fire armor", STR_LEN);
-
proto_store_string(&teaminfo.aaron->armor[1], "Fire pants", STR_LEN);
-
proto_store_string(&teaminfo.aaron->armor[2], "Fire boots", STR_LEN);
-
teaminfo.aaron->has_money = 0;
-
-
/**
-
* 1.结构体填充完数据后,我们需要将数据编码打包并复制到缓冲上。
-
* 2.在数据进行打包之前需要先判断打包后数据的长度是否比缓冲区大,
-
* 这时可以调用结构体对应的判断数据长度的函数,如team_info__get_packed_size()。
-
* 3.确认打包后长度确实比可使用的缓冲区小时,则可以直接调用结构体的打包函数将数据打包到
-
* 缓冲区上,如team_info__pack()。
-
*/
-
/** packaging data */
-
int len = team_info__get_packed_size(&teaminfo);
-
-
if (len > buff_len) {
-
printf("not enough memory..\n");
-
len = 0;
-
} else {
-
if (len != team_info__pack(&teaminfo, out_buff)) {
-
len = 0;
-
}
-
}
-
-
/**
-
* 1.释放动态分配的空间。
-
*/
-
/** free */
-
int i = 0;
-
for(; i < teaminfo.leader->n_armor; i++) {
-
free(teaminfo.leader->armor[i]);
-
}
-
free(teaminfo.leader->armor);
-
free(teaminfo.leader->weapon);
-
free(teaminfo.leader->name);
-
free(teaminfo.leader);
-
-
-
i = 0;
-
for(; i < teaminfo.aaron->n_armor; i++) {
-
free(teaminfo.aaron->armor[i]);
-
}
-
free(teaminfo.aaron->armor);
-
free(teaminfo.aaron->weapon);
-
free(teaminfo.aaron->name);
-
free(teaminfo.aaron);
-
-
return len;
-
}
-
-
int protobuf_game_parse(
-
unsigned char * in_buff,
-
int buff_len)
-
{
-
/**
-
* 调用解包函数,得到结构体指针
-
*/
-
/** parse data */
-
TeamInfo * teaminfo = team_info__unpack(NULL, buff_len, in_buff);
-
-
/**
-
* 获取数据,只要成员是指针都必须判断该指针是否为空,optional的其它类型也需要进行判断
-
*/
-
if (teaminfo->leader) {
-
PersonalInfo *leader = teaminfo->leader;
-
printf("leader->name [%s]\n",leader->name);
-
printf("leader->year [%d]\n",leader->year);
-
printf("leader->weapon [%s]\n",leader->weapon);
-
-
int i = 0;
-
for (; i < leader->n_armor; i++) { //数组需要判断长度
-
printf("leader->armor[%d] [%s]\n", i, leader->armor[i]);
-
}
-
-
if (leader->has_money) { //判断是否有money
-
printf("leader->money [%d]\n",leader->money);
-
}
-
}
-
-
printf("\n");
-
-
if (teaminfo->aaron) {
-
PersonalInfo *aaron = teaminfo->aaron;
-
printf("aaron->name [%s]\n",aaron->name);
-
printf("aaron->year [%d]\n",aaron->year);
-
printf("aaron->weapon [%s]\n",aaron->weapon);
-
-
int i = 0;
-
for (; i < aaron->n_armor; i++) {
-
printf("aaron->armor[%d] [%s]\n", i, aaron->armor[i]);
-
}
-
-
if (aaron->has_money) {
-
printf("leader->money [%d]\n",aaron->money);
-
}
-
}
-
-
/** free memory */
-
team_info__free_unpacked(teaminfo, NULL);
-
return 0;
-
}
-
-
void proto_test_demo(void)
-
{
-
#define BUFF_LEN 1024
-
unsigned char buff[BUFF_LEN] = {0};
-
int len = 0;
-
-
/* 打包数据 */
-
len = protobuf_game_pack(buff, BUFF_LEN);
-
-
/* 解包数据 */
-
protobuf_game_parse(buff, len);
-
}
-
-
int main(int argc, char **argv)
-
{
-
proto_test_demo();
-
}
运行结果:
-
[root@192 test_game]# cc -o main game.pb-c.c main.c protobuf-c.c
-
[root@192 test_game]# ./main
-
leader->name [leader]
-
leader->year [20]
-
leader->weapon [Lightning sword]
-
leader->armor[0] [Lightning armor]
-
leader->armor[1] [Lightning pants]
-
leader->armor[2] [Lightning boots]
-
leader->money [1000]
-
-
aaron->name [aaron]
-
aaron->year [18]
-
aaron->weapon [Fire sword]
-
aaron->armor[0] [Fire armor]
-
aaron->armor[1] [Fire pants]
-
aaron->armor[2] [Fire boots]
通过理解以上代码基本上可以使用protobuf-c了,更多的细节请参考:
1.4 注意事项
1.大端机编译protobuf-c.c时要定义WORDS_BIGENDIAN宏,不然float类型的打包会有问题。
2.protobuf-c的打包采用了zigzag编码,压缩了数据。可以参靠下边这个链接,学习一下zigzag编码原理。
http://blog.csdn.net/zgwangbo/article/details/51590186
阅读(7104) | 评论(0) | 转发(0) |