
标题:解锁C++高性能网络编程:Boost.Asio入门指南
什么是Boost.Asio?
Boost.Asio是一个跨平台的C++库,用于网络和底层I/O编程。它提供了一个现代化的、基于C++的异步编程模型,极大地简化了涉及套接字、定时器、串口等I/O操作的程序开发。
Boost库就像标准库的前瞻一样,创新的提供一个适合C++发展的方向,为C++标准的发展和更新提供了良好的启发和指导的作用,作为一个C++工程师/ 学者,了解Boost的使用是很有必要的。
为什么选择Boost.Asio?
- 高性能与可扩展性: 基于事件驱动和异步模型,Asio能够用极少的线程处理成千上万的并发连接,非常适合需要高吞吐量和低延迟的服务器应用。
- 跨平台: 在Windows、Linux、macOS等系统上提供一致的API,底层封装了如epoll(Linux)、kqueue(macOS/BSD)、IOCP(Windows)等操作系统特有的高性能I/O机制。
- 纯粹的头文件库(大部分): 大部分功能只需包含头文件即可使用,易于集成到项目中。
- 成为标准库的基石: Boost.Asio的设计思想直接影响了C++20标准中的网络库(
std::net)。
核心概念:同步 vs. 异步
理解这两种操作模式是掌握Asio的关键。
-
同步操作:
- 当你调用一个同步函数(如
socket.read)时,调用线程会一直阻塞,直到操作完成(数据成功读取或发生错误)。 - 逻辑简单直观,但一个线程在同一时间只能处理一个连接,资源利用率低。
// 伪代码:同步读取 char data[1024]; size_t len = socket.read_some(buffer(data)); // 线程在这里等待,直到有数据到来 process_data(data, len); // 处理数据 - 当你调用一个同步函数(如
-
异步操作:
- 当你调用一个异步函数(如
async_read)时,函数会立即返回,而不会等待操作完成。你需要提供一个完成处理程序(Completion Handler),即一个回调函数。 - 当异步操作在后台完成时,Asio会调用你提供的处理程序。
- 调用线程在等待期间可以自由地去处理其他任务,从而最大限度地提高CPU利用率。
// 伪代码:异步读取 void on_data_received(error_code ec, size_t len) { if (!ec) { process_data(buffer_, len); // 再次发起异步读取,形成循环 socket.async_read_some(buffer(buffer_), on_data_received); } } // 启动第一次异步读取 socket.async_read_some(buffer(buffer_), on_data_received); // 此时,当前线程可以立即去做别的事情 - 当你调用一个异步函数(如
Asio的心脏:io_context
io_context是Boost.Asio的核心。它是在应用程序和操作系统I/O服务之间的接口。你需要它来做几乎所有事情:
- 分发I/O事件: 它负责检测底层的I/O操作(如套接字数据到达、定时器超时)是否完成,并调用相应的完成处理程序。
- 运行事件循环: 你必须调用
io_context::run()方法来启动事件循环。这个循环会持续运行,等待并分发事件,直到所有工作都完成。
Boost库的底层基于事件循环的机制
不了解事件驱动的IO多路复用的可以先了解一下,无论是Linux下面的epoll, macos下面的kqueue, 还是Windows下面的ICOP,本质都是基于事件触发的IO多路复用的模型,对于一般的阻塞式服务器来说,一个执行流同时只能被一个IO任务阻塞, 如果一个IO任务陷入了阻塞, 这个服务器都会陷入阻塞,有的人就会想, 我是不是采用多线程的模式,给每一个IO任务都分配一个线程作为执行流,当然是可以的,在一定程度上,这确实提高了服务器同时处理任务的能力,但是对于业务巨大的服务器来说,服务器的线程也显得太过重,因此,大佬就提出了基于事件的IO处理模型。具体的细节可以自己去浏览。
动手实践:一个简单的异步TCP Echo服务器
这是Asio官网提供的样例
#include <boost/asio.hpp>
#include <iostream>
#include <memory>
using boost::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() {
// 使用shared_from_this()确保Session对象在异步操作期间保持存活
auto self(shared_from_this());
socket_.async_read_some(
boost::asio::buffer(data_, max_length),
[this, self](boost::system::error_code ec, std::size_t length) {
if (!ec) {
// 读取成功,将数据写回(Echo)
do_write(length);
} else {
// 发生错误(如客户端断开连接)
std::cerr << "Read error: " << ec.message() << std::endl;
}
});
}
void do_write(std::size_t length) {
auto self(shared_from_this());
boost::asio::async_write(
socket_,
boost::asio::buffer(data_, length),
[this, self](boost::system::error_code ec, std::size_t /*length*/) {
if (!ec) {
// 写入成功,继续读取下一条消息
do_read();
} else {
std::cerr << "Write error: " << ec.message() << std::endl;
}
});
}
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](boost::system::error_code ec, tcp::socket socket) {
if (!ec) {
// 新连接建立成功,创建一个Session对象来管理它
std::make_shared<Session>(std::move(socket))->start();
std::cout << "New client connected!" << std::endl;
} else {
std::cerr << "Accept error: " << ec.message() << std::endl;
}
// 继续接受下一个连接
do_accept();
});
}
tcp::acceptor acceptor_;
};
int main() {
try {
boost::asio::io_context io_context;
Server server(io_context, 8080); // 监听8080端口
std::cout << "Echo server is running on port 8080..." << std::endl;
// 运行事件循环,开始处理异步操作
io_context.run();
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return 0;
}
代码解析:
Session类: 管理一个独立的客户端连接。它使用async_read_some和async_write进行异步的读和写。当一个操作完成时,它会触发下一个操作,形成一个持续的处理循环。Server类: 使用tcp::acceptor来异步地接受新的客户端连接。每当有新连接时,它就创建一个Session对象并将其启动。shared_from_this: 这是关键。它确保了在异步操作 pending 时,Session对象不会被意外销毁,防止了悬空指针和内存访问错误。io_context.run(): 主函数中最重要的调用。它启动了Asio的事件循环,让整个异步机制开始运转。

执行流程
- 初始化acceptor, 首先注册accet事件
- 连接到来accet事件就绪,执行accet回调
- accet事件内容就是创建一个Session对象, 并且注册一个read事件
- 对方发送数据, read事件就绪,执行read回调
- read事件内容就是处理发送的数据,并且生成发送数据,注册发送回调
- 任务处理完毕,发送数据就绪,执行发送回调
注意生命周期问题
这里的实例代码中也存在一些C++的小细节,比如auto self(shared_from_this), 为什么代码要这么写,熟悉异步操作的肯定一下就能反应过来,Boost.Asio是一个异步框架,也就是说我不知道真正处理回调的时机是什么时候,我只是注册了一个回调函数,我们的回调函数通过lambda表达式进行了注册, 我们传入了this指针,这样就能保证回调函数里面访问的是对应的Session对象,但是由于这里我们外部并没有对Session对象就行生命周期的管理,也就是说在调用accept的回调函数时创建的Session对象会随着栈的析构而析构,所以,为了保证Seesion对象的声明周期
- 我们将
Session继承一个std::enable_shared_from_this的标准基类,这样,我们就可以创建一个智能指针的副本self并传递给lambda表达式,这样在lambda表达式的作用域的内部,就保存了一份Session,或者说指针,这样由于智能指针计数器>1所以对象不会被析构。(这也是我们Boost官方样例中的做法) - 我们在外部定义一个双端链表管理
Session对象, 这样Session对象的声明周期的管理者就成了双端链表,但是,我们这个时候Session对象中还应该保存一份双端链表中自己所在位置的ListNode指针,在Session注册write回调函数里面,通过ListNode指针将Session对象从双端链表中删去,避免内存泄露。
进一步学习:
- Boost.Asio 官方文档
- C++ Networking TS (std::net)
希望这篇博客能帮助你更好了解Boost.Asio
2966

被折叠的 条评论
为什么被折叠?



