关于c++:一个基于c11标准的序列化反序列化协议与实现

2次阅读

共计 4386 个字符,预计需要花费 11 分钟才能阅读完成。

elti

elti 是什么?

一个基于 c ++11 规范的序列化 / 反序列化协定与实现,与 json,xml 等文本协定相比,elti 能够传递二进制数据;与 protobuf 协定相比,elti 配置简略,即拿即用。

elti 是相似于 bson 和 cbor 的序列化协定,相较于 bson 协定空间利用率不现实(https://blog.csdn.net/m0_38110132/article/details/77716792),cbor 的 c /c++ 实现又太少(没有找到宽泛应用的稳固的轮子),elti 旨在提供一个在 效率,接口易用性,扩展性,空间利用率 等方面有一个平衡稳固的体现,elti 次要用来做数据传输而不是展现,即 elti不思考可读性

github : https://github.com/chloro-pn/…

格局

Element := Key : Value

Value := 

       | Map

       | Array

       | Data

Map := {Element}

ARRAY := {Value}

Data := [byte0, byte1, ... byten]

反对平台

linux, macOS

第三方库

elti 应用 https://github.com/sorribas/varint.c 作为对整形数据进行变长编码的库。应用 https://github.com/catchorg/Catch2 作为单元测试库。

Building

just run make. 配置库文件 + 头文件门路即可应用。

Test

采纳 Catch2 作为单元测试库,于 test 文件夹下。

BenchMark

耗时(s) elti protobuf rapidjson nlohmann/json
测试数据 1 0.060 0.0056 0.037 0.070
测试数据 2 0.079 0.026 0.352 0.770
测试数据 3 0.365 0.235 3.68 7.225

具体信息见 benchmark/BENCH_MARK.md

TODO

  • 减少定位器定位后果到 Value(Data, Map, Array)的转化操作,目前定位器对于数据的拜访能力无限,例如不反对拜访数组 长度类型
  • 反对复合类型的序列化 / 反序列化机制,例如:
class test {
private:
  classA a;
  classB b;
};

seri(const test& t, std::vector<uint8\_t\>& container) {seri(a);
  seri(b);
}

目前这种操作不反对,没有对 container 做进一步形象。

应用

#include "elti.h"

大多数状况下导入 elti.h 头文件即可

elti::Map* map = elti::makeMap();
map->set("name", elti::makeData("nanpang"));
map->set("age", elti::makeData(27));
map->set("sex", elti::makeData(1));
map->set("eof", elti::makeData(false));
elti::Data* data = elti::makeData(elti::varintNum(14553));
map->set("flow_id", data);
std::string content(4096, 'a');
map->set("content", elti::makeData(content));
elti::Array* array = elti::makeArray();
for(int i= 0; i < 10; ++i) {array->push_back(elti::makeData(i));
}
map->set("ids", array);

构建须要序列化的数据,通过 elti::makeMap, elti::makeArray 和 elti::makeData 三个 API 去结构元对象,并通过元对象间的组合模式构建数据的构造。elti 的构造在逻辑上和 json 类似,同样采取 key-value 的模式(这里的 value 能够是 Map,Array 或者 Data),一个 Map 对象能够存储 key-value 的汇合,通过 set 接口一个 Array 对象能够存储 value 的汇合,通过 push_back 接口。一个 Data 对象存储具体的数据,包含 字符串,int8_t,uint8_t,…int64_t,uint64_t,varintNum, 二进制数据(std::vector<uint8_t>),bool 等类型。

注:

  • varintNum 类型是变长编码的整形数据,用以压缩空间。
  • elti 目前不反对浮点数的序列化 / 反序列化
  • 二进制数据通过 std::vector<uint8_t> 或者 std::string 传递。
  • 所有通过 set,push_back 接口放入的对象由 elti 负责开释,不须要使用者开释。
elti::Root root(map);
std::string result;
root.seri(result);

通过应用 Map 类型对象,Array 类型对象或者 Data 类型对象结构 elti::Root 对象(同样由 elti 负责开释此根对象),通过 seri 接口实现序列化。

elti::Root new_root;
size_t offset = new_root.parse(result.data());
assert(offset == result.size());

通过默认构造函数结构 elti::Root 对象,通过 parse 接口实现反序列化,该接口返回的数据为序列化数据的总长度,在没有产生谬误的状况下应该与传入的序列化数据长度相等。

auto arr = new_root["ids"];
for(int i = 0; i < arr.size(); ++i) {std::cout << "ids index :" << i << "id :" << arr[elti::num(i)].get<int>() << std::endl;}
std::cout << "flow id :" << new_root["flow_id"].get<elti::varintNum>().getNum() << std::endl;
std::cout << "name :" << new_root["name"].get<std::string>() << std::endl;
std::cout << "content size :" << new_root["content"].get<std::string>().size() << std::endl;

数据的拜访,Root 类型重载了 operator[],拜访 Map 对象通过 operator[](const char*)接口,拜访 Array 对象通过 operator[](num)接口和 size()接口,拜访 Data 对象通过.get<>()接口。注:

  • Root 假如用户依照正确的类型拜访对应的对象,通过 assert 进行判断,如果通过接口拜访了不对应的类型,debug 模式下会报错,release 模式下后果未定义。

变长编码

elti 在存储元信息时应用变长编码以节俭空间,用户也能够通过传递给 Data 类型 varintNum 类型对象以应用变长编码传递整形数据(目前变长编码仅反对无符号类型):

elti::Data* data = elti::makeData(elti::varintNum(14553)); // 编码
std::cout << "flow id :" << new_root["flow_id"].get<elti::varintNum>().getNum() << std::endl; // 解码

自定义序列化 / 反序列化接口

elti 仅提供根本类型的序列化机制,你也能够通过实例化以下两个模板参数来定制特定类型的序列化,反序列化接口:

template<typename T>
void seri(const T& obj, std::vector<uint8_t>& container);

template<typename T>
T parse(const std::vector<uint8_t>& container);

例子:

class test {
public:
  test(int a, std::string n): age(a), name(n) { }
  //just for test.
  int age;
  std::string name;
};

namespace elti {
template<>
void seri(const test& obj, std::vector<uint8_t>& container) {container.resize(sizeof(obj.age) + obj.name.size());
  memcpy(&container.front(), &obj.age, sizeof(obj.age));
  memcpy(&container.front() + sizeof(obj.age), obj.name.data(), obj.name.size());
}

template<>
test parse(const std::vector<uint8_t>& container) {
  int age;
  std::string name;
  memcpy(&age, &container.front(), sizeof(age));
  name.append((char*)(&container.front() + sizeof(age)), container.size() - sizeof(age));
  return test(age, name);
}
}

int main() {elti::Map* obj = elti::makeMap();
  // 调用自定义的 seri 接口
  obj->set("obj", elti::makeData(test(25, "nanpang")));
  elti::Root root(obj);
  std::string result;
  root.seri(result);
  
  elti::Root new_root;
  size_t offset = new_root.parse(result.data());
  assert(offset == result.length());
  // 调用自定义的 parse 接口
  test t = new_root["obj"].get<test>();
  std::cout << "age :" << t.age << "name :" << t.name << std::endl;
  return 0;
}

定位器

  // 应用序列化数据指针结构一个定位器对象。elti::PositionerRoot pst(result.data());
  // 应用定位器对象如同应用 Root 对象,然而定位器只会解析必要门路并定位数据,跳过不相干的数据。std::string book = pst["books"][elti::num(1)].get<std::string>();
正文完
 0