尝试使用 boost::serialization 通过 boos::asio 套接字发送派生的 class

Trying to send an derived class through a boos::asio socket using boost::serialization

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

假设子 class 是 PacketA,基础 class 是 Packet。

我能够在客户端程序中序列化 PacketA,但每当我尝试在服务器中反序列化它时,它都会抛出以下错误:

terminate called after throwing an instance of 'boost::archive::archive_exception' what(): unregistered class

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

我是否犯了任何明显的错误或者我是否错误地使用了 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;

Packet.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);

Packet.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);

};

PacketA.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)

PacketA.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;
}

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

#include <boost/archive/text_iarchive.hpp>

此要求在 the docs 中有解释:

BOOST_CLASS_EXPORT in the same source module that includes any of the archive class headers will instantiate code required to serialize polymorphic pointers of the indicated type to the all those archive classes. If no archive class headers are included, then no code will be instantiated.

Note that the implemenation of this functionality requires that the BOOST_CLASS_EXPORT macro appear after the inclusion of any archive class headers for which code is to be instantiated.

补充说明

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

  • 请注意,您未能以 NUL 终止接收缓冲区,这 意味着您将调用 UB 除非发送的数据包包含它(并且它 适合缓冲区大小)。所以下面将是什么的开始 更安全的反序列化看起来像:

    std::array<char, MAX_PACKET_SIZE> buffer {'[=11=]'}; // 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 {'[=16=]'}; // 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