Protobuf和FlatBuffers以及ByteBuffer的应用

首先来一发pb3的IDL代码Helloworld.proto

syntax = "proto2";

package proto.helloworld;

message HelloWorld
{
    required int32 id = 1;        // id
    required string str = 2;    // str
    optional int32 opt = 3;        // optional field
}

使用命令行或者编写一个bat批处理文件

@set path=..\..\..\..\third_party\protobuf\bin\release;%path%
@cls

:: /////////////////////////////////////////////////////////////
::
:: 编译Protobuf协议
::
:: /////////////////////////////////////////////////////////////
protoc --version

protoc --cpp_out=../ ./Helloworld.proto

pause
执行以上的批处理文件,将会在其目录的上一层目录下生成两个源码文件:Helloworld.pb.h和Helloworld.pb.cc.
将生成的这俩文件拖进工程里面去,至于vc的StdAfx,预编译头的问题,很好解决.在cc文件上点右键->属性->C/C++->预编译头->预编译头(不使用预编译头),这事儿就妥妥的解决了.

以下列举序列化到三种目标里去的实例
1.std::string

void testString()
{
    //////////////////////////////////////////////////////////////////////////
    // 编码
    //////////////////////////////////////////////////////////////////////////
    proto::helloworld::HelloWorld msg_encode;
    msg_encode.set_id(10086);
    msg_encode.set_str("hello world");
    msg_encode.set_opt(10000);

    std::string str_data;
    bool encode_ok = msg_encode.SerializeToString(&str_data);
    ASSERT(encode_ok);

    //////////////////////////////////////////////////////////////////////////
    // 解码
    //////////////////////////////////////////////////////////////////////////
    proto::helloworld::HelloWorld msg_decode;
    bool decode_ok = msg_decode.ParseFromString(str_data);
    ASSERT(decode_ok);
    size_t n = str_data.length();

    ASSERT(msg_decode.id() == 10086);
    ASSERT(msg_decode.str().compare("hello world") == 0);
    ASSERT(msg_decode.opt() == 10000);
}

2.ByteArray(char [])

void testByteArray()
{
    //////////////////////////////////////////////////////////////////////////
    // 编码
    //////////////////////////////////////////////////////////////////////////
    proto::helloworld::HelloWorld msg_encode;
    msg_encode.set_id(10086);
    msg_encode.set_str("hello world");
    msg_encode.set_opt(10000);

    char msg_buf[1024];
    ZeroMemory(msg_buf, sizeof(msg_buf));
    bool encode_ok = msg_encode.SerializeToArray(msg_buf, sizeof(msg_buf));
    ASSERT(encode_ok);
    int encode_size = msg_encode.ByteSize();

    //////////////////////////////////////////////////////////////////////////
    // 解码
    //////////////////////////////////////////////////////////////////////////
    proto::helloworld::HelloWorld msg_decode;
    bool decode_ok = msg_decode.ParseFromArray(msg_buf, encode_size);
    ASSERT(decode_ok);

    ASSERT(msg_decode.id() == 10086);
    ASSERT(msg_decode.str().compare("hello world") == 0);
    ASSERT(msg_decode.opt() == 10000);
}

3.std::fstream

void testStream()
{
    //////////////////////////////////////////////////////////////////////////
    // 编码
    //////////////////////////////////////////////////////////////////////////
    proto::helloworld::HelloWorld msg_encode;
    msg_encode.set_id(10086);
    msg_encode.set_str("hello world");
    msg_encode.set_opt(10000);

    std::fstream output("./msg_bin", std::ios::out | std::ios::trunc | std::ios::binary);
    bool encode_ok = msg_encode.SerializeToOstream(&output);
    ASSERT(encode_ok);
    output.close();

    //////////////////////////////////////////////////////////////////////////
    // 解码
    //////////////////////////////////////////////////////////////////////////
    std::fstream input("./msg_bin", std::ios::in | std::ios::binary);
    proto::helloworld::HelloWorld msg_decode;
    bool decode_ok = msg_decode.ParseFromIstream(&input);
    ASSERT(decode_ok);

    ASSERT(msg_decode.id() == 10086);
    ASSERT(msg_decode.str().compare("hello world") == 0);
    ASSERT(msg_decode.opt() == 10000);
}
以上就是ProtoBuf的基本用法了.

