Reputation: 457
Here is the situation:
I have a Peer class which stores a boost::asio::ip::tcp::socket
, and a Network class which has a set (vector/map/whatever) of Peers.
For the moment, Peer::listen()
starts the async read, binding the completion of the event to Peer::handle_incoming_message
. This means Peer has to look for the asio::error::eof
to know whether the distant peer has disconnected.
Peer can't call Network::remove_peer()
because I want this method to destroy the Peer object (haven't actually tried, but I guess returning from remove_peer
would crash or something if the Peer object that called it has been destroyed).
So right now all I'm doing is set the Peer object's state to PEER_STATE_DEAD
, and periodically I check all the Peers inside the Network's set and remove all those with state PEER_STATE_DEAD
.
I would like Network to have a handle_incoming_message([...], Peer* p)
method, which would actually only check whether eof
was raised and if not, call p->receive()
which would read from the buffer given to async_read
.
So ideally the Peer class would have a ListenHandler object which would be initialized to Network::handle_incoming_message([...], this)
at construction (or shortly after). The thing is, I can't seem to compile with this setup.
Simplified code:
Network:
class Network:
{
[...]
void handle_incoming_message(const system::error_code& error,
Peer* emitter);
[...]
};
void Network::add_peer(shared_ptr<Peer> p)
{
peers.push_back(p);
p->start_listening(bind(&Network::handle_incoming_message, this,
asio::placeholders::error, p.get() ));
}
Peer:
class Peer
{
public:
typedef function<void (const system::error_code&, Peer*)> Handler;
void start_listening(Peer::Handler _listen_handler);
void listen();
[...]
private:
Handler listen_handler;
char last_message[MESSAGE_SIZE];
[...]
};
void Peer::start_listening(Peer::Handler _listen_handler)
{
listen_handler = _listen_handler;
set_state(PEER_STATE_CONNECTED);
listen();
}
void Peer::listen()
{
memset(last_message, 0, MESSAGE_SIZE);
socket->async_read_some(asio::buffer(last_message), listen_handler);
}
void Peer::receive()
{
cout << get_address() << ": " << last_message << endl;
listen();
}
G++:
g++ -c -Wall -D DEBUG_ON -Iinclude -I/usr/include src/Peer.cpp
In file included from /usr/include/boost/asio.hpp:30:0,
from include/Peer.hpp:4,
from src/Peer.cpp:3:
/usr/include/boost/asio/basic_stream_socket.hpp: In instantiation of
‘void boost::asio::basic_stream_socket<Protocol, StreamSocketService>::async_read_some(const MutableBufferSequence&, const ReadHandler&)
[with
MutableBufferSequence = boost::asio::mutable_buffers_1;
ReadHandler = boost::function<void(const boost::system::error_code&, Peer*)>;
Protocol = boost::asio::ip::tcp;
StreamSocketService = boost::asio::stream_socket_service<boost::asio::ip::tcp>]’:
src/Peer.cpp:30:69: required from here
/usr/include/boost/asio/basic_stream_socket.hpp:785:57: error: invalid conversion from ‘long unsigned int’ to ‘Peer*’ [-fpermissive]
In file included from /usr/include/boost/function/detail/maybe_include.hpp:23:0,
from /usr/include/boost/function/detail/function_iterate.hpp:14,
from /usr/include/boost/preprocessor/iteration/detail/iter/forward1.hpp:57,
from /usr/include/boost/function.hpp:64,
from include/Peer.hpp:5,
from src/Peer.cpp:3:
/usr/include/boost/function/function_template.hpp:754:17: error:
initializing argument 2 of ‘boost::function2<R, T1, T2>::result_type boost::function2<R, T1, T2>::operator()(T0, T1) const
[with
R = void;
T0 = const boost::system::error_code&;
T1 = Peer*;
boost::function2<R, T1, T2>::result_type = void]’ [-fpermissive]
I kind of understand that it's trying to use the ReadHandler constructor with (error_code, bytes_transferred).
I've read this question and it seems bind is "smart enough to not pass [stuff]", but then does that mean that there's no way to (inhales) bind a read handler to a non-static method from another class accepting other parameters than error_code, bytes_transferred?
(I tried changing the Handler typedef to void(error_code, bytes_transferred, Peer*) but it didn't work either)
Alternatively, if any of this seems to reek of bad design, I'm open to suggestions as to how to handle things better. If the problem somehow comes from not having the call to bind()
inside the call to async_read()
, I guess I could make Network call async_read()
...
Upvotes: 0
Views: 2031
Reputation: 51911
In short, change the boost::function
type to only accept one argument. The bound function only has one unknown value (indicated with boost::asio::placeholders::error
), as the peer is already bound:
typedef boost::function<void (const boost::system::error_code&, Peer*)> Handler;
becomes:
typedef boost::function<void (const boost::system::error_code&)> Handler;
When initializing the async_read_some
operation, use boost::bind
to adapt the Handler
to a type that meets the requirements of Read Handler.
socket->async_read_some(asio::buffer(last_message), listen_handler);
becomes:
socket->async_read_some(asio::buffer(last_message),
boost::bind(listen_handler, boost::asio::placeholders::error));
For further details, the error is because the boost::function
type does not meet the type requirements for Boost.Asio's Read Handler. A Read Handler is required to:
const boost::system::error_code
as its first argument.const std::size_t
as its second argument.The reason this does not surface when boost::bind
is used directly is that the unspecified functor type returned from boost::bind
has an operator()
accepting two arguments of an unspecified type. These arguments are only forwarded to the bound function if placeholders were provided in the binding process. Otherwise, the extra arguments are silently ignored/discarded.
Thus, Boost.Asio will always call the handler with boost::system::error_code
and std::size_t
. In the original code, the handler type is:
boost::function<void (const boost::system::error_code&, Peer*)>
This type fails to meet the Read Handler type requirement. Boost.Asio tries to invoke the handler with the second argument as const std::size_t
, resulting in the compiler error failing the conversion between std::size_t (long unsigned int)
and Peer*
.
invalid conversion from ‘long unsigned int’ to ‘Peer*’
By wrapping Handler
with boost::bind
, it adapts the Handler
functor to meet the Read Handler requirements.
boost::bind(listen_handler, boost::asio::placeholders::error);
Although Boost.Asio will invoke the resulting functor with boost::system::error_code
and std::size_t
, the functor will invoke Handler
only providing boost::system::error_code
, as std::size_t
will be discarded. This blog illustrates the passing and discarding of arguments.
Here is a minimal complete example:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/function.hpp>
class Peer {};
class Network
{
public:
void handle_incoming_message(const boost::system::error_code& error,
Peer* peer)
{
std::cout << error.message() << "\n"
<< "Peer is: " << peer << std::endl;
};
};
typedef boost::function<void (const boost::system::error_code&)> Handler;
int main()
{
Network network;
Peer peer;
std::cout << "Peer is: " << &peer << std::endl;
// Create the handler, that accepts a single argument.
Handler handler =
boost::bind(&Network::handle_incoming_message, &network,
boost::asio::placeholders::error,
&peer);
// Create io_service and socket.
boost::asio::io_service io_service;
boost::asio::ip::tcp::socket socket(io_service);
// Initialize operation, adapting Handler to meet Read Handler requirements.
socket.async_read_some(boost::asio::null_buffers(),
boost::bind(handler,
boost::asio::placeholders::error));
io_service.run();
}
And the output:
Peer is: 0xbfb2a6d2 Bad file descriptor Peer is: 0xbfb2a6d2
Upvotes: 1