首页 > 解决方案 > 尝试使用 boost::serialization 通过 boos::asio 套接字发送派生类

问题描述

我正在尝试使用 UDP 通过 boost::asio 套接字发送一个作为派生类实例的对象。

假设子类是 PacketA,基类是 Packet。

我能够在客户端程序中对 PacketA 进行序列化,但是每当我尝试在服务器中对其进行反序列化时,它都会引发以下错误:

在抛出“boost::archive::archive_exception”的实例后调用终止什么():未注册的类

为了尝试解决这个问题,我BOOST_CLASS_EXPORT_IMPLEMENT在 PacketA cpp 文件和BOOST_CLASS_EXPORT_KEY头文件中添加了宏,而在 Packet 类中我没有添加任何宏,但它仍然不起作用。由于boost docs的这一部分,我添加了这些宏。我也尝试使用该register_type()函数来注册子类,但我也没有成功,而且解决方案似乎比宏更糟糕。

我是否犯了任何明显的错误,或者我错误地使用了 API?

代码:

反序列化:

        udp::endpoint senderEndPoint;
        char buffer[MAX_PACKET_SIZE] = {"\n"};
        int bytes = socket->receive_from(boost::asio::buffer(buffer, MAX_PACKET_SIZE), senderEndPoint, 0,error);
   
        std::stringstream stringStream(buffer);
        boost::archive::text_iarchive ia{stringStream};
        Packet *packet; //<-It throws the exception in this line but If I switch this pointer to 
                        //PacketA it works fine but the idea is to deserialize multiple child 
                        //packets that came from the sockets.
        ia & packet; 
        packet->bytes = 0;
        packet->senderEndPoint = senderEndPoint;

数据包.cpp:

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include "Packet.hpp"
template<class Archive>
void Packet::serialize(Archive &ar, unsigned int version) {
  //I didnt add any code in here since I don't really need to serialize any information just the child packets
}

template void Packet::serialize(boost::archive::text_iarchive &arch, const unsigned int version);

template void Packet::serialize(boost::archive::text_oarchive &arch, const unsigned int version);

数据包.hpp:

#include <boost/serialization/access.hpp>
#include <boost/serialization/export.hpp>
#include <boost/asio/ip/udp.hpp>

using PacketType = std::string;

class Packet {
public:
    friend class boost::serialization::access;

    /*Some variables and functions from packet*/

    template<class Archive>
    void serialize(Archive &, unsigned int version);

};

数据包A.cpp:

#include "PacketA.hpp"
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/base_object.hpp>

/*Some other functions*/

template<class Archive>
void PacketA::serialize(Archive &ar, unsigned int version) {
    ar & boost::serialization::base_object<Packet>(*this);
    ar & boost::serialization::make_nvp("PacketType", packetType);
}

BOOST_CLASS_EXPORT_IMPLEMENT(PacketA)

数据包A.hpp:

#include <boost/serialization/export.hpp>
#include "../Packet.hpp"

class PacketA : public Packet {
public:
    PacketType packetType = "PacketA";

    friend class boost::serialization::access;

    /*Some functions*/

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

BOOST_CLASS_EXPORT_KEY(PacketA)

要序列化我正在使用此功能的所有数据包:

std::stringstream foo::serializePacket(Packet *packet) { //<-Here the *packet could be any 
                                                         //packet child
    std::stringstream ss;
    boost::archive::text_oarchive oa{ss};
    oa & packet;
    return ss;
}

标签: c++serializationboostboost-asio

解决方案


您的注册实现看不到输入存档定义,因为 PacketA.cpp 未能包括:

#include <boost/archive/text_iarchive.hpp>

文档中解释了此要求:

包含任何归档类头文件的同一源模块中的 BOOST_CLASS_EXPORT 将实例化将指定类型的多态指针序列化到所有这些归档类所需的代码。如果没有包含归档类头文件,则不会实例化任何代码。

请注意,此功能的实现要求 BOOST_CLASS_EXPORT 宏出现在包含要为其实例化代码的任何存档类标头之后。

补充说明

  • 类层次结构需要是虚拟的,才能通过指针进行多态(反)序列化。最简单的方法是确保它正在添加virtual析构函数。

  • 请注意,您无法 NUL 终止接收缓冲区,这意味着您将调用UB,除非发送的数据包包含它(并且它适合缓冲区大小)。因此,以下将是更安全的反序列化的开始:

    std::array<char, MAX_PACKET_SIZE> buffer {'\0'}; // fill with NULs
    boost::system::error_code error;
    int bytes = socket->receive_from(boost::asio::buffer(buffer, MAX_PACKET_SIZE), senderEndPoint, 0,error);
    
    if (!error) {
        std::stringstream stringStream(std::string(buffer.data(), bytes));
        boost::archive::text_iarchive ia{stringStream};
        Packet* packet = nullptr;
        ia & packet;
        packet->bytes = 0;
        packet->senderEndPoint = senderEndPoint;
    }
    

完整的测试演示

  • 文件Packet.hpp

     #include <boost/serialization/access.hpp>
     #include <boost/serialization/export.hpp>
     #include <boost/asio/ip/udp.hpp>
     #include <string>
    
     using PacketType = std::string;
    
     class Packet {
     public:
         virtual ~Packet() = default;
         friend class boost::serialization::access;
    
         /*Some variables and functions from packet*/
         int bytes = 0;
         boost::asio::ip::udp::endpoint senderEndPoint;
    