FlatBuffers

FlatBuffers支持将ProtoBuf的IDL转换为自己的IDL,只需要简单的一行命令:

flatc.exe --proto Helloworld.proto
该命令将会生成一个FlatBuffers的IDL文件:Helloworld.fbs.

Helloworld.fbs它看起来像是这样的:

// Generated from Helloworld.proto

namespace fbs.helloworld;

table HelloWorld {
  id:int = 0 (id: 0);
  str:string (required, id: 1);
  opt:int = 0 (id: 2);
}

// 定义之后将会提供GetHelloWorld,VerifyHelloWorldBuffer,FinishHelloWorldBuffer三个方法.
root_type HelloWorld;
这是我基于生成的IDL修改过的.

我们可以用下面这个bat去生成代码:

flatc.exe --cpp -o ../ Helloworld.fbs
pause
执行之后,它将在当前目录的上一层目录下生成一个代码文件:Helloworld_generated.h

使用方法大概是这样的:

void testFlatBuffer()
{
    //////////////////////////////////////////////////////////////////////////
    // 编码
    //////////////////////////////////////////////////////////////////////////

    flatbuffers::FlatBufferBuilder builder;

    int32_t id = 10086;
    const char *str = "hello world";
    int32_t opt = 10000;

#if 0
    auto strName = builder.CreateString(str);
    auto root = fbs::helloworld::CreateHelloWorld(builder, id, strName, opt);
#else
    auto root = fbs::helloworld::CreateHelloWorldDirect(builder, id, str, opt);
#endif

#if 0
    builder.Finish(root);
#else
    FinishHelloWorldBuffer(builder, root);
#endif

    auto p = builder.GetBufferPointer();
    auto sz = builder.GetSize();

    auto bufferpointer =
        reinterpret_cast<const char *>(builder.GetBufferPointer());
    std::string buffer;
    buffer.assign(bufferpointer, bufferpointer + builder.GetSize());
    size_t n = buffer.length();

    builder.ReleaseBufferPointer();

    //////////////////////////////////////////////////////////////////////////
    // 解码
    //////////////////////////////////////////////////////////////////////////
#if 0
    auto decode = flatbuffers::GetRoot<proto::helloworld::HelloWorld>(buffer.c_str());
#else
    auto decode = fbs::helloworld::GetHelloWorld(buffer.c_str());
#endif
    assert(decode->id() == 10086);
    assert( strcmp(decode->str()->c_str(), "hello world") == 0);
    assert(decode->opt() == 10000);
}

ByteBuffer
关于这个嘛,看以前的文章:http://www.cppblog.com/tx7do/archive/2015/06/12/145865.html
协议定义如下:

#include "ByteBuffer.h"

// 声明序列化
#define NET_APPEND(STRUCT_TYPE)\
    static ByteBuffer& operator<<(ByteBuffer& lht, const STRUCT_TYPE& rht)

// 声明解序列化
#define NET_READ(STRUCT_TYPE)\
    static const ByteBuffer& operator>>(const ByteBuffer& lht, STRUCT_TYPE& rht)

namespace bb
{
    namespace helloworld
    {

        struct CMD_HelloWorld
        {

            int32            id;        // id
            std::string        str;    // str
            int32            opt;    // optional field

            CMD_HelloWorld()
            {
                this->id = 0;
                this->opt = 0;
                this->str.clear();
            }
        };
        NET_APPEND(CMD_HelloWorld)
        {
            lht << rht.id
                << rht.str
                << rht.opt;
            return lht;
        };
        NET_READ(CMD_HelloWorld)
        {
            lht >> rht.id
                >> rht.str
                >> rht.opt;
            return lht;
        };

    }
}
使用的代码是这样的:

