Boost.Asio 教程:从基础到高级应用 – wiki基地

Boost.Asio 教程:从基础到高级应用

Boost.Asio 是一个跨平台的 C++ 库,用于网络和低层 I/O 编程,提供了一致的异步模型。Asio 这个名字代表 “Asynchronous Input Output”,但它不仅仅支持异步操作,也支持同步操作。Asio 可以用于开发各种网络应用,如客户端、服务器、以及使用串口、定时器等的应用。

本文将带您深入了解 Boost.Asio,从基础概念到高级应用,逐步掌握其核心功能和用法。

1. 基础入门

1.1 核心概念

  • I/O 服务 (io_context/io_service): Asio 的核心是一个 I/O 服务对象。它提供了提交 I/O 请求和接收 I/O 完成通知的机制。在较新版本的 Asio 中推荐使用 io_context,而在较旧的版本中使用 io_service(已被弃用,但为了兼容性仍然存在)。
  • I/O 对象 (Sockets, Timers, …): I/O 对象代表可以执行 I/O 操作的实体,如套接字(sockets)、定时器(timers)、串口(serial ports)等。
  • 操作 (Operations): I/O 操作是你在 I/O 对象上执行的动作,如读取(read)、写入(write)、连接(connect)、接受连接(accept)等。
  • 异步操作 (Asynchronous Operations): Asio 的核心优势在于其异步模型。异步操作允许你在不阻塞调用线程的情况下启动 I/O 操作。当操作完成时,Asio 会通过回调函数(callback)、future/promise 或协程(coroutines)通知你。
  • 处理器 (Handlers): 处理器是当异步操作完成时被 Asio 调用的函数对象。它们通常是回调函数、lambda 表达式或绑定到成员函数的 std::bind 表达式。
  • 缓冲区 (Buffers): 缓冲区用于存储在 I/O 操作中传输的数据。Asio 提供了多种缓冲区类型,如 boost::asio::bufferstd::arraystd::vector 等。
  • 端点 (Endpoints): 端点表示网络通信的地址信息,如 IP 地址和端口号。Asio 使用 ip::tcp::endpoint (TCP) 和 ip::udp::endpoint (UDP) 来表示端点。
  • 解析器(Resolver): 通过域名获取IP地址,将字符串形式的地址信息解析为endpoint

1.2 第一个 Asio 程序:同步计时器

“`c++

include

include

int main() {
boost::asio::io_context io; // 创建 I/O 服务

boost::asio::steady_timer t(io, boost::asio::chrono::seconds(5)); // 创建一个5秒的定时器
t.wait(); // 同步等待定时器到期

std::cout << “Hello, world!” << std::endl;
return 0;
}
“`

这个简单的程序演示了 Asio 的基本用法:

  1. 创建 io_context 对象。
  2. 创建一个 steady_timer 对象,并设置超时时间为5秒。
  3. 调用 wait() 方法同步等待定时器到期。
  4. 定时器到期后,程序继续执行,输出 “Hello, world!”。

1.3 异步计时器

“`c++

include

include

void print(const boost::system::error_code& /e/) {
std::cout << “Hello, world!” << std::endl;
}

int main() {
boost::asio::io_context io;

boost::asio::steady_timer t(io, boost::asio::chrono::seconds(5));
t.async_wait(&print); // 异步等待,并指定回调函数

io.run(); // 运行 I/O 服务,直到所有异步操作完成

return 0;
}
“`

这个程序与前一个程序类似,但使用了异步等待:

  1. async_wait() 方法启动一个异步等待操作。当定时器到期时,Asio 会调用 print 函数。
  2. io.run() 方法会阻塞,直到所有异步操作完成(在本例中是定时器到期)并且没有其他工作可做。
  3. io.run() 阻塞时,程序不会消耗大量 CPU 资源,因为它在等待事件。

2. 网络编程

2.1 TCP 同步回显服务器

