google protobuf学习笔记:编译安装、序列化、反序列化

简介

protobuf也叫protocol buffer是google 的一种数据交换的格式,它独立于语言,独立于平台。google 提供了多种语言的实现:java、c#、c++、go 和 python,每一种实现都包含了相应语言的编译器以及库文件。由于它是一种二进制的格式,比使用 xml 、json进行数据交换快许多。可以把它用于分布式应用之间的数据通信或者异构环境下的数据交换。作为一种效率和兼容性都很优秀的二进制数据传输格式,可以用于诸如网络传输、配置文件、数据存储等诸多领域。 protobuf在各种rpc的实现上都占据重要角色。

优点

 性能好/效率高  代码生成机制  支持“向后兼容”和“向前兼容”  支持多种编程语言

缺点

 应用不够广(相比xml和json)  二进制格式导致可读性差  缺乏自描述

安装

 github源代码下载地址:https://github.com/google/protobuf 源码包中的src/README.md, 有详细的安装说明,安装过程如下: 1、解压压缩包:unzip protobuf-master.zip 2、进入解压后的文件夹:cd protobuf-master 3、安装所需工具:sudo apt-get install autoconf automake libtool curl make g++ unzip 4、自动生成configure配置文件:./autogen.sh 5、配置环境:./configure 6、编译源代码(时间比较长):make 7、安装:sudo make install 8、刷新动态库:sudo ldconfig

编译.proto文件

 protoc:protobuf自带的编译工具,将.proto文件生成指定的类  –cpp_out:将生成的C++代码文件放到等号后面指定的目录,这里也指定当前目录

    通过protoc工具编译.proto文件时,编译器将生成所选择语言的代码,这些代码可以操作在.proto文件中定义的消息类型,包括获取、设置字段值,将消息序列化到一个输出流中,以及从一个输入流中解析消息。对C++来说,编译器会为每个.proto文件生成一个.h文件和一个.cc文件,.proto文件中的每一个消息有一个对应的类。

编译代码

