Hello,
I have got a problem serializing data via TCP/IP.
My application is highly inspired of the asio example on serialization.
I have a server (my class \"Recepteur\"), and a client (my class \"Logger\").
I try to serialize two kine of data :
- Loggable
- HumanReadable.
Loggable is a public base of HumanReadable, later I will have other Loggable class to serialize (OneDPlotable etc...).
Look at my two serializable classes :
[code]
// file Loggable.hpp
#ifndef LOGGABLE_HPP
#define LOGGABLE_HPP
#include <boost/serialization/serialization.hpp>
#include <boost/serialization/version.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/export.hpp>
#include <string>
class Loggable
{
protected :
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive& _archive, const unsigned int _version)
{
std::cout << \"serialize loggable part\" << std::endl;
_archive & m_channelName;
std::cout << \"channel name : \" << m_channelName << std::endl;
}
public :
std::string m_channelName;
Loggable()
{
m_channelName = \"NonInit\";
}
Loggable(std::string _channelName)
{
m_channelName = _channelName;
}
virtual ~Loggable(){}
};
BOOST_CLASS_VERSION(Loggable, 0)
BOOST_CLASS_EXPORT_GUID(Loggable, \"Loggable\")
#endif
// end of file
// file HumanReadable.hpp
#ifndef HUMAN_READABLE_HPP
#define HUMAN_READABLE_HPP
#include \"Loggable.hpp\"
#include <boost/serialization/string.hpp>
#include <boost/serialization/base_object.hpp>
#include <string>
class HumanReadable : public Loggable
{
protected :
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive& _archive, const unsigned int _version)
{
_archive.template register_type<HumanReadable>();
std::cout << \"serialize HR part\" << std::endl;
_archive & boost::serialization::base_object<Loggable>(*this);
_archive & m_message;
std::cout << \"Message : \" << m_message << std::endl;
}
public :
std::string m_message;
HumanReadable() {}
HumanReadable(std::string _channelName, std::string _message):
Loggable(_channelName)
{
m_message = _message;
}
};
BOOST_CLASS_VERSION(HumanReadable, 0)
BOOST_CLASS_EXPORT_GUID(HumanReadable, \"HumanReadable\");
//end of file
[/code]
My TCP IP Server called Recepteur (receiver in english) is the following class :
[code]
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include \"connection.hpp\" // Must come before boost/serialization headers.
#include \"HumanReadable.hpp\"
namespace LibLogger
{
class Recepteur
{
public:
Recepteur(boost::asio::io_service& io_service, unsigned short port): acceptor_(io_service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
{
connection_ptr new_conn(new connection(acceptor_.io_service()));
acceptor_.async_accept(new_conn->socket(), boost::bind(&Recepteur::handle_accept, this, boost::asio::placeholders::error, new_conn));
}
void handle_accept(const boost::system::error_code& e, connection_ptr conn)
{
if (!e)
{
conn->async_read(m_hr, boost::bind(&Recepteur::handle_read, this, boost::asio::placeholders::error));
connection_ptr new_conn(new connection(acceptor_.io_service()));
acceptor_.async_accept(new_conn->socket(), boost::bind(&Recepteur::handle_accept, this, boost::asio::placeholders::error, new_conn));
}
else
{
std::cerr << \"Erreur dans accept : \" << e.message() << std::endl;
}
}
void handle_read(const boost::system::error_code& e)
{
if (!e)
{
// Print out the data that was received.
std::cout << \"Log recue du channel : \" << m_hr.m_channelName << \" : \" << m_hr.m_message << std::endl;
}
else
{
// An error occurred.
std::cerr <<\"Erreur dans le read : \" << e.message() << std::endl;
}
}
private:
/// The acceptor object used to accept incoming socket connections.
boost::asio::ip::tcp::acceptor acceptor_;
HumanReadable m_hr;
};
} // namespace
[/code]
My TCP IP client called logger is the following class :
[code]
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include \"connection.hpp\" // Must come before boost/serialization headers.
#include \"HumanReadable.hpp\"
namespace LibLogger
{
class Logger
{
public:
Logger(boost::asio::io_service& io_service, const std::string& host, const std::string& service)
{
connection_ = connection_ptr(new connection(io_service));
boost::asio::ip::tcp::resolver resolver(io_service);
boost::asio::ip::tcp::resolver::query query(host, service);
boost::asio::ip::tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
connection_->socket().async_connect(endpoint, boost::bind(&Logger::handle_connect, this,
boost::asio::placeholders::error, ++endpoint_iterator));
}
void handle_connect(const boost::system::error_code& e, boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
{
if(e)
{
if (endpoint_iterator != boost::asio::ip::tcp::resolver::iterator())
{
connection_->socket().close();
boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
connection_->socket().async_connect(endpoint, boost::bind(&Logger::handle_connect, this,
boost::asio::placeholders::error, ++endpoint_iterator));
}
else
{
std::cerr << e.message() << std::endl;
}
}
else
{
Debug();
}
}
void Debug()
{
HumanReadable hr(\"TotoChannel\", \"Coucou le monde\");
connection_->async_write(hr, boost::bind(&Logger::handle_write, this, boost::asio::placeholders::error, connection_));
}
void handle_write(const boost::system::error_code& e, connection_ptr conn){}
private:
connection_ptr connection_;
};
} // namespace s11n_example
[/code]
And of course I have a custom \"connection\" class which is approximatively the same than the connection class of the asio example on serialization, have a look :
[code]
namespace LibLogger
{
class connection : public boost::enable_shared_from_this<connection>
{
public:
/// Constructor.
~connection()
{
std::cout << \"Destruction de la connection\" << std::endl;
}
connection(boost::asio::io_service& io_service)
: socket_(io_service)
{
}
boost::asio::ip::tcp::socket& socket()
{
return socket_;
}
/// Asynchronously write a data structure to the socket.
template <typename T, typename Handler>
void async_write(const T& t, Handler handler)
{
// Serialize the data first so we know how large it is.
std::ostringstream archive_stream;
boost::archive::text_oarchive archive(archive_stream);
archive << t;
outbound_data_ = archive_stream.str();
// Format the header.
std::ostringstream header_stream;
header_stream << std::setw(header_length) << std::hex << outbound_data_.size();
if (!header_stream || header_stream.str().size() != header_length)
{
// Something went wrong, inform the caller.
boost::system::error_code error(boost::asio::error::invalid_argument);
socket_.io_service().post(boost::bind(handler, error));
return;
}
outbound_header_ = header_stream.str();
std::vector<boost::asio::const_buffer> buffers;
buffers.push_back(boost::asio::buffer(outbound_header_));
buffers.push_back(boost::asio::buffer(outbound_data_));
boost::asio::async_write(socket_, buffers, handler);
}
/// Asynchronously read a data structure from the socket.
template <typename T, typename Handler>
void async_read(T& t, Handler handler)
{
// Issue a read operation to read exactly the number of bytes in a header.
void (connection::*f)( const boost::system::error_code&, T&, boost::tuple<Handler>) = &connection::handle_read_header<T, Handler>;
boost::asio::async_read(socket_, boost::asio::buffer(inbound_header_, header_length),
boost::bind(f, shared_from_this(), boost::asio::placeholders::error, boost::ref(t), boost::make_tuple(handler)));
}
template <typename T, typename Handler>
void handle_read_header(const boost::system::error_code& e, T& t, boost::tuple<Handler> handler)
{
std::cout <<\"reading header\" << std::endl;
if (e)
{
std::cout << \"Error : \" << e.message() << std::endl;
boost::get<0>(handler)(e);
}
else
{
// Determine the length of the serialized data.
std::istringstream is(std::string(inbound_header_, header_length));
std::size_t inbound_data_size = 0;
if (!(is >> std::hex >> inbound_data_size))
{
// Header doesn\'t seem to be valid. Inform the caller.
boost::system::error_code error(boost::asio::error::invalid_argument);
boost::get<0>(handler)(error);
return;
}
// Start an asynchronous call to receive the data.
inbound_data_.resize(inbound_data_size);
void (connection::*f)( const boost::system::error_code&, T&, boost::tuple<Handler>) = &connection::handle_read_data<T, Handler>;
boost::asio::async_read(socket_, boost::asio::buffer(inbound_data_), boost::bind(f, shared_from_this(), boost::asio::placeholders::error, boost::ref(t), handler));
}
}
/// Handle a completed read of message data.
template <typename T, typename Handler>
void handle_read_data(const boost::system::error_code& e, T& t, boost::tuple<Handler> handler)
{
std::cout << \"Reading data\" << std::endl;
if (e)
{
std::cout << \"Error\" << std::endl;
boost::get<0>(handler)(e);
}
else
{
try
{
std::string archive_data(&inbound_data_[0], inbound_data_.size());
std::cout << \"read : \" << archive_data << std::endl;;
std::istringstream archive_stream(archive_data);
boost::archive::text_iarchive archive(archive_stream);
archive >> t;
}
catch (std::exception& e)
{
// Unable to decode data.
std::cout << \"Erreur durant lecture des données : \" << e.what() << std::endl;
boost::system::error_code error(boost::asio::error::invalid_argument);
boost::get<0>(handler)(error);
return;
}
// Inform caller that data has been received ok.
boost::get<0>(handler)(e);
}
}
public:
boost::asio::ip::tcp::socket socket_;
enum { header_length = 8 };
std::string outbound_header_;
std::string outbound_data_;
char inbound_header_[header_length];
std::vector<char> inbound_data_;
};
typedef boost::shared_ptr<connection> connection_ptr;
} // namespace s11n_example
[/code]
Then, I instanciate a Receiver (TCP IP server) and a logger (TCP IP client) in two different processes :
[code]
// File mainReceiver.cpp
int main(int argc, char** argv)
{
io_service io_service;
LibLogger::Recepteur recepteur(io_service, 1000);
io_service.run();
return 0;
}
// end of file
// File mainLogger.cpp
int main(int argc, char**argv)
{
boost::asio::io_service io_service;
LibLogger::Logger Logger(io_service, \"127.0.0.1\", \"1000\");
io_service.run();
usleep(1000000);
//Logger.Debug();
return 0;
}
//end of file
[/code]
Looks like great isn\'t it ?
I execute my two applications, and the sender console output is :
[code]
serialize HR part
serialize loggable part
channel name : TotoChannel
Message : Coucou le monde
Destruction de la connection
[/code]
All the serialization process seems to be ok !
My receiver console output is :
[code]
reading header
La taille : 73
Reading data
read : 22 serialization::archive 7 1 0
0 1 0
1 11 TotoChannel 15 Coucou le monde
serialize HR part
serialize loggable part
channel name :
Message : 1
Log recue du channel : : 1
Destruction de la connection
[/code]
Thanks to the lines 4 to 7 we can see that all the informations are in the string \"archive_data\" in connection::handle_read_data.
But thanks to the lines 10 and 11 I can see that the \"deserialization\" process doesn\'t work at all !
Does someone can explain me why ?
I tried to serialize only a Loggable object and all works well.
Cheers,
A. BARRAL