“`c++

include

include

using boost::asio::ip::tcp;

int main() {
try {
boost::asio::io_context io_context;

    tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 12345)); // 监听端口12345

    while (true) {
        tcp::socket socket(io_context);
        acceptor.accept(socket); // 接受客户端连接

        boost::system::error_code ignored_error;
        boost::asio::streambuf request_buf;
        boost::asio::read_until(socket, request_buf, "\n", ignored_error); // 读取直到遇到换行符

        std::string message(boost::asio::buffer_cast<const char*>(request_buf.data()), request_buf.size());
        boost::asio::write(socket, boost::asio::buffer(message), ignored_error);  //回写数据
    }
}
catch (std::exception& e) {
    std::cerr << "Exception: " << e.what() << std::endl;
}

return 0;

}
“`

这个程序实现了一个简单的 TCP 回显服务器:

  1. 创建 io_contexttcp::acceptor
  2. acceptor 监听指定的端口(12345)。
  3. 在一个循环中,acceptor.accept(socket) 接受客户端的连接。
  4. boost::asio::read_until 从 socket 读取数据,直到遇到换行符
  5. boost::asio::write 将收到的数据写回客户端。
  6. 这是一个同步服务器,一次只能处理一个客户端。

2.2 TCP 异步回显服务器

“`c++

include

include

include

using boost::asio::ip::tcp;

class session : public std::enable_shared_from_this {
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(
boost::asio::buffer(data_, max_length),
this, self {
if (!ec) {
do_write(length);
}
});
}

void do_write(std::size_t length) {
auto self(shared_from_this());
boost::asio::async_write(
socket_, boost::asio::buffer(data_, length),
this, self {
if (!ec) {
do_read();
}
});
}

private:
tcp::socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};

class server {
public:
server(boost::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 {
if (!ec) {
std::make_shared(std::move(socket))->start();
}
do_accept();
});
}

tcp::acceptor acceptor_;
};

int main() {
try {
boost::asio::io_context io_context;
server s(io_context, 12345);
io_context.run();
} catch (std::exception& e) {
std::cerr << “Exception: ” << e.what() << “\n”;
}
return 0;
}
“`

这个异步回显服务器比同步版本复杂,但可以同时处理多个客户端连接:

  1. session 类: 每个客户端连接都由一个 session 对象管理。session 使用 shared_from_this()std::shared_ptr 来管理自身的生命周期。
  2. do_read()do_write(): 这两个方法使用 async_read_some()async_write() 进行异步读写。它们使用 lambda 表达式作为处理器。
  3. server 类: server 类负责接受新的连接。do_accept() 方法使用 async_accept() 异步接受连接。
  4. main() 函数: 创建 io_contextserver 对象,然后调用 io_context.run() 启动事件循环。

2.3 TCP 客户端

“`c++

include

include

include

using boost::asio::ip::tcp;
int main(int argc, char* argv[])
{
try
{
if (argc != 3)
{
std::cerr << “Usage: client \n”;
return 1;
}
boost::asio::io_context io_context;
tcp::resolver resolver(io_context);
//将服务器的地址信息解析为端点序列
tcp::resolver::results_type endpoints = resolver.resolve(argv[1], argv[2]);

    tcp::socket socket(io_context);
    //连接到服务器
    boost::asio::connect(socket, endpoints);

    for (;;)
    {
        boost::array<char, 128> buf;
        boost::system::error_code error;
        //从控制台读取数据
        std::cin.getline(buf.data(), buf.size());
        size_t len = socket.write_some(boost::asio::buffer(buf), error);

        if (error == boost::asio::error::eof)
            break; // Connection closed cleanly by peer.
        else if (error)
            throw boost::system::system_error(error); // Some other error.
        //从服务器读取返回的数据
        size_t len_rec = socket.read_some(boost::asio::buffer(buf), error);

        //打印服务器返回的数据
        std::cout.write(buf.data(), len_rec);
    }
}
catch (std::exception& e)
{
    std::cerr << e.what() << std::endl;
}

return 0;

}
“`

  1. 解析服务器地址: 使用 tcp::resolver 将主机名和服务名(端口)解析为端点。
  2. 连接服务器: 使用 boost::asio::connect() 函数连接到服务器。
  3. 发送和接收数据: 循环读取用户输入,发送到服务器,然后接收服务器的响应。
  4. 循环从控制台读取输入,并发送到服务器,打印服务器的返回信息