         template<class Archive>
         void serialize(Archive & /*ar*/, unsigned version);
     };
    
  • 文件Packet.cpp

     #include <boost/archive/text_iarchive.hpp>
     #include <boost/archive/text_oarchive.hpp>
     #include <boost/serialization/string.hpp>
     #include "Packet.hpp"
     template <class Archive> void Packet::serialize(Archive& /*ar*/, unsigned /*version*/)
     {
         // I didnt add any code in here since I don't really need to serialize any
         // information just the child packets
     }
    
     template void Packet::serialize(
         boost::archive::text_iarchive& arch, const unsigned int version);
    
     template void Packet::serialize(
         boost::archive::text_oarchive& arch, const unsigned int version);
    
  • 文件PacketA.hpp

     #include <boost/serialization/export.hpp>
     #include "Packet.hpp"
    
     #define DECLARE_PACKET(Name)                                                   \
         struct Name : Packet {                                                     \
             PacketType packetType = #Name;                                         \
             /*Some functions*/                                                     \
                                                                                    \
           private:                                                                 \
             friend class boost::serialization::access;                             \
             template <class Archive>                                               \
             void serialize(Archive& ar, unsigned int version);                     \
         };                                                                         \
                                                                                    \
         BOOST_CLASS_EXPORT_KEY(Name)
    
     DECLARE_PACKET(PacketA)
     DECLARE_PACKET(PacketB)
     DECLARE_PACKET(PacketC)
     DECLARE_PACKET(PacketD)
     DECLARE_PACKET(PacketE)
    
  • 文件PacketA.cpp

     #include "PacketA.hpp"
     #include <boost/archive/text_oarchive.hpp>
     #include <boost/archive/text_iarchive.hpp>
     #include <boost/serialization/base_object.hpp>
    
     #define IMPLEMENT_PACKET(Name)                                                 \
         /*Some other functions*/                                                   \
                                                                                    \
         template <class Archive>                                                   \
         void Name::serialize(Archive& ar, unsigned /*version*/)                    \
         {                                                                          \
             ar& boost::serialization::base_object<Packet>(*this);                  \
             ar& boost::serialization::make_nvp("PacketType", packetType);          \
         }                                                                          \
                                                                                    \
         BOOST_CLASS_EXPORT_IMPLEMENT(Name)
    
    
     IMPLEMENT_PACKET(PacketA)
     IMPLEMENT_PACKET(PacketB)
     IMPLEMENT_PACKET(PacketC)
     IMPLEMENT_PACKET(PacketD)
     IMPLEMENT_PACKET(PacketE)
    
  • 文件test.cpp

     #include <boost/asio.hpp>
     #include <iostream>
     #include <iomanip>
     using boost::asio::ip::udp;
    
     #include "PacketA.hpp"
     #include <boost/archive/text_iarchive.hpp>
     #include <boost/archive/text_oarchive.hpp>
     #include <boost/core/demangle.hpp> // for test output
    
     static constexpr size_t MAX_PACKET_SIZE = 1024;
    
     std::unique_ptr<Packet> receive_packet(uint16_t port) {
         boost::asio::io_context io;
         udp::endpoint senderEndPoint;
         auto socket = std::make_unique<udp::socket>(io, udp::endpoint { {}, port });
    
         std::array<char, MAX_PACKET_SIZE> buffer {'\0'}; // fill with NULs
         boost::system::error_code error;
         int bytes = 0 = socket->receive_from(boost::asio::buffer(buffer, MAX_PACKET_SIZE), senderEndPoint, 0,error);
    
         Packet* packet = nullptr;
    
         if (!error) {
             {
                 std::stringstream stringStream(std::string(buffer.data(), bytes));
                 boost::archive::text_iarchive ia{stringStream};
                 ia & packet;
             }
    
             packet->bytes = 0;
             packet->senderEndPoint = senderEndPoint;
         }
    
         return std::unique_ptr<Packet>(packet); // take ownership
     }
    
     struct foo {
         static std::stringstream serializePacket(Packet* packet);
     };
    
     std::stringstream foo::serializePacket(Packet* packet)
     { //<-Here the *packet could be any packet child
         std::stringstream ss;
         boost::archive::text_oarchive oa { ss };
         oa& packet;
         return ss;
     }
    
     template <typename Type>
     void send() {
         auto request = std::make_unique<Type>();
         auto msg = foo::serializePacket(request.get()).str();
    
         boost::asio::system_executor ex;
         udp::socket s { ex };
         s.open(udp::v4());
         s.send_to(boost::asio::buffer(msg), { {}, 9977 });
     }
    
     template <typename Type>
     void test_roundtrip() {
         auto fut = std::async(std::launch::async, receive_packet, 9977);
    
         std::this_thread::yield(); // be reasonably sure the read started
         send<Type>();
    
         if (auto p = fut.get()) {
             std::cout << "Deserialized a "
                       << boost::core::demangle(typeid(*p).name()) << " packet"
                       << std::endl;
         }
     }
    
     int main() {
         test_roundtrip<PacketA>();
         test_roundtrip<PacketB>();
         test_roundtrip<PacketC>();
         test_roundtrip<PacketD>();
         test_roundtrip<PacketE>();
     }
    

印刷

Deserialized a PacketA packet
Deserialized a PacketB packet
Deserialized a PacketC packet
Deserialized a PacketD packet
Deserialized a PacketE packet

推荐阅读