You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何用C++ Promise/Future将同步Winsock2代码转为异步?

在C++中用Promise/Future封装Winsock2的同步阻塞代码

好问题!其实思路和你在JavaScript里写Promise是异曲同工的——我们需要把阻塞的sendtorecvfrom放到后台线程里,用C++标准库的std::promisestd::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

火山引擎 最新活动