3. 高级应用

3.1 使用协程 (Coroutines)

Boost.Asio 支持 C++20 的协程,可以使异步代码看起来更像同步代码,提高可读性和可维护性。

“`c++

include

include

include

include

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

boost::asio::awaitable echo(tcp::socket socket) {
try {
char data[1024];
for (;;) {
std::size_t n = co_await socket.async_read_some(boost::asio::buffer(data), boost::asio::use_awaitable);
co_await boost::asio::async_write(socket, boost::asio::buffer(data, n), boost::asio::use_awaitable);
}
} catch (std::exception& e) {
std::printf(“echo Exception: %s\n”, e.what());
}
}

boost::asio::awaitable listener() {
auto executor = co_await this_coro::executor;
tcp::acceptor acceptor(executor, {tcp::v4(), 12345});
for (;;) {
tcp::socket socket = co_await acceptor.async_accept(boost::asio::use_awaitable);
co_spawn(executor, echo(std::move(socket)), boost::asio::detached);
}
}

int main() {
try {
boost::asio::io_context io_context(1);

boost::asio::signal_set signals(io_context, SIGINT, SIGTERM);
signals.async_wait([&](auto, auto){ io_context.stop(); });

co_spawn(io_context, listener(), boost::asio::detached);

io_context.run();

} catch (std::exception& e) {
std::printf(“Exception: %s\n”, e.what());
}
return 0;
}
“`

这个例子使用协程实现了一个回显服务器:

  1. echo() 协程: 负责处理每个客户端连接的读写操作。使用 co_await 关键字来等待异步操作完成。
  2. listener() 协程: 负责接受新的连接。使用 co_await 等待 async_accept() 完成。
  3. co_spawn(): 用于启动一个新的协程。
  4. boost::asio::use_awaitable: 一个特殊的 token,用于指示 Asio 使用协程。
  5. 信号处理: 使用signal_set来处理中断信号,优雅的关闭程序

3.2 使用 Strand

在多线程环境中,如果你需要保证一组处理器(handlers)按顺序执行,即使它们在不同的线程中,可以使用 boost::asio::strand

“`c++

include

include

include

include

using namespace boost::asio;

int main() {
io_context io_context;
//创建一个strand
strand my_strand(io_context.get_executor());

// 提交任务到 strand,保证顺序执行
for (int i = 0; i < 5; ++i) {
post(my_strand, i {
std::cout << “Task ” << i << ” executed in thread ” << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
});
}
//创建多个线程去执行任务
std::vector threads;
for (int i = 0; i < 3; ++i) {
threads.emplace_back(&io_context { io_context.run(); });
}

for (auto& thread : threads) {
thread.join();
}
return 0;
}
“`

在这个例子中:

  1. 创建了一个 strand 对象 my_strand
  2. 使用 post() 函数将任务提交到 my_strand。即使这些任务在不同的线程中执行(由 io_context.run() 所在的多个线程决定),它们也会按照提交的顺序执行。

3.3 自定义 I/O 对象

Asio 允许你创建自定义的 I/O 对象,以支持特定的协议或设备。这需要你实现一些特定的 traits 和函数,以与 Asio 的异步模型集成。 这部分比较复杂,此处不做展开,可以参考Boost.Asio的官方文档。

4. 总结

Boost.Asio 是一个功能强大且灵活的库,可用于开发各种网络和低层 I/O 应用。本文介绍了 Asio 的核心概念、基本用法以及一些高级应用,包括:

  • I/O 服务、I/O 对象、操作、异步操作、处理器、缓冲区、端点
  • 同步和异步计时器
  • TCP 同步和异步回显服务器和客户端
  • 使用协程简化异步编程
  • 使用 Strand 保证处理器执行顺序
  • 自定义 I/O 对象 (略)

通过学习和实践,你可以逐步掌握 Boost.Asio,并将其应用于你的项目中。记住,Asio 的文档非常详细,是学习和解决问题的重要资源。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部