You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Boost Beast与协程的HTTP客户端问题:co_await响应异常

Boost Beast + C++20协程异步HTTP客户端问题解答

首次尝试基于Boost Beast和C++20协程实现异步HTTP客户端,编译通过但调试发现未进入task_main_GET或do_session协程。参考Stack Overflow示例并遵循@sehe建议未使用自定义协程,但对Asio用法不熟悉,同时有以下疑问:

  • 如何通过executor调用co_spawn?目前仅能传入io_context使其运行,但这并非推荐用法;
  • 不理解阻塞式run()函数的意义,希望实现可通过co_await获取响应的异步HTTP客户端,但run()是阻塞的该如何处理?希望应用逻辑无需回调即可链式调用。

原代码

#include "openssl/conf.h"

#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/experimental/promise.hpp>
#include <boost/asio/experimental/use_promise.hpp>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/detached.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <string>

namespace this_coro = boost::asio::this_coro;

#include <boost/certify/extensions.hpp>
#include <boost/certify/https_verification.hpp>

#include <syncstream>

namespace beast = boost::beast;
namespace http = beast::http;
namespace net = boost::asio;
namespace ssl = boost::asio::ssl;
using tcp = boost::asio::ip::tcp;
using boost::asio::use_awaitable;

//------------------------------------------------------------------------------

#define use_void
#ifdef use_void
using T = void;
#else
using T = std::string;
#endif

boost::asio::awaitable<T>
do_session(
    std::string host,
    std::string port,
    std::string target,
    ssl::context& ctx)
{
    beast::error_code ec;
    auto ex = co_await net::this_coro::executor;

    tcp::resolver resolver(ex);
    beast::ssl_stream<beast::tcp_stream> stream(ex, ctx);

    if(! SSL_set_tlsext_host_name(stream.native_handle(), host.c_str()))
    {
        ec.assign(static_cast<int>(::ERR_get_error()), net::error::get_ssl_category());
        std::cerr << ec.message() << "\n";
        co_return;
    }

    try{    
        auto const results = co_await resolver.async_resolve(host, port, use_awaitable);
        beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(30));
        co_await beast::get_lowest_layer(stream).async_connect(results, use_awaitable);
        beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(30));
        co_await stream.async_handshake(ssl::stream_base::client, use_awaitable);

        http::request<http::string_body> req{http::verb::get, target, 11};
        req.set(http::field::host, host);
        req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);

        beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(30));
        co_await http::async_write(stream, req, use_awaitable);

        beast::flat_buffer b;
        http::response<http::dynamic_body> res;

        auto [ec, bytes] =
            co_await http::async_read(stream, b, res, boost::asio::as_tuple(use_awaitable));

        std::cout << res << std::endl;

        beast::get_lowest_layer(stream).expires_after(std::chrono::seconds(30));

        if(ec == net::error::eof)
        {
            if(ec != net::error::eof)
                throw beast::system_error(ec);
        }
        else
        {
            std::cout << res << std::endl;
            co_await stream.async_shutdown(use_awaitable);
        }

    }
    catch(beast::system_error const& se)
    {
        throw;
    }
}

//------------------------------------------------------------------------------

boost::asio::awaitable<T> task_main_GET(net::io_context &ioc)
{
    auto const host = "https://microsoftedge.github.io/Demos/json-dummy-data/64KB.json";
    auto const port = "443";
    auto const target = "/";

    std::osyncstream(std::cout) << "GET " << host << ":" << port << target << std::endl;
    ssl::context ctx{ssl::context::tlsv12_client};

    ctx.set_verify_mode(ssl::context::verify_peer );
    boost::certify::enable_native_https_server_verification(ctx);

    auto ex = this_coro::executor;
    auto task = boost::asio::co_spawn(
        ioc,
        [&]() mutable -> boost::asio::awaitable<std::string>
        {
            co_await do_session(host, port, target, ctx);
        },
        use_awaitable
    );

#ifdef use_void
    co_return;
#else
    std::string responseText = co_await task;
    std::osyncstream(std::cout) << "response:\n" << responseText << std::endl;
    co_return responseText ;
#endif
}

int main()
{
    net::io_context ioc;
    auto task = boost::asio::co_spawn(ioc, task_main_GET(ioc), use_awaitable);
    ioc.run();
    return EXIT_SUCCESS;
}

原CMakeLists.txt

cmake_minimum_required(VERSION 3.5)

