cpp asio
Asio 是一个跨平台的 C++ 库,用于网络和低级 I/O 编程,提供了一套异步操作的框架。Boost.Asio 是其最知名的版本,但独立(standalone)版本的 Asio 允许在不依赖 Boost 的情况下使用。
1. Asio 库概述
Standalone Asio 是一个轻量级的网络编程库,设计目标是提供高效、异步的 I/O 操作。它支持以下功能:
- 网络编程:TCP、UDP、ICMP 等协议的客户端和服务器开发。
- 异步 I/O:通过回调、协程(C++20 起支持)或 Future/Promise 模型处理异步操作。
- 跨平台:支持 Windows、Linux、macOS 等操作系统。
- 低级 I/O:文件操作、串口通信、定时器等。
与 Boost.Asio 相比,standalone Asio 不依赖 Boost 的其他组件(如 Boost.System),因此更轻量,适合需要减少依赖的场景。
2. 安装与配置
下载
Standalone Asio 可从 GitHub 或其官方网站(think-async.com
)获取最新版本。下载后,解压到项目目录。
配置
- 头文件:将 Asio 的
include
目录添加到项目的头文件路径。- 核心头文件位于
asio/include/asio.hpp
。
- 核心头文件位于
- 编译:
- Asio 是头文件库,无需单独编译库文件,只需包含头文件。
- 确保编译器支持 C++11 或更高版本(推荐 C++17/C++20 以使用协程等高级功能)。
- 宏定义:
- 使用 standalone Asio 时,需定义
ASIO_STANDALONE
宏以启用独立模式。 - 如果不使用系统提供的错误码(如
std::error_code
),可定义ASIO_NO_EXCEPTIONS
或ASIO_NO_TYPEID
来禁用异常或 RTTI。 - 示例编译命令(g++):
g++ -std=c++17 -Ipath/to/asio/include -DASIO_STANDALONE main.cpp -pthread
- 注意:需要链接
pthread
(Linux/Unix)或ws2_32
(Windows)等系统库。
- 使用 standalone Asio 时,需定义
依赖
- Standalone Asio 不依赖 Boost,但需要标准库支持(如
<thread>
、<chrono>
等)。 - 如果使用 SSL 功能,需链接 OpenSSL 库。
3. 核心概念
Asio 的核心是异步操作和事件循环,以下是关键组件:
3.1 io_context
asio::io_context
是 Asio 的核心调度器,负责管理异步操作的事件循环。- 每个
io_context
维护一个任务队列,处理 socket、定时器等异步事件。 - 通常在程序启动时创建,并通过
run()
方法运行事件循环。
3.2 异步操作
Asio 支持以下异步模型:
- 回调:通过传递回调函数(如 lambda 或函数对象)处理异步操作结果。
- 协程(C++20):使用
co_await
和co_yield
简化异步代码。 - Future/Promise:通过
std::future
或 Asio 的use_future
实现同步风格的异步调用。
3.3 错误处理
- Asio 使用
std::error_code
表示操作结果,允许显式检查错误,而非抛出异常。 - 异步操作的回调函数通常接受一个
std::error_code
参数。
3.4 主要类
- Sockets:
asio::ip::tcp::socket
(TCP)、asio::ip::udp::socket
(UDP)。 - Endpoints:
asio::ip::tcp::endpoint
(表示 IP 地址和端口)。 - Acceptors:
asio::ip::tcp::acceptor
(用于服务器接受连接)。 - Timers:
asio::steady_timer
(用于定时任务)。 - Buffers:
asio::buffer
(用于数据读写)。
4. 基本用法
以下是 standalone Asio 的典型使用场景和代码示例。
4.1 简单的 TCP 客户端
创建一个连接到服务器的 TCP 客户端,发送消息并接收响应。
#include <asio.hpp>
#include <iostream>
using asio::ip::tcp;
int main() {
try {
// 创建 io_context
asio::io_context io_context;
// 创建 socket
tcp::socket socket(io_context);
// 定义服务器地址和端口
tcp::endpoint endpoint(asio::ip::make_address("127.0.0.1"), 8080);
// 连接到服务器
socket.connect(endpoint);
// 发送数据
std::string message = "Hello, Asio!";
asio::write(socket, asio::buffer(message));
// 接收响应
char reply[1024];
size_t reply_length = socket.read_some(asio::buffer(reply));
std::cout << "Reply: " << std::string(reply, reply_length) << std::endl;
// 关闭 socket
socket.close();
}
catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}
说明:
asio::write
和read_some
是同步操作,适合简单场景。asio::buffer
包装数据以供读写。- 使用
try-catch
处理可能的异常。
4.2 异步 TCP 服务器
实现一个异步 TCP 服务器,接受客户端连接并回显消息。
#include <asio.hpp>
#include <iostream>
#include <memory>
using asio::ip::tcp;
class Session : public std::enable_shared_from_this<Session> {
public:
Session(tcp::socket socket) : socket_(std::move(socket)) {}
void start() {
do_read();
}
private:
void do_read() {
auto self(shared_from_this());
socket_.async_read_some(
asio::buffer(data_, max_length),
[this, self](std::error_code ec, std::size_t length) {
if (!ec) {
do_write(length);
}
});
}
void do_write(std::size_t length) {
auto self(shared_from_this());
asio::async_write(
socket_,
asio::buffer(data_, length),
[this, self](std::error_code ec, std::size_t /*length*/) {
if (!ec) {
do_read();
}
});
}
tcp::socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
class Server {
public:
Server(asio::io_context& io_context, short port)
: acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) {
do_accept();
}
private:
void do_accept() {
acceptor_.async_accept(
[this](std::error_code ec, tcp::socket socket) {
if (!ec) {
std::make_shared<Session>(std::move(socket))->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
};
int main() {
try {
asio::io_context io_context;
Server server(io_context, 8080);
io_context.run();
}
catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}
说明:
- 使用
async_read_some
和async_write
实现异步读写。 std::enable_shared_from_this
确保Session
对象的生命周期安全。io_context.run()
运行事件循环,处理所有异步操作。- 服务器持续监听
8080
端口,接受新连接并创建Session
处理。
4.3 使用定时器
Asio 提供 steady_timer
用于定时任务,适合实现延迟或定时操作。
#include <asio.hpp>
#include <iostream>
void print(const std::error_code& /*ec*/) {
std::cout << "Timer expired!" << std::endl;
}
int main() {
asio::io_context io_context;
asio::steady_timer timer(io_context, std::chrono::seconds(5));
timer.async_wait(&print);
io_context.run();
return 0;
}
说明:
steady_timer
在 5 秒后触发回调函数print
。async_wait
是异步等待定时器到期。
4.4 使用 C++20 协程
如果编译器支持 C++20,Asio 提供协程支持,简化异步代码。
#include <asio.hpp>
#include <iostream>
using asio::ip::tcp;
using asio::awaitable;
awaitable<void> echo(tcp::socket socket) {
char data[1024];
for (;;) {
auto n = co_await socket.async_read_some(asio::buffer(data), asio::use_awaitable);
co_await asio::async_write(socket, asio::buffer(data, n), asio::use_awaitable);
}
}
awaitable<void> server(asio::io_context& io_context, short port) {
tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), port));
for (;;) {
auto socket = co_await acceptor.async_accept(asio::use_awaitable);
co_spawn(io_context, echo(std::move(socket)), asio::detached);
}
}
int main() {
try {
asio::io_context io_context;
co_spawn(io_context, server(io_context, 8080), asio::detached);
io_context.run();
}
catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}
说明:
- 使用
co_await
和awaitable
简化异步操作,代码更像同步风格。 co_spawn
用于启动协程。- 需要编译器支持 C++20 和 Asio 的协程功能(确保 Asio 版本足够新)。
5. 高级用法
5.1 多线程支持
Asio 支持多线程运行 io_context
:
- 创建多个线程,每个线程调用
io_context.run()
。 - 确保 socket 和其他资源不被多个线程同时访问(通常每个 socket 绑定到一个
io_context
)。
示例:
asio::io_context io_context;
Server server(io_context, 8080);
std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i) {
threads.emplace_back([&io_context] { io_context.run(); });
}
for (auto& t : threads) {
t.join();
}
5.2 SSL/TLS 支持
Asio 支持 SSL/TLS(需要 OpenSSL)。示例:
#include <asio/ssl.hpp>
asio::ssl::context ssl_context(asio::ssl::context::tls);
ssl_context.load_verify_file("ca.pem");
asio::ssl::stream<tcp::socket> ssl_socket(io_context, ssl_context);
- 配置
ssl::context
并使用ssl::stream
包装普通 socket。
5.3 自定义分配器
Asio 允许自定义内存分配器以优化性能:
asio::io_context io_context;
asio::io_context::work work(io_context); // 防止 io_context 提前退出
5.4 UDP 示例
UDP 是无连接协议,适合广播或低延迟场景:
#include <asio.hpp>
#include <iostream>
using asio::ip::udp;
int main() {
asio::io_context io_context;
udp::socket socket(io_context, udp::endpoint(udp::v4(), 0));
udp::endpoint receiver(asio::ip::make_address("127.0.0.1"), 8080);
socket.send_to(asio::buffer("Hello UDP"), receiver);
io_context.run();
return 0;
}
6. 注意事项
- 错误处理:
- 始终检查
std::error_code
或使用异常处理。 - 异步操作可能因网络中断、超时等失败,需妥善处理。
- 始终检查
- 性能:
- 避免在回调中执行耗时操作,防止阻塞事件循环。
- 使用多线程或多
io_context
提高并发性能。
- 平台差异:
- Windows 使用 IOCP,Linux 使用 epoll,macOS 使用 kqueue,Asio 自动适配。
- 某些功能(如文件 I/O)可能在特定平台上不可用。
- 调试:
- 启用日志或使用调试器检查异步操作的状态。
- 确保
io_context.run()
被调用,否则异步任务不会执行。
Demo:使用 cpp-httplib + Asio 协程 实现简单时间服务器
下面给出一个最小可运行的示例,使用 standalone Asio 的协程(C++20)处理后端逻辑,并使用 cpp-httplib 提供 HTTP 接口。该示例将在 /time 路径返回服务器当前时间的字符串。
说明与依赖:
- 需要编译器支持 C++20 协程(例如 g++ 11+ 或 clang 对应版本)。
- 需要将 Asio 的 include 路径加入编译器,并定义宏
ASIO_STANDALONE
。 - 需要包含 cpp-httplib 的单头文件
httplib.h
。 - 编译时示例命令(Linux):
g++ -std=c++20 -I/path/to/asio/include -I/path/to/cpp-httplib -DASIO_STANDALONE time_server.cpp -pthread -o time_server
示例代码(time_server.cpp):
// time_server.cpp
#include <asio.hpp>
#include <asio/awaitable.hpp>
#include <asio/use_awaitable.hpp>
#include <asio/co_spawn.hpp>
#include <asio/detached.hpp>
#define CPPHTTPLIB_OPENSSL_SUPPORT
#include "httplib.h" // https://github.com/yhirose/cpp-httplib (单头)
#include <chrono>
#include <ctime>
#include <iostream>
#include <string>
using asio::ip::tcp;
using asio::awaitable;
using asio::use_awaitable;
/**
* 获取当前时间字符串(本地时间)
*/
static std::string current_time_string() {
using namespace std::chrono;
auto now = system_clock::now();
std::time_t t = system_clock::to_time_t(now);
char buf[64];
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", std::localtime(&t));
return std::string(buf);
}
/**
* 一个简单的 awaitable 协程,用于定期(或按需)生成时间数据。
* 本示例中我们不需要复杂的异步 socket 操作,核心是展示如何在
* Asio 协程上下文中与 httplib 一起工作。
*/
awaitable<void> asio_worker(asio::io_context& io_ctx) {
// 这里可以放置任何需要在 Asio 事件循环中执行的协程任务
// 本示例只是挂起,保持 io_context 运行,实际的 HTTP 服务由 cpp-httplib 启动
co_return;
}
int main() {
try {
// 创建 Asio io_context(协程运行时)
asio::io_context io_ctx(1);
// 启动一个协程(示例占位)
asio::co_spawn(io_ctx, asio_worker(io_ctx), asio::detached);
// 在单独线程中运行 io_context(与 httplib HTTP 服务器并行)
std::thread asio_thread([&]{
try {
io_ctx.run();
} catch (const std::exception& e) {
std::cerr << "Asio thread exception: " << e.what() << std::endl;
}
});
// 使用 cpp-httplib 提供 HTTP 接口
httplib::Server svr;
svr.Get("/time", [](const httplib::Request& /*req*/, httplib::Response& res) {
// 返回当前时间字符串
res.set_content(current_time_string(), "text/plain; charset=utf-8");
});
// 监听 8080 端口(本地)
std::cout << "HTTP time server listening on http://0.0.0.0:8080/time\n";
svr.listen("0.0.0.0", 8080);
// 当 svr.listen 返回时,停止 io_context 并等待线程结束
io_ctx.stop();
if (asio_thread.joinable()) asio_thread.join();
}
catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
return 1;
}
return 0;
}
运行后,访问:http://127.0.0.1:8080/time 将会返回类似:
2025-09-23 19:01:02
注意事项:
- 该示例把 Asio 的 io_context 与 cpp-httplib 的事件循环分离到不同线程,避免了两者事件循环直接冲突。
- 在更复杂的场景中,可将 HTTP 请求处理委派到 Asio 协程中以完成异步 I/O(例如通过 socket)。
- 如果需要把全部逻辑都放到 Asio 协程中(例如自实现 HTTP 或使用支持 Asio 的 HTTP 库),可以用 acceptor + tcp socket + awaitable 来完全基于 Asio 实现。
7. 总结
Standalone Asio 是一个功能强大且灵活的异步 I/O 库,适合开发高性能网络应用。通过回调、协程或 Future 等方式,开发者可以根据需求选择合适的异步模型。以下是关键点:
- 核心组件:
io_context
、socket、acceptor、timer 等。 - 异步模型:回调、协程、Future。
- 跨平台:支持多种操作系统,配置简单。
- 扩展性:支持多线程、SSL、自定义分配器等高级功能。