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

基于Boost ASIO:同类同io_context下同步异步TCP操作实现问询

问题

我希望使用Boost ASIO编写一个TCPClient类,实现TCP连接创建、数据收发功能,支持同步(带超时阻塞)和异步两种调用方式,且用户无需接触任何ASIO相关内容(包括创建io_context对象)。

当前类的大致实现如下(不含收发方法):

class TCPClient {
public:
    TCPClient() {
        deadline_.expires_from_now(std::chrono::duration<int>::max());
        check_deadline();
    }

    // --- Callbacks ---
    using ConnectCallback = std::function<void()>;

    // Synchronous and asynchronous
    auto connect(const std::string& host, int port, int timeout=0) -> void;
    auto connect_async(const std::string& host, int port, ConnectCallback callback) -> void;

    auto close() -> void {
        if(socket_) {
            boost::system::error_code ignored_ec;
            socket_->close(ignored_ec);
        }
    }

private:

    /**
     * Adapted from Boost ASIO timeout example
     * Replaced deadline_timer with steady_timer to make it compatible with std::chrono
    */
    auto check_deadline() -> void {
        // Close socket if the timer has expired
        if(deadline_.expires_at() <= std::chrono::steady_clock::now()) {
            close();
            deadline_.expires_from_now(std::chrono::duration<int>::max());
        }

        deadline_.async_wait( [this](const auto& ec) {
            check_deadline();
        });
    }

    auto socket() -> tcp::socket& {
        // Create socket if it doesn't exist
        if(!socket_) {
            socket_ = std::make_unique<tcp::socket>(io_);
        }

        // Open interface 
        if(!socket_->is_open()) {
            socket_->open(tcp::v4());
        }
        return *socket_;
    }

    boost::asio::io_context io_;
    std::unique_ptr<tcp::socket> socket_;
    boost::asio::steady_timer deadline_;
};

借助ASIO的deadline机制,我已实现了同步阻塞的connect方法:

auto TCPClient::connect(const std::string& host, int port, int timeout) -> void {
    // Resolve remote endpoint
    auto query = tcp::resolver::query(host, std::to_string(port));
    auto iter = tcp::resolver(io_).resolve(query);

    // setup deadline
    if(timeout) deadline_.expires_from_now(std::chrono::seconds(timeout));

    // start asynchronous connection
    boost::system::error_code ec = net::error::would_block;     // Never a valid error code if async operation fails
    socket().async_connect( iter->endpoint(), [&ec](const auto& e) {
        ec = e;
    });

    do {
        io_.run_one();
    } while(ec == net::error::would_block);

    if(ec || !socket().is_open()) {
        throw SocketException( get_error_message(ec) );
    }
}

可以看到,同步方法底层使用异步操作,通过反复调用io_context.run_one()等待完成。现咨询:实现connect_async方法的最佳方案是什么?我曾考虑每次调用异步操作时启动线程执行io_context.run(),但未成功且担心同步异步方法混用会引发问题;是否需要拆分阻塞/非阻塞操作的类?由于所有通信需共用同一个socket,无法使用独立的io_context对象。

解决方案

核心思路:用后台线程持续托管io_context

你的同步方法依赖run_one()手动驱动io_context,而异步操作需要io_context持续运行来处理事件回调。最佳方案是在TCPClient构造时启动一个后台线程,专门负责运行io_context.run(),这样同步和异步操作能共享同一个io_context,无需重复启停线程,也不会出现混用冲突。

具体实现步骤

  1. 添加线程成员与生命周期管理
    在类中添加后台线程变量,构造时启动线程运行io_.run(),析构时停止io_context并等待线程退出,避免资源泄漏。

  2. 实现connect_async方法
    直接发起异步连接操作,在回调中处理连接结果(包括错误),无需手动管理io_context的运行。同时复用已有的deadline机制,支持异步连接的超时控制。

  3. 保证操作的串行性
    由于Boost ASIO的socket对象本身不是线程安全的,需确保同一时间只有一个连接/收发操作在执行。可以通过互斥锁保护socket的访问,避免并发调用同步/异步方法导致的未定义行为。

修改后的完整代码示例

#include <boost/asio.hpp>
#include <functional>
#include <memory>
#include <thread>
#include <mutex>
#include <stdexcept>

