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能够有效的提高性能.
目录
相关文章
|
机器学习/深度学习 存储 并行计算
一篇就够:高性能推理引擎理论与实践 (TensorRT)
本文分享了关于 NVIDIA 推出的高性能的深度学习推理引擎 TensorRT 的背后理论知识和实践操作指南。
14531 9
一篇就够:高性能推理引擎理论与实践 (TensorRT)
|
Linux 网络安全 开发工具
【freeSwitch】——centos 7 安装教程及常见问题
【freeSwitch】——centos 7 安装教程及常见问题
1255 0
【freeSwitch】——centos 7 安装教程及常见问题
|
存储 固态存储 索引
搜索和推荐统一存储层的新进展和思考
我们在2017年统一了搜索和推荐场景下的HA3、iGraph、RTP和DII四大引擎的存储层(参见统一之战),帮助它们取得了的更迅速的迁移能力、更快速的数据恢复能力和更丰富的数据召回能力。 最近一年来,我们在统一的存储框架上又做了进一步的演进,下面将分别从架构、Build服务以及存储模型角度介绍我们的新进展和思考。   1.架构   在我们的传统架构(参见统一之战)中,
3140 0
|
5月前
|
JSON Java 定位技术
抖音虚拟位置修改器,快手小红书陌陌均支持,jar最新xposed插件
这个代码实现了一个GPS位置模拟器,主要功能包括: 基于基准位置生成随机GPS坐标点
|
自然语言处理 Java 测试技术
序列化性能之巅:使用Fury替换Protobuf/Flatbuffers实现10倍加速
问题背景Protobuf/Flatbuffers是业界广泛使用的序列化库,服务于大量的业务场景。但随着业务场景的复杂化,Protobuf/Flatbuffers逐渐不能满足性能需求开始成为系统瓶颈,在这种情况下,用户不得不手写大量序列化逻辑来进行极致性能优化,但这带来了三个问题:大量字段手写序列化逻辑冗长易出错;手写重复序列化逻辑开发效率低下;难以处理发送端和接收端字段变更的前后兼容性问题;这里将
2542 0
|
Java Maven Kotlin
在 build.gradle.kts 添加 阿里云仓库
在 build.gradle.kts 添加 阿里云仓库
2643 0
|
存储 Java API
Flink 状态清除的演进之路
对于流计算程序来说,肯定会用到状态(state),假如状态不自动清除,并且随着作业运行的时间越来越久,就会累积越多越多的状态,就会影响任务的性能,为了有效的控制状态的大小,Flink从1.6.0开始引入了状态的生存时间(TTL)功能,这样就可以实现自动清理状态,控制状态的大小.本文主要介绍一下Flink从1.6.0开始到1.9.1的状态清理不断的演进之路. Flink1.6.0状态清除 Apache Flink 的 1.6.0 版本引入了状态生存时间特性。它使流处理应用程序的开发人员能够配置算子的状态,使其在定义的生存时间超时后被清除。
|
XML 自然语言处理 开发者
定制化IDL文件设计:面向具体需求的接口定义方法
定制化IDL文件设计:面向具体需求的接口定义方法
467 2
|
人工智能 弹性计算 并行计算
技术改变AI发展:CUDA Graph优化的底层原理分析(GPU底层技术系列一)
随着人工智能(AI)的迅速发展,越来越多的应用需要巨大的GPU计算资源。CUDA是一种并行计算平台和编程模型,由Nvidia推出,可利用GPU的强大处理能力进行加速计算。
107077 1
|
运维 搜索推荐 调度
Ha3搜索引擎简介
Ha3是阿里巴巴搜索团队开发的搜索引擎平台,它为阿里集团包括淘宝、天猫在内的核心业务提供搜索服务支持。
24708 1