通过boost :: serialization序列化多个std :: shared_ptr并通过boost :: asio发送

时间:2017-05-13 20:47:43

标签: boost boost-asio boost-serialization

我希望通过boost asio从客户端向服务器传输shared_ptr个对象。这是我的代码:

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/asio.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <chrono>
#include <iostream>
#include <sstream>
#include <string>
#include <thread>

using namespace std;

class Message {
public:
    Message() {
    }

    virtual ~Message() {
    }

    string text;

private:
    friend class boost::serialization::access;

    template <class Archive>
    void serialize(Archive &ar, const unsigned int version) {
        ar &text;
    }
};

BOOST_CLASS_EXPORT(Message)

void runClient() {
    // Give server time to startup
    this_thread::sleep_for(chrono::milliseconds(3000));

    boost::asio::ip::tcp::iostream stream("localhost", "3000");
    boost::archive::text_oarchive archive(stream);

    for (int i = 0; i < 10; i++) {
        std::shared_ptr<Message> dl = std::make_shared<Message>();
        stringstream ss;
        ss << "Hello " << i;
        dl->text = ss.str();
        archive << dl;
    }

    stream.close();
    cout << "Client shutdown" << endl;
}

void handleIncommingClientConnection(boost::asio::ip::tcp::acceptor &acceptor) {
    boost::asio::ip::tcp::iostream stream;

    acceptor.accept(*stream.rdbuf());

    boost::archive::text_iarchive archive(stream);

    while (true) {
        std::shared_ptr<Message> m;

        try {
            archive >> m;
            cout << m->text << endl;
        } catch (std::exception &ex) {
            cout << ex.what() << endl;

            if (stream.eof()) {
                cout << "eof" << endl;
                stream.close();
                cout << "Server: shutdown client handling..." << endl;
                break;
            } else
                throw ex;
        }
    }
}

void runServer() {
    boost::asio::io_service ios;
    boost::asio::ip::tcp::endpoint endpoint = boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 3000);
    boost::asio::ip::tcp::acceptor acceptor(ios, endpoint);

    handleIncommingClientConnection(acceptor);
}

int main(int argc, char **argv) {
    thread clientThread(runClient);
    thread serverThread(runServer);

    clientThread.join();
    serverThread.join();

    return 0;
}

这是程序输出:

Hello 0
Hello 1
Hello 2
Hello 3
Hello 3
Hello 3
Hello 3
Hello 3
Client shutdown
Hello 3
Hello 3
input stream error
eof
Server: shutdown client handling...

我期待以下输出:

Hello 0
Hello 1
Hello 2
Hello 3
Hello 4
Hello 5
Hello 6
Hello 7
Client shutdown
Hello 8
Hello 9
input stream error
eof
Server: shutdown client handling...

shared_ptr更改为简单对象(std::shared_ptr<Message> m;Message m)时,一切都按预期工作。我想坚持shared_ptr。我需要改变什么?

单独进行序列化似乎有效:

stringstream stream;

{
    boost::archive::text_oarchive archive(stream);
    std::shared_ptr<Message> dl = std::make_shared<Message>();
    stringstream ss;
    ss << "Hello World!";
    dl->text = ss.str();
    archive << dl;
}

{
    boost::archive::text_iarchive archive(stream);
    std::shared_ptr<Message> m;
    archive >> m;
    cout << m->text << endl;
}

输出:Hello World!

1 个答案:

答案 0 :(得分:2)

您遇到的问题是由object tracking完成Boost.Serialization

  

取决于类的使用方式和其他因素,序列化   对象可以通过内存地址跟踪。这可以防止相同   从多次写入或读取存档的对象。   这些存储的地址也可用于删除创建的对象   在一个加载过程中,被抛出一个中断   异常。

文档实际上预示着这个特定问题的发生:

  

这可能会导致编程中的问题[原文如此]副本不同   对象从同一地址保存。

此外,关于对象跟踪的Class Serialization Traits文档告诉我们,在这种特定情况下,启用了对象跟踪:

  

默认跟踪特征是:

     
      
  • 对于primitive,track_never。
  •   
  • 对于指针,track_never。也就是说,默认情况下不会跟踪地址。
  •   
  • 所有当前的序列化包装,例如boost :: serialization :: nvp,track_never。
  •   
  • 对于所有其他类型,track_selectively。即当且仅当一个或多个对象被跟踪时,才会跟踪序列化对象的地址   以下是真的:      
        
    • 此类型的对象是程序中通过指针序列化的任何位置。
    •   
    • 该类明确“已导出” - 请参阅下文。
    •   
    • 该类在存档
    • 中明确“注册”   
  •   

回到你的情况 - 在客户端,由于你的循环体的编写方式,第5(及以下)Message实例被分配在与第4 Message个实例相同的地址。您可以通过检查每次迭代中dl.get()的值来验证这一点。 (在我对coliru的测试中,所有实例都分配在同一地址,因此YMMV)。

由于对象跟踪的工作原理,所有shared_ptr个实例都被认为指向同一个Message实例(即使您同时更改了值 - 库不会发生这种情况),所以额外的事件只是序列化为附加引用。在反序列化时...说实话,这有内存泄漏和/或悬空引用问题的气味(意见,尚未对此进行详细调查)。

总结一下,显示的代码的主要问题是它打破了序列化库的先决条件,即你正在序列化一些常量状态,而在反序列化时你重新创建了相同的状态。

解决此问题的一种方法是初始化std::vector shared_ptr<Message>包含要在此特定事务中传输的所有消息。同样,您可以在另一侧反序列化整个矢量。如果您希望有一些持久连接,那么将框架添加到协议中,每个框架包含一个包含一个消息序列的存档。

使这项工作的最小代码修改 - 添加包括

#include <boost/serialization/vector.hpp>

更改runClient()

void runClient() {
    // Give server time to startup
    this_thread::sleep_for(chrono::milliseconds(3000));

    boost::asio::ip::tcp::iostream stream("127.0.0.1", "3000");

    std::vector<std::shared_ptr<Message>> messages;
    for (int i = 0; i < 10; i++) {
        std::shared_ptr<Message> dl = std::make_shared<Message>();
        stringstream ss;
        ss << "Hello " << i;
        dl->text = ss.str();
        messages.emplace_back(dl);
    }

    boost::archive::text_oarchive archive(stream);
    archive << messages;

    stream.close();
    cout << "Client shutdown" << endl;
}

然后改变handleIncommingClientConnection(...)

void handleIncommingClientConnection(boost::asio::ip::tcp::acceptor &acceptor) {
    boost::asio::ip::tcp::iostream stream;

    acceptor.accept(*stream.rdbuf());

    boost::archive::text_iarchive archive(stream);

    while (true) {
        try {
            std::vector<std::shared_ptr<Message>> messages;
            archive >> messages;
            for (auto const& m : messages) {
                cout << m->text << endl;
            }
        } catch (std::exception &ex) {
            cout << ex.what() << endl;

            if (stream.eof()) {
                cout << "eof" << endl;
                stream.close();
                cout << "Server: shutdown client handling..." << endl;
                break;
            } else
                throw ex;
        }
    }
}

NB :这不会为多个帧添加任何支持 - 客户端在发送一个消息向量后应该关闭连接,否则行为是未定义的。

Sample on Coliru

更多资源: