试图找出一种可移植的数据保存方法

Trying to figure out a portable data saving approach

本文关键字:可移植 一种 数据 方法 保存      更新时间:2023-10-16

我有一个程序正在英特尔Edison(32位Yocto Linux)上运行。它读取传感器数据,然后将传感器数据写入文件。数据以1 int和13 double的数据包形式出现,每秒有100个数据包到达。一段时间后,我将从中提取文件,并使用x64 windows计算机上运行的工具读取这些文件。

目前,我正在将数据作为一个原始文本文件来编写(因为字符串很好,而且是可移植的)。然而,由于将为此编写大量数据,我正在寻找节省空间的方法。然而,我正在努力想办法做到这一点,这样在另一边的解释中就不会丢失任何数据。

我最初的想法是继续创建一个看起来像这样的结构:

struct dataStruct{
  char front;
  int a;
  double b, c, d, e, f, g, h, i, j, l, m, n, o;
  char end;
}

然后将其并集如下:

union dataUnion{
  dataStruct d;
  char[110] c;
}
//110 was chosen because an int = 4 char, and a double = 8 char,
//so 13*8 = 104, and therefore d = 1 + 4 + 13*8 + 1 = 110

然后将char数组写入文件。然而,阅读告诉我,这样的实现可能不一定在操作系统之间兼容(更糟糕的是……它可能在某些时候工作,而在其他时候不工作……)

所以我想知道,有没有一种便携的方法可以保存这些数据,而不只是将其保存为原始文本?

正如其他人所说:序列化可能是解决问题的最佳方案。

由于您处于资源受限的环境中,我建议您使用MsgPack之类的软件。它只有头部(给定C++11编译器),非常轻,格式简单,C++接口也很好。它甚至允许您非常容易地序列化用户定义的类型(即类/结构):

// adapted from https://github.com/msgpack/msgpack-c/blob/master/QUICKSTART-CPP.md
#include <msgpack.hpp>
#include <vector>
#include <string>
struct dataStruct {
    int a;
    double b, c, d, e, f, g, h, i, j, l, m, n, oo;  // yes "oo", because "o" clashes with msgpack :/
    MSGPACK_DEFINE(a, b, c, d, e, f, g, h, i, j, l, m, n, oo);
};
int main(void) {
    std::vector<dataStruct> vec;
    // add some elements into vec...
    // you can serialize dataStruct directly
    msgpack::sbuffer sbuf;
    msgpack::pack(sbuf, vec);
    msgpack::unpacked msg;
    msgpack::unpack(&msg, sbuf.data(), sbuf.size());
    msgpack::object obj = msg.get();
    // you can convert object to dataStruct directly
    std::vector<dataStruct> rvec;
    obj.convert(&rvec);
}

作为替代方案,您可以查看谷歌的FlatBuffers。它看起来资源效率很高,但我还没有尝试过。

EDIT:下面是一个完整的示例,说明整个序列化-文件I/O-反序列化循环:

