Другое

Исправление ошибки async_connect в Boost.Beast при обновлении с версии 1.78 до 1.89

Узнайте, как исправить ошибки компиляции async_connect в Boost.Beast при обновлении с версии Boost 1.78 до 1.89. Полное руководство с примерами кода и советами по миграции.

Как исправить ошибку компиляции async_connect в Boost.Beast после обновления с Boost 1.78 до 1.89?

У меня есть класс WebSocket, который подключается асинхронно с использованием Boost.Asio и Boost.Beast. Следующий код работал нормально с Boost 1.78, но после обновления до последней версии Boost теперь я получаю ошибку компиляции:

cpp
std::future<void> Websocket::async_connect() {
    io_thread_ = std::thread([this]() {
        ioc_.run();
    });

    // Начинаем разрешение имени
    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;
        }

Ошибка, которую я получаю:

No operator* matches argument of type
std::tuple<boost::system::error_code, boost::asio::ip::basic_resolver_results<tcp>>

В Boost 1.78 resolve_fut.get() возвращал тип, который можно было напрямую разыменовать для получения результатов tcp::endpoint. В последней версии Boost, похоже, async_resolve(..., net::use_future) теперь возвращает кортеж вместо одного объекта.

Я пробовал такие варианты:

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

но это все равно не компилируется.

Как мне обновить этот код, чтобы он работал с последней версией Boost? Как правильно извлечь конечные точки из future, возвращаемого async_resolve?

Ошибка компиляции возникает из-за того, что в Boost 1.89 изменился тип возвращаемого значения функции async_resolve(..., net::use_future). Теперь она возвращает кортеж, содержащий как код ошибки, так и результаты резолвера, а не только результаты напрямую. Вот как исправить ваш код:

В Boost 1.89 async_resolve(..., net::use_future) теперь возвращает std::future<std::tuple<error_code, resolver_results>> вместо std::future<resolver_results>. Вам необходимо извлечь как код ошибки, так и результаты из кортежа.

Вот исправленная версия вашего кода:

cpp
std::future<void> Websocket::async_connect() {
    io_thread_ = std::thread([this]() {
        ioc_.run();
    });

    // Начинаем разрешение имени
    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 [err, results] = resolve_fut.get();
            
            if (err) {
                throw boost::system::system_error(err);
            }

            // Используем resolver_results с async_connect
            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("Успешное подключение к WebSocket!!!");
            ReceiveMsg();

        } catch (const std::exception& e) {
            webSocketLogger_.error("Ошибка подключения: {}", e.what());
            throw;
        }
    });
}

Основные изменения:

  1. Извлечение ошибки и результатов из кортежа: auto [err, results] = resolve_fut.get();
  2. Проверка ошибок: Добавлена обработка ошибок перед продолжением подключения
  3. Обновление вызова async_connect: Изменено с *results на просто results, так как async_connect теперь принимает resolver_results напрямую

Содержание


Понимание изменений в Boost.Beast

Ошибка возникает из-за того, что в Boost 1.89 были внесены изменения в функцию async_resolve при использовании с net::use_future. Тип возвращаемого значения изменился с:

  • Boost 1.78: std::future<ip::tcp::resolver::results_type>
  • Boost 1.89: std::future<std::tuple<error_code, ip::tcp::resolver::results_type>>

Это изменение обеспечивает лучшую обработку ошибок и согласованность между асинхронными операциями Asio. Формат кортежа соответствует шаблону, используемому другими асинхронными операциями Asio при комбинировании с use_future.

Полный пример исправленного кода

Вот полный, работающий версии вашего метода async_connect:

cpp
std::future<void> Websocket::async_connect() {
    io_thread_ = std::thread([this]() {
        ioc_.run();
    });

    // Начинаем разрешение имени
    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 [err, results] = resolve_fut.get();
            
            if (err) {
                throw boost::system::system_error(err);
            }

            // Подключаемся к первому разрешенному конечному пункту
            auto connect_fut = beast::get_lowest_layer(ws_).async_connect(results, net::use_future);
            connect_fut.get();

            // Выполняем SSL-рукопожатие
            auto ssl_handshake_fut = ws_.next_layer().async_handshake(ssl::stream_base::client, net::use_future);
            ssl_handshake_fut.get();

            // Настраиваем таймаут WebSocket
            ws_.set_option(websocket::stream_base::timeout::suggested(beast::role_type::client));
            
            // Выполняем рукопожатие WebSocket
            auto ws_handshake_fut = ws_.async_handshake(host_, target_, net::use_future);
            ws_handshake_fut.get();
            
            webSocketLogger_.info("Успешное подключение к WebSocket!!!");
            ReceiveMsg();

        } catch (const std::exception& e) {
            webSocketLogger_.error("Ошибка подключения: {}", e.what());
            throw;
        }
    });
}

Альтернативные подходы к обработке ошибок

Использование std::get для доступа к элементам кортежа

Если вы предпочитаете не использовать структурированное связывание, вы можете получить доступ к элементам кортежа напрямую:

cpp
auto resolve_result = resolve_fut.get();
auto err = std::get<0>(resolve_result);
auto results = std::get<1>(resolve_result);

if (err) {
    throw boost::system::system_error(err);
}

Использование .then() для лучшего асинхронного потока

Для более современных асинхронных шаблонов рассмотрите использование .then():

cpp
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 {
    return resolve_fut.then([this](auto fut) {
        auto [err, results] = fut.get();
        if (err) {
            throw boost::system::system_error(err);
        }
        
        // Продолжаем с логикой подключения...
        return std::async(std::launch::async, [this, results]() mutable {
            // Остальной код подключения
        });
    });
});

Руководство по миграции с Boost 1.78 на 1.89

При обновлении с Boost 1.78 на 1.89 обратите внимание на следующие изменения:

1. Тип возвращаемого значения async_resolve

cpp
// Старый (1.78)
auto results = resolve_fut.get();  // Только результаты

// Новый (1.89)
auto [err, results] = resolve_fut.get();  // Ошибка + результаты

2. Параметр async_connect

cpp
// Старый (1.78)
auto connect_fut = beast::get_lowest_layer(ws_).async_connect(*results, net::use_future);

// Новый (1.89)
auto connect_fut = beast::get_lowest_layer(ws_).async_connect(results, net::use_future);

3. Шаблон обработки ошибок

Всегда проверяйте код ошибки из асинхронных операций:

cpp
auto [err, results] = resolve_fut.get();
if (err) {
    throw boost::system::system_error(err);
}

Распространенные проблемы и решения

Проблема: “Нет совпадающей функции для вызова ‘async_connect’”

Решение: Удалите оператор разыменования (*) из results:

cpp
// Неправильно
auto connect_fut = beast::get_lowest_layer(ws_).async_connect(*results, net::use_future);

// Правильно
auto connect_fut = beast::get_lowest_layer(ws_).async_connect(results, net::use_future);

Проблема: Структурированное связывание не работает

Решение: Убедитесь, что ваш компилятор поддерживает структурированное связывание C++17 (большинство современных компиляторов поддерживают).

Проблема: Утечки памяти с futures

Решение: Убедитесь, что вы перемещаете (move) futures, а не копируете их:

cpp
// Хорошо
[resolve_fut = std::move(resolve_fut)]

// Плохо (может вызвать проблемы)
[resolve_fut]

Проблема: Обработка нескольких конечных точек

Если вы хотите обрабатывать несколько разрешенных конечных точек (попробовать следующую, если текущая не удалась):

cpp
auto [err, results] = resolve_fut.get();
if (err) {
    throw boost::system::system_error(err);
}

for (auto const& endpoint : results) {
    try {
        auto connect_fut = beast::get_lowest_layer(ws_).async_connect(endpoint, net::use_future);
        connect_fut.get();
        break; // Успех, выходим из цикла
    } catch (const std::exception&) {
        continue; // Пробуем следующую конечную точку
    }
}

Эти изменения должны решить вашу ошибку компиляции и сделать ваш код совместимым с Boost 1.89, сохраняя ту же функциональность.

Авторы
Проверено модерацией
Модерация