如何将向量序列化为字符数组

Posted

技术标签:

【中文标题】如何将向量序列化为字符数组【英文标题】:How to serialize a vector into an array of chars 【发布时间】:2016-04-09 10:55:08 【问题描述】:

我有一个向量定义为:

std::vector<message *>

消息在哪里:

struct message
    static unsigned int last_id;
    unsigned int id;
    std::string msg;
    std::string timestamp;

我的目标是使用 Winsock 发送此信息(从服务器到客户端),但这仅允许发送 WinSock2.h 中出现的字符。考虑到这一点,我想序列化一个字符数组中的所有信息(id、msg 和时间戳)以便将它们全部发送,并且在客户端中,具有反序列化的函数以便具有相同的向量 I在服务器上。

我该如何实现它?

感谢任何帮助。

【问题讨论】:

请查看minimal reproducible example。你能展示你尝试过的东西吗? 我一直在寻找很多方法来实现它,但我找不到任何有效的解决方案。任何开始序列化的关键事实将不胜感激。 我对Winsock不熟悉,但可能和我用过的差不多。想象一下,您的所有 4 个项目都“压缩”成一个字符向量(我使用 std::string)。目的地如何找到各个字段?也许是您自己发明的协议?一个(有点)相关的区域被称为“持久存储”,我相信有些包存在,但不记得名字了。祝你好运。 从客户端发送到服务器我使用自己的协议(数据以“;”分隔,即我发送“cmd;username;msg;”。发送时出现问题从服务器到客户端,我需要发送更多信息(各种用户消息,关注者等),发送所有由“;”分隔的信息非常乏味。无论如何感谢您的回答! 为什么要将时间戳存储为字符串? 【参考方案1】:

以下提供了一种解决序列化问题的简单方法。

但请注意,它不可移植。它假设双方(客户端/服务器)的环境条件相同,即字节顺序和 sizeof int 和 size_t。在编写服务器/客户端程序时,这种假设可能无法令人满意,您的代码也应该处理这方面的问题。

例如,如果您可以说 32 位对于 id 值和字符串的长度来说是足够的,那么您可以在序列化时使用 htonl,在反序列化时使用 ntohl。

序列化器:

class MessageSerializer

public:
    MessageSerializer(const message& messageStruct)
    : m_msgRef(messageStruct)
    , m_msgLength(m_msgRef.msg.length())
    , m_timeLength(m_msgRef.timestamp.length())
    

    size_t RequiredBufferSize() const
    
        return sizeof(int) + sizeof(size_t)*2 + m_msgLength + m_timeLength;
    

    void Serialize(void* buffer) const
    
        PushNum     (buffer, m_msgRef.id);
        PushString  (buffer, m_msgRef.msg.c_str(), m_msgLength);
        PushString  (buffer, m_msgRef.timestamp.c_str(), m_timeLength);
    
private:
    const message&  m_msgRef;
    const size_t    m_msgLength;
    const size_t    m_timeLength;

    template<typename INTEGER>
    void PushNum(void*& buffer, INTEGER num) const
    
        INTEGER* ptr = static_cast<INTEGER*>(buffer);
        //copying content
        *ptr = num;
        //updating the buffer pointer to point the next position to copy
        buffer = ++ptr;
    
    void PushString(void*& buffer, const char* cstr, size_t length) const
    
        PushNum(buffer, length);
        //copying string content
        memcpy(buffer, cstr, length);
        //updating the buffer pointer to point the next position to copy
        char* ptr = static_cast<char*>(buffer);
        ptr += length;
        buffer = ptr;
    
;

反序列化器:

class MessageDeserializer

public:
    MessageDeserializer(const char* messageBuffer)
    : m_msgBuffer(messageBuffer)
    

    void Deserialize(message& messageOut)
    
        messageOut.id           = PopNum<int>(m_msgBuffer);
        messageOut.msg          = PopString(m_msgBuffer);
        messageOut.timestamp    = PopString(m_msgBuffer);
    

private:

    const void* m_msgBuffer;

    template<typename INTEGER>
    INTEGER PopNum(const void*& buffer) const
    
        const INTEGER* ptr = static_cast<const INTEGER*>(buffer);
        //copying content
        INTEGER retVal = *ptr;
        //updating the buffer pointer to point the next position to copy
        buffer = ++ptr;

        return retVal;
    

    std::string PopString(const void*& buffer) const
    
        size_t length = PopNum<size_t>(buffer);
        const char* ptr = static_cast<const char*>(buffer);
        //copying content
        std::string retVal(ptr, length);
        //updating the buffer pointer to point the next position to copy
        ptr += length;
        buffer = ptr;

        return retVal;
    
;

那么你的使用代码可能是这样的:

//...
MessageSerializer serializer(*myVector[i]);
char* buffer = new char[serializer.RequiredBufferSize()];
serializer.Serialize(buffer);

和:

//...
message myMsg;
MessageDeserializer(input).Deserialize(myMsg);

【讨论】:

谢谢@R.G.该代码似乎可以工作,但是在创建序列化程序时编译之前出现错误。它在*myVector[i](已经被我的vector&lt;message *&gt; 替换)中说没有运算符匹配这些操作数“*”。操作数类型为:* std::vector>。我尝试创建新向量以查看问题是否与我有关,但问题仍然存在。非常感谢任何帮助! 尝试写带括号的表达式,像这样:*(myVector[i])(Operator Precedence确保表达式的意思相同,但它可能会引出语法错误的原因)。如果它仍然存在,请在此处输入变量定义 (myVecor) 的完整语句,以及它在上述表达式中的使用时间。 问题已解决!我正在访问整个消息向量而不是单个消息。感谢您的时间@R.G.【参考方案2】:

您可以使用Boost serialization library 将结构保存/加载到字符数组中。 boost 库在 C++ 中被广泛使用,如果你不熟悉它,我建议你看看它。

您可以学习使用Boost 套接字,而不是使用winsock,并使您的C++ 代码几乎可以在任何平台上运行,而不仅仅是Windows,但这是另一个主题。

这是一个如何序列化你的向量,并从套接字的另一端恢复它的例子:

#include <vector>
#include <boost/serialization/nvp.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>

struct message 
    static unsigned int last_id;
    unsigned int id;
    std::string msg;
    std::string timestamp;

    template <class ArchiveT>
    void serialize(ArchiveT& ar, const unsigned int /*version*/) // function used to serialize (save/load) data from the boost serialization library
    
        ar & boost::serialization::make_nvp("LastId", last_id);
        ar & boost::serialization::make_nvp("Id", id);
        ar & boost::serialization::make_nvp("Msg", msg);
        ar & boost::serialization::make_nvp("Timestamp", timestamp);
    
;

unsigned int message::last_id;

template <class T>
void serialize_save(const T& obj, std::string& outString)

    std::stringstream binaryOut;
    boost::archive::binary_oarchive outArchive(binaryOut);
    outArchive << obj;

    outString = binaryOut.str();


template <class T>
void serialize_load(T& dataOut, const void* data, const size_t dataSize)

    const char* dataPtr = reinterpret_cast<const char*>(data);
    std::string dataString(dataPtr, dataPtr + dataSize);
    std::stringstream dataStream(dataString);
    boost::archive::binary_iarchive binArchive(dataStream);
    binArchive >> dataOut;



void init_vector(std::vector<message*>& vect) 
    const size_t vectorSize = 2;

    vect.resize(vectorSize);
    for (size_t i = 0; i < vectorSize; i++) 
        vect[i] = new message();
        vect[i]->last_id = 0;
        vect[i]->id = 1;
        vect[i]->msg = "This is a message";
        vect[i]->timestamp = "12:02pm";
    


int main() 
    std::vector<message*> messages;
    init_vector(messages); // initialize the vector. set it to any data

    std::string outputBuffer;
    serialize_save(messages, outputBuffer); // save the vector to a string (array of char)

    socket_write(outputBuffer.c_str(), outputBuffer.size()); // write the serialized data to the socket

    // on the reception side
    std::string receiveBuffer;
    socket_read(receiveBuffer); // receive socket data

    std::vector<message*> receivedMessages;
    serialize_load(receivedMessages, receiveBuffer.c_str(), receiveBuffer.size()); // from the array of character recover the vector
    // here the vector receivedMessages contains the same values saved in init_vector()

如果您愿意,可以通过更改 boost::archive::binary_iarchive 对象来更改导出格式。例如,将其替换为 boost::archive::xml_iarchive 以将对象序列化为 XML。该库还提供其他格式。另一个优点是它支持版本控制。

【讨论】:

谢谢@J-Mik。感谢您的回答,但我需要在 Winsock 中完成。无论如何,我会看看 Boost 序列化库,以防它在其他方面对我有帮助。

以上是关于如何将向量序列化为字符数组的主要内容,如果未能解决你的问题,请参考以下文章

序列化为 JSON 字符串时如何修复 OutOfMemoryException?

如何将对象的json数组反序列化为字典[重复]

将 JSON 反序列化为 C# 对象以在网格中将嵌套数组显示为字符串

javascript-js将form表单序列化[json字符串数组对象]

字符串,向量,数组

测试序列化编码