// adapted from:
// https://github.com/msgpack/msgpack-c/blob/master/QUICKSTART-CPP.md
// https://github.com/msgpack/msgpack-c/wiki/v1_1_cpp_unpacker#msgpack-controls-a-buffer
#include <msgpack.hpp>
#include <fstream>
#include <iostream>
using std::cout;
using std::endl;
struct dataStruct {
    int a;
    double b, c, d, e, f, g, h, i, j, l, m, n, oo;  // yes "oo", because "o" clashes with msgpack :/
    MSGPACK_DEFINE(a, b, c, d, e, f, g, h, i, j, l, m, n, oo);
};
std::ostream& operator<<(std::ostream& out, const dataStruct& ds)
{
    out << "[a:" << ds.a << " b:" << ds.b << " ... oo:" << ds.oo << "]";
    return out;
}
int main(void) {
    // serialize
    {
        // prepare the (buffered) output file
        std::ofstream ofs("log.bin");
        // prepare a data structure
        dataStruct ds;
        // fill in sample data
        ds.a  = 1;
        ds.b  = 1.11;
        ds.oo = 101;
        msgpack::pack(ofs, ds);
        cout << "serialized: " << ds << endl;
        ds.a  = 2;
        ds.b  = 2.22;
        ds.oo = 202;
        msgpack::pack(ofs, ds);
        cout << "serialized: " << ds << endl;
        // continuously receiving data
        //while ( /* data is being received... */ ) {
        //
        //    // initialize ds...
        //
        //    // serialize ds
        //    // You can use any classes that have the following member function:
        //    // https://github.com/msgpack/msgpack-c/wiki/v1_1_cpp_packer#buffer
        //    msgpack::pack(ofs, ds);
        //}
    }
    // deserialize
    {
        // The size may decided by receive performance, transmit layer's protocol and so on.
        // prepare the input file
        std::ifstream ifs("log.bin");
        std::streambuf* pbuf = ifs.rdbuf();
        const std::size_t try_read_size = 100;  // arbitrary number...
        msgpack::unpacker unp;
        dataStruct ds;
        // read data while there are still unprocessed bytes...
        while (pbuf->in_avail() > 0) {
            unp.reserve_buffer(try_read_size);
            // unp has at least try_read_size buffer on this point.
            // input is a kind of I/O library object.
            // read message to msgpack::unpacker's internal buffer directly.
            std::size_t actual_read_size = ifs.readsome(unp.buffer(), try_read_size);
            // tell msgpack::unpacker actual consumed size.
            unp.buffer_consumed(actual_read_size);
            msgpack::unpacked result;
            // Message pack data loop
            while(unp.next(result)) {
                msgpack::object obj(result.get());
                obj.convert(&ds);
                // use ds
                cout << "deserialized: " << ds << endl;
            }
            // All complete msgpack message is proccessed at this point,
            // then continue to read addtional message.
        }
    }
}

输出:

serialized: [a:1 b:1.11 ... oo:101]
serialized: [a:2 b:2.22 ... oo:202]
deserialized: [a:1 b:1.11 ... oo:101]
deserialized: [a:2 b:2.22 ... oo:202]

您需要序列化数据。既然我可以假设助推不是一种选择,你就必须手动完成。

真正的可移植性(未签名除外)这是一个令人头疼的问题。然而,如果你知道你使用的所有系统都使用相同的编码(例如,有符号整数的二进制补码和浮点的IEE754),那么你很幸运,你可以使用基本的位操作来做到这一点。

您需要使用掩码逐字节设置缓冲区。

唯一需要做的不同的事情取决于机器的字节序。

不要重新发明轮子。这正是谷歌协议缓冲区旨在解决的问题——以一种不需要人类可读的方式在计算机之间传输定义良好的数据。(EG与JSON或XML相反)

或者,你可以去真正的老skool,在ASN.1 上阅读

为了完整起见,这里是数据序列化格式的比较,所以可以随意选择毒药。

最好的方法是使用序列化(ProtoBuf、Thrift等)。但是,如果你不能使用它,并且需要"原始"解决方案,唯一的方法就是在所有平台上使用相同大小的特殊类型来描述你的结构:

struct dataStruct{
  uint32_t a;  // see cstdint.h or boost
  /// ...
}

您也需要小心字节顺序。因此,无论何时序列化(将其传递到"另一端"或保存到文件中),都必须始终将所有字段转换为小端序(或大端序)。

还有一件事需要记住的是结构包装)参见

#pragma pack(1)

__attribute__((packed))

这是一个广泛的主题,所以最简单的解决方案是使用序列化程序。

我会告诉您文本是最安全的。将其保存为原始int和doubles会导致big/little endian问题,并可能导致双布局格式的问题。如果不将不同的值相互分隔,即使是转到文本也可能会引起问题。

另一种选择是定义自己的"通用"格式,并在写/读操作中转换为该格式。。。也许输出int作为文本,但作为伪科学表示法文本,比如5个字符的尾数值、"e"和2/3个字符的指数。

相关文章: