I'm looking to use the ASIO standalone library (not Boost ASIO), I am trying to set up a client to connect to a server on a specific port.
I saw in the porthopper example that it is possible to get the endpoint without having to deal with an iterator.
asio::io_service io_service;
// Determine the location of the server.
tcp::resolver resolver(io_service);
tcp::resolver::query query(host_name, port);
tcp::endpoint remote_endpoint = *resolver.resolve(query);
I am trying to do the resolve of the query using resolver async_resolve() member function.
This is the code I currently have:
asio::io_service IOService;
asio::ip::tcp::resolver resolver(IOService);
asio::ip::tcp::resolver::query query(ADDRESS, PORT);
resolver.async_resolve(query,
[this](const tcp::endpoint srvEndpoint, std::error_code error)
{
IOService->post(
[this, error, srvEndpoint]
{
handle_resolve_handler(error, srvEndpoint);
});
});
Is there a way to do what was shown in the porthopper example but perform it asynchronously?
Oh, but
tcp::endpoint remote_endpoint = *resolver.resolve(query);
deals with the iterator very much! It uses it to dereference. Note that cute star? It's the pointer indirection operator.
As for your call:
resolver.async_resolve(query,
[this](const tcp::endpoint srvEndpoint, std::error_code error)
{
IOService->post(
[this, error, srvEndpoint]
{
handle_resolve_handler(error, srvEndpoint);
});
});
That does not satisfy the completion handler requirements. Indeed, trying to compile it with Boost Asio² gives a slew of errors: Live On Coliru:
main.cpp:12:14: required from here
/usr/local/include/boost/asio/ip/basic_resolver.hpp:163:5: error: static assertion failed: ResolveHandler type requirements not met
BOOST_ASIO_RESOLVE_HANDLER_CHECK(
^
/usr/local/include/boost/asio/ip/basic_resolver.hpp:163:5: error: no match for call to '(Demo::doResolve()::<lambda(boost::asio::ip::tcp::endpoint, boost::system::error_code)>) (const boost::system::error_code&, const boost::asio::ip::basic_resolver_iterator<boost::asio::ip::tcp>&)'
BOOST_ASIO_RESOLVE_HANDLER_CHECK(
The docs say:
Lo and behold, there's your iterator again! This is by no means an accident. The design of the library is such that async calls will always return the same data regardless of the chosen interface.¹
Cobbling it together: Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
using boost::system::error_code;
using asio::ip::tcp;
struct Demo {
Demo(asio::io_service& svc) : _svc(svc) {}
void doResolve() {
resolver.async_resolve(query, [this](error_code error, tcp::resolver::iterator it) {
tcp::endpoint ep = error? tcp::endpoint{} : *it;
_svc.post([this, error, ep] { handle_resolve_handler(error, ep); });
});
}
private:
asio::io_service& _svc;
tcp::resolver resolver {_svc};
tcp::resolver::query query {"www.google.com", "https"};
void handle_resolve_handler(error_code ec, tcp::endpoint srvEndpoint) {
std::cout << "handle_resolve_handler: " << ec.message() << " " << srvEndpoint << "\n";
}
};
int main() {
asio::io_service svc;
Demo x(svc);
x.doResolve();
svc.run();
}
Prints³:
handle_resolve_handler: Success 216.58.213.196:443
¹ cf. the difference when using coroutines (yield or yield[ec]), asio::use_future etc.: How to set error_code to asio::yield_context
² basically s/boost::system::error_code/std::error_code/
³ On systems with network access
Related
With these present in other parts of my codebase,
namespace net = boost::asio;
using boost::asio::ip::tcp;
boost::asio::io_context& io_context_;
tcp::acceptor acceptor_;
void server::on_accept(boost::beast::error_code ec, boost::asio::ip::tcp::socket socket);
I have noticed that this piece of code compiles:
auto strand = net::make_strand(io_context_);
std::shared_ptr<server> this_pointer = shared_from_this();
acceptor_.async_accept(
strand,
boost::beast::bind_front_handler(&server::on_accept, this_pointer)
);
whereas this does not:
auto strand = net::make_strand(io_context_);
std::shared_ptr<server> this_pointer = shared_from_this();
auto call_next = boost::beast::bind_front_handler(&server::on_accept, this_pointer);
acceptor_.async_accept(
strand,
call_next
);
and it fails with the error
/usr/include/boost/beast/core/detail/bind_handler.hpp:251:45: error: cannot convert ‘boost::beast::detail::bind_front_wrapper<void (server::*)(boost::system::error_code, boost::asio::basic_stream_socket<boost::asio::ip::tcp>), std::shared_ptr<server> >’ to ‘void (server::*)(boost::system::error_code, boost::asio::basic_stream_socket<boost::asio::ip::tcp>)’ in initialization
251 | , args_(std::forward<Args_>(args)...)
I am very curious why passing the value returned from bind_front_handler directly to the async_accept would work but storing that value in a variable and then passing that variable would not work.
I also understand very little about Boost and Beast right now, but here it appears to me like I am forgetting something very basic about C++ itself. Why are both of those piece of code not equivalent?
Indeed, you should not be doing that. The bind-front wrapper wants to be a temporary (in that it is move only). You could "fix" it by doing
acceptor_.async_accept(strand, std::move(call_next));
(after which you will have to remember that call_next may not be used again because it has been moved-from).
I would personally go the other way - as this helper was clearly intended - and write the idiomatic
acceptor_.async_accept(
make_strand(io_context_),
bind_front_handler(&server::on_accept, shared_from_this()));
Which replaces the entire function.
Demo
Live On Coliru
#include <boost/beast.hpp>
#include <boost/asio.hpp>
#include <iostream>
namespace net = boost::asio;
namespace beast = boost::beast;
using boost::system::error_code;
using net::ip::tcp;
struct server : std::enable_shared_from_this<server> {
server() {
acceptor_.listen();
}
void start(){
using beast::bind_front_handler;
acceptor_.async_accept(
make_strand(io_context_),
bind_front_handler(&server::on_accept, shared_from_this()));
}
void wait() {
work_.reset();
if (thread_.joinable())
thread_.join();
}
private:
void on_accept(error_code ec, tcp::socket&& socket) {
std::cout << "Accepted connection from " << socket.remote_endpoint() << "\n";
//// loop to accept more:
// start();
}
net::io_context io_context_;
tcp::acceptor acceptor_{io_context_, {{}, 9999}};
net::executor_work_guard<net::io_context::executor_type> work_{
io_context_.get_executor()};
std::thread thread_{[this] { io_context_.run(); }};
};
int main()
{
auto s = std::make_shared<server>();
s->start();
s->wait();
}
With
g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp
./a.out& sleep .5; nc 127.0.0.1 9999 <<<'hello world'; wait
Prints e.g.
Accepted connection from 127.0.0.1:36402
I got this code online and have been trying to add a timer to it so that it reads a packet every so often. I can't seem to figure out how to pass a callback function to the boost::async_wait command because I am getting this error:
server1.cpp: In member function ‘void UDPClient::handle_receive(const boost::system::error_code&, size_t)’:
server1.cpp:51:66: error: invalid use of non-static member function ‘void UDPClient::time_to_receive(const boost::system::error_code&)’
boost::asio::placeholders::error));
^
server1.cpp:33:6: note: declared here
void UDPClient::time_to_receive(const boost::system::error_code& error)
^~~~~~~~~
UDPClient Class:
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/array.hpp>
#include <iostream>
#include <boost/date_time/posix_time/posix_time.hpp>
using boost::asio::ip::udp;
class UDPClient
{
public:
boost::asio::io_service io_service;
udp::socket socket;
udp::endpoint receiver_endpoint;
boost::asio::deadline_timer timer;
boost::array<char, 1024> recv_buffer;
UDPClient();
void time_to_receive(const boost::system::error_code& error);
void do_receive();
void handle_receive(const boost::system::error_code& error, size_t);
};
UDPClient::UDPClient()
: io_service(),
socket(io_service, {udp::v4(), 3643}),
timer(io_service, boost::posix_time::seconds(2))
{
do_receive();
io_service.run();
}
void UDPClient::time_to_receive(const boost::system::error_code& error)
{
do_receive();
}
void UDPClient::do_receive()
{
socket.async_receive_from(boost::asio::buffer(recv_buffer), receiver_endpoint,
boost::bind(&UDPClient::handle_receive, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void UDPClient::handle_receive(const boost::system::error_code& error, size_t bytes_transferred)
{
std::cout << "Received: '" << std::string(recv_buffer.begin(), recv_buffer.begin()+bytes_transferred) << "'\n";
timer.async_wait(boost::bind(time_to_receive,
boost::asio::placeholders::error));
}
int main()
{
UDPClient updclient;
}
One question I'm trying to answer with this code is if I spam the server from a client with a bunch of UDP packets will the server ignore all the packets during the async_wait?
Also, my main goal is to put this code into a quadcopter code I have. Will it work the way it is written to instantiate this class and have it read packets from a ground station to get user input?
The way you use bind with member function is wrong. Use it like shown below:
timer.async_wait(boost::bind(&UDPClient::time_to_receive, this,
boost::asio::placeholders::error));
As to why it is like that, I would recommend you to read the boost docs for that.
Also, I have modified the code to make it actually run like a server without exiting. For that I made following 2 changes:
Initialzation of io_service in main function and pass its reference to the class.
Initialize a io_service_work object. This acts as a perennial source of work to the io_service. Thus, io_service never returns from the run function unless the work object is destroyed.
The complete source:
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/array.hpp>
#include <iostream>
#include <boost/date_time/posix_time/posix_time.hpp>
using boost::asio::ip::udp;
class UDPClient
{
public:
boost::asio::io_service& io_service;
udp::socket socket;
udp::endpoint receiver_endpoint;
boost::asio::deadline_timer timer;
boost::array<char, 1024> recv_buffer;
UDPClient(boost::asio::io_service&);
void time_to_receive(const boost::system::error_code& error);
void do_receive();
void handle_receive(const boost::system::error_code& error, size_t);
};
UDPClient::UDPClient(boost::asio::io_service& ios)
: io_service(ios),
socket(io_service, {udp::v4(), 3643}),
timer(io_service, boost::posix_time::seconds(2))
{
do_receive();
}
void UDPClient::time_to_receive(const boost::system::error_code& error)
{
do_receive();
}
void UDPClient::do_receive()
{
socket.async_receive_from(boost::asio::buffer(recv_buffer), receiver_endpoint,
boost::bind(&UDPClient::handle_receive, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void UDPClient::handle_receive(const boost::system::error_code& error, size_t bytes_transferred)
{
std::cout << "Received: '" << std::string(recv_buffer.begin(), recv_buffer.begin()+bytes_transferred) << "'\n";
timer.expires_from_now(boost::posix_time::seconds(2));
timer.async_wait(boost::bind(&UDPClient::time_to_receive, this,
boost::asio::placeholders::error));
}
int main()
{
boost::asio::io_service ios;
boost::asio::io_service::work wrk(ios);
UDPClient updclient(ios);
ios.run();
}
NOTE: Even though it is a server, the class is name Client. I am ignoring that :)
All of the boost examples work until I try to implement the exact same thing myself. I'm starting to think there must be an order of creation or io_service ownership for things to block properly.
My server structure is as follows:
class Server {
public:
Server(unsigned short port)
: ioService_(), acceptor_(ioService_), socket_(ioService_) {
acceptClient(); // begin async accept
}
void start(); // runs ioService_.run();
private:
void acceptClient();
asio::io_service ioService_;
tcp::acceptor acceptor_;
tcp::socket socket_;
Cluster cluster_; // essentially just a connection manager
};
The acceptClient() function works like this:
void Server::acceptClient() {
acceptor_.async_accept(socket_, [this](const system::error_code& e){
if(!acceptor_.is_open()) return;
if(!e) {
cluster_.add(std::make_shared<Client>(std::move(socket_), cluster_));
}
acceptClient();
});
}
I'm not sure if you need an outline of the Client class since the server should run and block even with no clients.
The creation of the server goes as follows:
try {
Server server(port);
server.start(); // this calls the server's member io_service's run();
} catch (const std::exception& e) {
std::cerr << e.what(); << std::endl;
}
The problem is the server instantly closes after that call. The program starts and then exits with no errors. Is there something that io_service.run() relies on? e.g. some form of asynchronous link that I've forgotten? My learned this design from boost asio's http server design but I've worked it to fit my basic purposes. The problem is some boost examples establish a new member boost tcp::socket in the client itself rather than moving the server's to the client so I'm quite confused. They also tend to use boost's versions of std::bind instead of lambdas which etc.
So, can anyone give me a brief rundown on how to create a basic, stripped, async server since the boost examples are really confusing since the code conventions differ per example. I was wondering if anybody noticed anything straight away that would cause my server to instantly close.
Thanks.
I tested async_accept with the following code which sends Hello to clients connecting to the port. At least there is the creation of endpoint object, acceptor.open(endpoint.protocol()), acceptor.bind(endpoint) and acceptor.listen() calls that seem to be missing from your code.
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <string>
using namespace boost::asio;
void handle_accept(
io_service * ios,
ip::tcp::acceptor * acceptor,
ip::tcp::socket * socket,
const boost::system::error_code & error)
{
if (!error) {
std::string msg("Hello\n");
socket->send(buffer(msg, msg.length()));
ip::tcp::socket * temp = new ip::tcp::socket(*ios);
acceptor->async_accept(*temp,
boost::bind(handle_accept,
ios, acceptor, temp,
placeholders::error));
}
}
int main(void)
{
io_service ios;
ip::tcp::socket socket(ios);
ip::tcp::acceptor acceptor(ios);
ip::tcp::endpoint endpoint(ip::tcp::v4(), 1500);
acceptor.open(endpoint.protocol());
acceptor.set_option(ip::tcp::acceptor::reuse_address(true));
acceptor.bind(endpoint);
acceptor.listen();
acceptor.async_accept(socket,
boost::bind(handle_accept,
&ios, &acceptor, &socket,
placeholders::error));
ios.run();
/*
acceptor.accept(socket);
std::string msg("Hello\n");
socket.send(buffer(msg, msg.length()));
*/
}
A version with a Server class and a lambda as a argument for async_accept:
#include <boost/asio.hpp>
#include <functional>
#include <string>
using namespace boost::asio;
class Server {
public:
Server(unsigned short port) : ios(), acceptor(ios), socket(ios),
endpoint(ip::tcp::v4(), port) {
acceptor.open(endpoint.protocol());
acceptor.set_option(ip::tcp::acceptor::reuse_address(true));
acceptor.bind(endpoint);
acceptor.listen();
nsocket = &socket;
}
void run() {
std::function<void (const boost::system::error_code &)> f;
f = [&f, this] (const boost::system::error_code & error) {
if (!error) {
std::string msg("Hello\n");
nsocket->send(buffer(msg, msg.length()));
nsocket = new ip::tcp::socket(ios);
acceptor.async_accept(*nsocket, f);
}
};
acceptor.async_accept(socket, f);
ios.run();
}
protected:
io_service ios;
ip::tcp::acceptor acceptor;
ip::tcp::socket socket;
ip::tcp::endpoint endpoint;
ip::tcp::socket * nsocket;
};
int main(void)
{
Server srv(1500);
srv.run();
}
I have minor experience with c++ and facing some issue with boost-asio.
I want to rewrite standard boost-asio async-http-client example (http://www.boost.org/doc/libs/1_58_0/doc/html/boost_asio/example/cpp03/http/client/async_client.cpp) in following way.
My goal is to have 2 classes;
AsyncHttpClient(that stores host and has member function that will send async calls to specified path).
AsyncHttpConnection (that takes io_service, host, path as parameters
and follows the flow specified in boost-asio async-http-client
example)
I have the following implementation
using boost::asio::ip::tcp;
class AsyncHttpConnection {
public:
AsyncHttpConnection(
boost::asio::io_service& io_service,
std::string host,
std::string path) : resolver_(io_service),
socket_(io_service),
host_(host),
path_(path)
{
tcp::resolver::query query(host_, "http");
resolver_.async_resolve(query,
boost::bind(&AsyncHttpConnection::handle_resolve,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::iterator));
}
private:
std::string host_;
std::string path_;
tcp::resolver resolver_;
tcp::socket socket_;
boost::asio::streambuf request_;
boost::asio::streambuf response_;
void handle_resolve(
const boost::system::error_code& err,
tcp::resolver::iterator endpoint_iterator)
{
if (!err) {
// code here
} else {
std::cout << err.message() << std::endl; // GOT "Operation Canceled" here
}
}
// list of other handlers
};
class AsyncHttpClient {
public:
AsyncHttpClient(
boost::asio::io_service& io_service,
std::string host) : host_(host)
{
io_service_ = &io_service; // store address of io_service
}
void async_call(std::string path)
{
AsyncHttpConnection(*io_service_, host_, path);
}
private:
std::string host_;
boost::asio::io_service* io_service_; // pointer, because io_service is uncopyable;
};
int main(int argc, char* argv[])
{
boost::asio::io_service io_service;
AsyncHttpClient boost(io_service, "www.boost.org");
boost.async_call("/doc/libs/1_51_0/doc/html/boost_asio/example/http/client/async_client.cpp");
io_service.run();
}
I got an error "Operation Canceled" in this particular way;
If I instantiate AsyncHttpConnection in following way
int main(int argc, char* argv[])
{
boost::asio::io_service io_service;
AsyncHttpConnection(io_service, "www.boost.org", "path");
io_service.run();
}
I got everything working perfectly, I think the issue is using pointer to io_service. How can I solve this issue, if io_service object is uncopyable?
void async_call(std::string path) {
AsyncHttpConnection(*io_service_, host_, path);
}
The body constructs a temporary object of type AsyncHttpConnection. So, before the statement completes, the destructor for this type runs.
The default destructor does member-wise destruction. So it triggers the destructor tcp::resolver resolver_. The documentation for this class states that any pending asynchronous operation will be canceled on doing so.
In principle the "alternative" main has exactly the same problem (and indeed it fails with Operation canceled on my box). If it doesn't for you you're getting very fortunate timing of events.
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);