protobuf

protobuf


Google Protocol Buffer(简称 Protobuf)是一种轻便高效的结构化数据存储格式,平台无关、语言无关、可扩展,可用于通讯协议和数据存储等领域。

优点

  • 平台无关,语言无关,可扩展;
  • 提供了友好的动态库,使用简单;
  • 解析速度快,比对应的XML快约20-100倍;
  • 序列化数据非常简洁、紧凑,与XML相比,其序列化之后的数据量约为1/3到1/10;

编译安装

源码: https://github.com/google/protobuf
依赖: build-essential autoconf automake libtool curl

$ ./autogen.sh
$ ./configure
$ make
$ make check
$ sudo make install

syntax

// rs.UserInfo.proto

syntax = "proto2";

package rs;
//import "xxx.proto";

message UserInfo{
	optional uint32 id = 1;
	required string name = 2;
	required string pswd = 3;
	
	enum Type{
		ROOT = 0,
		ADMIN = 1,
		USUAL = 2,
		VISITOR = 4,
	}
	required Type type = 4;
	
	repeated string phone = 5;
	repeated string email = 6;
}

basic

  • syntax表示使用语法版本
  • package类似c++中的namespace,归类并避免命名冲突
  • import引入其他文件
  • message用于定义结构体
  • enum用于枚举

字段修饰符

proto2:

  • required:必须的
  • optional:可选的
  • repeated:重复的(类比数组,可以为0)

proto3:

  • singular:单一的(默认,可以省略)
  • repeated:重复的

字段类型

proto type类型码二进制
bool、enum、int32、int64、sint32、sint64、uint32、uint640Varint可变长整型
fixed64、sfixed64、double1固定64bits
string、bytes、inner message、repeated field2Length-delimted
group start(deprecated)3
group end(deprecated)4
fixed32、sfixed32、float5固定32bits

varint

Varint 是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。比如对于 int32 类型的数字,一般需要 4 个 byte 来表示。但是采用 Varint,对于很小的 int32 类型的数字,则可以用 1 个 byte 来表示。当然凡事都有好的也有不好的一面,采用 Varint 表示法,大的数字则需要 5 个 byte 来表示。从统计的角度来说,一般不会所有的消息中的数字都是大数,因此大多数情况下,采用 Varint 后,可以用更少的字节数来表示数字信息。

编码规则:

  • 使用Little-Endian小端字节序;
  • 每个字节最高位是标志位,0表示结束,1表示继续;后7为保存数据;
  • 因为负数最高位为1,所以先要进行int2uint转换,采用ZigZag方法实现;
uint32 Zig(int32 value){
    return (uint)((value << 1) ^ (value >> 31));    
}

int32 Zag(uint32 value){
    return (-(value & 0x01)) ^ ((value >> 1) & ~( 1<< 31));
}
Original(原始值)(编码后的值)EncodeAs
00
-11
12
-23
24
21474836474294967294
-21474836484294967295

for example:

typenum
dec300
hex0x012C
bin00000001 00101100
小端00101100 00000001
七位X0101100 X0000010
varint10101100 00000010

message存储格式

message采用key[-lenght]-value存储格式

  • key = field_num << 3 | data_type
  • 对于Lenght-delimited即data_type == 2类型数据紧跟着length信息,标识value的长度
  • key、length、data_type == 0的value都采用varint编码

生成代码

使用protoc编译proto文件即可自动生成代码,下面以cpp为例

protoc UserInfo.proto --cpp_out=cpp

在cpp/rs目录下可以发现生成了两个文件UserInfo.pb.h,UserInfo.pb.cc
下面我们分析下生成代码提供的类和方法。

基类::google::protobuf::Message

序列化

  • SerializedAsString(),SerializedToString(std::string*):序列化为std::string
  • SerializedToArray(void*,int):序列化为byte数组
  • SerializedToOstream(ostream*):序列化到输出流

反序列化

  • ParseFromString(std::string& data):从字符串中反序列化
  • ParseFromArray(const void *,int):从字节序中反序列化
  • ParseFromIstream(istream*):从输入流中反序列化

getter、setter


all:

  • clear_field():重置字段

非repeated字段:

  • field():获取字段值,只读
  • set_field():设置字段
  • mutable_filed():返回字段指针,用于修改

repeated字段:

  • filed(int):获取字段对应索引值,只读
  • set_filed(int,x):设置字段对应索引值
  • mutable_filed(int):返回字段对应索引指针,用于修改
  • add_filed():添加字段
  • filed_size():返回字段元素个数

TestCase

#include "rs/UserInfo.pb.h"

using namespace rs;

int main(int argc, char* argv[]){
    UserInfo user;
    user.set_id(1);
    user.set_name("hw");
    user.set_pswd("123456");
    user.set_type(UserInfo_Type_ADMIN);
    user.add_phone("13012345678");
    std::string strUser = user.SerializeAsString();
    user.PrintDebugString();
    printf("protobuf bytes:%d\n\n", strUser.size());

    UserInfo user1;
    user1.ParseFromString(strUser);
    user1.PrintDebugString();
    char json[1024];
    snprintf(json, 1024, "{id:%d,name:%s,pswd:%s,type:%d,phone:[%s]}",
        user1.id(), user1.name().c_str(), user1.pswd().c_str(), user1.type(), user1.phone(0).c_str());
    puts(json);

    printf("phone_size:%d\n", user1.phone_size());
    printf("json bytes:%d\n", strlen(json));

    return 0;
}

示例代码展示了对UserInfo类的使用,序列化和反序列化的结果

编译:

g++ -g -Wall main.cpp rs/UserInfo.pb.cc -o test -lprotobuf

运行结果:

id: 1
name: "hw"
pswd: "123456"
type: ADMIN
phone: "13012345678"
protobuf bytes:29

id: 1
name: "hw"
pswd: "123456"
type: ADMIN
phone: "13012345678"
{id:1,name:hw,pswd:123456,type:1,phone:[13012345678]}
phone_size:1
json bytes:53

可以发现protobuf比json要节省很多字节,这是protobuf以牺牲可读性换来的

参考资料

google protobuf手册

ithewei CSDN认证博客专家 c/c++ Qt libhv
编程之路,其路漫漫,吾将上下而求索
https://github.com/ithewei
https://hewei.blog.csdn.net
©️2020 CSDN 皮肤主题: 代码科技 设计师:Amelia_0503 返回首页