Boost::Asio Synchronous Client with Timeout

Boost::Asio synchronous client with timeout

I've made a helper facility to do any Asio async operation "synchronously" with a timeout here, look for await_operation:

  • boost::asio + std::future - Access violation after closing socket

You should be able to adapt the pattern for your sample.

Demo

It took a while since I wanted to test this with an ftp server.

Notes:

  • you didn't resolve the address (effectively requiring the user to type in IP address)
  • you didn't make sure commands were closed with newline
  • you didn't handle any kind of input error

Fixing these things and using my await_operation you'd get this:

#include <cstdlib>
#include <cstring>
#include <iostream>
#include <thread>
#include <chrono>
#include <boost/asio.hpp>
#include <boost/asio/high_resolution_timer.hpp>

#define TIMEOUT std::chrono::seconds(5)
#define MAX_MESSAGE_SIZE 4096
using boost::asio::ip::tcp;

enum { max_length = 2048 };

struct Service {
using error_code = boost::system::error_code;

template<typename AllowTime, typename Cancel> void await_operation_ex(AllowTime const& deadline_or_duration, Cancel&& cancel) {
using namespace boost::asio;

ioservice.reset();
{
high_resolution_timer tm(ioservice, deadline_or_duration);
tm.async_wait([&cancel](error_code ec) { if (ec != error::operation_aborted) std::forward<Cancel>(cancel)(); });
ioservice.run_one();
}
ioservice.run();
}

template<typename AllowTime, typename ServiceObject> void await_operation(AllowTime const& deadline_or_duration, ServiceObject& so) {
return await_operation_ex(deadline_or_duration, [&so]{ so.cancel(); });
}

boost::asio::io_service ioservice;
};

int main()
{
while(true)
{
try
{
Service service;

std::cout << "Enter FTP server address to connect or END to finish: " << std::endl;

std::string address;
if (std::cin >> address) {
if (address == "END") break;
} else {
if (std::cin.eof())
break;
std::cerr << "Invalid input ignored\n";
std::cin.clear();
std::cin.ignore(1024, '\n');

continue;
}

tcp::socket s(service.ioservice);
tcp::resolver resolver(service.ioservice);

boost::asio::async_connect(s, resolver.resolve({address, "21"}), [](boost::system::error_code ec, tcp::resolver::iterator it) {
if (ec) throw std::runtime_error("Error connecting to server: " + ec.message());
std::cout << "Connected to " << it->endpoint() << std::endl;
});
service.await_operation_ex(TIMEOUT, [&]{
throw std::runtime_error("Error connecting to server: timeout\n");
});

auto receive = [&] {
boost::asio::streambuf sb;
size_t bytes;

boost::asio::async_read_until(s, sb, '\n', [&](boost::system::error_code ec, size_t bytes_transferred) {
if (ec) throw std::runtime_error("Error receiving message: " + ec.message());
bytes = bytes_transferred;

std::cout << "Received message is: " << &sb;
});

service.await_operation(TIMEOUT, s);
return bytes;
};

receive(); // banner

auto send = [&](std::string cmd) {
boost::asio::async_write(s, boost::asio::buffer(cmd), [](boost::system::error_code ec, size_t /*bytes_transferred*/) {
if (ec) throw std::runtime_error("Error sending message: " + ec.message());
});
service.await_operation(TIMEOUT, s);
};

auto ftp_command = [&](std::string cmd) {
send(cmd + "\r\n");
receive(); // response
};

//ftp_command("USER bob");
//ftp_command("PASS hello");

while (true) {
std::cout << "Enter command: ";

std::string request;
if (!std::getline(std::cin, request))
break;

ftp_command(request);
}

}
catch (std::exception const& e)
{
std::cerr << "COMMUNICATIONS ERROR " << e.what() << "\n";
}
}

return 0;
}

Which, in my test run, prints e.g.:

Sample Image

C++ Boost ASIO: how to read/write with a timeout?

This has been brought up on the asio mailing lists, there's a ticket requesting the feature as well. To summarize, it is suggested to use asynchronous methods if you desire timeouts and cancellability.


If you cannot convert to asynchronous methods, you might try the SO_RCVTIMEO and SO_SNDTIMEO socket options. They can be set with setsockopt, the descriptor can be obtained with the boost::asio::ip::tcp::socket::native method. The man 7 socket man page says