void testByteBuffer()
{
    //////////////////////////////////////////////////////////////////////////
    // 编码
    //////////////////////////////////////////////////////////////////////////
    bb::helloworld::CMD_HelloWorld msg_encode;
    msg_encode.id = 10086;
    msg_encode.str = "hello world";
    msg_encode.opt = 10000;

    ByteBuffer sendBuffer;
    sendBuffer.clear();
    sendBuffer << msg_encode;
    
    auto p = sendBuffer.contents();
    auto sz = sendBuffer.size();
    
    //////////////////////////////////////////////////////////////////////////
    // 解码
    //////////////////////////////////////////////////////////////////////////
    ByteBuffer recvBuffer;
    recvBuffer.clear();
    recvBuffer.append((uint8*)p, sz);
        bb::helloworld::CMD_HelloWorld msg_decode;
    recvBuffer >> msg_decode;

    assert(msg_decode.id == 10086);
    assert(strcmp(msg_decode.str.c_str(), "hello world") == 0);
    assert(msg_decode.opt == 10000);
}

总结
相比PB来说,FBS不需要额外的指定一个外部的缓存,它内置了一个缓存,大概这就是它快的缘故吧.
序列化之后的空间占用结果是:protobuf:19 flatbuffers:48 bytebuffer:20.
从空间上看,FBS是并不占优啊.
以前,一直使用的是ByteBuffer,因为简单,而且无论是从空间上还是时间上都还算划算.但是要手写序列化的代码,相对来说,比较烦人.所以还是PB,FBS这样的利用IDL自动生成代码的方式方便.
PB现在支持几乎所有语言,是一个相当成熟的解决方案了.
FBS就目前来说,支持的语言并不多,官方只支持:C++,Java,C#,Go,Python,JS,C,PHP,Ruby.
PB的IDL各大编辑器几乎都支持它的语法染色,然而FBS却并没有,这个看起来也很让人蛋疼.
PB相对慢,但是空间占用小,它更适合外网传输,并且对时间并不是那么要求高的应用;
FBS相对快,但是空间占用较大,它在RPC,内网传输,以及对时间要求很高的场景上会很适合,在手机登移动平台下,计算性能不高的场景下也是适合的.
总的来说,我要做网游的通讯协议,还是PB更加的适合.
FBS更适合的是Android,iOS这样的移动平台,因为它们对性能要求比较高,使用FBS能够有效的提高性能.

时间: 2024-08-13 21:37:25

Protobuf和FlatBuffers以及ByteBuffer的应用的相关文章

【转】【UNITY3D 游戏开发之五】GOOGLE-PROTOBUF与FLATBUFFERS数据的序列化和反序列化

本站文章均为 李华明Himi 原创,转载务必在明显处注明:  转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/unity3d-game/1607.html  关于Protobuf 通过本文的转载和分享的相关链接,足够了解使用了,所以这里不赘述了.但是这里Himi顺便提一下"FlatBuffers" ,它是 Protocol Buffers升级版,其主要区别在于FlatBuffers在访问数据前不需要解析/拆包这一步.            

Android 上的数据格式 FlatBuffers 介绍

本文讲的是Android 上的数据格式 FlatBuffers 介绍, JSON 格式 - 一个基本上人人知道的.轻量级的.并被现代服务器所广泛使用的数据格式.相对过时的.讨厌的 XML 数据格式来说,它量级轻.易于人们阅读.对开发人员也更为友好. JSON 是一种独立于语言存在的数据格式,但是它解析数据并将之转换成如 Java 对象时,会消耗我们的时间和内存资源.几天前,Facebook 宣称自己的 Android app 在数据处理的性能方面有了极大的提升.在几乎整个 app 中,他们放弃了

