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

通过boost::序列化并通过boost::asio发送多个std::shared_ptr,boost,boost-asio,boost-serialization,Boost,Boost Asio,Boost Serialization,我想通过boost asio将共享的\u ptr对象从客户端传输到服务器。这是我的密码: #include <boost/archive/text_iarchive.hpp> #include <boost/archive/text_oarchive.hpp> #include <boost/asio.hpp> #include <boost/serialization/export.hpp> #include <boost/serializ

我想通过boost asio将
共享的\u 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 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 m;
更改为
Message m
)时,一切正常。我想坚持使用
共享\u 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;
}
stringstream;
{
boost::archive::text\u oarchive归档(流);
std::shared_ptr dl=std::make_shared();
细流ss;
ss text=ss.str();
归档>m;

cout text您遇到的问题都是由您自己解决的

根据类的使用方式和其他因素,序列化 对象可以通过内存地址进行跟踪。这防止了相同的情况 对象以避免多次写入存档或从存档中读取。 这些存储的地址还可用于删除创建的对象 在装载过程中,由于投掷 例外

该文档实际上预示了这一特定问题的发生:

这可能会在程序[sic]中导致问题,其中 对象从同一地址保存

此外,有关对象跟踪的文档告诉我们,在这种特殊情况下,启用了对象跟踪:

默认跟踪特征为:

  • 对于primitive,track_never
  • 对于指针,track_never。也就是说,默认情况下不跟踪地址的地址
  • 所有当前的序列化包装器,如boost::serialization::nvp、track\u never
  • 对于所有其他类型,有选择地跟踪\即当且仅当一个或多个 事实如下:
    • 此类型的对象位于通过指针序列化的程序中的任何位置
    • 该类显式“导出”-请参见下文
    • 该类在存档中显式“注册”

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

由于对象跟踪的工作方式,所有这些
共享的\u ptr
实例都被认为指向相同的
消息
实例(即使您同时更改了值——库不希望发生这种情况),因此附加的引用被序列化为附加引用。在反序列化时…老实说,这闻起来有内存泄漏和/或悬空引用问题(观点,尚未对此进行详细调查)

综上所述,如图所示的代码的主要问题是它破坏了序列化库的一个先决条件,即您正在序列化某个常量状态,而在反序列化时您将重新创建该状态

解决这一问题的一种方法是使用一个初始化的
std::vector
of
shared_ptr
,其中包含要在此特定事务中传输的所有消息。类似地,您可以在另一端对整个向量进行反序列化。如果您希望有一些持久连接,则使用每个帧c向协议添加帧包含包含一个消息序列的存档


只需对代码进行少量修改即可实现此功能--添加include

#include <boost/serialization/vector.hpp>
并将
handleincommmingclientconnection(…)
更改为:

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;
}
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;
        }
    }
}
void handleincommmingclientconnection(boost::asio::ip::tcp::acceptor&acceptor){
boost::asio::ip::tcp::iostream流;
acceptor.accept(*stream.rdbuf());
boost::archive::text\u iarchive归档(流);
while(true){
试一试{
std::向量消息;
存档>>信息;
用于(自动const&m:消息){

cout text问题是,每次迭代都会销毁旧消息并创建一条新消息。正如在您的案例中发生的那样,在第四次迭代后,新消息对象被分配到相同的地址——在我对coliru的测试中,所有消息对象都发生了这种情况。您可以通过打印
dl.get的值轻松验证这一点()
。现在,为什么这是一个问题?原始指针是序列化框架如何判断两个共享指针是否指向同一个对象的。(检查存档的内容)。基本上,有一个要求,即所有共享指针都指向整个序列化序列之前的某些对象,并且在序列期间不会被修改(或者至少您已经序列化的任何内容都不应更改,并且唯一对象需要具有唯一地址).有点相关:好了。请仔细阅读这些文档,它们非常清晰。我以前没有使用过这个特定的库,肯定不是在这种情况下,挖掘参考文献来编写答案证实了我根据评论得出的实验结论。谢谢你提一个有用的问题:)您可能应该问问自己,为什么假装正在序列化共享对象,而事实上并非如此。这就是错误的根源。
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;
        }
    }
}