Search code examples
c++c++17qt-creatorboost-asio

How to make a timeout at receiving in boost::asio udp::socket?


I create an one-thread application which exchanges with another one via UDP. When the second is disconnecting, my socket::receive_from blocks and I don't know how to solve this problem not changing the entire program into multi-threads or async interactions.

I thought that next may be a solution:

std::chrono::milliseconds timeout{4};
boost::system::error_code err;
data_t buffer(kPackageMaxSize);
std::size_t size = 0;

const auto status = std::async(std::launch::async,
    [&]{
        size = socket_.receive_from(boost::asio::buffer(buffer), dst_, 0, err);
    }
).wait_for(timeout);

switch (status)
{
    case std::future_status::timeout: /*...*/ break;
}

But I achieved a new problem: Qt Creator (GDB 11.1) (I don't have ability to try something yet) began to fall when I am debugging. If it runs without, the solution also not always works.


Solution

  • PS. As for "it doesn't work when debugging", debugging (specifically breakpoints) obviously changes timing. Also, keep in mind network operations have varying latency and UDP isn't a guaranteed protocol: messages may not be delivered.


    Asio stands for "Asynchronous IO". As you might suspect, this means that asynchronous IO is a built-in feature, it's the entire purpose of the library. See overview/core/async.html: Concurrency Without Threads

    It's not necessary to complicate with std::async. In your case I'd suggest using async_receive_from with use_future, as it is closest to the model you opted for:

    Live On Coliru

    #include <boost/asio.hpp>
    #include <iostream>
    #include <iomanip>
    namespace net = boost::asio;
    using net::ip::udp;
    
    using namespace std::chrono_literals;
    constexpr auto kPackageMaxSize = 65520;
    using data_t = std::vector<char>;
    
    int main() {
        net::thread_pool ioc;
    
        udp::socket socket_(ioc, udp::v4());
        socket_.bind({{}, 8989});
    
        udp::endpoint ep;
        data_t        buffer(kPackageMaxSize);
        auto          fut =
            socket_.async_receive_from(net::buffer(buffer), ep, net::use_future);
    
        switch (fut.wait_for(4ms)) {
            case std::future_status::ready: {
                buffer.resize(fut.get()); // never blocks here
                std::cout << "Received " << buffer.size() << " bytes: "
                          << std::quoted(
                                 std::string_view(buffer.data(), buffer.size()))
                          << "\n";
                break;
            }
            case std::future_status::timeout:
            case std::future_status::deferred: {
                std::cout << "Timeout\n";
                socket_.cancel(); // stop the IO operation
                // fut.get() would throw system_error(net::error::operation_aborted)
                break;
            }
        }
    
        ioc.join();
    }
    

    The Coliru output:

    Received 12 bytes: "Hello World
    "
    

    Locally demonstrating both timeout and successful path:

    enter image description here