Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Follow up: Boost serialized custom C++ object passed over ZeroMQ pull socket

This is a follow up problem that I opened up earlier in another thread at Boost: De-serializing a custom C++ object passed over ZeroMQ pull socket. The problem in that thread has been resolved based on the answer provided. Now I have another problem at runtime. Please, see the below description. I am realtively new to C++ realm so I appreciate if you tell me any necessity for improvement in any part of the code provided in addition to what I descibed under problem statment.

Description:

I have a C++ class named GenericMessage which simply holds an id and data as its members (See code snippet 2 below - GenericMessage.hxx). My intention is to serialize an instance of this class and send it via a ZeroMQ socket which implements the push pattern.

The serialization and sending task has been implemented in class ZMQHandler (see sendToBE function) which is placed in a header file name ZMQHandler.hxx shown in the code snippet 3 below. This class is instantiated by TestFE.cxx shown in the 4rd code snippet below.

The receiving and de-serialization of the GenericMessage instance is implemented in TestBE.cxx available in the 5th code snippet below. My intention is to receive the GenericMessage instance over the ZMQ socket (i.e. pull socket), de-serialize it and then print its members to standard output.

I verified that seriazalition and the GenericMessage object that is transferred over the ZeroMQ socket works fine. Deserialization seems to work as well cause I dont get any exception or segmentation fault like thing.

Problem Statement:

What is expected from the code in TestBE.cxx (see code snippet 5) is to receive the GenericMessage object over the ZeroMQ socket deserialize it and then print its two members namely id and data which is a string object in this case. More precisely it should first print the content of the char stream it gets and then the members of the de-serialized object. Instead, it does not print these members at all. Al, it puts weird symbols including question marks into the received char stream. Please see the 1st code snippet below, you'll see my point.

QUESTIONs:

i) Why cannot I get the expected output? Why do I see the question marked weird symbols in the output? Why I don't see the id and data fields printed although they are visible in the printed char stream?

ii) The data field in the GenericMessage class is a template type which is set to std::string for testing purposes. However, in real use, I plan to transfer a much more complex object in a serialized form. In this respect, do you think the use of classes boost::archive::text_iarchive and boost::archive::text_oarchive is useful. Should I use binary instead? If so, is there some pitfalls/possible problems that you think I should be aware of? Thanks in advance.

SNIPPET 1: Program output vs. expected output

  *******************
  The EXPECTED OUTPUT
  *******************
  Connecting to FE...
  CHAR [22 serialization::archive 9 0 1 0
  0 1 12 Hello there!]
  ID: 1
  Data: Hello there!

  CHAR [22 serialization::archive 9 0 1 0
  0 2 12 Hello there!]
  ID: 2
  Data: Hello there!

  CHAR [22 serialization::archive 9 0 1 0
  0 3 12 Hello there!]
  ID: 3
  Data: Hello there!

  ......


  *************************
  PRINTED OUTPUT IN REALITY
  *************************
  Connecting to FE...
  CHAR [22 serialization::archive 9 0 1 0
  0 1 12 Hello there!]
  ID: 1
  Data: Hello there!

  //continues in increasing order same as above until the 18th message in the following
  CHAR [22 serialization::archive 9 0 1 0
  0 18 12 Hello there!]
  ID: 0
  Data: 

  //!!!!AFTER the 18th message I got question marks in the printed char stream!!!!!
  CHAR [22 serialization::archive 9 0 1 0
  0 19 12 Hello there!���]
  ID: 0
  Data: 

  CHAR [22 serialization::archive 9 0 1 0
  0 20 12 Hello there!���]
  ID: 0
  Data: 

CODE SNIPPET 2 (GenericMessage.hxx)

  #include <iostream>
  #include <string>
  #include <sstream>
  #include <boost/serialization/serialization.hpp>
  #include <boost/archive/binary_oarchive.hpp>
  #include <boost/archive/binary_iarchive.hpp>
  #include <boost/archive/text_oarchive.hpp>
  #include <boost/archive/text_iarchive.hpp>

  template <class T>
  class GenericMessage {
  public:
    GenericMessage(): 
      beId(-1)
    {}

    GenericMessage(int id, T msg): 
       beId(id), 
       data(msg)
    {}

    ~GenericMessage(){}

    T getData()
    {
      return data;
    }


    std::string toString()
    {
       std::ostringstream ss;
       ss << getBeId();
       std::string ret =  ss.str();

      return ("ID: " + ret + " DATA: " + getData());
    }

    void setBeId(int id)
    {
      beId = id;
    }

    int getBeId()
    {
      return beId;
    }


  private:
    friend class boost::serialization::access;

    int beId;
    T data;


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

   };

