关于Boost.Asio实验性promise的组合与异步连接握手函数实现的技术咨询
Hey there! Let's tackle your question about implementing asyncConnectAndHandshake with Boost.Asio's experimental promises. First, let's break down what you need: a function that chains TCP connect and (optional) SSL handshake, returning a promise that works seamlessly with both co_await and regular callback-based usage.
Do you need async_compose?
Short answer: Probably not. async_compose is great for building custom async operations that follow Asio's initiation pattern, but since you're already using experimental promises and have Boost.Cobalt included, coroutine chaining is a much cleaner approach. It lets you write linear, readable code instead of dealing with nested callbacks or manual state management.
Proper Implementation with Coroutines
Here's an adjusted version of your SocketWrapper class that meets all your requirements. I've added necessary member management, error handling, and ensured the returned promise supports both coroutine and callback usage:
#pragma once #include <boost/asio.hpp> #include <boost/asio/ssl.hpp> #include <boost/cobalt.hpp> #include <boost/asio/experimental/promise.hpp> #include <boost/asio/experimental/use_promise.hpp> #include <optional> #include <cassert> class SocketWrapper { private: using SslStream = boost::asio::ssl::stream<boost::asio::ip::tcp::socket>; // Core members (initialized via constructor) boost::asio::ip::tcp::socket tcp_socket_; std::optional<SslStream> ssl_stream_; // Only initialized if SSL is enabled bool use_ssl_; boost::asio::ssl::context ssl_ctx_; // Reusable SSL context public: // Constructor to set up executor, SSL flag, and context SocketWrapper(boost::asio::any_io_executor ex, bool use_ssl) : tcp_socket_(std::move(ex)), use_ssl_(use_ssl), ssl_ctx_(boost::asio::ssl::context::tls_client) { if (use_ssl_) { ssl_stream_.emplace(std::move(tcp_socket_), std::move(ssl_ctx_)); } } // The async function with the required return type boost::asio::experimental::promise<void(boost::system::error_code), boost::asio::any_io_executor, std::allocator<void>> asyncConnectAndHandshake(const boost::asio::ip::basic_resolver_results<boost::asio::ip::tcp>& resolver_results) { namespace asio = boost::asio; namespace cobalt = boost::cobalt; // Wrap the entire async sequence in a coroutine task return cobalt::make_promise([this, resolver_results]() -> cobalt::task<void(boost::system::error_code)> { boost::system::error_code ec; try { // Step 1: Perform TCP connect if (use_ssl_) { co_await asio::async_connect(ssl_stream_->next_layer(), resolver_results, asio::experimental::use_promise); } else { co_await asio::async_connect(tcp_socket_, resolver_results, asio::experimental::use_promise); } // Step 2: Optional SSL handshake if (use_ssl_) { co_await ssl_stream_->async_handshake(asio::ssl::stream_base::client, asio::experimental::use_promise); } // Return success (empty error code) co_return ec; } catch (const boost::system::system_error& e) { // Capture and return any errors encountered ec = e.code(); co_return ec; } }).as_experimental_promise(); } private: // Helper methods to access underlying sockets/stream bool isSSL() const { return use_ssl_; } boost::asio::ip::tcp::socket& underlyingSocket() { return use_ssl_ ? ssl_stream_->next_layer() : tcp_socket_; } const boost::asio::ip::tcp::socket& underlyingSocket() const { return use_ssl_ ? ssl_stream_->next_layer() : tcp_socket_; } SslStream& stream() { assert(use_ssl_ && "SSL stream not initialized!"); return *ssl_stream_; } const SslStream& stream() const { assert(use_ssl_ && "SSL stream not initialized!"); return *ssl_stream_; } };
Key Details Explained
- Coroutine Chaining: Using Boost.Cobalt's
tasklets us chain async operations withco_await, making the code flow like synchronous code while remaining fully asynchronous. - Promise Conversion:
cobalt::make_promisecreates a task that we convert to Asio's experimental promise withas_experimental_promise(), which supports bothco_awaitand callback invocation viaoperator(). - Error Handling: The try-catch block captures any system errors from async operations and returns the corresponding error code, aligning with the promise's signature.
- SSL Stream Management:
std::optional<SslStream>ensures we only initialize the SSL stream when needed, avoiding unnecessary overhead for non-SSL connections.
Usage Examples
Here's how you can use the function in both coroutine and callback contexts:
// Usage in a coroutine boost::cobalt::task<void> coroutine_example(SocketWrapper& wrapper, auto resolver_results) { boost::system::error_code ec = co_await wrapper.asyncConnectAndHandshake(resolver_results); if (ec) { std::cerr << "Connection failed: " << ec.message() << "\n"; } else { std::cout << "Connected and handshaked successfully!\n"; } } // Usage with a callback void callback_example(SocketWrapper& wrapper, auto resolver_results) { wrapper.asyncConnectAndHandshake(resolver_results)([](boost::system::error_code ec) { if (ec) { std::cerr << "Connection failed: " << ec.message() << "\n"; } else { std::cout << "Connected and handshaked successfully!\n"; } }); }
Final Takeaway
You don't need async_compose here—coroutines are the simpler, more maintainable choice for chaining these async operations. The implementation above gives you a single promise that handles the entire connect-handshake sequence, works with both co_await and callbacks, and properly manages errors and resources.




