I am trying to use the custom allocaters feature of c++ asio library (http://think-async.com/Asio/asio-1.10.6/doc/asio/overview/core/allocation.html). My functions code is all in namespace bb, as is a custom allocation function void* asio_handler_allocate(std::size_t size, ...). I expected ADL to pick my custom version, but for some reason it results in an ambiguity:
c:\mysrv\asio\detail\handler_alloc_helpers.hpp(38): error C2668: 'bb::asio_handler_allocate': ambiguous call to overloaded function
1> c:\mysrv\connection.hpp(16): note: could be 'void *bb::asio_handler_allocate(std::size_t,...)' [found using argument-dependent lookup]
1> c:\mysrv\asio\impl\handler_alloc_hook.ipp(27): note: or 'void *asio::asio_handler_allocate(std::size_t,...)'
1> c:\mysrv\asio\detail\handler_alloc_helpers.hpp(38): note: while trying to match the argument list '(std::size_t, bb::Server::do_accept::<lambda_18e060fa7342c1167c1b66e6dfdfd1b2> *)'
Any explanation as to why the second one also matches and/or as to how to use this feature correctly would be appreciated
Thanks
P.S. I am adding the boost-asio tag since it is supposed the same library but only in a different namespace. I am actually using the stand-alone c++11 version fouind here http://think-async.com/
Here is a simplified example:
#include "asio.hpp"
#include <memory>
#include <iostream>
namespace bb {
void* asio_handler_allocate(std::size_t size, ...) {
std::cerr << 'H' << ' ' << /**h <<*/ ' ' << size << '\n';
return asio::asio_handler_allocate(size);
}
class Connection
: public std::enable_shared_from_this<Connection>
{
public:
Connection(asio::ip::tcp::socket socket)
: socket_(std::move(socket))
{
}
void start()
{
do_read();
}
private:
void do_read()
{
auto self(shared_from_this());
socket_.async_read_some(asio::buffer(data_),
[this, self](std::error_code ec, std::size_t length)
{
if (!ec)
{
do_write(length);
}
});
}
void do_write(std::size_t length)
{
auto self(shared_from_this());
asio::async_write(socket_, asio::buffer(data_, length),
[this, self](std::error_code ec, std::size_t /*length*/)
{
if (!ec)
{
do_read();
}
});
}
asio::ip::tcp::socket socket_;
std::array<char, 1024> data_;
};
class Server
{
public:
Server(asio::io_service& io_service, short port)
: acceptor_(io_service, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)),
socket_(io_service)
{
do_accept();
}
private:
void do_accept()
{
acceptor_.async_accept(socket_,
[this](std::error_code ec)
{
if (!ec)
{
std::make_shared<Connection>(std::move(socket_))->start();
}
do_accept();
});
}
asio::ip::tcp::acceptor acceptor_;
asio::ip::tcp::socket socket_;
};
}
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: server <port>\n";
return 1;
}
asio::io_service io_service;
bb::Server s(io_service, std::atoi(argv[1]));
io_service.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
ADL merely add argument's namespaces to the list of namespaces where the name should be looked up (this is not very precise, but for the purpose of your question, its enough). It does not make an overload in that namespace the preferable one automatically. If two equally ranked match are found, an ambiguous still happens. In your case, the two are exactly the same.
If you read the ASIO document you linked, you will know that the correct way to declare the ADL overload is
void* asio_handler_allocate(size_t, Handler);
Where Handler is the user defined handler type. The above declaration can also have possible CV qualifiers for Handler. In this case, the two overload found are one with the second parameter a concrete type and another with .... Any legal match with a typed parameter is ranked higher than the variadic arguments (not to be confused with C++11 variadic template arguments). Therefore, there would be no ambiguous match
Related
when I try to deserialize a struct on the server, I am receiving incorrect values from initial transmission. I created a struct with member variables of the same type (2 and 1); however, when transmitting receive zeros and unsure why.
Does the incoming data in connection.hpp need to be of the same type, which I am sending? Any suggestions ?
Client:
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <vector>
#include "connection.hpp" // Must come before boost/serialization headers.
#include <boost/serialization/vector.hpp>
#include "info.hpp"
class client
{
public:
/// Constructor starts the asynchronous connect operation.
client(boost::asio::io_service& io_service,
const std::string& host, const std::string& service)
: connection_(io_service)
{
Packet p;
p.a = 2;
p.b = 1;
packet_.push_back(p);
// Resolve the host name into an IP address.
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);
// Start an asynchronous connect operation.
boost::asio::async_connect(connection_.socket(), endpoint_iterator,
boost::bind(&client::handle_connect, this,
boost::asio::placeholders::error));
}
/// Handle completion of a connect operation.
void handle_connect(const boost::system::error_code& e)
{
if (!e)
{
connection_.async_write(packet_,
boost::bind(&client::handle_write, this,
boost::asio::placeholders::error));
}
else
{
std::cerr << e.message() << std::endl;
}
}
void handle_write(const boost::system::error_code& e)
{
}
private:
connection connection_;
std::vector<Packet> packet_;
};
}
int main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if (argc != 3)
{
std::cerr << "Usage: client <host> <port>" << std::endl;
return 1;
}
boost::asio::io_service io_service;
s11n_example::client client(io_service, argv[1], argv[2]);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
Info:
#ifndef Packet_HPP
#define Packet_HPP
namespace s11n_example {
struct Packet
{
int a;
int b;
template <typename Archive>
void serialize(Archive& ar, const unsigned int version)
{
int a;
int b;
}
};
} // namespace s11n_example
#endif
Server:
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/lexical_cast.hpp>
#include <iostream>
#include <vector>
#include "connection.hpp" // Must come before boost/serialization headers.
#include <boost/serialization/vector.hpp>
#include "info.hpp"
namespace s11n_example {
/// Serves stock quote information to any client that connects to it.
class server
{
public:
/// Constructor opens the acceptor and starts waiting for the first incoming
/// connection.
server(boost::asio::io_service& io_service, unsigned short port)
: acceptor_(io_service,
boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
{
// Create the data to be sent to each client.
// Start an accept operation for a new connection.
connection_ptr new_conn(new connection(acceptor_.get_io_service()));
acceptor_.async_accept(new_conn->socket(),
boost::bind(&server::handle_accept, this,
boost::asio::placeholders::error, new_conn));
}
/// Handle completion of a accept operation.
void handle_accept(const boost::system::error_code& e, connection_ptr conn)
{
if (!e)
{
conn->async_read(packet_,
boost::bind(&server::handle_read, this,
boost::asio::placeholders::error, conn));
}
// Start an accept operation for a new connection.
connection_ptr new_conn(new connection(acceptor_.get_io_service()));
acceptor_.async_accept(new_conn->socket(),
boost::bind(&server::handle_accept, this,
boost::asio::placeholders::error, new_conn));
}
/// Handle completion of a write operation.
void handle_read(const boost::system::error_code& e, connection_ptr conn)
{
// Print out the data that was received.
for (std::size_t i = 0; i < packet_.size(); ++i)
std::cout << packet_[i].a << "\n";
}
private:
/// The acceptor object used to accept incoming socket connections.
boost::asio::ip::tcp::acceptor acceptor_;
/// The data to be sent to each client.
std::vector<Packet> packet_;
};
} // namespace s11n_example
int main(int argc, char* argv[])
{
try
{
// Check command line arguments.
if (argc != 2)
{
std::cerr << "Usage: server <port>" << std::endl;
return 1;
}
unsigned short port = boost::lexical_cast<unsigned short>(argv[1]);
boost::asio::io_service io_service;
s11n_example::server server(io_service, port);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
Connection
#ifndef SERIALIZATION_CONNECTION_HPP
#define SERIALIZATION_CONNECTION_HPP
#include <boost/asio.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/tuple/tuple.hpp>
#include <iomanip>
#include <string>
#include <sstream>
#include <vector>
namespace s11n_example {
/// The connection class provides serialization primitives on top of a socket.
/**
* Each message sent using this class consists of:
* #li An 8-byte header containing the length of the serialized data in
* hexadecimal.
* #li The serialized data.
*/
class connection
{
public:
/// Constructor.
connection(boost::asio::io_service& io_service)
: socket_(io_service)
{
}
/// Get the underlying socket. Used for making a connection or for accepting
/// an incoming connection.
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_.get_io_service().post(boost::bind(handler, error));
return;
}
outbound_header_ = header_stream.str();
// Write the serialized data to the socket. We use "gather-write" to send
// both the header and the data in a single write operation.
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_),
boost::bind(f,
this, boost::asio::placeholders::error, boost::ref(t),
boost::make_tuple(handler)));
}
/// Handle a completed read of a message header. The handler is passed using
/// a tuple since boost::bind seems to have trouble binding a function object
/// created using boost::bind as a parameter.
template <typename T, typename Handler>
void handle_read_header(const boost::system::error_code& e,
T& t, boost::tuple<Handler> handler)
{
if (e)
{
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, 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)
{
if (e)
{
boost::get<0>(handler)(e);
}
else
{
// Extract the data structure from the data just received.
try
{
std::string archive_data(&inbound_data_[0], inbound_data_.size());
std::istringstream archive_stream(archive_data);
boost::archive::text_iarchive archive(archive_stream);
archive >> t;
}
catch (std::exception& e)
{
// Unable to decode data.
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);
}
}
private:
/// The underlying socket.
boost::asio::ip::tcp::socket socket_;
/// The size of a fixed length header.
enum { header_length = 8 };
/// Holds an outbound header.
std::string outbound_header_;
/// Holds the outbound data.
std::string outbound_data_;
/// Holds an inbound header.
char inbound_header_[header_length];
/// Holds the inbound data.
std::vector<char> inbound_data_;
};
typedef boost::shared_ptr<connection> connection_ptr;
} // namespace s11n_example
#endif // SERIALIZATION_CONNECTION_HPP
Like last time, this function makes no sense:
template <typename Archive>
void serialize(Archive& ar, const unsigned int version)
{
int a;
int b;
}
It just declares to integers and doesn't even touch the archive. Like before the fix for that is to ... serialize the data:
struct Packet {
int a, b;
template <typename Ar>
void serialize(Ar& ar, unsigned) { ar& a& b; }
};
Also, like last time, you're using a very old version of the sample:
1.49 is very old. It's very unlikely that's actually the version you are using. Try a more recent version (like https://www.boost.org/doc/libs/1_80_0/doc/html/boost_asio/examples/cpp03_examples.html#boost_asio.examples.cpp03_examples.serialization) or the one matching your version of boost.
I'm trying to pass a socket along a connection handshake, and use std::bind to do so. The compile issue I'm getting (in one continuous block, which I've added spaces to for readability) is:
'std::_Bind<_Functor(_Bound_args ...)>::_Bind(_Functor&&, _Args&& ...)
[with _Args = {socket_state**, std::function<void(socket_state*)>&, boost::asio::basic_socket_acceptor<boost::asio::ip::tcp, boost::asio::executor>&, boost::asio::io_context&};
_Functor = void (*)(socket_state*, std::function<void(socket_state*)>&, boost::asio::basic_socket_acceptor<boost::asio::ip::tcp>&, boost::asio::io_context&);
_Bound_args = {socket_state**, std::function<void(socket_state*)>, boost::asio::basic_socket_acceptor<boost::asio::ip::tcp, boost::asio::executor>, boost::asio::io_context}]':
My code is below, with the error appearing to nag at the std::bind arguments given to boost::asio::acceptor.async_accept(socket, ...) and the parameters for the accept_new_client method
void start_server(std::function<void(socket_state*)>& func, tcp::acceptor& acceptor, boost::asio::io_context& context)
{
acceptor.listen();
// Start client connection loop
networking::wait_for_client(func, acceptor, context);
}
void wait_for_client(std::function<void(socket_state*)>& func, tcp::acceptor& acceptor, boost::asio::io_context& context)
{
boost::asio::ip::tcp::socket socket(context);
// socket_state is its own class which links a particular socket with an ID and buffer data
// it also holds a function to indicate which part of the connection handshake it needs to go to next
socket_state* state = new socket_state(func, &socket);
acceptor.async_accept(socket, std::bind(&networking::accept_new_client, state, func, acceptor, context));
}
void accept_new_client(socket_state* state, std::function<void(socket_state*)>& func, tcp::acceptor& acceptor, boost::asio::io_context& context)
{
state->on_network_action(state);
wait_for_client(func, acceptor, context);
}
It seems like they would match, but you can see the error state my std::bind arguments are socket_state** instead of socket_state*, and boost::asio::basic_socket_acceptor<boost::asio::ip::tcp, boost::asio::executor>& instead of boost::asio::basic_socket_acceptor<boost::asio::ip::tcp>&.
I have no idea what the "with _Args" vs. "_Bound_args" is either.
There's many problems in this code.
The shared pointer seems to be at the wrong level of abstraction. You would want the entire "connection" type to be of shared lifetime, not just the socket. In your case, socket_state is a good candidate.
Regardless, your socket is a local variable that you pass a stale pointer to inside socket_state. Socket-state looks like it will necessarily be leaked.
So that will never work already.
Next up, the bind is binding all parameters eagerly, leaving a nullary signature. That's not what any overload accepts [no pun intended]. You need to match
AcceptHandler or
MoveAcceptHandler
Let's go for AcceptHandler. Also, let's not bind all the redundant args (func was already in the socket_stateremember,io_context` is overshared etc.).
In general it looks like you need to develop confidence in knowing where your state is. E.g. this line is is symptomatic:
state->on_network_action(state);
Since on_network_action is a member function of socket_state, there should never be any need to pass the state as an argument (it will be this implicitly). The same thing goes for acceptor and contest in all occurrences.
Demo
Fixing all the above, using std::shared_ptr and bind (you already did), notice the placeholder::_1 to accept the error_code etc.)
Live On Coliru
#include <boost/asio.hpp>
#include <memory>
#include <iostream>
namespace ba = boost::asio;
using namespace std::chrono_literals;
using boost::system::error_code;
using ba::ip::tcp;
struct socket_state;
using Callback = std::function<void(socket_state&)>;
struct socket_state : std::enable_shared_from_this<socket_state> {
Callback _callback;
tcp::socket _socket;
template <typename Executor>
socket_state(Callback cb, Executor ex) : _callback(cb)
, _socket(ex)
{
}
void on_network_action() {
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
};
struct networking {
using StatePtr = std::shared_ptr<socket_state>;
explicit networking(ba::io_context& ctx, Callback callback)
: context(ctx)
, callback(callback)
{
}
ba::io_context& context;
tcp::acceptor acceptor {context, {{}, 8989}};
Callback callback;
void start_server()
{
std::cout << "start_server" << std::endl;
acceptor.listen();
wait_for_client(); // Start client connection loop
}
void stop_server() {
std::cout << "stop_server" << std::endl;
acceptor.cancel();
acceptor.close();
}
void wait_for_client()
{
std::cout << "wait_for_client" << std::endl;
// socket_state is its own class which links a particular socket with
// an ID and buffer data it also holds a function to indicate which
// part of the connection handshake it needs to go to next
auto state =
std::make_shared<socket_state>(callback, context.get_executor());
acceptor.async_accept(state->_socket,
std::bind(&networking::accept_new_client, this,
std::placeholders::_1, state));
}
void accept_new_client(error_code ec, StatePtr state)
{
if (ec) {
std::cout << "accept_new_client " << ec.message() << std::endl;
return;
}
std::cout << "accept_new_client " << state->_socket.remote_endpoint()
<< std::endl;
state->on_network_action();
wait_for_client();
}
};
int main() {
ba::io_context ctx;
networking server(ctx, [](socket_state&) {
std::cout << "This is our callback" << std::endl;
});
server.start_server();
ctx.run_for(5s);
server.stop_server();
ctx.run();
}
With some random connections:
start_server
wait_for_client
accept_new_client 127.0.0.1:54376
void socket_state::on_network_action()
wait_for_client
accept_new_client 127.0.0.1:54378
void socket_state::on_network_action()
wait_for_client
accept_new_client 127.0.0.1:54380
void socket_state::on_network_action()
wait_for_client
accept_new_client 127.0.0.1:54382
void socket_state::on_network_action()
wait_for_client
stop_server
accept_new_client Operation canceled
Note that version makes the comments
// socket_state is its own class which links a particular socket with
// an ID and buffer data it also holds a function to indicate which
// part of the connection handshake it needs to go to next
no longer complete lies :)
I'm trying to create an asynchronous tcp server which accepts connection. I'm about to use an accept handler function object which looks like the following:
template <typename Function>
struct callback
{
Function func_;
callback()
{
}
callback(Function&& f)
: func_(std::forward<Function>(f))
{
}
template <typename ...Args>
void operator() (Args&& ...args)
{
func_(std::forward<Args>(args)...);
}
};
My server class:
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <session.hpp>
#include <handler.hpp>
class server
{
private:
typedef callback<boost::function<void(const boost::system::error_code&, session_ptr&)>> accept_handler;
boost::asio::io_service& io_service_;
tcp::acceptor acceptor_;
accept_handler handler_;
public:
server(boost::asio::io_service& io_service, short port)
: io_service_(io_service),
acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
{
session_ptr new_session = session_ptr(new session(io_service_));
auto callback = boost::bind(&server::handle_accept,
this,
boost::asio::placeholders::error,
new_session);
handler_ = accept_handler(std::move(callback));
acceptor_.async_accept(new_session->socket(),
handler_);
}
void handle_accept(const boost::system::error_code& error, session_ptr new_session)
{
if (!error)
{
new_session->start();
new_session.reset(new session(io_service_));
acceptor_.async_accept(new_session->socket(),
handler_);
}
}
};
But when I try to compile I get following error:
error: no match for call to ‘(boost::function&)>) (const boost::system::error_code&)’
func_(std::forward(args)...);
So I must only use handler which meet AcceptHandler requirements
struct accept_handler
{
...
void operator()(
const boost::system::error_code& ec)
{
...
}
...
};
or there is a solution to use overloaded variadic template opreator() ?
UPDATED After realizing the real mistake:
The good news is: the error is quite simply fixed by changing one line:
typedef callback<boost::function<void(const boost::system::error_code&, session_ptr&)>> accept_handler;
into
typedef callback<boost::function<void(const boost::system::error_code&)>> accept_handler;
The previous definition was simply wrong for all reasons:
it doesn't fit the handler requirements
it also doesn't match the bind expression: bind(&server::handle_accept, this, asio::placeholders::error, new_session). Note it has only 1 placeholder (asio::placeholders::error) so it couldn't possibly work with 2 parameters
Live On Coliru
#include <boost/function.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
namespace ba = boost::asio;
using ba::ip::tcp;
struct session : std::enable_shared_from_this<session> {
session(ba::io_service& svc) : _socket(svc) {}
void start() {} // do something asynchronously
tcp::socket& socket() { return _socket; }
tcp::socket _socket;
};
using session_ptr = std::shared_ptr<session>;
template <typename Function>
struct callback
{
Function func_;
callback(Function&& f = {}) : func_(std::forward<Function>(f)) {
}
template <typename ...Args>
void operator() (Args&& ...args) {
func_(std::forward<Args>(args)...);
}
};
class server
{
private:
typedef callback<boost::function<void(const boost::system::error_code&)>> accept_handler;
ba::io_service& io_service_;
tcp::acceptor acceptor_;
accept_handler handler_;
public:
server(ba::io_service& io_service, short port)
: io_service_(io_service),
acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
{
session_ptr new_session = session_ptr(new session(io_service_));
handler_ = accept_handler(bind(&server::handle_accept, this, ba::placeholders::error, new_session));
acceptor_.async_accept(new_session->socket(), handler_);
}
void handle_accept(const boost::system::error_code& error, session_ptr new_session)
{
if (!error)
{
new_session->start();
new_session.reset(new session(io_service_));
acceptor_.async_accept(new_session->socket(),
handler_);
}
}
};
int main() {
}
Simplify: Remove Redundant Wrappers
The only thing I'd note here is that quite clearly, none callback<>, accept_handler have any use:
Live On Coliru
class server
{
private:
ba::io_service& io_service_;
tcp::acceptor acceptor_;
public:
server(ba::io_service& io_service, short port)
: io_service_(io_service),
acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
{
do_accept();
}
void do_accept() {
session_ptr new_session = session_ptr(new session(io_service_));
acceptor_.async_accept(new_session->socket(), bind(&server::handle_accept, this, ba::placeholders::error, new_session));
}
void handle_accept(const boost::system::error_code& error, session_ptr new_session)
{
if (!error) {
new_session->start();
do_accept();
}
}
};
Even Simpler: Capturing Lambda
You can do without the bind, the placeholder, the handle_accept member all at the same time:
Live On Coliru
class server
{
private:
ba::io_service& io_service_;
tcp::acceptor acceptor_;
public:
server(ba::io_service& io_service, short port)
: io_service_(io_service),
acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
{
do_accept();
}
void do_accept() {
session_ptr new_session = std::make_shared<session>(io_service_);
acceptor_.async_accept(new_session->socket(), [this,new_session](const boost::system::error_code& error) {
if (!error) {
new_session->start();
do_accept();
}
});
}
};
Old Answer
In the first reading I had assumed you ran into a classic pitfall I frequently run into with Boost Asio and polymorphic lambdas. Sorry.
Indeed, in the variadic case the concept check cannot verify the handler requirements. My approach here would be to disable the requirement checks:
#define BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS 1
The main thing lost is friendlier error messages if there is a mismatch:
Asio 1.6.0 / Boost 1.47
Added friendlier compiler errors for when a completion handler does not meet the necessary type requirements. When C++0x is available (currently supported for g++ 4.5 or later, and MSVC 10), static_assert is also used to generate an informative error message. This checking may be disabled by defining BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS.
See http://www.boost.org/doc/libs/1_65_1/doc/html/boost_asio/history.html
i am trying to code with boost asio in socket programming.
after setting boost in my ubuntu eclipse environment, i tried the sample code in boost web site.
but an error occur on acceptor.accept() function with invalid argument, like below
how can i fix this error ?
Invalid arguments '
Candidates are:
void accept(boost::asio::basic_socket<#10000,#10001> &, std::enable_if<&0[std::is_convertible<boost::asio::ip::tcp,#10000>::value],void>::type *)
boost::system::error_code accept(boost::asio::basic_socket<#10000,#10001> &, boost::system::error_code &, std::enable_if<&0[std::is_convertible<boost::asio::ip::tcp,#10000>::value],void>::type *)
void accept(boost::asio::basic_socket<boost::asio::ip::tcp,#10000> &, boost::asio::ip::basic_endpoint<boost::asio::ip::tcp> &)
boost::system::error_code accept(boost::asio::basic_socket<boost::asio::ip::tcp,#10000> &, boost::asio::ip::basic_endpoint<boost::asio::ip::tcp> &, boost::system::error_code &)
' boostTest.cpp /boostTest line 41 Semantic Error
and this is the code i am trying
#include <ctime>
#include <iostream>
#include <string>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
std::string make_daytime_string()
{
using namespace std; // For time_t, time and ctime;
time_t now = time(0);
return ctime(&now);
}
int main()
{
try
{
boost::asio::io_service io_service;
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 13));
for (;;)
{
tcp::socket socket(io_service);
acceptor.accept(socket);
std::string message = make_daytime_string();
boost::system::error_code ignored_error;
boost::asio::write(socket, boost::asio::buffer(message), ignored_error);
}
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
i am one who questioned about invalid argument error.
it seems that i have found a remedy, i still don't understand why though.
when i fixed argument of accept function, the error disappear.
the error seemed to tell that the function needs additional argument std::enable_if or boost::asio::ip::basic_endpoint
so i just add boost::asio::ip::tcp::endpoint like below
after that, it worked
tcp::endpoint endpoint = tcp::endpoint(tcp::v4(), port);
a.accept(sock, endpoint);
#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
class session
{
public:
session(boost::asio::io_service& io_service)
: socket_(io_service)
{
}
tcp::socket& socket()
{
return socket_;
}
void start()
{
socket_.async_read_some(boost::asio::buffer(data_, max_length - 1),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
data_[bytes_transferred] = '\0';
if(NULL != strstr(data_, "quit"))
{
this->socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both);
this->socket().close(); // how to make this dispatch "handle_read()" with a "disconnected" flag?
}
else
{
boost::asio::async_write(socket_,
boost::asio::buffer(data_, bytes_transferred),
boost::bind(&session::handle_write, this,
boost::asio::placeholders::error));
socket_.async_read_some(boost::asio::buffer(data_, max_length - 1),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
}
else
{
delete this;
}
}
void handle_write(const boost::system::error_code& error)
{
if (!error)
{
//
}
else
{
delete this;
}
}
private:
tcp::socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
class server
{
public:
server(boost::asio::io_service& io_service, short port)
: io_service_(io_service),
acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
{
session* new_session = new session(io_service_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
void handle_accept(session* new_session,
const boost::system::error_code& error)
{
if (!error)
{
new_session->start();
new_session = new session(io_service_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
else
{
delete new_session;
}
}
private:
boost::asio::io_service& io_service_;
tcp::acceptor acceptor_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: async_tcp_echo_server <port>\n";
return 1;
}
boost::asio::io_service io_service;
using namespace std; // For atoi.
server s(io_service, atoi(argv[1]));
io_service.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
While experimenting with boost::asio I've noticed that within the calls to async_write()/async_read_some() there is a usage of the C++ "new" keyword.
Also, when stressing this echo server with a client (1 connection) that sends for example 100,000 times some data, the memory usage of this program is getting higher and higher.
What's going on? Will it allocate memory for every call? Or am I wrong? Asking because it doesn't seem right that a server app will allocate, anything. Can I handle it, say with a memory pool?
Another side-question:
See the "this->socket().close();" ?
I want it, as the comment right to it says, to dispatch that same function one last time, with a disconnection error. Need that to do some clean-up. How do I do that?
Thank you all gurus (:
In the hope that someone will contribute something...
Further in my experiments at boost::asio I've decided that right after the server app is up & running I'll put a breakpoint at C++'s 'new' code, i.e at "new.cpp" # function "void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)". Note, I'm using MSVC 2008.
Using the code above, of the original post:
Now that the BP is on I'm connecting one client.
Allocation is done (several times) (as expected) (I know it because the debugger stops at the 'new' keyword as I set) and the new client is now ready to send/receive data.
I send "hi" from the client to the server.
The BP at 'new' is hit at handle_read().
The source to is was the call to async_write() (I stack trace with MSVC).
Hitting F5 (continue) generates another breakpoint at 'new' - this time the async_read_some() call generated it.
Conclusion:
Each such operation generates a call to 'new' !!!!!! Worst case a real server might have!
So, further on looking for some way to use some sort of memory pool so these 'new' calls won't exist brought me to the example: "allocation".
Path to it: ".......\boost_1_43_0\libs\asio\example\allocation\".
Doing the same with this new code (written below) gave me cheering results;
Calls to async_write() and async_read_some() do not generate a call to 'new'.
So far it's nice, but to be honest I can't say I understand exactly how this is done; The allocator is broke down into several pieces as you can see, and that makes things a bit confusing to me.
make_custom_alloc_handler() <--- what exactly does it do?
What's shared_from_this()??
I see that a "session" object has the member "handler_allocator allocator_". Does every "session" object holds a pool of these objects?! Can I have one of this, at the "server" class level which will be shared or something?
"allocator" example code:
//
// server.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <cstdlib>
#include <iostream>
#include <boost/aligned_storage.hpp>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
// Class to manage the memory to be used for handler-based custom allocation.
// It contains a single block of memory which may be returned for allocation
// requests. If the memory is in use when an allocation request is made, the
// allocator delegates allocation to the global heap.
class handler_allocator
: private boost::noncopyable
{
public:
handler_allocator()
: in_use_(false)
{
}
void* allocate(std::size_t size)
{
if (!in_use_ && size < storage_.size)
{
in_use_ = true;
return storage_.address();
}
else
{
return ::operator new(size);
}
}
void deallocate(void* pointer)
{
if (pointer == storage_.address())
{
in_use_ = false;
}
else
{
::operator delete(pointer);
}
}
private:
// Storage space used for handler-based custom memory allocation.
boost::aligned_storage<1024> storage_;
// Whether the handler-based custom allocation storage has been used.
bool in_use_;
};
// Wrapper class template for handler objects to allow handler memory
// allocation to be customised. Calls to operator() are forwarded to the
// encapsulated handler.
template <typename Handler>
class custom_alloc_handler
{
public:
custom_alloc_handler(handler_allocator& a, Handler h)
: allocator_(a),
handler_(h)
{
}
template <typename Arg1>
void operator()(Arg1 arg1)
{
handler_(arg1);
}
template <typename Arg1, typename Arg2>
void operator()(Arg1 arg1, Arg2 arg2)
{
handler_(arg1, arg2);
}
friend void* asio_handler_allocate(std::size_t size,
custom_alloc_handler<Handler>* this_handler)
{
return this_handler->allocator_.allocate(size);
}
friend void asio_handler_deallocate(void* pointer, std::size_t /*size*/,
custom_alloc_handler<Handler>* this_handler)
{
this_handler->allocator_.deallocate(pointer);
}
private:
handler_allocator& allocator_;
Handler handler_;
};
// Helper function to wrap a handler object to add custom allocation.
template <typename Handler>
inline custom_alloc_handler<Handler> make_custom_alloc_handler(
handler_allocator& a, Handler h)
{
return custom_alloc_handler<Handler>(a, h);
}
class session
: public boost::enable_shared_from_this<session>
{
public:
session(boost::asio::io_service& io_service)
: socket_(io_service)
{
}
tcp::socket& socket()
{
return socket_;
}
void start()
{
socket_.async_read_some(boost::asio::buffer(data_),
make_custom_alloc_handler(allocator_,
boost::bind(&session::handle_read,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
boost::asio::async_write(socket_,
boost::asio::buffer(data_, bytes_transferred),
make_custom_alloc_handler(allocator_, boost::bind(&session::handle_write, shared_from_this(), boost::asio::placeholders::error))
);
}
}
void handle_write(const boost::system::error_code& error)
{
if (!error)
{
socket_.async_read_some(boost::asio::buffer(data_),
make_custom_alloc_handler(allocator_,
boost::bind(&session::handle_read,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
}
private:
// The socket used to communicate with the client.
tcp::socket socket_;
// Buffer used to store data received from the client.
boost::array<char, 1024> data_;
// The allocator to use for handler-based custom memory allocation.
handler_allocator allocator_;
};
typedef boost::shared_ptr<session> session_ptr;
class server
{
public:
server(boost::asio::io_service& io_service, short port)
: io_service_(io_service),
acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
{
session_ptr new_session(new session(io_service_));
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
void handle_accept(session_ptr new_session,
const boost::system::error_code& error)
{
if (!error)
{
new_session->start();
new_session.reset(new session(io_service_));
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
}
private:
boost::asio::io_service& io_service_;
tcp::acceptor acceptor_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: server <port>\n";
return 1;
}
boost::asio::io_service io_service;
using namespace std; // For atoi.
server s(io_service, atoi(argv[1]));
io_service.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
To answer your second question, you could use io_service::post by giving it a boost::bind parameter bound to session::handle_read and whatever error code you want.