美文网首页
protobuf 初探

protobuf 初探

作者: 有何不可12317 | 来源:发表于2019-01-09 12:32 被阅读0次

    一 简介

    Google Protocol Buffer(简称Protobuf)是Google公司内部的混合语言数据标准,用于RPC系统和持续数据存储系统。是一种轻便高效的可用于通讯协议数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。

    与XML相比,Protocol buffers序列化后的码流更小、速度更快、操作更简单。你只需要将要被序列化的数据结构定义一次(译注:使用.proto文件定义),便可以使用特别生成的源代码(译注:使用protobuf提供的生成工具)轻松的使用不同的数据流完成对这些结构数据的读写操作,即使你使用不同的语言(译注:protobuf的跨语言支持特性)。你甚至可以更新你的数据结构的定义(译注:就是更新.proto文件内容)而不会破坏依赖“老”格式编译出来的程序。

    二 特点

    优点

    (1)、性能好/效率高,序列化和反序列化的时间开销都很小。(注:参看《全方位评测:Protobuf性能到底有没有比JSON快5倍?》 http://www.52im.net/thread-772-1-1.html
    (2)、支持向后兼容和向前兼容,协议中增加新域不会影响依赖原协议的客户端。
    (3)、平台无关、语言无关、可扩展。
    (4)、支持多种编程语言,目前支持Java、C++、Python、Java Lite、Ruby、JavaScript、Object-C、C#、Go。

    2、 缺点:
    (1)、二进制格式导致可读性差,为了提高性能,protobuf采用二进制编码,可读性差。
    (2)、缺乏自描述,二进制的协议内容必须配合.proto文件的定义才有含义。

    三 历史及版本

    Protobuf最初是在Google开发的,用以解决索引服务器的请求、响应协议。

    Protobuf现在是Google公司内部的通用语言数据标准,已经在使用的有超过48162种报文格式定义和超过12182个.proto文件。它们广泛用于RPC系统或持续的数据存储系统 。

    由于Google在开源protobuf之前,已经在使用protobuf的第二个版本,所以开源时定的是proto2(从V2.0.0开始),目前的最新版本是proto3。

    四 一个简单的例子

    下面通过一个简单的例子了解protobuf怎么使用,后续在详细具体细节

    1 准备工作

    本文使用 Protobuf 和 C++ 开发一个十分简单的例子程序。

    该程序由两部分组成。第一部分被称为 Writer,第二部分叫做 Reader。

    Writer 负责将一些结构化的数据写入一个磁盘文件,Reader 则负责从该磁盘文件中读取结构化数据并打印到屏幕上。

    准备用于演示的结构化数据是 HelloWorld,它包含两个基本数据:

    ID,为一个整数类型的数据
    Str,这是一个字符串
    

    2 编辑.proto文件

    首先我们需要编写一个 proto 文件,定义我们程序中需要处理的结构化数据,在 protobuf 的术语中,结构化数据被称为 Message。proto 文件非常类似 java 或者 C 语言的数据定义。代码清单如下

    syntax = "proto2";
    package lm; 
    message helloworld 
    { 
       required int32     id = 1;  // ID 
       required string    str = 2;  // str 
       optional int32     opt = 3;  //optional field 
    }
    

    一个比较好的习惯是认真对待 proto 文件的文件名。比如将命名规则定于如下:

    packageName.MessageName.proto
    

    在上例中,package 名字叫做 lm,定义了一个消息 helloworld,该消息有三个成员,类型为 int32 的 id,另一个为类型为 string 的成员 str。opt 是一个可选的成员,即消息中可以不包含该成员。所以命名为:

    lm.helloworld.proto
    

    3 编译 .proto 文件

    写好 proto 文件之后就可以用 Protobuf 编译器将该文件编译成目标语言了。本例中我们将使用 C++。

    假设您的 proto 文件存放在 $SRC_DIR 下面,您也想把生成的文件放在同一个目录下,则可以使用如下命令:

    #!/bin/bash
    
    SRC_DIR=`pwd`
    PROTO_FILE=lm.helloworld.proto
    DST_DIR=`pwd`
    protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/$PROTO_FILE
    

    命令将生成两个文件:

    lm.helloworld.pb.h , 定义了 C++ 类的头文件
    lm.helloworld.pb.cc , C++ 类的实现文件

    在生成的头文件中,定义了一个 C++ 类 helloworld,后面的 Writer 和 Reader 将使用这个类来对消息进行操作。诸如对消息的成员进行赋值,将消息序列化等等都有相应的方法。

    4 使用C++编写Write程序

    使用 Protobuf,Writer 的工作很简单,需要处理的结构化数据由 .proto 文件描述,经过上一节中的编译过程后,该数据化结构对应了一个 C++ 的类,并定义在 lm.helloworld.pb.h 中。对于本例,类名为 lm::helloworld。

    Writer 需要 include 该头文件,然后便可以使用这个类了。

    现在,在 Writer 代码中,将要存入磁盘的结构化数据由一个 lm::helloworld 类的对象表示,它提供了一系列的 get/set 函数用来修改和读取结构化数据中的数据成员,或者叫 field。

    当我们需要将该结构化数据保存到磁盘上时,类 lm::helloworld 已经提供相应的方法来把一个复杂的数据变成一个字节序列,我们可以将这个字节序列写入磁盘。

    对于想要读取这个数据的程序来说,也只需要使用类 lm::helloworld 的相应反序列化方法来将这个字节序列重新转换会结构化数据。这同我们开始时那个“123”的想法类似,不过 Protobuf 想的远远比我们那个粗糙的字符串转换要全面,因此,我们不如放心将这类事情交给 Protobuf 吧。

    #include<iostream>
    #include <fstream>
    #include "lm.helloworld.pb.h"
    using namespace std;
    
    
    int main(void)
    {
    
        lm::helloworld msg1;
        msg1.set_id(101);
        msg1.set_str("hello");
    
        // Write the new address book back to disk.
        fstream output("./log", ios::out | ios::trunc | ios::binary);
    
        if (!msg1.SerializeToOstream(&output)) {
            cerr << "Failed to write msg." << endl;
            return -1; 
        }   
        return 0;
    }
    

    Msg1 是一个 helloworld 类的对象,set_id() 用来设置 id 的值。SerializeToOstream 将对象序列化后写入一个 fstream 流。

    编译命令为:

    g++  write_main.cc lm.helloworld.pb.cc -o cpp_write        \
        -std=c++11                                                 \
        `pkg-config --cflags --libs protobuf`
    

    5 使用C++编写Read程序

    #include<iostream>
    #include <fstream>
    #include "lm.helloworld.pb.h"
    using namespace std;
    
    
    void ListMsg(const lm::helloworld & msg) {
        cout << msg.id() << endl;
        cout << msg.str() << endl;
    }
    
    int main(int argc, char* argv[]) {
    
        lm::helloworld msg1;
    
        {   
            fstream input("./log", ios::in | ios::binary);
            if (!msg1.ParseFromIstream(&input)) {
                cerr << "Failed to parse address book." << endl;
                return -1; 
            }   
        }   
    
        ListMsg(msg1);
    
        return 0;
    }
    

    同样,Reader 声明类 helloworld 的对象 msg1,然后利用 ParseFromIstream 从一个 fstream 流中读取信息并反序列化。此后,ListMsg 中采用 get 方法读取消息的内部信息,并进行打印输出操作。

    暂时执行结果是cpp_write出错,报错

    Failed to write msg.
    

    表明是msg1.SerializeToOstream(&output)出错,正在查询.........

    参考

    [1] 博客.Protobuf的介绍
    [2] 博客.什么是protocol buffers
    [3] 博客.Google Protocol Buffer 的使用和原理
    [4] 博客.linux下安装protobuf教程+示例(详细)

    相关文章

      网友评论

          本文标题:protobuf 初探

          本文链接:https://www.haomeiwen.com/subject/seuwrqtx.html