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)获取最新版本。下载后,解压到项目目录。

配置

  1. 头文件:将 Asio 的 include 目录添加到项目的头文件路径。
    • 核心头文件位于 asio/include/asio.hpp
  2. 编译
    • Asio 是头文件库,无需单独编译库文件,只需包含头文件。
    • 确保编译器支持 C++11 或更高版本(推荐 C++17/C++20 以使用协程等高级功能)。
  3. 宏定义
    • 使用 standalone Asio 时,需定义 ASIO_STANDALONE 宏以启用独立模式。
    • 如果不使用系统提供的错误码(如 std::error_code),可定义 ASIO_NO_EXCEPTIONSASIO_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 不依赖 Boost,但需要标准库支持(如 <thread><chrono> 等)。
  • 如果使用 SSL 功能,需链接 OpenSSL 库。

3. 核心概念

Asio 的核心是异步操作事件循环,以下是关键组件:

3.1 io_context

  • asio::io_context 是 Asio 的核心调度器,负责管理异步操作的事件循环。
  • 每个 io_context 维护一个任务队列,处理 socket、定时器等异步事件。
  • 通常在程序启动时创建,并通过 run() 方法运行事件循环。

3.2 异步操作

Asio 支持以下异步模型:

  1. 回调:通过传递回调函数(如 lambda 或函数对象)处理异步操作结果。
  2. 协程(C++20):使用 co_awaitco_yield 简化异步代码。
  3. Future/Promise:通过 std::future 或 Asio 的 use_future 实现同步风格的异步调用。

3.3 错误处理

  • Asio 使用 std::error_code 表示操作结果,允许显式检查错误,而非抛出异常。
  • 异步操作的回调函数通常接受一个 std::error_code 参数。

3.4 主要类

  • Socketsasio::ip::tcp::socket(TCP)、asio::ip::udp::socket(UDP)。
  • Endpointsasio::ip::tcp::endpoint(表示 IP 地址和端口)。
  • Acceptorsasio::ip::tcp::acceptor(用于服务器接受连接)。
  • Timersasio::steady_timer(用于定时任务)。
  • Buffersasio::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::writeread_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_someasync_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_awaitawaitable 简化异步操作,代码更像同步风格。
  • 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. 注意事项

  1. 错误处理
    • 始终检查 std::error_code 或使用异常处理。
    • 异步操作可能因网络中断、超时等失败,需妥善处理。
  2. 性能
    • 避免在回调中执行耗时操作,防止阻塞事件循环。
    • 使用多线程或多 io_context 提高并发性能。
  3. 平台差异
    • Windows 使用 IOCP,Linux 使用 epoll,macOS 使用 kqueue,Asio 自动适配。
    • 某些功能(如文件 I/O)可能在特定平台上不可用。
  4. 调试
    • 启用日志或使用调试器检查异步操作的状态。
    • 确保 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、自定义分配器等高级功能。