g++ *.cpp *.c *.cc `pkg-config --cflags --libs protobuf`

 反引号(` ):反引号的作用就是将反引号内的linux命令执行  pkg-config 是通过库提供的一个.pc文件获得库的各种必要信息的,包括版本信息、编译和连接需要的参数等。  pkg-config –cflags protobuf:列出指定共享库的预处理和编译flags  pkg-config –libs protobuf:列出指定共享库的链接flags

在.proto文件中定义消息格式

消息由至少一个字段组合而成,类似于C语言中的结构体,每个字段都有一定的格式:

数据类型 字段名称 = 唯一的编号标签值;

syntax = "proto3"; //指定版本信息,不指定会报错

message Person  //message为关键字,作用为定义一种消息类型
{
    string name = 1;    //姓名
    int32 id = 2;       //id
    string email = 3;   //邮件
}

·数据类型

.proto类型

C++类型

备注

double

double

64位浮点数

float

float

32位浮点数

int32

int32

32位整数

int64

int64

64位整数

uint32

uint32

32位无符号整数

uint64

uint64

64位无符号整数

sint32

int32

32位整数,处理负数效率比int32更高

sint32

sint64

64位整数,处理负数效率比int64更高

fixed32

uint32

总是4个字节。如果数值总是比总是比228大的话,这个类型会比uint32高效。

fixed64

uint64

总是8个字节。如果数值总是比总是比256大的话,这个类型会比uint64高效。

sfixed32

int32

总是4个字节

sfixed64

int64

总是8个字节

bool

bool

布尔类型

string

string

一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本

bytes

string

处理多字节的语言字符、如中文

enum

enum

枚举

message

object of class

自定义的消息类型

proto文件即消息协议原型定义文件,在该文件中我们可以通过使用描述性语言,来良好的定义我们程序中需要用到数据格式。通过查看头文件,可以发现针对每个字段都会大致生成如下几种函数,以name为例。可以看出,对于每个字段会生成一个clear清除函数(clear_name)、set函数(set_name)、get函数(name和mutable_name)。

void clear_name();
void set_name(const ::std::string& value);
void set_name(const char* value);
void set_name(const char* value, size_t size);
const ::std::string& name() const;
::std::string* mutable_name();

C数组的序列化和反序列化

#include <iostream>
#include "person.pb.h"

using namespace std;

int main()
{
    char buf[1024];
    int len;

    GOOGLE_PROTOBUF_VERIFY_VERSION;

    Person obj;
    obj.set_name("gongluck");
    obj.set_id(1);
    *obj.mutable_email() = "http://blog.csdn.net/gongluck93";
    len = obj.ByteSize();
    cout << "len = " << len << endl;
    obj.SerializeToArray(buf, len);

    Person obj2;
    obj2.ParseFromArray(buf, len);
    cout << "name = " << obj2.name() << endl;
    cout << "id = " << obj2.id() << endl;
    cout << "email = " << obj2.email() << endl;

    google::protobuf::ShutdownProtobufLibrary();

    return 0;
}

C++ String的序列化和反序列化

#include <iostream>
#include "person.pb.h"

using namespace std;

int main()
{
    string str;

    GOOGLE_PROTOBUF_VERIFY_VERSION;

    Person obj;
    obj.set_name("gongluck");
    obj.set_id(1);
    *obj.mutable_email() = "http://blog.csdn.net/gongluck93";
    obj.SerializeToString(&str);

    Person obj2;
    obj2.ParseFromString(str);
    cout << "name = " << obj2.name() << endl;
    cout << "id = " << obj2.id() << endl;
    cout << "email = " << obj2.email() << endl;

    google::protobuf::ShutdownProtobufLibrary();

    return 0;
}

文件描述符序列化和反序列化

#include <unistd.h>
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "person.pb.h"

using namespace std;

int main()
{
    int fd = open("./testFileDesc.xxx", O_CREAT|O_TRUNC|O_RDWR, 0664);

    GOOGLE_PROTOBUF_VERIFY_VERSION;

    Person obj;
    obj.set_name("gongluck");
    obj.set_id(1);
    *obj.mutable_email() = "http://blog.csdn.net/gongluck93";
    obj.SerializeToFileDescriptor(fd);
    fsync(fd);
    lseek(fd, 0, SEEK_SET);

    Person obj2;
    obj2.ParseFromFileDescriptor(fd);
    cout << "name = " << obj2.name() << endl;
    cout << "id = " << obj2.id() << endl;
    cout << "email = " << obj2.email() << endl;

    google::protobuf::ShutdownProtobufLibrary();

    return 0;
}

C++ stream 序列化和反序列化

#include <iostream>
#include <fstream>
#include "person.pb.h"

using namespace std;

int main()
{
    fstream file("testStream.xxx", ios::in|ios::out|ios::trunc|ios::binary);

    GOOGLE_PROTOBUF_VERIFY_VERSION;

    Person obj;
    obj.set_name("gongluck");
    obj.set_id(1);
    *obj.mutable_email() = "http://blog.csdn.net/gongluck93";
    obj.SerializeToOstream(&file);
    file.flush();
    file.seekg(0, ios::beg);

    Person obj2;
    obj2.ParseFromIstream(&file);
    cout << "name = " << obj2.name() << endl;
    cout << "id = " << obj2.id() << endl;
    cout << "email = " << obj2.email() << endl;

    google::protobuf::ShutdownProtobufLibrary();

    file.close();

    return 0;
}

repeated限定修饰符

repeated 代表可重复,我们可以理解为数组。

syntax = "proto3";

message Person
{
    string name = 1;
    int32 id = 2;
    string email = 3;
}

message AddressBook
{
    repeated Person people = 1;
}

而对于字段修饰符为repeated的字段生成的函数,则稍微有一些不同,如people字段,则编译器会为其产生如下的代码:

int people_size() const;
void clear_people();
const ::Person& people(int index) const;
::Person* mutable_people(int index);
::Person* add_people();
::google::protobuf::RepeatedPtrField< ::Person >* mutable_people();
const ::google::protobuf::RepeatedPtrField< ::Person >& people() const;

·例子

#include <iostream>
#include <fstream>
#include "person.pb.h"

using namespace std;

int main()
{
    fstream file("testStream.xxx", ios::in|ios::out|ios::trunc|ios::binary);

    GOOGLE_PROTOBUF_VERIFY_VERSION;

    AddressBook obj;

    Person* p1 = obj.add_people();
    p1->set_name("gongluck");
    p1->set_id(1);
    *(p1->mutable_email()) = "http://blog.csdn.net/gongluck93";

    Person* p2 = obj.add_people();
    p2->set_name("panzhikun");
    p2->set_id(2);
    *(p2->mutable_email()) = "panzhikun@gg.com";

    obj.SerializeToOstream(&file);
    file.flush();
    file.seekg(0, ios::beg);

    AddressBook obj2;
    obj2.ParseFromIstream(&file);
    for(int i= 0; i< obj.people_size(); ++i)
    {
        Person per = obj2.people(i);
        cout << "name = " << per.name() << endl;
        cout << "id = " << per.id() << endl;
        cout << "email = " << per.email() << endl;
    }

    google::protobuf::ShutdownProtobufLibrary();

    file.close();

    return 0;
}

枚举

syntax = "proto3";

message Person
{
    string name = 1;
    int32 id = 2;
    string email = 3;

    enum PhoneType
    {
        MOBLIE = 0;//首成员必须为0
        HOME = 1;
        WORK = 2;
    }
    message PhoneNumber
    {
        string number = 1;
        PhoneType type = 2;
    }
    repeated PhoneNumber phones = 4;
}

message AddressBook
{
    repeated Person people = 1;
}

·例子

#include <iostream>
#include <fstream>
#include "person.pb.h"

using namespace std;

int main()
{
    fstream file("testStream.xxx", ios::in|ios::out|ios::trunc|ios::binary);

    GOOGLE_PROTOBUF_VERIFY_VERSION;

    AddressBook obj;

    Person* p1 = obj.add_people();
    p1->set_name("gongluck");
    p1->set_id(1);
    *(p1->mutable_email()) = "http://blog.csdn.net/gongluck93";

    Person::PhoneNumber* phone1 = p1->add_phones();
    phone1->set_number("110");
    phone1->set_type(Person::MOBLIE);
    Person::PhoneNumber* phone2 = p1->add_phones();
    phone2->set_number("120");
    phone2->set_type(Person::WORK);

    obj.SerializeToOstream(&file);
    file.flush();
    file.seekg(0, ios::beg);

    AddressBook obj2;
    obj2.ParseFromIstream(&file);
    for(int i= 0; i< obj.people_size(); ++i)
    {
        Person per = obj2.people(i);
        cout << "name = " << per.name() << endl;
        cout << "id = " << per.id() << endl;
        cout << "email = " << per.email() << endl;
        for(int j=0; j< per.phones_size(); ++j)
        {
            Person::PhoneNumber phonenum= per.phones(j);
            switch(phonenum.type())
            {
            case Person::MOBLIE:
                cout << "mobile : " ;
                break;
            case Person::WORK:
                cout << "work : ";
                break;
            case Person::HOME:
                cout << "home : ";
                break;
            default:
                cout << "Not Know : ";
                break;
            }
            cout << phonenum.number() << endl;
        }
    }

    google::protobuf::ShutdownProtobufLibrary();

    file.close();

    return 0;
}

 .proto文件新增一个可选的package声明符,用来防止不同的消息类型有命名冲突。包的声明符会根据使用语言的不同影响生成的代码。对于C++,产生的类会被包装在C++的命名空间中。

syntax = "proto3";

package Test;//package

message Person
{
    string name = 1;
    int32 id = 2;
    string email = 3;

    enum PhoneType
    {
        MOBLIE = 0;//首成员必须为0
        HOME = 1;
        WORK = 2;
    }
    message PhoneNumber
    {
        string number = 1;
        PhoneType type = 2;
    }
    repeated PhoneNumber phones = 4;
}

message AddressBook
{
    repeated Person people = 1;
}

·例子

using namespace Test;
#include <iostream>
#include <fstream>
#include "person.pb.h"

using namespace std;
using namespace Test;

int main()
{
    fstream file("testStream.xxx", ios::in|ios::out|ios::trunc|ios::binary);

    GOOGLE_PROTOBUF_VERIFY_VERSION;

    AddressBook obj;

    Person* p1 = obj.add_people();
    p1->set_name("gongluck");
    p1->set_id(1);
    *(p1->mutable_email()) = "http://blog.csdn.net/gongluck93";

    Person::PhoneNumber* phone1 = p1->add_phones();
    phone1->set_number("110");
    phone1->set_type(Person::MOBLIE);
    Person::PhoneNumber* phone2 = p1->add_phones();
    phone2->set_number("120");
    phone2->set_type(Person::WORK);

    obj.SerializeToOstream(&file);
    file.flush();
    file.seekg(0, ios::beg);

    AddressBook obj2;
    obj2.ParseFromIstream(&file);
    for(int i= 0; i< obj.people_size(); ++i)
    {
        Person per = obj2.people(i);
        cout << "name = " << per.name() << endl;
        cout << "id = " << per.id() << endl;
        cout << "email = " << per.email() << endl;
        for(int j=0; j< per.phones_size(); ++j)
        {
            Person::PhoneNumber phonenum= per.phones(j);
            switch(phonenum.type())
            {
            case Person::MOBLIE:
                cout << "mobile : " ;
                break;
            case Person::WORK:
                cout << "work : ";
                break;
            case Person::HOME:
                cout << "home : ";
                break;
            default:
                cout << "Not Know : ";
                break;
            }
            cout << phonenum.number() << endl;
        }
    }

    google::protobuf::ShutdownProtobufLibrary();

    file.close();

    return 0;
}

导入定义

syntax = "proto3";//指定版本信息,不指定会报错

import "info.proto"; //导入定义

package tutorial; //package声明符

message Person //message为关键字,作用为定义一种消息类型
{
    string name = 1;    //姓名
    int32 id = 2;       //id
    string email = 3; //邮件

    enum PhoneType //枚举消息类型
    {
        MOBILE = 0; //proto3版本中,首成员必须为0,成员不应有相同的值
        HOME = 1;
        WORK = 2;
    }

    message PhoneNumber
    {
        string number = 1;
        PhoneType type = 2;
    }

    repeated PhoneNumber phones = 4; //phones为数组

    //info定义在"info.proto"
    //类型格式:包名.信息名
    infopack.info tmp = 5;
}

message AddressBook
{
    repeated Person people = 1;
}

github

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Spark学习技巧

JAVA之ClassLoader

JAVA基础系列之ClassLoader 一,Java类的加载、链接与初始化 1,加载:查找并加载类的二进制数据 • 通过一个类的全限定名来获取定义此类的二进制...

23290
来自专栏java一日一条

Java反射在JVM的实现

反射使程序代码能够接入装载到JVM中的类的内部信息,允许在编写与执行时,而不是源代码中选定的类协作的代码,是以开发效率换运行效率的一种手段。这使反射成为构建灵活...

25030
来自专栏程序员互动联盟

【编程基础】extern "C"的用法解析

1.引言   C++语言的创建初衷是“a better C”,但是这并不意味着C++中类似C语言的全局变量和函数所采用的编译和连接方式与C语言完全相同。作为一种...

35630
来自专栏Ryan Miao

java并发编程读书笔记(1)-- 对象的共享

1. 一些原则 RIM(Remote Method Invocation):远程方法调用 Race Condition:竞态条件 Servlet要满足多个线程的...

37080
来自专栏Ryan Miao

java线程(2)--同步和锁

参考转载:http://rainyear.iteye.com/blog/1734311 http://turandot.iteye.com/blog/17040...

40770
来自专栏IT可乐

Spring详解(三)------DI依赖注入

  上一篇博客我们主要讲解了IOC控制反转,也就是说IOC 让程序员不在关注怎么去创建对象,而是关注与对象创建之后的操作,把对象的创建、初始化、销毁等工作交给s...

22350
来自专栏谦谦君子修罗刀

静态内存区域解析

通常在代码中产生的bug,往往是源于概念不清晰。知己知彼百战不殆,对内存这块了如指掌,能极大优化代码的性能。 一、内存四区建立流程讲解 ? 如上图所示,首先操作...

28160
来自专栏coderhuo

可怕的extern关键字一、不利之处二、例子三、分析四、正确做法

如果函数原型改变的话,每个extern声明的地方都要改一遍。 如果有地方没改到呢? 我们通过一个例子来看下悲剧是怎么发生的。

10620
来自专栏aCloudDeveloper

局部变量,静态局部变量,全局变量,静态全局变量在内存中的存放区别(转)

     我们先来看内存中的几大区:  内存到底分几个区? 下面有几种网上的理解,我整理一下: 一:  1、栈区(stack)— 由编译器自动分配释放 ,存放函...

41980
来自专栏desperate633

Java动态代理与静态代理静态代理动态代理

我们先看一个简单的例子,当我们需要程序中加入方法执行的日志信息的时候,很显然我们最容易想到的实现方法,就是在方法前后插入日志记录信息。

16520

扫码关注云+社区

领取腾讯云代金券