精华内容
下载资源
问答
  • protobuf使用详解
    千次阅读
    2018-08-29 16:53:05

    什么是protobuf

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

    优点

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

    使用步骤

    1 编写proto文件

    首先需要一个proto文件,其中定义了我们程序中需要处理的结构化数据:

    // Filename: addressbook.proto  
    
    syntax="proto2";  //表明使用protobuf的编译器版本为v2,目前最新的版本为v3
    package addressbook;//声明一个包名,用来防止不同的消息类型命名冲突,类似于namespace
    
    import "src/help.proto";      //举例用,编译时去掉 导入一个外部proto文件中的定义,
                                  //类似于C++中的include
    
    message Person {              //message是Protobuf中的结构化数据,类似于C++中的类
                                  //可以在其中定义需要处理的数据
        required string name = 1; //声明一个名为name,数据类型为string的required字段
                                  //字段的标识号为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 person_info = 1;
    }

    2 生成C++文件

    protoc是proto文件的编译器,可以将proto文件编译成C++、Java、Python三种代码文件,编译格式如下

    protoc -I=$SRC_DIR --cpp_out=$DST_DIR /path/to/file.proto

    上面的命令会生成xxx.pb.h 和 xxx.pb.cc两个C++文件。

    protoc的具体使用可执行protoc -help,输出内容如下

    Usage: protoc [OPTION] PROTO_FILES
    Parse PROTO_FILES and generate output based on the options given:
      -IPATH, --proto_path=PATH   Specify the directory in which to search for
                                  imports.  May be specified multiple times;
                                  directories will be searched in order.  If not
                                  given, the current working directory is used.
      --version                   Show version info and exit.
      -h, --help                  Show this text and exit.
      --encode=MESSAGE_TYPE       Read a text-format message of the given type
                                  from standard input and write it in binary
                                  to standard output.  The message type must
                                  be defined in PROTO_FILES or their imports.
      --decode=MESSAGE_TYPE       Read a binary message of the given type from
                                  standard input and write it in text format
                                  to standard output.  The message type must
                                  be defined in PROTO_FILES or their imports.
      --decode_raw                Read an arbitrary protocol message from
                                  standard input and write the raw tag/value
                                  pairs in text format to standard output.  No
                                  PROTO_FILES should be given when using this
                                  flag.
      --descriptor_set_in=FILES   Specifies a delimited list of FILES
                                  each containing a FileDescriptorSet (a
                                  protocol buffer defined in descriptor.proto).
                                  The FileDescriptor for each of the PROTO_FILES
                                  provided will be loaded from these
                                  FileDescriptorSets. If a FileDescriptor
                                  appears multiple times, the first occurrence
                                  will be used.
      -oFILE,                     Writes a FileDescriptorSet (a protocol buffer,
        --descriptor_set_out=FILE defined in descriptor.proto) containing all of
                                  the input files to FILE.
      --include_imports           When using --descriptor_set_out, also include
                                  all dependencies of the input files in the
                                  set, so that the set is self-contained.
      --include_source_info       When using --descriptor_set_out, do not strip
                                  SourceCodeInfo from the FileDescriptorProto.
                                  This results in vastly larger descriptors that
                                  include information about the original
                                  location of each decl in the source file as
                                  well as surrounding comments.
      --dependency_out=FILE       Write a dependency output file in the format
                                  expected by make. This writes the transitive
                                  set of input file paths to FILE
      --error_format=FORMAT       Set the format in which to print errors.
                                  FORMAT may be 'gcc' (the default) or 'msvs'
                                  (Microsoft Visual Studio format).
      --print_free_field_numbers  Print the free field numbers of the messages
                                  defined in the given proto files. Groups share
                                  the same field number space with the parent 
                                  message. Extension ranges are counted as 
                                  occupied fields numbers.
    
      --plugin=EXECUTABLE         Specifies a plugin executable to use.
                                  Normally, protoc searches the PATH for
                                  plugins, but you may specify additional
                                  executables not in the path using this flag.
                                  Additionally, EXECUTABLE may be of the form
                                  NAME=PATH, in which case the given plugin name
                                  is mapped to the given executable even if
                                  the executable's own name differs.
      --cpp_out=OUT_DIR           Generate C++ header and source.
      --csharp_out=OUT_DIR        Generate C# source file.
      --java_out=OUT_DIR          Generate Java source file.
      --javanano_out=OUT_DIR      Generate Java Nano source file.
      --js_out=OUT_DIR            Generate JavaScript source.
      --objc_out=OUT_DIR          Generate Objective C header and source.
      --php_out=OUT_DIR           Generate PHP source file.
      --python_out=OUT_DIR        Generate Python source file.
      --ruby_out=OUT_DIR          Generate Ruby source file.
    

    3 使用C++文件

    现在编写一个main.cc文件

    #include <iostream>
    #include "addressbook.pb.h"
    
    int main(int argc, const char* argv[])
    {
        addressbook::AddressBook person;
        addressbook::Person* pi = person.add_person_info();
    
        pi->set_name("aut");
        pi->set_id(1219);
        std::cout << "before clear(), id = " << pi->id() << std::endl;
        pi->clear_id();
        std::cout << "after  clear(), id = " << pi->id() << std::endl;
        pi->set_id(1087);
        if (!pi->has_email())
            pi->set_email("autyinjing@126.com");
    
        addressbook::Person::PhoneNumber* pn = pi->add_phone();
        pn->set_number("021-8888-8888");
        pn = pi->add_phone();
        pn->set_number("138-8888-8888");
        pn->set_type(addressbook::Person::MOBILE);
    
        uint32_t size = person.ByteSize();
        unsigned char byteArray[size];
        person.SerializeToArray(byteArray, size);
    
        addressbook::AddressBook help_person;
        help_person.ParseFromArray(byteArray, size);
        addressbook::Person help_pi = help_person.person_info(0);
    
        std::cout << "*****************************" << std::endl;
        std::cout << "id:    " << help_pi.id() << std::endl;
        std::cout << "name:  " << help_pi.name() << std::endl;
        std::cout << "email: " << help_pi.email() << std::endl;
    
        for (int i = 0; i < help_pi.phone_size(); ++i)
        {
            auto help_pn = help_pi.mutable_phone(i);
            std::cout << "phone_type: " << help_pn->type() << std::endl;
            std::cout << "phone_number: " << help_pn->number() << std::endl;
        }
        std::cout << "*****************************" << std::endl;
    
        return 0;
    }

    4 编译生成可执行文件

    编译格式和普通的C++代码一样,但是要加上 -lprotobuf -pthread 

    g++ main.cc xxx.pb.cc -I $INCLUDE_PATH -L $LIB_PATH -lprotobuf -pthread 

    5 输出结果

    before clear(), id = 1219
    after  clear(), id = 0
    *****************************
    id:   1087
    name: aut
    email: autyinjing@126.com
    phone_type: 1
    phone_number: 021-8888-8888
    phone_type: 0
    phone_number: 138-8888-8888
    *****************************

     

    更多相关内容
  • protobuf 使用详解

    2010-10-29 18:35:01
    详细介绍了protobuf 在程序中的使用方法
  • Protobuf详解使用

    千次阅读 2020-09-04 02:04:09
    Xml、Json是目前常用的数据交换格式,它们直接使用字段名称维护序列化后类实例中字段与数据之间的映射关系,一般用字符串的形式保存在序列化后的字节流中。消息和消息的定义相对独立,可读性较好。但序列化后的数据...

    Protobuf全称是Google Protocol Buffer,是一种高效轻便的结构化数据存储方式,可用于(数据)通信协议、数据存储等。

    Xml、Json是目前常用的数据交换格式,它们直接使用字段名称维护序列化后类实例中字段与数据之间的映射关系,一般用字符串的形式保存在序列化后的字节流中。消息和消息的定义相对独立,可读性较好。但序列化后的数据字节很大,序列化和反序列化的时间较长,数据传输效率不高。
    xml、json是用字段名称来确定类实例中字段之间的独立性,所以序列化后的数据多了很多描述信息,增加了序列化后的字节序列的容量。
    Protobuf和Xml、Json序列化的方式不同,采用了二进制字节的序列化方式,序列化与反序列化不需要解析相应的节点属性和多余的描述信息,用字段索引和字段类型通过算法计算得到字段之前的关系映射,从而达到更高的时间效率和空间效率,特别适合对数据大小和传输速率比较敏感的场合使用。
    结论
    protobuf是由字段索引(fieldIndex)与数据类型(type)计算(fieldIndex<<3|type)得出的key维护字段之间的映射且只占一个字节,所以相比json与xml文件,protobuf的序列化字节没有过多的key与描述符信息,所以占用空间要小很多。直接是2进数据序列化与反序列化不需要解析相应的节点属性和多余的描述信息所以时间效率要高。

    • 优势
      与语言无关,平台无关
      Protobuf支持Java, C++, Python等多种语言,支持多个平台。
      高效
      比XML,Json更小(3~10倍),更快(20 ~ 100倍),更为简单。
      扩展性,兼容性好
      你可以更新数据结构,而不影响和破坏原有的旧程序。

    • 缺点
      二进制格式导致可读性差(二进制格式)
      缺乏自描述

    • 序列化反序列化:

    序列化:在传递和保存对象时.保证对象的完整性和可传递性。对象转换为有序字节流,以便在网络上传输或者保存在本地文件中。
    反序列化:根据字节流中保存的对象状态及描述信息,通过反序列化重建对象。
    作用:将数据结构或对象转换成能够被存储和传输(例如网络传输)的格式,同时应当要保证这个序列化结果在之后(可能是另一个计算环境中)能够被重建回原来的数据结构或对象。

    pb与xml Json比较
    protobuf 数据类型
    在这里插入图片描述
    repeated 数组/集合 repeated User users = 1
    Protobuf 使用

    • 创建.proto文件,定义数据结构
      protobuf将一种结构称为一个message类型
    message Person {
      required string name = 1;
      required int32 id = 2; [default = 0]
      optional string email = 3;
    
      repeated int32 samples = 4 
    
    }
    

    其中Person是message这种结构的名称,name、id、email是其中的Field,每个Field保存着一种数据类型,后面的1、2、3是Filed对应的数字id。id在115之间编码只需要占一个字节,包括Filed数据类型和Filed对应数字id,在162047之间编码需要占两个字节,所以最常用的数据对应id要尽量小一些。

    Field最前面的required,optional,repeated是这个Filed的规则,分别表示该数据结构中这个Filed有且只有1个,可以是0个或1个,可以是0个或任意个。optional后面可以加default默认值,如果不加,数据类型的默认为0,字符串类型的默认为空串。repeated后面加[packed=true]会使用新的更高效的编码方式。
    引用其它message类在同一个文件中,可以直接引用定义过的message类型。在同一个项目中,可以用import来导入其它message类

    • message 类型都有一个或多个具有唯一编号的字段,每个字段都有一个名称和一个值类型,其中值类型可以是数字(整数或浮点数),布尔值,字符串,原始字节,甚至(如上例所示)其它 protocol buffer message 类型

    • message具有的字段,形式为:

    message xxx {
      // 字段规则:required -> 字段只能也必须出现 1 次
      // 字段规则:optional -> 字段可出现 0 次或多次
      // 字段规则:repeated -> 字段可出现任意多次(包括 0)
      // 类型:int32、int64、sint32、sint64、string、32-bit ....
      // 字段编号:0 ~ 536870911(2 28-1)(除去 19000 到 19999 之间的数字)
      字段规则 类型 名称 = 字段编号;
    }
    
    import "myproject/other_protos.proto";
    
    • 在另一个文件中,import 这个proto之后,可以对Person这个message进行扩展。
    extend Person {
      optional int32 bar = 126;
    }
    

    一旦定义了 messages,就可以在 .proto 文件上运行 protocol buffer 编译器来生成指定语言的数据访问类。这些类为每个字段提供了简单的访问器(如 name()和 set_name()),以及将整个结构序列化为原始字节和解析原始字节的方法 - 例如,选择的语言是 Java,则运行编译器上面的例子将生成一个名为 Person 的类。然后,你可以在应用程序中使用此类来填充,序列化和检索 Person 的 messages。于是你可以写一些这样的代码:

    person = Person.newBuilder() ;
    person.set_name("John Doe");
    person.set_id(1234);
    person.set_email("jdoe@example.com");
    

    你的 .proto 文件将生成什么?
    当你在 .proto 上运行 protocol buffer 编译器时,编译器将会生成所需语言的代码,这些代码可以操作文件中描述的 message 类型,包括获取和设置字段值、将 message 序列化为输出流、以及从输入流中解析出 message。

    对于 C++,编译器从每个 .proto 生成一个 .h 和 .cc 文件,其中包含文件中描述的每种 message 类型对应的类。
    对于 Java,编译器为每个 message 类型生成一个 .java 文件(类),以及用于创建 message 类实例的特殊 Builder 类。
    Python 有点不同 - Python 编译器生成一个模块,其中包含 .proto 中每种 message 类型的静态描述符,然后与元类一起使用以创建必要的 Python 数据访问类。
    对于 Go,编译器会生成一个 .pb.go 文件,其中包含对应每种 message 类型的类型。

    1、在project的build.gradle中配置

     dependencies {
            classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.2'
        }
    

    2、在app的build.gradle中配置

    apply plugin: 'com.android.application'
    apply plugin: 'com.google.protobuf'
    
    android {
        sourceSets {
            main {
                proto {
                    //main目录新建proto目录
                    srcDir 'src/main/proto'
                    include '**/*.proto'
                }
                java {
                    srcDir 'src/main/java'
                }
            }
        }
    }
    
    //构建task
    protobuf {
        protoc {
            artifact = 'com.google.protobuf:protoc:3.1.0'
        }
    
        generateProtoTasks {
            all().each { task ->
                task.builtins {
                    remove java
                }
                task.builtins {
                    java {}
                    // Add cpp output without any option.
                    // DO NOT omit the braces if you want this builtin to be added.
                    cpp {}
                }
            }
        }
        //生成目录
        generatedFilesBaseDir = "$projectDir/src/generated"
    }
    
    dependencies {
        compile 'com.google.protobuf:protobuf-java:3.1.0'
        compile 'com.google.protobuf:protoc:3.1.0'
    }
    

    3、Android studio 安装插件
    在这里插入图片描述

    其他语言
    使用Protobuf前首先需要准备protobuf需要的Jar包以及在windows下的可执行文件(protoc.exe)通过protoc生成对应文件使用。
    例如:
    在命令行中定位到protoc.exe所在文件夹,执行“protoc --java_out ./ *.proto”命令
    在这里插入图片描述

    在Android中调用

    1.将protobuf需要的Jar包导入到项目中

    2.将生成的java文件拷贝到项目中对应的包下,就可以直接调用了
    ProtoInfo info= ProtoInfo.newBuilder().setId(1001).setName(“jack”).build();

    展开全文
  • ProtoBuf - 详解

    2021-08-08 15:16:46
    之前在网络通信和通用数据交换等应用场景中经常使用的技术是 JSON 或 XML,而在最近的开发中接触到了 Google 的 ProtoBuf。 在查阅相关资料学习 ProtoBuf 以及研读其源码之后,发现其在效率、兼容性等方面非常出色...

    之前在网络通信和通用数据交换等应用场景中经常使用的技术是 JSON 或 XML,而在最近的开发中接触到了 Google 的 ProtoBuf。

    在查阅相关资料学习 ProtoBuf 以及研读其源码之后,发现其在效率、兼容性等方面非常出色。在以后的项目技术选型中,尤其是网络通信、通用数据交换等场景应该会优先选择 ProtoBuf。

    自己在学习 ProtoBuf 的过程中翻译了官方的主要文档,一来当然是在学习 ProtoBuf,二来是培养阅读英文文档的能力,三来是因为 Google 的文档?不存在的!

    看完这些文档对 ProtoBuf 应该就有相当程度的了解了。

    翻译文档见 [索引]文章索引,导航为翻译 - 技术 - ProtoBuf 官方文档。

    但是官方文档更多的是作为查阅和权威参考,并不意味着看完官方文档就能立马理解其原理。

    本文以及接下来的几篇文章会对 ProtoBuf 的编码、序列化、反序列化、反射等原理做一些详细介绍,同时也会尽量将这些原理表达的更为通俗易懂。

    何为 ProtoBuf

    我们先来看看官方文档给出的定义和描述:

    protocol buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。

    Protocol Buffers 是一种灵活,高效,自动化机制的结构数据序列化方法-可类比 XML,但是比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单。

    你可以定义数据的结构,然后使用特殊生成的源代码轻松的在各种数据流中使用各种语言进行编写和读取结构数据。你甚至可以更新数据结构,而不破坏由旧数据结构编译的已部署程序。

    简单来讲, ProtoBuf 是结构数据序列化[1] 方法,可简单类比于 XML[2],其具有以下特点:

    • 语言无关、平台无关。即 ProtoBuf 支持 Java、C++、Python 等多种语言,支持多个平台
    • 高效。即比 XML 更小(3 ~ 10倍)、更快(20 ~ 100倍)、更为简单
    • 扩展性、兼容性好。你可以更新数据结构,而不影响和破坏原有的旧程序

    序列化[1]:将结构数据对象转换成能够被存储和传输(例如网络传输)的格式,同时应当要保证这个序列化结果在之后(可能在另一个计算环境中)能够被重建回原来的结构数据或对象。
    更为详尽的介绍可参阅 维基百科
    类比于 XML[2]:这里主要指在数据通信和数据存储应用场景中序列化方面的类比,但个人认为 XML 作为一种扩展标记语言和 ProtoBuf 还是有着本质区别的。

    使用 ProtoBuf

    对 ProtoBuf 的基本概念有了一定了解之后,我们来看看具体该如何使用 ProtoBuf。
    第一步,创建 .proto 文件,定义数据结构,如下例1所示:

    // 例1: 在 xxx.proto 文件中定义 Example1 message
    message Example1 {
        optional string stringVal = 1;
        optional bytes bytesVal = 2;
        message EmbeddedMessage {
            int32 int32Val = 1;
            string stringVal = 2;
        }
        optional EmbeddedMessage embeddedExample1 = 3;
        repeated int32 repeatedInt32Val = 4;
        repeated string repeatedStringVal = 5;
    }
    

    我们在上例中定义了一个名为 Example1 的 消息,语法很简单,message 关键字后跟上消息名称:

    message xxx {
    
    }
    

    之后我们在其中定义了 message 具有的字段,形式为:

    message xxx {
      // 字段规则:required -> 字段只能也必须出现 1 次
      // 字段规则:optional -> 字段可出现 0 次或1次
      // 字段规则:repeated -> 字段可出现任意多次(包括 0)
      // 类型:int32、int64、sint32、sint64、string、32-bit ....
      // 字段编号:0 ~ 536870911(除去 19000 到 19999 之间的数字)
      字段规则 类型 名称 = 字段编号;
    }
    

    在上例中,我们定义了:

    • 类型 string,名为 stringVal 的 optional 可选字段,字段编号为 1,此字段可出现 0 或 1 次
    • 类型 bytes,名为 bytesVal 的 optional 可选字段,字段编号为 2,此字段可出现 0 或 1 次
    • 类型 EmbeddedMessage(自定义的内嵌 message 类型),名为 embeddedExample1 的 optional 可选字段,字段编号为 3,此字段可出现 0 或 1 次
    • 类型 int32,名为 repeatedInt32Val 的 repeated 可重复字段,字段编号为 4,此字段可出现 任意多次(包括 0)
    • 类型 string,名为 repeatedStringVal 的 repeated 可重复字段,字段编号为 5,此字段可出现 任意多次(包括 0)

    关于 proto2 定义 message 消息的更多语法细节,例如具有支持哪些类型,字段编号分配、import
    导入定义,reserved 保留字段等知识请参阅 [翻译] ProtoBuf 官方文档(二)- 语法指引(proto2)

    关于定义时的一些规范请参阅 [翻译] ProtoBuf 官方文档(四)- 规范指引

    第二步,protoc 编译 .proto 文件生成读写接口

    我们在 .proto 文件中定义了数据结构,这些数据结构是面向开发者和业务程序的,并不面向存储和传输。

    当需要把这些数据进行存储或传输时,就需要将这些结构数据进行序列化、反序列化以及读写。那么如何实现呢?不用担心, ProtoBuf 将会为我们提供相应的接口代码。如何提供?答案就是通过 protoc 这个编译器。

    可通过如下命令生成相应的接口代码:

    // $SRC_DIR: .proto 所在的源目录
    // --cpp_out: 生成 c++ 代码
    // $DST_DIR: 生成代码的目标目录
    // xxx.proto: 要针对哪个 proto 文件生成接口代码
    
    protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/xxx.proto
    

    最终生成的代码将提供类似如下的接口:

    第三步,调用接口实现序列化、反序列化以及读写
    针对第一步中例1定义的 message,我们可以调用第二步中生成的接口,实现测试代码如下:

    //
    // Created by yue on 18-7-21.
    //
    #include <iostream>
    #include <fstream>
    #include <string>
    #include "single_length_delimited_all.pb.h"
    
    int main() {
        Example1 example1;
        example1.set_stringval("hello,world");
        example1.set_bytesval("are you ok?");
    
        Example1_EmbeddedMessage *embeddedExample2 = new Example1_EmbeddedMessage();
    
        embeddedExample2->set_int32val(1);
        embeddedExample2->set_stringval("embeddedInfo");
        example1.set_allocated_embeddedexample1(embeddedExample2);
    
        example1.add_repeatedint32val(2);
        example1.add_repeatedint32val(3);
        example1.add_repeatedstringval("repeated1");
        example1.add_repeatedstringval("repeated2");
    
        std::string filename = "single_length_delimited_all_example1_val_result";
        std::fstream output(filename, std::ios::out | std::ios::trunc | std::ios::binary);
        if (!example1.SerializeToOstream(&output)) {
            std::cerr << "Failed to write example1." << std::endl;
            exit(-1);
        }
    
        return 0;
    }
    

    关于 protoc 的使用以及接口调用的更多信息可参阅 [翻译] ProtoBuf 官方文档(九)- (C++开发)教程

    关于例1的完整代码请参阅 源码:protobuf 例1。其中的 single_length_delimited_all.* 为例子相关代码和文件。

    因为此系列文章重点在于深入 ProtoBuf 的编码、序列化、反射等原理,关于 ProtoBuf 的语法、使用等只做简单介绍,更为详见的使用教程可参阅我翻译的系列官方文档。

    关于 ProtoBuf 的一些思考

    官方文档以及网上很多文章提到 ProtoBuf 可类比 XML 或 JSON。

    那么 ProtoBuf 是否就等同于 XML 和 JSON 呢,它们是否具有完全相同的应用场景呢?

    个人认为如果要将 ProtoBuf、XML、JSON 三者放到一起去比较,应该区分两个维度。一个是数据结构化,一个是数据序列化。这里的数据结构化主要面向开发或业务层面,数据序列化面向通信或存储层面,当然数据序列化也需要“结构”和“格式”,所以这两者之间的区别主要在于面向领域和场景不同,一般要求和侧重点也会有所不同。数据结构化侧重人类可读性甚至有时会强调语义表达能力,而数据序列化侧重效率和压缩。

    从这两个维度,我们可以做出下面的一些思考。

    XML 作为一种扩展标记语言,JSON 作为源于 JS 的数据格式,都具有数据结构化的能力。

    例如 XML 可以衍生出 HTML (虽然 HTML 早于 XML,但从概念上讲,HTML 只是预定义标签的 XML),HTML 的作用是标记和表达万维网中资源的结构,以便浏览器更好的展示万维网资源,同时也要尽可能保证其人类可读以便开发人员进行编辑,这就是面向业务或开发层面的数据结构化

    再如 XML 还可衍生出 RDF/RDFS,进一步表达语义网中资源的关系和语义,同样它强调数据结构化的能力和人类可读。

    关于 RDF/RDFS 和语义网的概念可查询相关资料了解,或参阅 2-Answer 系列-本体构建模块(一)3-Answer 系列-本体构建模块(二) ,文中有一些简单介绍。

    JSON 也是同理,在很多场合更多的是体现了数据结构化的能力,例如作为交互接口的数据结构的表达。在 MongoDB 中采用 JSON 作为查询语句,也是在发挥其数据结构化的能力。

    当然,JSON、XML 同样也可以直接被用来数据序列化,实际上很多时候它们也是这么被使用的,例如直接采用 JSON、XML 进行网络通信传输,此时 JSON、XML 就成了一种序列化格式,它发挥了数据序列化的能力。但是经常这么被使用,不代表这么做就是合理。实际将 JSON、XML 直接作用数据序列化通常并不是最优选择,因为它们在速度、效率、空间上并不是最优。换句话说它们更适合数据结构化而非数据序列化。

    扯完 XML 和 JSON,我们来看看 ProtoBuf,同样的 ProtoBuf 也具有数据结构化的能力,其实也就是上面介绍的 message 定义。我们能够在 .proto 文件中,通过 message、import、内嵌 message 等语法来实现数据结构化,但是很容易能够看出,ProtoBuf 在数据结构化方面和 XML、JSON 相差较大,人类可读性较差,不适合上面提到的 XML、JSON 的一些应用场景。

    但是如果从数据序列化的角度你会发现 ProtoBuf 有着明显的优势,效率、速度、空间几乎全面占优,看完后面的 ProtoBuf 编码的文章,你更会了解 ProtoBuf 是如何极尽所能的压榨每一寸空间和性能,而其中的编码原理正是 ProtoBuf 的关键所在,message 的表达能力并不是 ProtoBuf 最关键的重点。所以可以看出 ProtoBuf 重点侧重于数据序列化 而非 数据结构化

    最终对这些个人思考做一些小小的总结:

    1. XML、JSON、ProtoBuf 都具有数据结构化数据序列化的能力
    2. XML、JSON 更注重数据结构化,关注人类可读性和语义表达能力。ProtoBuf 更注重数据序列化,关注效率、空间、速度,人类可读性差,语义表达能力不足(为保证极致的效率,会舍弃一部分元信息)
    3. ProtoBuf 的应用场景更为明确,XML、JSON 的应用场景更为丰富。



    参考:

    1、https://www.jianshu.com/p/a24c88c0526a
     

    展开全文
  • protobuf协议使用详解

    千次阅读 2020-10-12 13:34:02
    一、protobuf协议详解protobuf中,协议是由一系列的消息(message)组成的,如下所示: systax = "proto3"; //表明使用proto3语法;如果你没有指定这个,编译器会使用proto2语法;这个指定语法行必须是文件的非空...

    一、protobuf协议详解

    在protobuf中,协议是由一系列的消息(message)组成的,如下所示:

    systax = "proto3"; //表明使用proto3语法;如果你没有指定这个,编译器会使用proto2语法;这个指定语法行必须是文件的非空非注释的第一个行
    package School; //包名,类似于模块
    
    message Student { //消息,类似于类
    	required string name = 1 [default="张三"];
    	optional int32 chinese = 2 [default=0];
    	optional int32 math = 3 [default=0];
    	optional int32 english = 4 [default=0];
    }
    
    message Teacher {
    	required strint name = 1;
    	optional string class = 2;
    	optional string object = 3; 
    }
    
    message HengShuiZhongXue {
    	repeated Student student = 1; //message内可以嵌套message
    	repeated Teacher teachar = 2;
    }
    
    

    字段格式:


    限定修饰符① | 数据类型② | 字段名称③ = 字段编码值④ | 字段默认值⑤


    ①. 限定修饰符 required | optional | repeated

    required:表示是一个必须字段 ,必须相对于发送方,在发送消息之前必须设置该字段的值,对于接收方,必须能够识别该字段的意思。发送之前没有设置required字段或者无法识别required字段都会引发编解码异常,导致消息被丢弃。

    optional:表示是一个可选字段 ,可选对于发送方,在发送消息时,可以有选择性的设置或者不设置该字段的值。对于接收方,如果能够识别可选字段就进行相应的处理,如果无法识别,则忽略该字段,消息中的其它字段正常处理。

    repeated:表示该字段可以包含0~n个元素, 其特性和optional一样,但是每一次可以包含多个值,可以看作是一个数组


    ②. 数据类型

    Protobuf定义了一套基本数据类型,几乎都可以映射到C++\Java等语言的基础数据类型。

    protobuf 数据结构描述打包C++语言映射
    bool布尔类型1字节bool
    double64浮点数Ndouble
    float32浮点数Nfloat
    int3232位整数Nint
    uint32无符号32位整数Nunsigned int
    int6464位整数N__int64
    uint6464位无整数Nunsigned __int64
    sint3232位整数,处理负数效率更高Nint32
    sint6464位整数,处理负数效率更高N__int64
    fixed3232位无符号整数4unsigned int32
    fixed6464位无符号整数8unsigned __int64
    sfixed3232位整数,能以更高的效率处理负数4unsigned int32
    sfixed6464位整数8unsigned __int64
    string只能处理ASCII字符Nstd::string
    bytes用于处理多字节的语言字符,如中文Nstd::string
    enum可以包含一个用户自定义的枚举类型uint32N(uint32)enum
    message可以包含一个用户自定义的消息类型Nobject of class

    N:表示打包的字节并不是固定的,而是根据数据的大小或者长度决定的


    ③. 字段名称

    字段名称的命名与C、C++、Java等语言的变量命名方式几乎是相同的:字母、数字和下划线组成

    protobuf建议字段的命名采用以下划线分割的驼峰式,例如:first_name 而不是firstName


    ④. 字段编码值

    有了该值,通信双方才能互相识别对方的字段。当然相同的编码值,其限定修饰符和数据类型必须相同,编码值的取值范围为1~2^32

    其中1~15的编码时间和空间效率都是最高的,编码值越大,其编码的时间和空间效率就越低(相对于1~15), protobuf 建议把经常要传递的值把其字段编码设置为1-15之间的值。

    消息中的字段的编码值无需连续,只要是合法的,并且不能在同一个消息中有字段包含相同的编码值。

    建议:项目投入运营以后涉及到版本升级时的新增消息字段全部使用optional或者repeated,尽量不实用required。如果使用了required,需要全网统一升级,如果使用optional或者repeated可以平滑升级。


    ⑤. 字段默认值

    当在传递数据时,对于required数据类型,如果用户没有设置值,则使用默认值传递到对端。 对于optional字段,如果没有接收到optional字段,则设置为默认值。

    对于strings,默认是一个空string

    对于bytes,默认是一个空的bytes

    对于bools,默认是false

    对于数值类型,默认是0


    二、使用message

    //Demo.proto 协议格式文件
    syntax='proto3'
    package=Demo
    
    message Data {
    	optional int32 x = 1;
    	optional string str = 2;
    	repeated int32 d = 3;
    }
    

    2.1、类成员变量的访问

    • 获取成员变量:直接采用使用成员变量名(全部为小写);
    • 设置成员变量:使用成员变量名前加set_的方法
    //使用message
    #include <Demo.pb.h>
    #include <QDebug>
    
    Demo::Data data;
    data.set_x(20); //设置成员变量
    qDebug()<<data.x(); //获取成员变量
    

    对于普通成员变量(required和optional)

    • 提供has_方法判断变量值是否被设置;
    • 提供clear_方法清除设置的变量值 ;
    //使用message
    #include <Demo.pb.h>
    #include <QDebug>
    
    Demo::Data data;
    data.set_x(20); //设置成员变量
    qDebug()<<data.has_x(); //判断变量值是否被设置
    data.clear_x(); //清除x设置的变量值
    

    对于string类型

    • 提供了多种set_方法,其参数不同;
    • 提供了一个mutable_方法,返回变量值的可修改指针 ;
    //使用message
    #include <Demo.pb.h>
    #include <QDebug>
    
    Demo::Data data;
    data.set_str(20); //设置成员变量
    std::string* mutable_str(); //返回str变量值的可修改指针
    

    对于repeated变量

    • _size方法:返回变量的长度;
    • 通过下脚标访问其中的数据成员组;
    • 通过下脚标返回其中的成员的mutable_的方法
    • _add方法:增加一个成员
    //使用message
    #include <Demo.pb.h>
    #include <QDebug>
    
    Demo::Data data;
    for(int i=0; i<10; i++)
    {
        data.d_add(i); //向d中添加成员
    }
    for(int i=0; i<data.d_size(); i++)
        printf("%d\t",data.d(i)); //通过下脚标访问数据成员组
    

    三、序列化和反序列化

    3.1、序列化和反序列化有什么用?

    序列化和反序列化主要用在保存数据结构上,保存数据很简单,各种形式都可以,例如txt,但是如果想把数据恢复成原先的数据结构就没那么简单了。

    例如:下面是一个学生的结构体

    struct student {
    	QString name;
    	QString class;
    	long int stu_id;
    	float Chinese;
    	float Math;
    	float English;
    }
    

    假如把三年一班的学生记录为一个结构体数组struct student Class_3_1[max_length];
    把它存为.txt文件,如果想把它恢复成struct student Class_3_1[max_length];就得自己解析文件,特别麻烦。

    如果采用序列化可以把数据结构序列化为二进制数据进行存储,反序列化可以把存储的二进制数据再次恢复成之前的数据结构,很方便使用。

    3.2、序列化

    //文件后缀可以自定
    fileName = QFileDialog::getSaveFileName(0, QObject::tr("protobuf序列化"),currentPath,QObject::tr("TestData(*.TD)"));
    
    if (!fileName.isEmpty())
    {
    	if (!fileName.endsWith(".TD"))
    	{
    		fileName += ".TD";
    	}
    	
    	QFile file(fileName);  
    	if(file.open(QIODevice::WriteOnly))  
    	{  
    		// data 是一个 Demo::Data message对象
    		int nLength = data->ByteSize(); 
    		char* pbuf = new char[nLength];
    		data->SerializePartialToArray(pbuf,nLength); //序列化
    		if(nLength == file.write(pbuf,nLength))
    		{
    			qDebug()<<"SAVE_SUCESS";
    		}
    		else
    		{
    			qDebug()<<"SAVE_FAIL";
    		}
    	}
    	file.close();
    }
    

    3.3、反序列化

    fileName = QFileDialog::getOpenFileName(0, QObject::tr("读取参考曲线数据 "),sCurPath,QObject::tr("ReferenceLine(*.RL)"));
    
    Demo::Data data_1;
    
    if (!fileName.isEmpty())
    {	
    	QFile file(fileName);  
    	if(file.open(QIODevice::ReadOnly))  
    	{  
    		QByteArray array_para = file.readAll();
    		int nLen = array_para.length();
    		// data 是一个 Demo::Data message对象
    		if(!data_1->ParsePartialFromArray(array_para.data(),nLen)) //反序列化
    		{
    			qDebug()<<"LOAD_FAIL";
    		}
    		else
    		{
    			qDebug()<<"LOAD_SUCCESS";
    			
    			int X = data_1->x();
    			QString STR = QString::fromUtf8(data_1->str().data());
    			QVector<int> D;
    			for(int i=0; i<data_1->d_size(); i++)
    			{
    				D.push_back(data_1->d(i));
    			}
    		}	
    	}
    	file.close();
    }
    
    展开全文
  • Protobuf 使用

    千次阅读 2022-03-22 18:06:12
    Protobuf 使用 Protobuf 概念以及干什么的就不在这里说了,看这篇文章的应该都了解了,不了解的可以自己查一下。 总共分三步 第一步:下载配置 Protobuf 环境 Protobuf github 下载链接 里面有各个版本的 package,...
  • Protobuf使用手册.doc

    2019-08-25 20:59:24
    Protobuf使用手册,有很多实例,是入门学习protobuf的好资料。
  • Protobuf的简要介绍及使用详解

    千次阅读 2021-02-28 14:04:41
    一、protobuf的应用场景在官方文档中可以看到protocol buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法,它可用于(数据)通信协议、数据存储等。 Protocol Buffers 是一种灵活,高效,自动化机制的...
  • protobuf基本用法详解

    千次阅读 2020-05-08 00:59:48
    文章目录1、包名package2、option3、消息类型3.1 message3.2 字段规则3.3 标识号3.4 数据类型3.4.1 基本数据类型3.4.2 枚举类型3.4.3 map数据...4.2 import public5、更新Message消息类型原则6、protobuf扩展...
  • ProtoBuf使用说明

    2021-01-05 10:55:07
    一、Protobuf简介 protobuf(Google Protocol Buffers)是Google提供一个具有...windows环境下载protoc-2.5.0-win32.zip,下载后解压将protoc.exe拷贝至C:\Windows\System32目录下即可使用。 linux环境下在protobuf-2.5
  • 主要介绍了vue中使用protobuf踩坑记,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
  • Protobuf使用(C++)

    2020-04-20 23:32:17
    一、protobuf编译 [下载地址](https://github.com/protocolbuffers/protobuf/releases) PS:C++下载的protobuf-cpp-3.11.4.zip 1.1、设定生成项目属性 1.2、生成动态库 1.3、生成动态库 VS直接打开生成的工程,...
  • 由于Asp.net core 采用了全新的MiddleWare方式,因此使用protobuf序列化,只需要使用Protobuf-net修饰需要序列化的对象,并在MVC初始化的时候增加相应的Formatter就可以了。 没时间解释了,快上车。 通过NuGet获取...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,179
精华内容 2,071
关键字:

protobuf使用详解