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::buffer
、std::array
、std::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 的基本用法:
- 创建
io_context
对象。 - 创建一个
steady_timer
对象,并设置超时时间为5秒。 - 调用
wait()
方法同步等待定时器到期。 - 定时器到期后,程序继续执行,输出 “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;
}
“`
这个程序与前一个程序类似,但使用了异步等待:
async_wait()
方法启动一个异步等待操作。当定时器到期时,Asio 会调用print
函数。io.run()
方法会阻塞,直到所有异步操作完成(在本例中是定时器到期)并且没有其他工作可做。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 回显服务器:
- 创建
io_context
和tcp::acceptor
。 acceptor
监听指定的端口(12345)。- 在一个循环中,
acceptor.accept(socket)
接受客户端的连接。 boost::asio::read_until
从 socket 读取数据,直到遇到换行符boost::asio::write
将收到的数据写回客户端。- 这是一个同步服务器,一次只能处理一个客户端。
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
}
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;
}
“`
这个异步回显服务器比同步版本复杂,但可以同时处理多个客户端连接:
session
类: 每个客户端连接都由一个session
对象管理。session
使用shared_from_this()
和std::shared_ptr
来管理自身的生命周期。do_read()
和do_write()
: 这两个方法使用async_read_some()
和async_write()
进行异步读写。它们使用 lambda 表达式作为处理器。server
类:server
类负责接受新的连接。do_accept()
方法使用async_accept()
异步接受连接。main()
函数: 创建io_context
和server
对象,然后调用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
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;
}
“`
- 解析服务器地址: 使用
tcp::resolver
将主机名和服务名(端口)解析为端点。 - 连接服务器: 使用
boost::asio::connect()
函数连接到服务器。 - 发送和接收数据: 循环读取用户输入,发送到服务器,然后接收服务器的响应。
- 循环从控制台读取输入,并发送到服务器,打印服务器的返回信息
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
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
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;
}
“`
这个例子使用协程实现了一个回显服务器:
echo()
协程: 负责处理每个客户端连接的读写操作。使用co_await
关键字来等待异步操作完成。listener()
协程: 负责接受新的连接。使用co_await
等待async_accept()
完成。co_spawn()
: 用于启动一个新的协程。boost::asio::use_awaitable
: 一个特殊的 token,用于指示 Asio 使用协程。- 信号处理: 使用
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
// 提交任务到 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
for (int i = 0; i < 3; ++i) {
threads.emplace_back(&io_context { io_context.run(); });
}
for (auto& thread : threads) {
thread.join();
}
return 0;
}
“`
在这个例子中:
- 创建了一个
strand
对象my_strand
。 - 使用
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 的文档非常详细,是学习和解决问题的重要资源。