using namespace boost::asio;
using tcp = boost::asio::ip::tcp;

class SocketException : public std::runtime_error {
public:
    using std::runtime_error::runtime_error;
};

std::string get_error_message(const boost::system::error_code& ec) {
    return ec.message();
}

class TCPClient {
public:
    // 优化回调,携带错误信息方便用户处理失败场景
    using ConnectCallback = std::function<void(const boost::system::error_code&)>;

    TCPClient() 
        : deadline_(io_) {
        deadline_.expires_from_now(std::chrono::duration<int>::max());
        check_deadline();

        // 启动后台线程托管io_context
        work_ = std::make_unique<io_context::work>(io_);
        thread_ = std::thread([this]() {
            io_.run();
        });
    }

    ~TCPClient() {
        // 停止io_context并等待线程退出
        work_.reset(); // 让io_context的run()在所有操作完成后退出
        io_.stop();
        if(thread_.joinable()) {
            thread_.join();
        }
        close();
    }

    // 同步连接(带超时)
    auto connect(const std::string& host, int port, int timeout=0) -> void {
        std::lock_guard<std::mutex> lock(socket_mutex_);
        close(); // 先关闭已有连接

        tcp::resolver resolver(io_);
        auto endpoints = resolver.resolve(host, std::to_string(port));

        // 设置超时
        if(timeout > 0) {
            deadline_.expires_from_now(std::chrono::seconds(timeout));
        } else {
            deadline_.expires_from_now(std::chrono::duration<int>::max());
        }

        boost::system::error_code ec = boost::asio::error::would_block;
        socket().async_connect(endpoints, [&ec](const auto& e) {
            ec = e;
        });

        do {
            io_.run_one();
        } while(ec == boost::asio::error::would_block);

        if(ec || !socket().is_open()) {
            close();
            throw SocketException(get_error_message(ec));
        }
    }

    // 异步连接
    auto connect_async(const std::string& host, int port, ConnectCallback callback) -> void {
        std::lock_guard<std::mutex> lock(socket_mutex_);
        close(); // 先关闭已有连接

        tcp::resolver resolver(io_);
        resolver.async_resolve(host, std::to_string(port), 
            [this, callback=std::move(callback)](const boost::system::error_code& ec, tcp::resolver::results_type endpoints) {
                if(ec) {
                    callback(ec);
                    return;
                }

                // 发起异步连接
                socket().async_connect(endpoints, 
                    [this, callback=std::move(callback)](const boost::system::error_code& ec) {
                        if(ec || !socket().is_open()) {
                            close();
                        }
                        callback(ec);
                    });
            });
    }

    auto close() -> void {
        std::lock_guard<std::mutex> lock(socket_mutex_);
        if(socket_) {
            boost::system::error_code ignored_ec;
            socket_->close(ignored_ec);
            socket_.reset();
        }
    }

private:
    auto check_deadline() -> void {
        if(deadline_.expires_at() <= std::chrono::steady_clock::now()) {
            close();
            deadline_.expires_from_now(std::chrono::duration<int>::max());
        }

        deadline_.async_wait([this](const auto& ec) {
            if(!ec) { // 忽略取消错误
                check_deadline();
            }
        });
    }

    auto socket() -> tcp::socket& {
        if(!socket_) {
            socket_ = std::make_unique<tcp::socket>(io_);
        }
        if(!socket_->is_open()) {
            socket_->open(tcp::v4());
        }
        return *socket_;
    }

    io_context io_;
    std::unique_ptr<io_context::work> work_; // 防止io_context.run()无任务时退出
    std::thread thread_;
    std::unique_ptr<tcp::socket> socket_;
    steady_timer deadline_;
    std::mutex socket_mutex_; // 保护socket的并发访问
};

关键说明

  • io_context托管:通过io_context::work保证后台线程不会因为暂时没有异步任务而退出,确保随时能处理新的异步操作。
  • 同步异步兼容性:同步方法依然用run_one()驱动,而异步方法的回调会由后台线程自动处理,两者共享同一个io_context,不会冲突。
  • 线程安全:用std::mutex保护socket的所有操作,避免并发调用同步/异步方法导致的未定义行为。
  • 无需拆分类:通过上述设计,同一个TCPClient类可以同时支持同步和异步操作,满足共用socket的需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 16:16:02