使用独立 Asio 和 OpenSSL 实现 HTTPS 通信的完整指南
引言
在现代网络编程中,HTTPS 通信已成为安全数据传输的标准。C++ 开发者经常面临实现 HTTPS 客户端的挑战,特别是在选择网络库和配置依赖方面。本文将详细介绍如何使用独立 Asio(非 Boost 版本)结合 OpenSSL 实现 HTTPS 通信,涵盖从环境配置到实际代码实现的完整流程。
第一部分:环境搭建
1.1 安装 OpenSSL
Windows 系统:
- 访问 OpenSSL for Windows 下载页面
- 选择适合的版本(推荐 Win32 OpenSSL v3.x.x)
- 运行安装程序,记录安装路径(如
C:\OpenSSL-Win32) - 将 OpenSSL 的 bin 目录添加到系统 PATH 环境变量
1.2 获取独立 Asio
独立 Asio 是 Boost.Asio 的独立版本,不依赖 Boost 库:
- 访问 Asio 官网
- 下载最新版本的独立 Asio
- 解压到项目目录,如
E:\Project\ExLib\asio
1.3 配置开发环境
Visual Studio 配置:
-
项目属性 → C/C++ → 常规 → 附加包含目录:
E:\Project\ExLib\asio\include C:\OpenSSL-Win64\include -
项目属性 → 链接器 → 常规 → 附加库目录:
C:\OpenSSL-Win64\lib -
项目属性 → 链接器 → 输入 → 附加依赖项:
libssl.lib;libcrypto.lib;ws2_32.lib;crypt32.lib;
CMake 配置:
cmake
cmake_minimum_required(VERSION 3.15)
project(AsioHttpsClient)
set(CMAKE_CXX_STANDARD 11)
# 查找 OpenSSL
find_package(OpenSSL REQUIRED)
# 包含 Asio 头文件
include_directories(${PROJECT_SOURCE_DIR}/../ExLib/asio/include)
add_executable(AsioHttpsClient main.cpp)
# 链接库
target_link_libraries(AsioHttpsClient PRIVATE
OpenSSL::SSL
OpenSSL::Crypto
)
if(WIN32)
target_link_libraries(AsioHttpsClient PRIVATE ws2_32 crypt32)
endif()
第二部分:实现 HTTPS 客户端
2.1 基本 HTTPS POST 请求实现
cpp
// HttpsClient.hpp
#pragma once
#include <asio.hpp>
#include <asio/ssl.hpp>
#include <string>
#include <memory>
#include <functional>
namespace ssl = asio::ssl;
class HttpsClient {
public:
using ResponseCallback = std::function<void(int status,
const std::string& headers,
const std::string& body)>;
using ErrorCallback = std::function<void(const std::string& error)>;
HttpsClient(asio::io_context& io_context,
bool verify_certificate = true);
void post(const std::string& host,
const std::string& port,
const std::string& path,
const std::string& data,
const std::map<std::string, std::string>& headers = {},
ResponseCallback response_callback = nullptr,
ErrorCallback error_callback = nullptr);
void set_certificate_file(const std::string& cert_file);
private:
void on_resolve(const asio::error_code& ec,
asio::ip::tcp::resolver::results_type endpoints);
void on_connect(const asio::error_code& ec);
void on_handshake(const asio::error_code& ec);
void on_write(const asio::error_code& ec, size_t bytes_transferred);
void on_read_status_line(const asio::error_code& ec, size_t bytes_transferred);
void on_read_headers(const asio::error_code& ec, size_t bytes_transferred);
void on_read_body(const asio::error_code& ec, size_t bytes_transferred);
asio::io_context& io_context_;
ssl::context ssl_context_;
std::unique_ptr<ssl::stream<asio::ip::tcp::socket>> socket_;
asio::ip::tcp::resolver resolver_;
std::string host_;
std::string port_;
std::string path_;
std::string request_data_;
std::map<std::string, std::string> custom_headers_;
asio::streambuf request_;
asio::streambuf response_;
ResponseCallback response_callback_;
ErrorCallback error_callback_;
int http_status_code_;
std::string response_headers_;
std::string response_body_;
bool verify_certificate_;
std::string cert_file_;
};
cpp
// HttpsClient.cpp
#include "HttpsClient.hpp"
#include <iostream>
#include <sstream>
HttpsClient::HttpsClient(asio::io_context& io_context, bool verify_certificate)
: io_context_(io_context)
, ssl_context_(ssl::context::tls_client)
, resolver_(io_context)
, verify_certificate_(verify_certificate) {
// 设置 SSL 选项
if (verify_certificate) {
ssl_context_.set_default_verify_paths();
ssl_context_.set_verify_mode(ssl::verify_peer);
} else {
ssl_context_.set_verify_mode(ssl::verify_none);
}
}
void HttpsClient::set_certificate_file(const std::string& cert_file) {
cert_file_ = cert_file;
ssl_context_.load_verify_file(cert_file);
ssl_context_.set_verify_mode(ssl::verify_peer);
}
void HttpsClient::post(const std::string& host,
const std::string& port,
const std::string& path,
const std::string& data,
const std::map<std::string, std::string>& headers,
ResponseCallback response_callback,
ErrorCallback error_callback) {
host_ = host;
port_ = port;
path_ = path;
request_data_ = data;
custom_headers_ = headers;
response_callback_ = response_callback;
error_callback_ = error_callback;
// 创建 socket
socket_ = std::make_unique<ssl::stream<asio::ip::tcp::socket>>(
io_context_, ssl_context_);
// 设置 SNI 主机名
if (!SSL_set_tlsext_host_name(socket_->native_handle(), host.c_str())) {
if (error_callback_) {
error_callback_("Failed to set SNI hostname");
}
return;
}
// 开始异步解析
resolver_.async_resolve(host, port,
[this](const asio::error_code& ec,
asio::ip::tcp::resolver::results_type endpoints) {
on_resolve(ec, endpoints);
});
}
void HttpsClient::on_resolve(const asio::error_code& ec,
asio::ip::tcp::resolver::results_type endpoints) {
if (!ec) {
// 异步连接
asio::async_connect(socket_->lowest_layer(), endpoints,
[this](const asio::error_code& ec,
const asio::ip::tcp::endpoint&) {
on_connect(ec);
});
} else if (error_callback_) {
error_callback_("Resolve error: " + ec.message());
}
}
void HttpsClient::on_connect(const asio::error_code& ec) {
if (!ec) {
// 异步 SSL 握手
socket_->async_handshake(ssl::stream_base::client,
[this](const asio::error_code& ec) {
on_handshake(ec);
});
} else if (error_callback_) {
error_callback_("Connect error: " + ec.message());
}
}
void HttpsClient::on_handshake(const asio::error_code& ec) {
if (!ec) {
// 构建 HTTP 请求
std::ostream request_stream(&request_);
request_stream << "POST " << path_ << " HTTP/1.1\r\n";
request_stream << "Host: " << host_ << "\r\n";
request_stream << "Content-Type: application/json\r\n";
request_stream << "Content-Length: " << request_data_.length() << "\r\n";
// 添加自定义头部
for (const auto& header : custom_headers_) {
request_stream << header.first << ": " << header.second << "\r\n";
}
request_stream << "Connection: close\r\n";
request_stream << "\r\n";
request_stream << request_data_;
// 异步发送请求
asio::async_write(*socket_, request_,
[this](const asio::error_code& ec, size_t bytes_transferred) {
on_write(ec, bytes_transferred);
});
} else if (error_callback_) {
error_callback_("Handshake error: " + ec.message());
}
}
void HttpsClient::on_write(const asio::error_code& ec, size_t bytes_transferred) {
if (!ec) {
// 异步读取状态行
asio::async_read_until(*socket_, response_, "\r\n",
[this](const asio::error_code& ec, size_t bytes_transferred) {
on_read_status_line(ec, bytes_transferred);
});
} else if (error_callback_) {
error_callback_("Write error: " + ec.message());
}
}
void HttpsClient::on_read_status_line(const asio::error_code& ec,
size_t bytes_transferred) {
if (!ec) {
std::istream response_stream(&response_);
std::string http_version;
response_stream >> http_version >> http_status_code_;
std::string status_message;
std::getline(response_stream, status_message);
// 异步读取头部
asio::async_read_until(*socket_, response_, "\r\n\r\n",
[this](const asio::error_code& ec, size_t bytes_transferred) {
on_read_headers(ec, bytes_transferred);
});
} else if (error_callback_) {
error_callback_("Read status line error: " + ec.message());
}
}
void HttpsClient::on_read_headers(const asio::error_code& ec,
size_t bytes_transferred) {
if (!ec) {
std::istream response_stream(&response_);
std::string header;
while (std::getline(response_stream, header) && header != "\r") {
response_headers_ += header + "\n";
}
// 如果响应体中还有数据,先读取
if (response_.size() > 0) {
std::ostringstream oss;
oss << &response_;
response_body_ += oss.str();
}
// 继续读取响应体
asio::async_read(*socket_, response_,
asio::transfer_at_least(1),
[this](const asio::error_code& ec, size_t bytes_transferred) {
on_read_body(ec, bytes_transferred);
});
} else if (error_callback_) {
error_callback_("Read headers error: " + ec.message());
}
}
void HttpsClient::on_read_body(const asio::error_code& ec,
size_t bytes_transferred) {
if (!ec) {
// 将缓冲区数据添加到响应体
std::ostringstream oss;
oss << &response_;
response_body_ += oss.str();
// 继续读取
asio::async_read(*socket_, response_,
asio::transfer_at_least(1),
[this](const asio::error_code& ec, size_t bytes_transferred) {
on_read_body(ec, bytes_transferred);
});
} else if (ec == asio::error::eof) {
// 读取完成
if (response_callback_) {
response_callback_(http_status_code_,
response_headers_,
response_body_);
}
// 关闭连接
asio::error_code ignored_ec;
socket_->shutdown(ignored_ec);
} else if (error_callback_) {
error_callback_("Read body error: " + ec.message());
}
}
2.2 主程序示例
cpp
// Main.cpp
#include "HttpsClient.hpp"
#include <iostream>
#include <asio.hpp>
// 同步版本示例
void synchronous_https_post() {
try {
asio::io_context io_context;
// 创建 SSL 上下文
ssl::context ctx(ssl::context::tls_client);
// 对于测试,可以禁用证书验证
ctx.set_verify_mode(ssl::verify_none);
// 对于生产环境,应该启用证书验证
// ctx.set_default_verify_paths();
// ctx.set_verify_mode(ssl::verify_peer);
// 创建 SSL 流
ssl::stream<asio::ip::tcp::socket> socket(io_context, ctx);
// 设置 SNI
SSL_set_tlsext_host_name(socket.native_handle(), "httpbin.org");
// 解析和连接
asio::ip::tcp::resolver resolver(io_context);
auto endpoints = resolver.resolve("httpbin.org", "443");
asio::connect(socket.next_layer(), endpoints);
// SSL 握手
socket.handshake(ssl::stream_base::client);
// 发送 HTTP 请求
std::string request =
"POST /post HTTP/1.1\r\n"
"Host: httpbin.org\r\n"
"Content-Type: application/json\r\n"
"Content-Length: 29\r\n"
"User-Agent: AsioHttpsClient/1.0\r\n"
"Accept: application/json\r\n"
"Connection: close\r\n"
"\r\n"
R"({"name": "test", "value": 123})";
asio::write(socket, asio::buffer(request));
// 读取响应
asio::streambuf response;
asio::read_until(socket, response, "\r\n");
// 解析状态行
std::istream response_stream(&response);
std::string http_version;
unsigned int status_code;
std::string status_message;
response_stream >> http_version >> status_code;
std::getline(response_stream, status_message);
std::cout << "Status: " << status_code << " " << status_message << "\n";
// 读取头部
asio::read_until(socket, response, "\r\n\r\n");
std::string header;
while (std::getline(response_stream, header) && header != "\r") {
std::cout << header << "\n";
}
// 读取响应体
asio::error_code ec;
while (asio::read(socket, response, asio::transfer_at_least(1), ec)) {
std::cout << &response;
}
std::cout << "\n";
// 关闭连接
socket.shutdown();
} catch (std::exception& e) {
std::cerr << "Error: " << e.what() << "\n";
}
}
// 异步版本示例
void asynchronous_https_post() {
asio::io_context io_context;
HttpsClient client(io_context, false); // 禁用证书验证用于测试
std::cout << "Sending asynchronous HTTPS POST request...\n";
client.post(
"httpbin.org", // 主机
"443", // 端口
"/post", // 路径
R"({"name": "async_test", "value": 456})", // 数据
{ // 自定义头部
{"User-Agent", "AsioHttpsClient/2.0"},
{"X-Custom-Header", "CustomValue"}
},
// 响应回调
[](int status, const std::string& headers, const std::string& body) {
std::cout << "\n=== Response ===\n";
std::cout << "Status: " << status << "\n";
std::cout << "Headers:\n" << headers;
std::cout << "Body:\n" << body << "\n";
},
// 错误回调
[](const std::string& error) {
std::cerr << "Error: " << error << "\n";
}
);
// 运行 IO 上下文
io_context.run();
}
int main() {
std::cout << "1. Synchronous HTTPS POST:\n";
synchronous_https_post();
std::cout << "\n2. Asynchronous HTTPS POST:\n";
asynchronous_https_post();
return 0;
}
第三部分:高级主题
3.1 处理 SSL 证书验证
cpp
// 证书验证回调示例
bool certificate_verification_callback(bool preverified,
ssl::verify_context& ctx) {
// 可以在这里添加自定义的证书验证逻辑
char subject_name[256];
X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
std::cout << "Verifying certificate for: " << subject_name << "\n";
// 示例:检查证书是否自签名
if (X509_check_issued(cert, cert) == X509_V_OK) {
std::cout << "Certificate is self-signed\n";
}
return preverified; // 返回 true 表示验证通过
}
// 在 SSL 上下文中使用
ssl::context ctx(ssl::context::tls_client);
ctx.set_verify_mode(ssl::verify_peer);
ctx.set_verify_callback(certificate_verification_callback);
3.2 超时处理
cpp
class HttpsClientWithTimeout : public HttpsClient {
public:
HttpsClientWithTimeout(asio::io_context& io_context,
bool verify_certificate = true,
int timeout_seconds = 30)
: HttpsClient(io_context, verify_certificate)
, timeout_seconds_(timeout_seconds)
, timer_(io_context) {}
void post(const std::string& host,
const std::string& port,
const std::string& path,
const std::string& data,
const std::map<std::string, std::string>& headers = {},
ResponseCallback response_callback = nullptr,
ErrorCallback error_callback = nullptr) {
// 设置超时定时器
timer_.expires_after(std::chrono::seconds(timeout_seconds_));
timer_.async_wait([this, error_callback](const asio::error_code& ec) {
if (!ec) {
// 超时发生
if (socket_) {
asio::error_code ignored_ec;
socket_->lowest_layer().close(ignored_ec);
}
if (error_callback) {
error_callback("Request timeout");
}
}
});
// 调用基类的 post 方法
HttpsClient::post(host, port, path, data, headers,
response_callback,
error_callback);
}
private:
int timeout_seconds_;
asio::steady_timer timer_;
};
3.3 连接池和连接复用
cpp
class HttpsConnectionPool {
public:
HttpsConnectionPool(asio::io_context& io_context,
size_t pool_size,
const std::string& host,
const std::string& port,
bool verify_certificate = true)
: io_context_(io_context)
, host_(host)
, port_(port)
, verify_certificate_(verify_certificate) {
// 初始化连接池
for (size_t i = 0; i < pool_size; ++i) {
available_connections_.push_back(
std::make_shared<HttpsConnection>(io_context_,
verify_certificate_));
}
}
std::shared_ptr<HttpsConnection> acquire() {
std::lock_guard<std::mutex> lock(mutex_);
if (available_connections_.empty()) {
// 如果池为空,创建新连接
return std::make_shared<HttpsConnection>(io_context_,
verify_certificate_);
}
auto connection = available_connections_.back();
available_connections_.pop_back();
return connection;
}
void release(std::shared_ptr<HttpsConnection> connection) {
std::lock_guard<std::mutex> lock(mutex_);
available_connections_.push_back(connection);
}
private:
asio::io_context& io_context_;
std::string host_;
std::string port_;
bool verify_certificate_;
std::vector<std::shared_ptr<HttpsConnection>> available_connections_;
std::mutex mutex_;
};
class HttpsConnection {
// 连接封装类,支持连接复用
};
第四部分:常见问题解决
4.1 OpenSSL 安装问题
问题: "无法打开包括文件: 'openssl/conf.h'"
解决方案:
- 确保 OpenSSL 正确安装
- 在项目设置中添加正确的包含目录
- 检查 OpenSSL 版本兼容性
4.2 SSL 证书验证失败
问题: "certificate verify failed"
解决方案:
- 对于测试环境:禁用证书验证
- 对于生产环境:
- 使用
set_default_verify_paths()加载系统证书 - 或使用
load_verify_file()加载自定义证书 - 从 curl CA 证书 下载证书包
- 使用
4.3 内存泄漏问题
问题: Asio 和 OpenSSL 内存泄漏
解决方案:
- 确保正确关闭 SSL 连接
- 使用智能指针管理资源
- 在程序退出时调用 OpenSSL 清理函数
cpp
// 程序初始化
void initialize_openssl() {
OPENSSL_init_ssl(0, nullptr);
}
// 程序清理
void cleanup_openssl() {
EVP_cleanup();
}
测试程序
cpp
// Main.cpp - 简化版HTTPS客户端
#ifdef _WIN32
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0A00//0x0601
#endif
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#endif
#include <asio.hpp>
#include <asio/ssl.hpp>
#include <iostream>
#include <string>
#include <sstream>
using asio::ip::tcp;
namespace ssl = asio::ssl;
std::string create_https_post_request(const std::string& host,
const std::string& path,
const std::string& data) {
std::ostringstream request;
request << "POST " << path << " HTTP/1.1\r\n";
request << "Host: " << host << "\r\n";
request << "Content-Type: application/json\r\n";
request << "Content-Length: " << data.length() << "\r\n";
request << "Accept: application/json\r\n";
request << "User-Agent: AsioClient/1.0\r\n";
request << "Connection: close\r\n";
request << "\r\n";
request << data;
return request.str();
}
// SSL证书验证回调(总是返回true)
bool verify_certificate(bool preverified, ssl::verify_context& ctx) {
// 不进行证书验证,总是返回true
return true;
}
int main() {
try {
// URL信息
std::string host = "example.com";
std::string path = "/api/check";
int port = 443;
// POST 数据
std::string post_data = R"({"action": "check", "value": 123})";
// 创建IO上下文和SSL上下文
asio::io_context io_context;
ssl::context ssl_context(ssl::context::tls_client);
// 禁用证书验证 - 关键修改
ssl_context.set_verify_mode(ssl::verify_none);
// 或者使用自定义验证回调
// ssl_context.set_verify_mode(ssl::verify_peer);
// ssl_context.set_verify_callback(verify_certificate);
// 创建SSL流
ssl::stream<tcp::socket> ssl_stream(io_context, ssl_context);
// 设置SNI主机名
if (!SSL_set_tlsext_host_name(ssl_stream.native_handle(), host.c_str())) {
asio::error_code ec{ static_cast<int>(::ERR_get_error()), asio::error::get_ssl_category() };
throw asio::system_error(ec);
}
// 解析主机名
tcp::resolver resolver(io_context);
auto endpoints = resolver.resolve(host, std::to_string(port));
// 连接
asio::connect(ssl_stream.lowest_layer(), endpoints);
// SSL握手
ssl_stream.handshake(ssl::stream_base::client);
std::cout << "SSL handshake successful!" << std::endl;
// 创建HTTP请求
std::string request = create_https_post_request(host, path, post_data);
// 发送请求
asio::write(ssl_stream, asio::buffer(request));
// 读取响应
asio::streambuf response;
asio::read_until(ssl_stream, response, "\r\n");
// 检查响应状态
std::istream response_stream(&response);
std::string http_version;
unsigned int status_code;
std::string status_message;
response_stream >> http_version >> status_code;
std::getline(response_stream, status_message);
std::cout << "Response: " << http_version << " " << status_code
<< " " << status_message << std::endl;
// 读取响应头
asio::read_until(ssl_stream, response, "\r\n\r\n");
std::cout << "Headers:" << std::endl;
std::string header;
while (std::getline(response_stream, header) && header != "\r") {
std::cout << header << std::endl;
}
// 读取响应体
std::cout << "\nResponse body:" << std::endl;
if (response.size() > 0) {
std::cout << &response;
}
asio::error_code error;
while (asio::read(ssl_stream, response,
asio::transfer_at_least(1), error)) {
std::cout << &response;
}
if (error != asio::error::eof) {
std::cout << "Read error: " << error.message() << std::endl;
}
std::cout << std::endl;
// 关闭连接
ssl_stream.shutdown();
}
catch (std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
// 输出详细的错误信息
asio::error_code ec;
if ((ec = asio::error_code(::ERR_get_error(),
asio::error::get_ssl_category()))) {
std::cerr << "SSL Error: " << ec.message() << std::endl;
}
return 1;
}
return 0;
}
结果

结论
使用独立 Asio 和 OpenSSL 实现 HTTPS 通信需要正确处理 SSL/TLS 握手、证书验证和异步 I/O 操作。本文提供了从环境配置到高级功能的完整解决方案,包括:
- 环境搭建:OpenSSL 安装和 Asio 配置
- 基础实现:同步和异步 HTTPS POST 请求
- 高级功能:证书验证、超时处理、连接池
- 问题解决:常见错误和解决方案
通过本文的指南,你应该能够在项目中成功实现 HTTPS 通信,并根据需求进行定制扩展。记住,在生产环境中始终启用证书验证以确保通信安全。