【问题标题】:Trying to send an derived class through a boos::asio socket using boost::serialization尝试使用 boost::serialization 通过 boos::asio 套接字发送派生类
【发布时间】:2021-05-26 07:24:21
【问题描述】:

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

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

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

在抛出 'boost::archive::archive_exception' 的实例后调用终止 what(): 未注册的类

为了尝试解决这个问题,我在 PacketA cpp 文件中添加了宏 BOOST_CLASS_EXPORT_IMPLEMENT,在头文件中添加了 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);

};

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

【问题讨论】:

    标签: c++ serialization boost boost-asio


    【解决方案1】:

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

    #include <boost/archive/text_iarchive.hpp>
    

    the docs 中解释了此要求:

    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
    

    【讨论】:

    • 发现了更多问题并添加了具有多种数据包类型的往返测试演示
    猜你喜欢
    • 1970-01-01
    • 2011-10-07
    • 2013-08-16
    • 2017-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多