2

I have a Websocket class that connects asynchronously using Boost.Asio and Boost.Beast. The following code was working fine with Boost 1.78, but after updating to the latest Boost version, I now get a compile error:

std::future<void> Websocket::async_connect() {

    io_thread_ = std::thread([this]() {
        ioc_.run();
    });

    // Start resolve
    auto resolve_fut = resolver_.async_resolve(host_, port_, net::use_future);

    return std::async(std::launch::async, [this, resolve_fut = std::move(resolve_fut)]() mutable {
        try {
            auto results = resolve_fut.get();

            auto connect_fut = beast::get_lowest_layer(ws_).async_connect(*results, net::use_future);
            connect_fut.get();

            auto ssl_handshake_fut = ws_.next_layer().async_handshake(ssl::stream_base::client, net::use_future);
            ssl_handshake_fut.get();

            ws_.set_option(websocket::stream_base::timeout::suggested(beast::role_type::client));
            auto ws_handshake_fut = ws_.async_handshake(host_, target_, net::use_future);
            ws_handshake_fut.get();
            webSocketLogger_.info("Connected to WebSocket Successfully!!!");
            // std::cout << "Connected to WebSocket Successfully!!!\n";

            ReceiveMsg();

        } catch (const std::exception& e) {
            // std::cerr << "Connection failed: " << e.what() << "\n";
            webSocketLogger_.error("Connection Failed: {}", e.what());
            throw;
        }

The error I get is:

No operator* matches argument of type

std::tuple<boost::system::error_code, boost::asio::ip::basic_resolver_results<tcp>>

In Boost 1.78, resolve_fut.get() returned a type that could be directly dereferenced to get tcp::endpoint results. In the latest Boost, it seems async_resolve(..., net::use_future) now returns a tuple instead of a single object.

I have tried things like:

auto endpoints = std::get<1>(results);
auto [err,endpoints] = resolve_fut.get();

but it still doesn’t compile.

Question:
How should I update this code to work with the latest Boost version? What is the correct way to extract endpoints from the future returned by async_resolve?

1 Answer 1

3

The result type is no longer the iterator. So, get the iterator: *results.begin().

However, this should tip you off that you have broken logic: you're discarding all other resolved endpoints. This can lead to suboptimal connections or refused connections. Better to use results in the first place:

auto connect_fut = net::async_connect(beast::get_lowest_layer(ws_), results, net::use_future);

Regardless the code is very convoluted and cakes over synchronous code with layers of threads, futures and worse. I'd suggest to simplify, so instead of

auto connect_fut = net::async_connect(beast::get_lowest_layer(ws_), results, net::use_future);
connect_fut.get();

auto ssl_handshake_fut =
    ws_.next_layer().async_handshake(ssl::stream_base::client, net::use_future);
ssl_handshake_fut.get();

just

net::connect(beast::get_lowest_layer(ws_), results);
ws_.next_layer().handshake(ssl::stream_base::client);

Or simply: Live On Coliru

#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/beast.hpp>
#include <fmt/printf.h>
#include <future>
#include <print>
namespace net       = boost::asio;
namespace beast     = boost::beast;
namespace websocket = beast::websocket;
namespace ssl       = net::ssl;
using net::ip::tcp;

namespace {
    struct {
        static void info(std::string_view sv) { fmt::print("{}", sv); }
        static void error(auto const& fmt, auto const&... args) { fmt::print(fmt::runtime(fmt), args...); }
    } static thread_local webSocketLogger_;
} // namespace

struct Websocket {
    std::future<void> async_connect() {//
        return std::async(std::launch::async, &Websocket::do_connect, this);
    }

  private:
    void do_connect() try {
        auto results = resolver_.resolve(host_, port_);

        net::connect(beast::get_lowest_layer(ws_), results);
        ws_.next_layer().handshake(ssl::stream_base::client);

        ws_.set_option(websocket::stream_base::timeout::suggested(beast::role_type::client));
        ws_.handshake(host_, target_);
        webSocketLogger_.info("Connected to WebSocket Successfully!!!");
        // std::cout << "Connected to WebSocket Successfully!!!\n";

        ReceiveMsg();
    } catch (std::exception const& e) {
        // std::cerr << "Connection failed: " << e.what() << "\n";
        webSocketLogger_.error("Connection Failed: {}", e.what());
        throw;
    }

    net::thread_pool                            ioc_{1};
    tcp::resolver                               resolver_{ioc_};
    std::string                                 host_, port_, target_;
    ssl::context                                ctx_{ssl::context::tls_client};
    websocket::stream<ssl::stream<tcp::socket>> ws_{ioc_, ctx_};

    void ReceiveMsg() {}
};

int main() {
}
Sign up to request clarification or add additional context in comments.

1 Comment

By the way, to make proper composed operations, I'd suggest async_compose (full demo), or co_composed, also with full demo

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.