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

如何用C++ Asio实现HTTP/HTTPS通用Socket对象(抽象接口方案)

如何用Asio统一HTTP/HTTPS的Socket操作,避免重复代码

当然可以解决!你想要的这种统一抽象在Asio里完全能实现,我给你分享两种最实用的方案,帮你简化代码,不用再重复写asio::write这类逻辑。


方案一:基于抽象基类的多态实现

通过定义一个抽象基类,让TCP Socket和SSL Socket都实现这个接口,这样就能用基类指针/引用统一操作所有Socket类型,完美契合你想要的“一个对象搞定所有”的需求。

步骤1:定义抽象基类

先写一个包含核心操作的抽象基类,把你需要统一的write、connect等方法都声明为纯虚函数:

#include <asio.hpp>
#include <asio/ssl.hpp>
#include <memory>

class AbstractSocket {
public:
    virtual ~AbstractSocket() = default;

    // 统一的write接口(支持error_code和异常两种错误处理方式)
    virtual size_t write(const asio::const_buffer& buffer, asio::error_code& ec) = 0;
    virtual size_t write(const asio::const_buffer& buffer) = 0;

    // 统一的connect接口
    virtual void connect(const asio::ip::tcp::resolver::results_type& endpoints, asio::error_code& ec) = 0;
    virtual void connect(const asio::ip::tcp::resolver::results_type& endpoints) = 0;
};

步骤2:实现TCP Socket子类

让普通TCP Socket继承抽象基类,实现所有纯虚函数:

class TcpSocket : public AbstractSocket {
public:
    explicit TcpSocket(asio::io_context& io_context) : socket_(io_context) {}

    size_t write(const asio::const_buffer& buffer, asio::error_code& ec) override {
        return asio::write(socket_, buffer, ec);
    }

    size_t write(const asio::const_buffer& buffer) override {
        return asio::write(socket_, buffer);
    }

    void connect(const asio::ip::tcp::resolver::results_type& endpoints, asio::error_code& ec) override {
        asio::connect(socket_, endpoints, ec);
    }

    void connect(const asio::ip::tcp::resolver::results_type& endpoints) override {
        asio::connect(socket_, endpoints);
    }

    // 暴露底层socket(如果需要额外操作的话)
    asio::ip::tcp::socket& get_socket() { return socket_; }

private:
    asio::ip::tcp::socket socket_;
};

步骤3:实现SSL Socket子类

同样地,让SSL Stream继承抽象基类,注意SSL连接需要先连接底层TCP Socket,再执行握手:

class SslSocket : public AbstractSocket {
public:
    SslSocket(asio::io_context& io_context, asio::ssl::context& ssl_ctx) : socket_(io_context, ssl_ctx) {}

    size_t write(const asio::const_buffer& buffer, asio::error_code& ec) override {
        return asio::write(socket_, buffer, ec);
    }

    size_t write(const asio::const_buffer& buffer) override {
        return asio::write(socket_, buffer);
    }

    void connect(const asio::ip::tcp::resolver::results_type& endpoints, asio::error_code& ec) override {
        asio::connect(socket_.lowest_layer(), endpoints, ec);
        if (!ec) {
            socket_.handshake(asio::ssl::stream_base::client, ec);
        }
    }

    void connect(const asio::ip::tcp::resolver::results_type& endpoints) override {
        asio::error_code ec;
        connect(endpoints, ec);
        asio::detail::throw_error(ec, "connect");
    }

    // 暴露底层SSL stream(如果需要额外操作的话)
    asio::ssl::stream<asio::ip::tcp::socket>& get_socket() { return socket_; }

private:
    asio::ssl::stream<asio::ip::tcp::socket> socket_;
};

步骤4:统一使用Socket

现在你就可以像最开始期望的那样,用一个基类指针统一操作了:

int main() {
    asio::io_context io_context;
    asio::ssl::context ssl_ctx(asio::ssl::context::tls_client);
    
    // 根据参数判断是否使用HTTPS
    bool bIsHttps = true;
    std::unique_ptr<AbstractSocket> my_socket;

    if (bIsHttps) {
        my_socket = std::make_unique<SslSocket>(io_context, ssl_ctx);
    } else {
        my_socket = std::make_unique<TcpSocket>(io_context);
    }

    // 解析目标地址
    asio::ip::tcp::resolver resolver(io_context);
    auto endpoints = resolver.resolve("127.0.0.1", bIsHttps ? "443" : "80");

    // 统一连接
    my_socket->connect(endpoints);

    // 构造请求并统一写入
    std::string request = "GET /myservice HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n";
    my_socket->write(asio::buffer(request));

    // 后续的read等操作也可以统一处理,只要在基类中添加对应接口即可
    return 0;
}

方案二:使用std::variant(更轻量,无需继承)

如果你不想写继承体系,可以用C++17的std::variant来存储不同类型的Socket,配合std::visit统一执行操作,代码更简洁。

步骤1:定义Variant类型

先定义一个能容纳TCP Socket和SSL Stream的variant:

#include <asio.hpp>
#include <asio/ssl.hpp>
#include <variant>

using SocketVariant = std::variant<asio::ip::tcp::socket, asio::ssl::stream<asio::ip::tcp::socket>>;

步骤2:定义统一操作的访问器

用lambda或者函数对象作为访问器,处理不同Socket类型的操作:

// 统一处理write操作的访问器
struct WriteHandler {
    template <typename Socket>
    size_t operator()(Socket& socket, const asio::const_buffer& buffer) const {
        return asio::write(socket, buffer);
    }
};

// 统一处理connect操作的访问器(SSL需要额外握手)
struct ConnectHandler {
    const asio::ip::tcp::resolver::results_type& endpoints;

    void operator()(asio::ip::tcp::socket& socket) const {
        asio::connect(socket, endpoints);
    }

    void operator()(asio::ssl::stream<asio::ip::tcp::socket>& socket) const {
        asio::connect(socket.lowest_layer(), endpoints);
        socket.handshake(asio::ssl::stream_base::client);
    }
};

步骤3:统一使用Socket

现在可以直接用variant存储Socket,通过std::visit执行统一操作:

int main() {
    asio::io_context io_context;
    asio::ssl::context ssl_ctx(asio::ssl::context::tls_client);
    bool bIsHttps = true;

    SocketVariant my_socket;
    if (bIsHttps) {
        my_socket = asio::ssl::stream<asio::ip::tcp::socket>(io_context, ssl_ctx);
    } else {
        my_socket = asio::ip::tcp::socket(io_context);
    }

    // 解析地址
    asio::ip::tcp::resolver resolver(io_context);
    auto endpoints = resolver.resolve("127.0.0.1", bIsHttps ? "443" : "80");

    // 统一连接
    std::visit(ConnectHandler{endpoints}, my_socket);

    // 构造请求并统一写入
    std::string request = "GET /myservice HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n";
    std::visit(WriteHandler{}, my_socket, asio::buffer(request));

    // 或者用lambda直接处理,更灵活
    // std::visit([&](auto& socket) {
    //     asio::write(socket, asio::buffer(request));
    // }, my_socket);

    return 0;
}

注意事项

  • SSL上下文生命周期:无论用哪种方案,SSL上下文(asio::ssl::context)都要确保在Socket的生命周期内有效,避免悬空引用。
  • 错误处理:上面的示例用了抛出异常的方式,你可以根据需求改成error_code版本,更灵活地处理错误。
  • 扩展性:如果后续需要支持其他Socket类型(比如UDP),多态方案更容易扩展;variant方案则需要修改variant类型和访问器。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 09:05:54