CODE SNIPPET 3 (ZmqHandler.hxx)

   #include "zmq.hpp"
   #include "GenericMessage.hxx"
   #include <unistd.h>
   #include <cassert>

   template <class A>
   class ZmqHandler {
      public:

         ZmqHandler():
     mContext(1),
     mOutbHandlerSocket(mContext, ZMQ_PUSH)
         {    
             mOutbHandlerSocket.bind ("tcp://*:5555");       
         }


         ~ZmqHandler() {}

         void sendToBE(GenericMessage<A> *theMsg)
         {
            std::ostringstream archive_stream;
            boost::archive::text_oarchive archive(archive_stream);

            try
            {
                archive << theMsg;
            } catch (boost::archive::archive_exception& ex) {
                std::cout << "Archive Exception during deserializing:" << std::endl;
                std::cout << ex.what() << std::endl;           
            } catch (int e) {
                std::cout << "EXCEPTION " << e << std::endl; 
            }

           std::string outbound_data_ = archive_stream.str();
           // no need to use the c-style string function 'strlen'
           int len = outbound_data_.length();

           zmq::message_t msgToSend(len);
           memcpy( msgToSend.data(), outbound_data_.data(), len );

           mOutbHandlerSocket.send(msgToSend);
           std::cout << "SENT request: [" << theMsg->toString() << "]" << std::endl;
           std::cout << "LENGTH [" << len << "]" << std::endl;
         }   

        private:  
          zmq::context_t mContext;
          zmq::socket_t mOutbHandlerSocket;         
     };

CODE SNIPPET 4 (TestFE.cxx)

       #include "ZmqHandler.hxx"

       int main ()
       {
            ZmqHandler<std::string> zmqHandler;
            int counter = 1;

            while(1)
            {  
                std::string data = "Hello there!";
                GenericMessage<std::string> msg(counter, data);
                zmqHandler.sendToBE(&msg);
                counter++;
                sleep(1);
             }

             return 0;
        }

CODE SNIPPET 5 (TestBE.cxx)

       #include "zmq.hpp"
       #include "GenericMessage.hxx"
       #include <fstream>

       int main ()
       {
          //  Prepare our context and socket
          zmq::context_t context (1);
          zmq::socket_t socket (context, ZMQ_PULL);

         std::cout << "Connecting to FE..." << std::endl;
         socket.connect ("tcp://localhost:5555");

         while(1){
              zmq::message_t reply;
              socket.recv (&reply);

              const char *buf = static_cast<const char*>(reply.data());
              std::cout << "CHAR [" << buf << "]" << std::endl;

              std::string input_data_( buf, reply.size() ); 
              std::istringstream archive_stream(input_data_);
              boost::archive::text_iarchive archive(archive_stream);
              GenericMessage<std::string> theMsg;

              try
              {
                 archive >> theMsg;
              } catch (boost::archive::archive_exception& ex) {
                 std::cout << "Archive Exception during deserializing:" << std::endl;
                 std::cout << ex.what() << std::endl;           
              } catch (int e) {
                 std::cout << "EXCEPTION " << e << std::endl; 
              }

              std::cout << "ID: " << theMsg.getBeId() << std::endl;
              std::cout << "Data: " << theMsg.getData() << std::endl;

           }

            return 0;
         }
like image 380
F. Aydemir Avatar asked Nov 12 '22 12:11

F. Aydemir


1 Answers

When I build and run your code on my system, TestBE does throw the deserialization exception (every time). Here's what I did to fix it:

In your ZmqHandler class, change the method void sendToBE(GenericMessage<A> *theMsg) to void sendToBE(GenericMessage<A> theMsg). You can use a const& if you want, but you probably don't want to use a pointer here. In the same method, you need to change theMsg->XXX to theMsg.XXX, since theMsg is no longer a pointer.

In TestFE, zmqHandler.sendToBE(&msg); becomes zmqHandler.sendToBE(msg);.

If theMsg must be a pointer

In ZmqHandler, just change the line archive << theMsg to archive << *theMsg. That way, the archive's operator<< is working with the object, rather than a pointer to the object. The rest of your code can remain the same.

like image 125
Jacob Robbins Avatar answered Nov 15 '22 06:11

Jacob Robbins