SO_RCVTIMEO and SO_SNDTIMEO
Specify the receiving or sending timeouts until reporting an
error. The argument is a struct
timeval. If an input or output
function blocks for this period of
time, and data has been sent
or received, the return value of that function will be
the amount of data transferred; if no
data has been transferred and the
timeout has been reached then -1 is
returned with errno set to
EAGAIN or EWOULDBLOCK just as if the socket was specified to
be non-blocking. If the timeout is
set to zero (the default) then the
operation will never timeout.
Timeouts only have effect
for system calls that perform socket I/O (e.g., read(2),
recvmsg(2), send(2), sendmsg(2));
timeouts have no effect for select(2),
poll(2), epoll_wait(2), etc.

How to set a timeout on blocking sockets in boost asio?

Under Linux/BSD the timeout on I/O operations on sockets is directly supported by the operating system. The option can be enabled via setsocktopt(). I don't know if boost::asio provides a method for setting it or exposes the socket scriptor to allow you to directly set it -- the latter case is not really portable.

For a sake of completeness here's the description from the man page:

SO_RCVTIMEO and SO_SNDTIMEO

          Specify the receiving or sending  timeouts  until  reporting  an
error. The argument is a struct timeval. If an input or output
function blocks for this period of time, and data has been sent
or received, the return value of that function will be the
amount of data transferred; if no data has been transferred and
the timeout has been reached then -1 is returned with errno set
to EAGAIN or EWOULDBLOCK just as if the socket was specified to
be non-blocking. If the timeout is set to zero (the default)
then the operation will never timeout. Timeouts only have
effect for system calls that perform socket I/O (e.g., read(2),
recvmsg(2), send(2), sendmsg(2)); timeouts have no effect for
select(2), poll(2), epoll_wait(2), etc.

Timeout for boost::beast sync http client

Timeouts are not available for synchronous I/O in Asio. Since Beast is a layer above asio, it too does not support timeouts for synchronous I/O. If you want timeouts, you must use asynchronous APIs. You can use a stackful coroutine, or if you have a modern enough compiler you can experiment with stackless coroutines (co_await). These allow you to write code that appears synchronous but using asynchronous interfaces.

The Beast docs are clear on this:
"For portability reasons, networking does not provide timeouts or cancellation features for synchronous stream operations."

https://www.boost.org/doc/libs/1_70_0/libs/beast/doc/html/beast/using_io/timeouts.html

If you want to have timeouts on connect operations, use an instance of beast::tcp_stream and call the async_connect member function:
https://www.boost.org/doc/libs/1_70_0/libs/beast/doc/html/beast/using_io/timeouts.html#beast.using_io.timeouts.connecting

Can I read from a socket synchronously using Boost.Asio with a timeout on a multithreaded I/O service?

Boost.Asio's support for futures may provide an elegant solution. When an asynchronous operation is provided the boost::asio::use_future value as its completion handler, the initiating function will return a std::future object that will receive the result of the operation. Additionally, if the operation completes with failure, the error_code is converted into a system_error and passed to the caller via the future.

In the Boost.Asio C++11 Futures datytime client example, a dedicated thread runs the io_service, and the main thread initiates asynchronous operations then synchronously waits on the completion of the operations, such as below:

std::array<char, 128> recv_buf;
udp::endpoint sender_endpoint;
std::future<std::size_t> recv_length =
socket.async_receive_from(
boost::asio::buffer(recv_buf),
sender_endpoint,
boost::asio::use_future);

// Do other things here while the receive completes.

std::cout.write(
recv_buf.data(),
recv_length.get()); // Blocks until receive is complete.

When using futures, the overall approach to implementing a synchronous read with a timeout is the same as before. Instead of using a synchronous read, one would use an asynchronous read and asynchronously wait on a timer. The only minor change is that instead of instead of blocking on the io_service or periodically checking for the predicate, one would invoke future::get() to block until the operation has completed with success or failure (such as a timeout).

If C++11 is not available, then the return type for asynchronous operations can be customized for Boost.Thread's future, as demonstrated in this answer.

boost::asio timeouts example - writing data is expensive

The following things could be of interest: boost::asio strands is a mechanism of synchronising handlers. You only need to do this though if you are calling io_service::run from multiple threads AFAIK.

Also useful is the io_service::post method, which allows you execute code from the thread that has invoked io_service::run.



Related Topics



Leave a reply



Submit