FlatBuffers 体验

1. 背景 最近在项目中需要使用一种高效数据序列化的工具.碰巧在几篇文章中都提到了FlatBuffers 这个库.特别是 Android 性能优化典范第四季1中两个对比图,让我对它产生浓厚的兴趣.如下: (注:图片来自1) 可见,FlatBuffers 几乎从空间和时间复杂度上完胜其他技术,我决定详细调研一下此技术. FlatBuffers 是一个开源的跨平台数据序列化库,可以应用到几乎任何语言(C++, C#, Go, Java, JavaScript, PHP, Python),最开始是 G

VC 2012编译protobuf的出错处理

近来要学习protobuf的协议生成,需要从网上下载它的代码,从这个SVN地址下载: http://protobuf.googlecode.com/svn/trunk 下载完成之后,就可以到protobuf\vsprojects目录下找到VC2008的工程文件,然后打开工程进行转换,这个没有问题.但在编译过程里会出现两个问题,第一个问题如下: 1>------ Build started: Project: gtest_main, Configuration: Debug Win32 -----

在 muduo 中实现 protobuf 编解码器与消息分发器

本文是<一种自动反射消息类型的 Google Protobuf 网络传输方案>的延续,介绍如何将前文介绍 的打包方案与 muduo::net::Buffer 结合,实现了 protobuf codec 和 dispatcher. Muduo 的 下载地址: http://muduo.googlecode.com/files/muduo-0.1.9-alpha.tar.gz ,SHA1 dc0bb5f7becdfc0277fb35f6dfaafee8209213bc ,本文的完整代码可在线阅读

一种自动反射消息类型的 Google Protobuf 网络传输方案

这篇文章要解决的问题是:在接收到 protobuf 数据之后,如何自动创建具体的 Protobuf Message 对象 ,再做的反序列化."自动"的意思是:当程序中新增一个 protobuf Message 类型时,这部分代码不 需要 修改,不需要自己去注册消息类型.其实,Google Protobuf 本身具有很强的反射(reflection)功能, 可以 根据 type name 创建具体类型的 Message 对象,我们直接利用即可. 本文假定读者了解 Google Proto

Android在JNI中使用ByteBuffer的方法

  Android在JNI中使用ByteBuffer的方法 本文实例讲述了Android在JNI中使用ByteBuffer的方法.分享给大家供大家参考.具体如下: 一.ByteBuffer 定义 在NIO中,数据的读写操作始终是与缓冲区相关联的(读取时信道(SocketChannel)将数据读入缓冲区,写入时首先要将发送的数据按顺序填入缓冲区) 缓冲区是定长的,基本上它只是一个列表,它的所有元素都是基本数据类型.ByteBuffer是最常用的缓冲区,它提供了读写其他数据类型的方法,且信道的读写方

几种序列化协议(protobuf,xstream,jackjson,jdk,hessian)相关数据对比

最近研究了下google protobuf协议,顺便对比了一下json,xml,java序列化相关的数据对比,从几个纬度进行对比.   别人的相关测试数据: http://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking   测试纬度 序列化时间 反序列化时间 bytes大小 测试代码 准备protobuf文件 1.import "InnerMessage.proto"; 2.package demo; 3.opti

转 比较跨语言通讯框架:Apache Thrift和Google Protobuf

    前两天想在微博上发表一个观点:在现在的技术体系中,能用于描述通讯协议的方式很多,xml,json,protobuf,thrift,如果在有如此众多选择的基础上,在设计系统时,还自造协议,自己设计协议类型和解析方式,那么我只能说,您真的落后了,不是技术上,而是思想上.对于xml,和json我们不做过多描述了,参考相关文档就可以了.特别是json,如今在 web系统,页游系统的前后台通讯中,应用非常广泛.本文将重点介绍两种目前在大型系统中,应用比较普遍的两种通讯框架,thrift和Proto