jeancf jeancf - 9 days ago 4
C++ Question

std::future not working in Boost UDP socket async receive operation

I am writing a UDP server app with Boost that should listen on a socket for 5 seconds and if no datagram has been received within these 5 seconds, move on to do other things.

Inspired by some answers I decided to try the solution based on std::future.

The problem is that the call to

wait_for()
always times out as if no data was received. But if I set a breakpoint on the line that executes after the timeout and that I inspect variables I see that the buffer contains the received datagram and the
remote_endpoint
object contains the address of the client. In other words the socket receive works as expected but the std::future does not fire. why?

Here is my test server code:

#include <future>
#include <boost/asio.hpp>
#include <boost/asio/use_future.hpp>

using boost::asio::ip::udp;

int main()
{
try
{
boost::asio::io_service io_service;
udp::socket socket(io_service, udp::endpoint(udp::v4(), 10000));
char recv_buf[8];

for (;;)
{
ZeroMemory(recv_buf, 8);
udp::endpoint remote_endpoint;
std::future<std::size_t> recv_length;

recv_length = socket.async_receive_from(
boost::asio::buffer(recv_buf),
remote_endpoint,
0,
boost::asio::use_future);

if (recv_length.wait_for(
std::chrono::seconds(5)) == std::future_status::timeout)
{
printf("time out. Nothing received.\n");
}
else
{
printf("received something: %s\n", recv_buf);
}
}
}
catch (std::exception& e)
{
printf("Error: %s\n", e.what());
}
return 0;
}


I have been banging my head on this one for a while so any help would be appreciated. I am on Windows 10 with Visual Studio 2015.

Here is my test client code (in python, sorry).

import socket
import time

HOST = "server" # The remote host
PORT = 10000 # The same port as used by the server
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
address = socket.getaddrinfo(HOST, PORT)[0][-1]

while True:
s.sendto("ping\0", address)
time.sleep(1)

Answer

I found the solution. So to wrap this up, here is what needs to be done. My initial code needs 2 modifications.

(1) adding 2 lines at the beginning to launch a separate thread with io_service to monitor the time-out (as suggested by Tanner Sansbury)

boost::asio::io_service::work work(io_service);
std::thread thread([&io_service](){ io_service.run(); });

(2) call socket.cancel(); in the condition of socket time_out. If the socket operation is not cancelled, the socket will keep blocking despite the renewed calls to wait_for() (solution received on Boost's mailing list).

Here is the amended code for reference:

#include <future>
#include <boost/asio.hpp>
#include <boost/asio/use_future.hpp>

using boost::asio::ip::udp;

int main()
{
    try
    {
        boost::asio::io_service io_service;
        boost::asio::io_service::work work(io_service);
        std::thread thread([&io_service](){ io_service.run(); });

        udp::socket socket(io_service, udp::endpoint(udp::v4(), 10000));

        char recv_buf[8];

        for (;;)
        {
            ZeroMemory(recv_buf, 8);
            udp::endpoint remote_endpoint;
            std::future<std::size_t> recv_length;

            recv_length = socket.async_receive_from(
                boost::asio::buffer(recv_buf), 
                remote_endpoint, 
                0, 
                boost::asio::use_future);

            if (recv_length.wait_for(
                std::chrono::seconds(5)) == std::future_status::timeout)
            {
                printf("time out. Nothing received.\n");
                socket.cancel();
            }
            else
            {
                printf("received something: %s\n", recv_buf);
            }
        }
    }
    catch (std::exception& e)
    {
        printf("Error: %s\n", e.what());
    }
    return 0;
}

Thank you all for your help.