如何用C++ Promise/Future将同步Winsock2代码转为异步?
在C++中用Promise/Future封装Winsock2的同步阻塞代码
好问题!其实思路和你在JavaScript里写Promise是异曲同工的——我们需要把阻塞的sendto和recvfrom放到后台线程里,用C++标准库的std::promise和std::future来传递最终的结果或错误,调用者可以通过future异步获取结果,不用一直阻塞主线程。下面给你几种实用的实现方式:
1. 手动用std::promise+线程实现(最贴近JS Promise逻辑)
这种方式和你JS里手动创建Promise的逻辑完全对应:后台线程执行阻塞操作,完成后「resolve」结果,出错则「reject」异常。
代码实现
#include <future> #include <thread> #include <vector> #include <stdexcept> #include <winsock2.h> #include <string> // 封装异步请求函数,返回std::future<std::vector<char>>供调用者获取结果 std::future<std::vector<char>> sendRequest(SOCKET sock, const char* send_buf, int send_len, sockaddr_in& dest_addr) { // 创建promise对象,它会和future绑定,用来传递结果/异常 std::promise<std::vector<char>> request_promise; // 获取对应的future,返回给调用者 std::future<std::vector<char>> result_future = request_promise.get_future(); // 启动后台线程执行阻塞IO操作 std::thread( [prom = std::move(request_promise), sock, send_buf, send_len, dest_addr]() mutable { try { // 发送请求 int send_result = sendto( sock, send_buf, send_len, 0, reinterpret_cast<sockaddr*>(&dest_addr), sizeof(dest_addr) ); if (send_result == SOCKET_ERROR) { throw std::runtime_error( "sendto failed with error: " + std::to_string(WSAGetLastError()) ); } // 接收响应(这里预设缓冲区大小为1024,你可以根据需求调整) std::vector<char> recv_buffer(1024); int recv_result = recvfrom( sock, recv_buffer.data(), recv_buffer.size(), 0, nullptr, nullptr ); if (recv_result == SOCKET_ERROR) { throw std::runtime_error( "recvfrom failed with error: " + std::to_string(WSAGetLastError()) ); } // 调整缓冲区到实际接收的长度 recv_buffer.resize(recv_result); // 对应JS里的res(myBuffer):把结果传递给promise prom.set_value(std::move(recv_buffer)); } catch (...) { // 对应JS里的rej(error):把异常传递给promise prom.set_exception(std::current_exception()); } } ).detach(); // 分离线程,让它在后台独立运行 return result_future; }
使用示例
int main() { // 初始化Winsock的代码省略(记得调用WSAStartup) SOCKET udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (udp_sock == INVALID_SOCKET) { // 处理socket创建错误 return 1; } // 设置目标服务器地址(示例) sockaddr_in dest_addr{}; dest_addr.sin_family = AF_INET; dest_addr.sin_port = htons(12345); inet_pton(AF_INET, "127.0.0.1", &dest_addr.sin_addr); // 发起异步请求,立即返回future,不会阻塞 std::future<std::vector<char>> response_future = sendRequest(udp_sock, "Hello Server!", 13, dest_addr); // --- 这里可以做其他非阻塞的工作 --- // 当需要结果时,调用get():如果线程已完成则立即返回,否则阻塞等待 try { std::vector<char> response = response_future.get(); // 处理接收到的响应数据 printf("Received response: %.*s\n", static_cast<int>(response.size()), response.data()); } catch (const std::exception& e) { // 处理错误 printf("Request failed: %s\n", e.what()); } // 清理资源(关闭socket、调用WSACleanup) closesocket(udp_sock); WSACleanup(); return 0; }
2. 用std::async简化实现
如果不想手动管理线程,可以用C++11引入的std::async,它会自动处理线程的创建和管理,代码更简洁:
#include <future> #include <vector> #include <stdexcept> #include <winsock2.h> #include <string> std::future<std::vector<char>> sendRequest(SOCKET sock, const char* send_buf, int send_len, sockaddr_in& dest_addr) { // 用std::launch::async确保在新线程执行(默认策略可能延迟执行) return std::async(std::launch::async, [sock, send_buf, send_len, dest_addr]() { std::vector<char> recv_buffer(1024); // 发送请求 int send_result = sendto( sock, send_buf, send_len, 0, reinterpret_cast<sockaddr*>(&dest_addr), sizeof(dest_addr) ); if (send_result == SOCKET_ERROR) { throw std::runtime_error( "sendto failed with error: " + std::to_string(WSAGetLastError()) ); } // 接收响应 int recv_result = recvfrom( sock, recv_buffer.data(), recv_buffer.size(), 0, nullptr, nullptr ); if (recv_result == SOCKET_ERROR) { throw std::runtime_error( "recvfrom failed with error: " + std::to_string(WSAGetLastError()) ); } recv_buffer.resize(recv_result); return recv_buffer; }); }
使用方式和上面完全一样,std::async会帮我们创建线程并返回对应的std::future。
3. C++20+ 协程实现(更贴近JS异步函数写法)
如果你的项目用C++20及以上,可以用协程来实现更优雅的异步逻辑,写法和JS的async/await几乎一致,不过需要自定义协程的promise类型,这里给个简单示例:
#include <coroutine> #include <future> #include <vector> #include <stdexcept> #include <winsock2.h> #include <string> // 自定义协程返回类型 struct AsyncResponse { struct promise_type { std::vector<char> result; std::exception_ptr exception; AsyncResponse get_return_object() { return {std::coroutine_handle<promise_type>::from_promise(*this)}; } std::suspend_never initial_suspend() noexcept { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void return_value(std::vector<char> val) { result = std::move(val); } void unhandled_exception() { exception = std::current_exception(); } }; std::coroutine_handle<promise_type> handle; ~AsyncResponse() { handle.destroy(); } // 模拟await逻辑 bool await_ready() noexcept { return false; } void await_suspend(std::coroutine_handle<> caller) { // 在后台线程执行协程 std::thread([this, caller]() { handle.resume(); caller.resume(); }).detach(); } std::vector<char> await_resume() { if (handle.promise().exception) { std::rethrow_exception(handle.promise().exception); } return std::move(handle.promise().result); } }; AsyncResponse sendRequest(SOCKET sock, const char* send_buf, int send_len, sockaddr_in& dest_addr) { std::vector<char> recv_buffer(1024); int send_result = sendto( sock, send_buf, send_len, 0, reinterpret_cast<sockaddr*>(&dest_addr), sizeof(dest_addr) ); if (send_result == SOCKET_ERROR) { throw std::runtime_error( "sendto failed with error: " + std::to_string(WSAGetLastError()) ); } int recv_result = recvfrom( sock, recv_buffer.data(), recv_buffer.size(), 0, nullptr, nullptr ); if (recv_result == SOCKET_ERROR) { throw std::runtime_error( "recvfrom failed with error: " + std::to_string(WSAGetLastError()) ); } recv_buffer.resize(recv_result); co_return recv_buffer; } // 使用示例(需要在协程函数中调用) AsyncResponse mainCoroutine() { // 初始化socket等省略... SOCKET udp_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); sockaddr_in dest_addr{}; dest_addr.sin_family = AF_INET; dest_addr.sin_port = htons(12345); inet_pton(AF_INET, "127.0.0.1", &dest_addr.sin_addr); // 用await等待结果,和JS的await一致 std::vector<char> response = co_await sendRequest(udp_sock, "Hello Server!", 13, dest_addr); printf("Received response: %.*s\n", static_cast<int>(response.size()), response.data()); closesocket(udp_sock); co_return {}; }
这种方式写出来的代码和JS异步函数几乎一样,不过协程是C++20的新特性,需要编译器支持(比如GCC 10+/Clang 14+/MSVC 2019+)。
内容的提问来源于stack exchange,提问作者Ben




