我希望通过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!
答案 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 :这不会为多个帧添加任何支持 - 客户端在发送一个消息向量后应该关闭连接,否则行为是未定义的。
更多资源: