• protobuf--数据序列化及反序列化


    ProtoBuf是一种灵活高效的独立于语言平台的结构化数据表示方法,可用于表示通信协议和数据存储等各方面,与XML相比,ProtoBuF更小更快更简单。你可以用定义自己ProtoBuf的数据结构,用ProtoBuf编译器生成特定语言的源代码,(如C++JavaPython等,目前 ProtoBuf对主流的编程语言都提供了支持)方便的进行序列化和反序列化。

    protobufgoogle旗下的一款平台无关,语言无关,可扩展的序列化结构数据格式。所以很适合用做数据存储和作为不同应用,不同语言之间相互通信的数据交换格式,只要实现相同的协议格式即同一proto文件被编译成不同的语言版本,加入到各自的工程中去。这样不同语言就可以解析其他语言通过 protobuf序列化的数据。目前官网提供了C++PythonJAVAGO等语言的支持。

    1. ProtoBuf安装

    protobuf包含两部分,一个是protobuf编译器,用于解析编译.proto文件为.pb.cc.pb.h文件,另一部分是protobuf runtime。不同编程语言有不同的runtime,主流语言基本都支持,如C++JavaPythonGo等。

    1.1 protoc安装

    可直接从https://github.com/protocolbuffers/protobuf/releases安装稳定版本。

    注:releases包含protocruntime两部分,分别属于不同安装包。C++版本源码包含protoc,不需要另外安装protoc,但其他语言都需要单独安装protoc

    1.2 protobuf runtime安装

    C++为例,参考:https://github.com/protocolbuffers/protobuf/tree/master/src 

        git clone https://github.com/protocolbuffers/protobuf.git
        cd protobuf
        git submodule update --init --recursive
        ./autogen.sh
         ./configure
         make
         make check
         sudo make install
         sudo ldconfig # refresh shared library cache.

    注:编译的库文件位于src目录下。

    2. ProtoBuf使用

    2.1应用protobuf步骤

    1. .proto文件中定义message格式。

    2. protoc编译.proto文件,生成.pb.cc.pb.h文件。

    3. 运用C++ protocol buffer API读写messages

    2.2应用protobuf示例

    在一个后缀名为.pro的文件中定义你所需的任何需要序列化和反序列的结构,类似于中我们C/C++语言中的结构体定义,也类似于JSON类定义,ProtoBuf文件的每一个属性都是一个键值对,引用谷歌官方的例子说明https://developers.google.cn/protocol-buffers/docs/cpptutorial,一个包含Person信息的.pro文件。

    syntax = "proto2";
    package tutorial;
    
    message Person{
        required string name = 1;
        required int32 id = 2;
        optional string email = 3;
    
        enum PhoneType {
            MOBILE = 0;
            HOME = 1;
            WORK = 2;
        }
    
        message PhoneNumber{
            required string number = 1;
            optional PhoneType type = 2 [default = HOME];
        }
    
        repeated PhoneNumber phone = 4;
    }
    
    message AddressBook{
        repeated Person people = 1;
    } 

    .pro 文件的每一个属性都是一个包含类型和名称的对,protobuf的类型可以是stringintbool,或者是其他ProtoBuf源文件中定位的类型等。还需指定该属性的一些特殊字段,比如optional表示该属性是可选的,required表示该属性是必须的,repeated表示该属性是一个list的集合(不知道用list类似形容是否合适,就差不多意思吧,包含1个或多个元素),包含了若干个相同类型的值,不过在protoBuf 3中,optionalrequired都不需要的了,如果配了两个,protoBuf 编译器还会报错,但是repeated的还是保留的,用来说明该字段是一个list

    As you can see, each field in the message definition has a unique number. These numbers are used to identify your fields in the message binary format, and should not be changed once your message type is in use. Note that field numbers in the range 1 through 15 take one byte to encode, including the field number and the field's type (you can find out more about this in Protocol Buffer Encoding). Field numbers in the range 16 through 2047 take two bytes. So you should reserve the field numbers 1 through 15 for very frequently occurring message elements. Remember to leave some room for frequently occurring elements that might be added in the future. https://developers.google.cn/protocol-buffers/docs/proto

    .protoBuf定义完以后,就可以使用ProtoBuf编译器进行编译,编译时需要指定你需要生成的代码的语言类型。.pro文件编译成功后会生成*.pro.pb.hh *.pro.pb.cc两个文件,*.pro源文件名称相同。在生成的文件中会生成一个你定义的消息类型的的类以及读取和设置该消息类型各个属性的 setGet方法等,比如上例中会生成一个Person类,和name()set_name()这样的成员方法,以及序列化和反序列的方法,你可以用 这些方法方便的读取和设置相应的属性,以及序列化和反序列化方法。

    protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto

    message

    #include <iostream>
    #include <fstream>
    #include <string>
    #include "addressbook.pb.h"
    
    using namespace std;
    //using namespace caffe;
    
    void PromptForAddress(tutorial::Person * person)
    {
        cout << "Enter person ID number:";
        int id;
        cin >> id;
        person->set_id(id);
        cin.ignore(256, '
    ');
    
        cout << "Enter name :";
        getline(cin, *person->mutable_name());
    
        cout << "Enter email address (blank for none): ";
        string email;
        getline(cin, email);
        if(!email.empty()){
            person->set_email(email);
        }
    
        while(true){
            cout << "Enter a phone number ( or leave blank to finish):";
            string number;
            getline(cin, number);
            if(number.empty()){
                break;
            }
    
            tutorial::Person::PhoneNumber *phone_number = person->add_phone();
            phone_number->set_number(number);
    
            cout << "Is this a mobile, home, or work phone?";
            string type;
            getline(cin, type);
            if(type == "mobile"){
                phone_number->set_type(tutorial::Person::MOBILE);
            } else if(type == "home"){
                phone_number->set_type(tutorial::Person::HOME);
            } else if(type == "work"){
                phone_number->set_type(tutorial::Person::WORK);
            } else {
                cout <<"Unknown phone type. Using default." <<endl;
            }
        }
    }
    
    int main(int argc, char **argv)
    {
        GOOGLE_PROTOBUF_VERIFY_VERSION;
    
        if(argc != 2){
            cerr <<"Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
            return -1;
        }
    
        tutorial::AddressBook address_book;
        fstream input(argv[1], ios::in | ios::binary);
        if(!input){
            cout << argv[1] << ": File not found. Creating a new file." << endl;
        } else if(!address_book.ParseFromIstream(&input)){
            cerr << "Failed to parse address book." << endl;
            return -1;
        }
    
        PromptForAddress(address_book.add_people());
    
        fstream output(argv[1], ios::out | ios::trunc | ios::binary);
        if(!address_book.SerializeToOstream(&output)){
            cerr << "Failed to write address book." << endl;
            return -1;
        }
    
        google::protobuf::ShutdownProtobufLibrary();
    
        return 0;
    }

    编译命令:

    g++ write.cpp addressbook.pb.cc -o write `pkg-config --cflags --libs protobuf` -std=c++11

    message

    #include <iostream>
    #include <fstream>
    #include <string>
    #include "addressbook.pb.h"
    
    using namespace std;
    
    void PrintInfo(const tutorial::AddressBook & address_book)
    {
        for(int i = 0; i < address_book.people_size(); i++){
            const tutorial::Person& person = address_book.people(i);
            
            cout << "Person ID: " << person.id() << endl;
            cout << " Name: " << person.name() << endl;
            if(person.has_email()){
                cout << "E-mail address: " << person.email() << endl;
            }
    
            for(int j = 0; j < person.phone_size(); j++){
                const tutorial::Person::PhoneNumber& phone_number = person.phone(j);
                switch(phone_number.type()){
                    case tutorial::Person::MOBILE:
                        cout << "Mobile phone #: ";
                        break;
                    case tutorial::Person::HOME:
                        cout << "Home phone #: ";
                        break;
                    case tutorial::Person::WORK:
                        cout << "Work phone #: ";
                        break;
                }
                cout << phone_number.number() << endl;
            }
        }
    }
    
    int main(int argc, char *argv[])
    {
        GOOGLE_PROTOBUF_VERIFY_VERSION;
    
        if(argc != 2){
            cerr << "Usage: " << argv[0] << " ADDRESS_BOOK_FILE" << endl;
            return -1;
        }
    
        tutorial::AddressBook address_book;
    
        fstream input(argv[1], ios::in | ios::binary);
        if(!address_book.ParseFromIstream(&input)){
            cerr << "Failed to parse address book." << endl;
            return -1;
        }
    
        PrintInfo(address_book);
    
        google::protobuf::ShutdownProtobufLibrary();
    
        return 0;
    }

    编译命令:

    g++ read.cpp addressbook.pb.cc -o read `pkg-config --cflags --libs protobuf` -std=c++11

    执行

    $ ./write test_addr
    test_addr: File not found. Creating a new file.
    Enter person ID number:10000
    Enter name :wang
    Enter email address (blank for none): 123456@163.com
    Enter a phone number ( or leave blank to finish):18000000000
    Is this a mobile, home, or work phone?home
    Enter a phone number ( or leave blank to finish):
    $ cat test_addr
    
    *
    wang▒N123456@163.com"
    
    $ ./read
    Usage: ./read ADDRESS_BOOK_FILE
    wang@ubuntu-wang:~/repository/protoc/example_me$ ./read test_addr
    Person ID: 10000
     Name: wang
    E-mail address: 123456@163.com
    Home phone #: 18000000000

    3. ProtoBuf兼容性

    ProtoBuf的一个亮点是提供了向前和向后的兼容性,你可以再你定义的消息格式中增加字段或者删除字段,当消息由一个增加字段后的系统向一个老 版本系统发送消息时,老版本解析该消息格式后会直接忽略新增的字段,而当消息从老版本发向新版本时,新版本解析该消息时会将新增的字段设置为默认值,所以 你不需要担心在系统中增加消息字段后的兼容问题以及版本升级配套问题。

     

    参考:

      1. https://developers.google.cn/protocol-buffers/docs/overview 官网

      2. https://github.com/protocolbuffers/protobuf github

      3. https://developers.google.cn/protocol-buffers/docs/cpptutorial c++示例应用解析

      4. Google Protocol Buffer 的使用和原理IBM developer

  • 相关阅读:
    【Stage3D学习笔记续】山寨Starling(十):高效游戏设计、纹理集和ATF
    【Stage3D学习笔记续】山寨Starling(九):上下文丢失处理方法
    【Stage3D学习笔记续】山寨Starling(八):核心优化(批处理)的实现
    echarts 百度图表
    java读properties文件 乱码
    百度地图 JSAPI使用 mark 定位地址 与周边覆盖物
    jstl format date
    MultipartEntity 乱码
    log4j与commons-logging,slf4j的关系
    maven 引入 net sf jsonlib 报错 has borken path
  • 原文地址:https://www.cnblogs.com/embedded-linux/p/9221970.html
Copyright © 2020-2023  润新知