project(boost_beast_example LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

if (MSVC)
    add_compile_options(/bigobj)
else ()
    add_compile_options(-Wa,-mbig-obj)
endif ()

set(OPENSSL_ROOT_DIR "C:/OpenSSL-Win64")
set(OPENSSL_INCLUDE_DIR "C:/OpenSSL-Win64/include")
find_package(OpenSSL REQUIRED)
link_directories("C:/OpenSSL-Win64/")
include_directories(${OPENSSL_INCLUDE_DIR})
message("OPENSSL_FOUND: " ${OPENSSL_FOUND})
message("OPENSSL_INCLUDE_DIR: " ${OPENSSL_INCLUDE_DIR})
message("OPENSSL_CRYPTO_LIBRARY: " ${OPENSSL_CRYPTO_LIBRARY})
message("OPENSSL_SSL_LIBRARY: " ${OPENSSL_SSL_LIBRARY})

set(Boost_DEBUG 1)
SET(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} "G:/SoftwareDev/libs/boost")
SET(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} "G:/SoftwareDev/libs/boost/bin/x64/lib/cmake")
find_package(Boost REQUIRED context system coroutine regex PATHS "G:/SoftwareDev/libs/boost/bin/x64/lib/cmake")
include_directories("G:/SoftwareDev/libs/boost/libs/beast")
include_directories(${Boost_INCLUDE_DIRS})
message("Boost_FOUND: " ${Boost_FOUND})
message("Boost_INCLUDE_DIRS: " ${Boost_INCLUDE_DIRS})
message("Boost_LIBRARY_DIRS: " ${Boost_LIBRARY_DIRS})
message("Boost_LIBRARIES: " ${Boost_LIBRARIES})
message("Boost_CONTEXT_LIBRARY: " ${Boost_CONTEXT_LIBRARY})
message("Boost_SYSTEM_LIBRARY: " ${Boost_SYSTEM_LIBRARY})

find_package(Threads REQUIRED)

add_executable(${PROJECT_NAME} main.cpp)

include_directories(libs/certify/include)
if(MSVC)
    target_link_libraries(${PROJECT_NAME} Crypt32.lib)
endif()
if(APPLE)
    target_link_libraries(${PROJECT_NAME} INTERFACE "-framework CoreFoundation" "-framework Security")
    set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "-Wl,-F/Library/Frameworks")
endif()

target_link_libraries(${PROJECT_NAME}  OpenSSL::Crypto OpenSSL::SSL)
target_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES} Threads::Threads)

问题分析与修复

1. 协程未执行的核心原因

  • host参数错误:传入了完整URL(https://microsoftedge.github.io/...),但tcp::resolver仅需纯域名(如microsoftedge.github.io),错误参数会导致解析失败并抛出异常,协程提前终止。
  • co_spawn调度方式错误:main中co_spawn使用use_awaitable但未对返回的awaitable执行co_await,导致协程未被调度执行,应改用detached直接将协程加入调度队列。

2. 疑问解答

问题1:通过executor调用co_spawn

从当前协程中获取executor(co_await this_coro::executor),并确保捕获变量的生命周期安全(避免悬空引用)。示例修改:

boost::asio::awaitable<T> task_main_GET(net::io_context& ioc)
{
    auto const host = "microsoftedge.github.io";
    auto const port = "443";
    auto const target = "/Demos/json-dummy-data/64KB.json";

    std::osyncstream(std::cout) << "GET " << host << ":" << port << target << std::endl;

    ssl::context ctx{ssl::context::tlsv12_client};
    ctx.set_verify_mode(ssl::context::verify_peer);
    boost::certify::enable_native_https_server_verification(ctx);

    // 获取当前协程的executor
    auto ex = co_await this_coro::executor;

    // 使用executor启动协程,通过move传递不可拷贝的ssl::context
    auto task = boost::asio::co_spawn(
        ex,
        [host, port, target, ctx = std::move(ctx)]() mutable -> boost::asio::awaitable<T>
        {
            co_await do_session(host, port, target, ctx);
        },
        use_awaitable
    );

#ifdef use_void
    co_return;
#else
    std::string responseText = co_await task;
    std::osyncstream(std::cout) << "response:\n" << responseText << std::endl;
    co_return responseText;
#endif
}

问题2:处理run()阻塞的问题

io_context::run()是Asio处理异步事件的核心,必须运行才能触发异步操作的完成回调。若希望应用逻辑无需显式管理run(),可采用两种方案:

方案A:后台线程运行io_context

将run()放在后台线程,协程完成后停止io_context并等待线程结束:

int main()
{
    net::io_context ioc;

    // 用detached调度协程
    boost::asio::co_spawn(ioc, task_main_GET(ioc), boost::asio::detached);

    // 后台线程处理异步事件
    std::thread io_thread([&ioc]() { ioc.run(); });

    // 执行其他业务逻辑...

    // 协程完成后停止io_context并等待线程退出
    ioc.stop();
    io_thread.join();

    return EXIT_SUCCESS;
}
方案B:使用thread_pool自动管理线程

asio::thread_pool内部管理线程池,无需手动创建线程,join()会等待所有任务完成:

int main()
{
    net::thread_pool pool;

    // 传入thread_pool的executor启动协程
    boost::asio::co_spawn(pool, task_main_GET(pool.get_executor()), boost::asio::detached);

    // 等待所有异步任务完成
    pool.join();

    return EXIT_SUCCESS;
}

内容的提问来源于stack exchange,提问作者user2366975

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 13:42:34