Boost 库实现异步高并发服务器
本工程以 Boost.Asio 网络编程为主线,涉及 Boost.Beast (HTTP/WebSocket)、Boost.UUID 等子库,根据项目工程中用到的关于 Boost 的内容系统化整理,包括:基本概念与思想、常用类及函数用法、编程范式与易错点。
一、总体概念与设计思想
1.1 从 Socket 编程到 Boost.Asio 的面向对象抽象
传统 C 语言 Socket 编程由离散的数据结构 (sockaddr / sockaddr_in)和一系列函数按固定次序组成:
| 角色 | 流程 |
|---|---|
| 服务器 | ① 创建监听 socket → bind() → listen() → accept() 接受客户端 connect();② 由 accept() 返回值得到与客户端通信的新 socket,用 recv()/send() 通信 |
| 客户端 | ① 创建通信 socket,直接 connect() 连接服务器;② 成功后用该 socket + recv()/send() 通信 |
要点:服务器监听 socket 需手动绑定固定 IP/端口;通信 socket 自动绑定本服务线程、本地 IP 和端口(服务器)/随机端口(客户端)。
Boost.Asio 在 Socket 编程之上用面向对象 思想做封装,并采用异步事件循环机制实现异步回调函数处理,以实现异步高并发:
| Socket 概念 | Boost.Asio 封装 |
|---|---|
sockaddr 数据结构(IP+端口) |
boost::asio::ip::tcp::endpoint 类 |
| 服务器(socket/bind/listen/accept) | ip::tcp::acceptor 类成员函数(无需专门监听 socket) |
| 客户端(socket/connect/recv/send) | ip::tcp::socket 类成员函数,额外提供 remote_endpoint()/local_endpoint() |
| 域名/IP 与端口解析 | ip::tcp::resolver |
| 高并发非阻塞通信 | boost::asio::io_context 异步事件循环 |
1.2 同步 vs 异步(阻塞 vs 非阻塞)
- 同步 :操作阻塞当前线程直到完成(如
read_some、write、connect),编码简单,适合客户端或低并发场景。 - 异步 :函数立即返回、不阻塞线程,由
io_context在后台完成后再调用回调函数(handler),适合服务器高并发场景。
关键规律:凡是使用异步 函数后,必须调用
io_context::run()或poll()驱动事件循环,否则异步操作永不执行。
1.3 Boost库异步回调函数签名规律
所有异步操作的回调形参里都有 boost::system::error_code,这个对象用来表示异步操作是否正确:
cpp
// 同步:结果通过返回值 + error_code& 获得
size_t n = socket.read_some(buffer, ec);
// 异步:结果在回调参数中,签名一般是 void(error_code, size_t)
socket.async_read_some(buffer, [](boost::system::error_code ec, std::size_t n){ ... });
二、头文件与模块说明
| 头文件 | 说明 | 本项目使用点 |
|---|---|---|
boost/asio.hpp |
Asio 全部模块与功能 | 各 .cpp/.h/.hpp |
boost/beast.hpp |
Beast 全部主要模块(Core/HTTP/WebSocket/Version) | beast_httpserver.hpp |
boost/beast/core.hpp |
Core:网络操作、缓冲区管理、错误处理 | (被 beast.hpp 包含) |
boost/beast/http.hpp |
HTTP 协议:请求/响应构造、解析、收发 | beast_httpserver.hpp |
boost/beast/version.hpp |
Beast 版本信息 | (被 beast.hpp 包含) |
boost/uuid/uuid.hpp |
boost::uuids::uuid 类型(128 位唯一标识) |
tcp_server.h |
boost/uuid/uuid_io.hpp |
UUID 输入输出支持(流操作、to_string) |
tcp_server.h |
boost/uuid/uuid_generators.hpp |
UUID 生成器类 | tcp_server.h |
三、核心基础设施类
1. boost::system::error_code
Boost 的错误类,默认构造值 0 表示无错误,非 0 表示错误码。
cpp
boost::system::error_code ec;
int v = ec.value(); // 获取错误码
std::string m = ec.message(); // 获取错误信息字符串
if (ec) { /* 隐式转换 bool:非 0 即 true */ }
2. boost::system::system_error
Boost 的异常类,内部封装 boost::system::error_code。
cpp
catch (boost::system::system_error& sec) {
sec.code().value(); // 取错误码
sec.what(); // 取错误信息字符串
}
同步操作的函数有两套版本:
- 抛异常版本 :不传
boost::system::error_code,失败抛boost::system::system_error,用try-catch统一处理。适合open/listen/bind/connect/accept。 - 错误码版本 :传
boost::system::error_code&,根据错误码精细处理。适合read_some/receive/read/write_some/send/write。
异步操作的函数特点:
对于所有异步操作都会要求在回调函数callback中的形参有boost::system::error_code对象,来接收异常操作的执行成功或失败情况设置的错误码,在回调函数内进行判断执行结果是否成功。
cpp
// 常用错误码
boost::asio::error::connection_reset // 异常断开连接
boost::asio::error::connection_eof // 正常断开连接(eof)
boost::asio::error::operation_aborted // 主动取消(如 cancel() 定时器)
3. boost::asio::buffer
模板函数 boost::asio::buffer 把各种数据转换成 I/O 缓冲区:
cpp
// 可转换:原生数组、智能指针数组、std::vector<T>、std::string、自定义连续缓冲区结构
socket.read_some(boost::asio::buffer(buff, 1024), ec); // 数组首地址+大小
boost::asio::write(sock, boost::asio::buffer(recv_data_ptr.get(), 1024), ec); // 智能指针数组
boost::asio::buffer(str); // string/vector 可不给大小,自动推断
返回类型:
boost::asio::mutable_buffer/mutable_buffers_1:可读写,用于接收;boost::asio::const_buffer/const_buffers_1:只读,用于发送。
4. boost::asio::io_context
Asio 的异步核心,管理着所有的异步操作(非阻塞方式)并且是线程安全的,可以被多线程共享。负责等待异步操作的完成,并调用相应的处理函数,所有异步回调函数必须在boost::asio::io_context::run启动后才会进行异步回调处理,但是在异步事件为空时会直接退出,因此通常需要采用boost::asio::executor_work_guard<boost::asio::io_context::executor_type>类进行管理。
在需要退出io_context需要退出异步事件循环时,该管理对象需要执行boost::asio::executor_work_guard::reset函数释放管理的io_context对象,然后io_context执行boost::asio::io_context::stop退出异步事件循环。
| 函数 | 作用 |
|---|---|
run() |
启动事件循环,阻塞当前线程 直到无异步任务或调用 stop()。通常在子线程中运行以与主线程并发 |
poll() |
run() 的非阻塞替代,立即处理当前已就绪的事件、不等待新事件进行阻塞,但需多次调用 |
stop() |
停止执行异步操作及其回调 |
reset() |
重新启动事件循环 |
post(callback) |
投递异步任务,在事件循环某时刻执行 |
dispatch(callback) |
投递异步任务;正在事件循环则立即执行,否则入队 |
get_executor() |
获取 io_context::executor_type 执行器对象 |
cpp
boost::asio::io_context ioc;
// ... 投递异步任务 ...
ioc.run(); // 阻塞驱动事件循环
5.boost::asio::executor_work_guard<T>
用于管理与T类型执行器相关的工作状态,常见的是用于管理io_context不会在异步事件为空时直接退出异步事件循环 ,在io_context需要退出异步事件循环时,需要去哦执行boost::asio::executor_work_guard<T>::reset函数释放管理。
io_context::get_executor()返回执行器,负责"任务如何执行"。executor_work_guard<T>:模板类,管理执行器的工作状态,防止io_context在异步事件为空时直接退出run()。
cpp
// 常用方式一:显式传 executor
boost::asio::executor_work_guard<boost::asio::io_context::executor_type>
work_guard(io_context.get_executor());
// 常用方式二:make_work_guard 直接由 io_context 构造(更简洁)
auto work_guard = boost::asio::make_work_guard(io_context);
work_guard.reset(); // 通知工作结束,之后 io_context 无任务时可退出
老版本(< 1.78.0)常用
boost::asio::io_context::work类管理,用法:work w(ioc);/w.reset(ioc);。
6. boost::asio::strand<T>
用于io_context对象同时在多个线程进行run启动时,该io_context异步事件循环的异步操作会被多个线程同时执行造成线程安全问题 。采用 boost::asio::strand<T>对象和io_context的异步回调函数采用boost::asio::bind_executor进行绑定 ,可以确保即使在多线程情况下启动同一个io_context, io_context 的异步回调函数在事件循环中按顺序(串行)执行,避免多线程同时访问共享资源造成数据竞争。
cpp
// 创建 strand,绑定到 io_context 执行器
boost::asio::strand<boost::asio::io_context::executor_type> _strand(io_context.get_executor());
// boost::asio::bind_executor将异步回调绑定到 strand 串行执行
socket.async_read_some(
boost::asio::buffer(...),
boost::asio::bind_executor(_strand, read_callback)
);
原理:线程取消直接调用方式,回调被投递到 strand 队列中由统一线程串行调用,换取线程安全(牺牲部分并发)。
7. boost::asio::steady_timer
基于单调时钟的定时器,时间到触发异步超时操作,需要绑定一个启动的io_context异步事件循环,定时器超时时在该异步事件循环中执行异步回调函数std::function<void(boost::system::error_code)>。
cpp
// 常用构造
boost::asio::steady_timer t(ioc); // 关联 io_context,未设到期
boost::asio::steady_timer t(ioc, boost::asio::chrono::seconds(60)); // 关联并设置相对到期时间
t.expires_after(boost::asio::chrono::seconds(60)); // 重新设置相对到期并开始计时
t.async_wait([](boost::system::error_code ec) { // 异步等待,到期调用回调
if (ec.value() == 0) { /* 到期 */ }
else if (ec.value() == boost::asio::error::operation_aborted) { /* 被 cancel */ }
});
t.wait(); // 同步阻塞等待到期
std::size_t n = t.cancel(); // 取消所有等待的异步操作,返回取消个数
std::size_t n = t.cancel_one(); // 取消一个
boost::asio::chrono 与 std::chrono 功能类似,含 steady_clock::duration、seconds、milliseconds 等特化。
8. boost::asio::signal_set
允许在异步操作中注册一个或多个信号,并在信号被触发时执行相应的异步回调函数,在创建时需要绑定一个启动的异步事件循环io_context对象和监听的信号,实现在执行其他任务的同时,监听注册的信号并执行回调函数处理,而不需要阻塞主线程。
回调函数类型std::function<void(boost::system::error_code ec,int single),其中如果ec为boost::system::errc::success,表示信号被成功捕获一个注册信号single;否则表示发生错误。
cpp
boost::asio::signal_set signals(io_context, SIGINT, SIGTERM); // 注册 Ctrl+C 与终止信号
signals.async_wait([](boost::system::error_code ec, int sig) {
// 回调签名:void(error_code, int)
if (ec.value() == 0) { /* 捕获到信号 sig,做相应处理 */ }
});
std::size_t n = signals.cancel(); // 取消挂起等待
可以使用优雅退出服务器:
cpp
signals.async_wait([&io_context, &iocontext_guard, &httpserver](auto, auto) {
iocontext_guard.reset(); // 告知外部 io_context 工作结束
io_context.stop(); // 停止事件循环
httpserver.stop(); // 停止内部通信线程池
});
四、Asio 基础网络库(IP / TCP)
1. ip::address / address_v4 / address_v6
ip::address_v4或ip::address_v6分别表示IPV4地址和IPV6地址,ip::address是通用的IP地址对象。
cpp
// 字符串 -> 地址
boost::asio::ip::address ip_address = boost::asio::ip::make_address("10.231.29.166"); // 推荐(自动识别 v4/v6)
boost::asio::ip::address ip_address = boost::asio::ip::address::from_string("10.231.29.166"); // 或静态成员
// 地址 -> 字符串
std::string ip_str = ip_address.to_string();
// 特殊地址
boost::asio::ip::address_v4::any(); // 任意本机 IPv4(服务器监听用)
boost::asio::ip::address_v6::any(); // 任意本机 IPv6
// 支持 `== != < <= > >= >>` 等运算符重载。
2. ip::tcp::protocol
表示 TCP 协议族的静态工具,提供一系列与TCP协议相关的方法和属性,通常不与用户代码直接交互,而是通过其他类(如boost::asio::ip::tcp::socket 和 boost::asio::ip::tcp::acceptor)来间接使用。
cpp
boost::asio::ip::tcp::protocol p = boost::asio::ip::tcp::v4(); // IPv4 TCP
boost::asio::ip::tcp::protocol p = boost::asio::ip::tcp::v6(); // IPv6 TCP
p.family(); // 地址族 AF_INET / AF_INET6
p.type(); // 套接字类型 SOCK_STREAM
p.protocol(); // 协议号 IPPROTO_TCP(6)
3. ip::tcp::endpoint
表示一个TCP连接的端点,包含IP地址和端口号,一个 TCP 端点 = IP 地址 + 端口号。
cpp
// 服务器:任意本机 IP + 端口(用于监听)
// IP协议版本+端口号创建
boost::asio::ip::tcp::endpoint ep(boost::asio::ip::tcp::v4(), 9998);
// IP地址+端口号创建
boost::asio::ip::tcp::endpoint ep2(boost::asio::ip::address_v4::any(), 9998);
// 客户端:指定服务器 IP + 端口
boost::asio::ip::tcp::endpoint server_ep(server_ip, server_port);
ep.address(); // ip::address
ep.port(); // unsigned short
ep.protocol(); // ip::tcp::protocol
4. ip::tcp::resolver
把主机名 (域名或 IP 字符串)和服务名 (服务名或端口字符串)解析成一系列ip::tcp::endpoint端点列表,在创建时需要传入异步事件循环boost::asio::io_context,用于进行异步解析操作ip::tcp::resolver::async_resolve ,同时也可以进行同步解析ip::tcp::resolver::resolve。
cpp
boost::asio::ip::tcp::resolver resolver(io_context);
// resolve同步解析(新版:返回 results_type 迭代器,可像 iterator 一样遍历)
boost::asio::ip::tcp::resolver::results_type results =
resolver.resolve(boost::asio::ip::tcp::v4(), host, service, ec);
// async_resolve异步解析(新版)
resolver.async_resolve(tcp::v4(), host, service,
[](boost::system::error_code ec, boost::asio::ip::tcp::resolver::results_type results){ ... });
旧版组件(配合老接口理解):
ip::tcp::resolver::query:保存解析请求(主机名/服务名/选项)。常用 flags:address_configured、all_matching、numeric_host、numeric_service。ip::tcp::resolver::iterator:端点迭代器,默认构造即末尾,配合while遍历所有解析结果。
cpp
resolver.resolve(query); // 返回 iterator(阻塞)
resolver.async_resolve(query, cb); // 异步
boost::asio::connect(socket, begin, end, ec); // 依次尝试每个端点直至成功
5. ip::tcp::socket(通信套接字)
用于表示一个TCP套接字,专门用于客户端和服务端的TCP通信 ,不用于服务器监听。内部封装Socket的同时,提供服务器与客户端通过套接字通信的连接 、读取 、发送 的同步或异步方法。在创建时需要传入一个异步事件循环boost::asio::io_context,用于实现异步事件回调操作的执行。
cpp
boost::asio::ip::tcp::socket socket(io_context); // 关联 io_context
打开
c++
// 使用某种IP协议打开socket,可以省略后续会自动检测协议版本进行打开。
open(ip::tcp::protocol protocol):
连接(客户端)
客户端通信套接字进行连接指定socket地址,包括指定IP地址和端口的ip::tcp::endpoint或ip::tcp::resolver解析出来的ip::tcp::endpoint列表,包括同步和异步两种方式。
cpp
// 同步
connect(const ip::tcp::endpoint& endpoint,boost::system::error_code& ec); // 连接指定端点
boost::asio::connect(ip::tcp::socket& socket, ip::tcp::resolver::iterator& begin,
ip::tcp::resolver::iterator& end, boost::system::error_code& ec); // 连接端点列表
// 异步
async_connect(const ip::tcp::endpoint& endpoint, std::function<void(boost::system::error_code)> handler);
boost::asio::async_connect(ip::tcp::socket& socket, ip::tcp::resolver::iterator& begin,
std::function<void(boost::system::error_code, ip::tcp::endpoint)> handler);
读取(对应 recv/read)
从通信套接字读取数据到接收缓冲区,返回读取的字符个数,并写入错误状态。包括同步版本和异步版本,同步版本会一直阻塞直到满足条件;异步版本会立即返回,在后台运行后通过异步事件循环io_context执行异步回调函数。
read_some、async_read_some函数只保证读取到数据,不保证读满缓冲区 。receive、boost::asio::read、async_receive、boost::asio::async_read保证读满缓冲区 。这些函数会返回两个值,包括:读取字节数和错误状态码 ,同步版本会直接返回读取字节数,同时对传入的boost::system::error_code&写入错误状态;异步版本会在回调函数中传入读取字节数size_t和错误状态boost::system::error_code。其中错误状态码非0时,表示读取错误;boost::asio::error::eof表示正常断开连接;boost::asio::error::connection_reset表示客户端异常。
c++
// 同步方式
size_t read_some(mutable_buffers_1& buff, boost::system::error_code& ec);
size_t receive(mutable_buffers_1& buff);
size_t boost::asio::read(ip::tcp::socket& socket,mutable_buffers_1& buff, boost::system::error_code& ec);
// 异步方式
async_read_some(mutable_buffers_1& buff, std::function<void(size_t, boost::system::error_code)> callback);
async_receive(mutable_buffers_1& buff, std::function<void(size_t, boost::system::error_code)> callback);
boost::asio::async_read(ip::tcp::socket, mutable_buffers_1& buff,
std::function<void(size_t, boost::system::error_code)> callback);
写入(对应 send/write)
通过通信套接字将发送缓冲区的数据进行发送,返回发送成功的字节数,并写入错误状态。类似的同样包括同步版本和异步版本,同步版本会一直阻塞直到满足条件;异步版本会立即返回,在后台运行后通过异步事件循环io_context执行异步回调函数。
write_some、async_write_some函数不保证写完发送待缓冲区数据 ,send、boost::asio::write和async_send、boost::asio::async_write函数保证可以写完待发送缓冲区数据 。这些函数会返回两个值,包括:发送字节数和错误状态码 ,同步版本会直接返回读取字节数,同时对传入的boost::system::error_code&写入错误状态;异步版本会在回调函数中传入发送字节数size_t和错误状态boost::system::error_code。
c++
// 同步方式
size_t write_some(const_buffers_1& buff, boost::system::error_code& ec);
size_t send(const_buffers_1& buff);
size_t boost::asio::write(ip::tcp::socket& socket, const_buffers_1& buff, boost::system::error_code& ec);
// 异步方式
async_write_some(const_buffers_1& buff, std::function<void(size_t, boost::system::error_code)> callback);
async_send(const_buffers_1& buff, std::function<void(size_t, boost::system::error_code)> callback);
boost::asio::async_write(ip::tcp::socket, const_buffers_1& buff,
std::function<void(size_t, boost::system::error_code)> callback);
boost::asio::buffer<T>缓冲区转换函数
可以将标准容器包括:std::vector、std::string等、原生数组、智能指针、自定义缓冲区等转换为Boost库的mutable_buffers_1或const_buffers_1类型,其中非const类型转换为mutable_buffers_1,const类型转换为const_buffers_1。
c++
// T为 数组 / 连续容器 / 字符串(可自动推断大小)
template <typename T>
mutable_buffers_1 buffer(T); // 非 const 数组,可读写
template <typename T>
const_buffers_1 buffer(const T); // const 数组,只读
// T为原生数组、智能指针、自定义缓冲区(显式指定大小)
template <typename T>
mutable_buffers_1 buffer(T* data, size_t size); // 可写内存块
template <typename T>
const_buffers_1 buffer(const T* data, size_t size); // 只读内存块
关闭
关闭通信套接字,包括全关闭、半关闭等。
cpp
socket.close(boost::system::error_code& ec); // 关闭并释放资源(触发四次挥手)
socket.shutdown(boost::asio::ip::tcp::socket::shutdown_send); // 只关发送
socket.shutdown(boost::asio::ip::tcp::socket::shutdown_receive); // 只关接收
socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both); // 全关
服务器大量连接时主动
close会造成close_wait资源浪费;优雅做法是shutdown(shutdown_send)后等客户端close。
端点信息
cpp
socket.remote_endpoint().address().to_string(); // 对端(客户端/服务器)IP
socket.remote_endpoint().port(); // 对端端口
socket.local_endpoint().address().to_string(); // 本地自动绑定 IP
socket.local_endpoint().port(); // 本地端口
6. ip::tcp::acceptor(服务器监听)
专门用于服务器监听并接受客户端的连接,内部会自动创建Socket套接字去监听客户端的连接,并提供一系列方法实现监听客户单:open、bind、listen、accept或async_accept函数。
构造函数
c++
// 默认构造创建未绑定的tcp::acceptor,需要手动open、bind、listen
acceptor(io_context& io_context);
// 创建未绑定的tcp::acceptor并进行打开,需要手动bind、listen
acceptor(io_context& io_context,ip::tcp::protocol protocol);
// 创建并初始化tcp::acceptor,自动完成open、bind、listen
acceptor(io_context& io_context, const ip::tcp::endpoint& endpoint);
打开、绑定、监听、接受函数
对应于Socket编程的socket、bind、listen、accept函数,实现服务器监听客户端的连接,接受客户端的连接包括同步方式和异步方式,同步方式会阻塞直到服务器监听到客户端的连接,异步方式会立即返回,在异步事件循环io_context中执行接受客户端连接后的回调函数。
c++
// 打开操作
// 静态函数ip::tcp::v4()、ip::tcp::v6()或ip::tcp::endpoint::protocol成员函数获取ip::tcp::protocol类型的IP协议对象
open(ip::tcp::protocol protocol):
// 绑定操作
// 将acceptor绑定到本地IP地址和端口号上,用于监听外部客户端对服务器的连接请求。
bind(const ip::tcp::endpoint& endpoint);
// 监听操作
// acceptor对象设置监听连接请求,backlog表示一次性最大的客户端连接请求个数,有默认值socket_base::max_connections为128。
listen(int backlog=socket_base::max_connections)
// 接受操作
// 先创建一个服务器与客户端通信的套接字socket传入
// 同步方式
void accept(ip::tcp::socket& socket);
// 异步方式
void async_accept(ip::tcp::socket& socket,std::function<const boost::system::error_code&> callback);
7. 字节序转换函数
字节序(大小端):主机多小端,网络传输用大端(网络字节序)。
cpp
// 主机字节序 -> 网络字节序
short h2n_short = boost::asio::detail::socket_ops::host_to_network_short(host_short); // 16 位
int h2n_long = boost::asio::detail::socket_ops::host_to_network_long(host_int); // 32 位
// 网络字节序 -> 主机字节序
short n2h_short = boost::asio::detail::socket_ops::network_to_host_short(net_short);
int n2h_long = boost::asio::detail::socket_ops::network_to_host_long(net_long);
二进制数据(整数、浮点)收发需做字节序转换;文本字符串一般无需转换。
五、Boost.Beast HTTP 网络库
1. HTTP 协议
基于 TCP 的无状态、请求-响应式应用层协议,短连接,通信完连接关闭。报文分包头(Header)和包体(Body):
- 请求 :请求行(方法 + 路径 + 版本,如
GET /index.html HTTP/1.1)+ 请求头(Host/Accept/User-Agent/Cookie/Connection...)+ 请求体(POST/PUT 携带数据)。 - 响应 :状态行(如
HTTP/1.1 200 OK)+ 响应头(Content-Type/Content-Length/Set-Cookie/Server...)+ 响应体。
用裸 Asio 手写 HTTP 服务器需自行解析请求行/请求头并构造响应,非常复杂。使用 Beast 的
http::request/http::response与http::async_read/http::async_write即可自动解析/封装 HTTP 报文。
2. 消息体 Body 类型
Body 决定消息体如何存储与处理:
| Body 类型 | 说明 | 常用函数 |
|---|---|---|
http::string_body |
字符串存消息体,适合小文本(JSON/XML) | std::string& data()、size() |
http::file_body |
文件系统文件,适合大文件传输 | open(path, beast::file_mode::scan, ec)、size() |
http::dynamic_body |
底层为 multi_buffer,动态增减,适合大小未知数据 | data()、size()、empty() |
file_mode:scan(读文件作响应体)、write(写文件)、write_new(覆盖/新建写)。
3. http::request / http::response
模板类型:request = http::message<true, Body, Fields>;response = http::message<false, Body, Fields>。
request 常用成员:
cpp
boost::beast::http::request<boost::beast::http::dynamic_body> req;
req.method(boost::beast::http::verb::get); // 设请求方法
req.method(); // 取 http::verb
req.target("/count"); // 设资源路径
req.version(11); // HTTP/1.1
req.body(); // 请求体引用
req.prepare_payload(); // 自动同步 Content-Length 等头部
req.set(boost::beast::http::field::content_type, "application/json"); // 设头部
req[boost::beast::http::field::host]; // 取头部
HTTP 字段枚举 http::field:host、accept、user_agent、cookie、server、content_type、content_length...
response 常用成员:
cpp
boost::beast::http::response<boost::beast::http::dynamic_body> res;
res.result(boost::beast::http::status::ok); // 200
res.result(boost::beast::http::status::not_found); // 404
res.version(_request.version()); // 版本跟随请求
res.keep_alive(false); // 短连接
res.set(boost::beast::http::field::server, "Beast");
res.set(boost::beast::http::field::content_type, "text/html");
boost::beast::ostream(res.body()) << "<html>...</html>"; // 流式写响应体
res.content_length(res.body().size());
http::status 常用:ok(200)、bad_request(400)、not_found(404)。
判断请求类型(服务器路由):
cpp
switch (_request.method()) {
case boost::beast::http::verb::get: ... break;
case boost::beast::http::verb::post: ... break;
default: ...
}
if (_request.target() == "/count") { ... }
4. http::async_read / http::async_write
cpp
// 异步读取并解析一个 HTTP 报文到 msg
boost::beast::http::async_read(_socket, _recv_buffer, _request,
[self](boost::system::error_code ec, std::size_t byte){ ... });
// stream: socket/tcp_stream; buffer: flat_buffer/multi_buffer;
// msg: request 或 response; handler: void(ec, size_t)
// 异步写一个 HTTP 报文
boost::beast::http::async_write(_socket, _response,
[self](boost::system::error_code ec, std::size_t byte){ ... });
5. Beast 常用辅助(buffer / ostream / flat_buffer)
boost::beast::ostream(body):把动态缓冲区当作std::ostream流式写入。boost::beast::buffers_to_string(buffers.data()):缓冲区 →std::string。boost::beast::flat_buffer(n):连续内存的动态缓冲区,data()/size()/consume(n)。boost::beast::multi_buffer:多片段缓冲区,data()/size()。boost::ignore_unused(x):显式忽略未使用参数,避免告警。
HTTP 服务器完整通信对象骨架 (本项目 HttpServer_Communicate):
cpp
class HttpServer_Communicate : public std::enable_shared_from_this<HttpServer_Communicate> {
boost::asio::ip::tcp::socket _socket;
boost::beast::flat_buffer _recv_buffer; // 收包缓冲
boost::beast::http::request<boost::beast::http::dynamic_body> _request;
boost::beast::http::response<boost::beast::http::dynamic_body> _response;
boost::asio::steady_timer _deadline_timer; // 超时断连
void start_http_communicate() { check_deadline(); recv_request(); }
// 1) async_read 收请求 -> 2) process_request 按 method/target 构造响应
// 3) async_write 发响应 -> shutdown(shutdown_send) + cancel() 定时器
};
本项目还演示了 HTTP 服务器接收 POST 的 JSON,用 jsoncpp 解析后回 JSON 响应:
cpp
std::string body_str = boost::beast::buffers_to_string(_request.body().data());
Json::Reader reader;
Json::Value recv_root;
bool ok = reader.parse(body_str, recv_root);
std::string email = recv_root["email"].asString();
Json::Value root; root["error"] = 0; root["msg"] = "提交数据成功!";
boost::beast::ostream(_response.body()) << root.toStyledString();
六、Boost.Beast WebSocket 网络库
WebSocket 本质是在 TCP 长连接之上的进一步封装,提供全双工 、基于帧的通信。两种建立方式:
- 先 TCP 连接、再协议升级 :把已连接 socket 转移构造为
websocket::stream,服务器读 HTTP 请求头判断Upgrade: websocket,回101,再握手升级。 - 直接构造 stream :从
stream取最低层裸 socket 连接,成功后用握手函数升级。
cpp
// 底层流类型:socket(基础)或 beast::tcp_stream(支持超时、速率限制等高级功能)
boost::beast::websocket::stream<boost::beast::tcp_stream> ws(ioc);
// 客户端/服务器握手
ws.handshake(host, target); // 客户端同步
ws.async_handshake(host, target, cb);// 客户端异步
ws.accept(); // 服务器同步
ws.async_accept(cb); // 服务器异步
// 收发(自动封装/解析 websocket 帧)
ws.async_read(buf, cb); // void(ec, size_t)
ws.async_write(buf, cb); // void(ec, size_t)
// 控制帧与关闭
ws.ping("reason"); / ws.async_ping(...);
ws.pong("reason"); / ws.async_pong(...);
ws.close(boost::beast::websocket::close_code::normal, "bye");
ws.async_close(...);
// 底层访问
ws.next_layer(); // 下一层(tcp)引用
boost::beast::get_lowest_layer(ws); // 最底层 socket
注意事项:
websocket::stream非线程安全,异步多线程下需用strand保证串行;若用tcp_stream作为底层,需禁用其自身超时,避免与 WebSocket 超时冲突(未定义行为)。
七、异步高并发服务器
1. 三种 Asio 多线程模型
-
多 io_context 并发、io_context 内串行 (
Asio_Iocontext_Pool):创建多个io_context并创建相应数量的子线程,让每一个io_context在子线程中执行run函数,实现每个io_context的异步事件循环在不同的子线程中进行。并提供一个外界使用io_context的接口给外界使用,在创建服务器与客户端通信的socket时使用,这样就会使基于该socket通信的异步读写事件在相应的io_context的异步事件循环中,从而在不同子线程中执行。c++class Asio_Iocontext_Pool:public Singleton<Asio_Iocontext_Pool> { friend class Singleton<Asio_Iocontext_Pool>;//声明CRTP方式的单例父类为友元 private: //无参构造函数:RAII思想 Asio_Iocontext_Pool(int core_num = std::thread::hardware_concurrency()):Singleton<Asio_Iocontext_Pool>(), vect_io_contexts(core_num) { //循环创建work_guard管理每一个io_context对象 for (int i = 0; i < core_num; i++) vect_work_guards.emplace_back(vect_io_contexts.at(i).get_executor()); //循环创建子线程,每个子线程运行一个io_context的run方法的异步事件循环,在该子线程内执行异步事件循环。 for (int i = 0; i < core_num; i++) vect_threads.emplace_back([this, i]() { vect_io_contexts[i].run();//每一个io_context在一个子线程中执行run函数进行在该子线程内异步事件循环 }); } //成员变量 std::vector<boost::asio::io_context> vect_io_contexts;//保存多个io_context对象,分别代表一个线程执行一部分socket的异步操作 std::vector<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> vect_work_guards;//管理io_context对象,防止没有异步事件直接退出异步事件循环 std::vector<std::thread> vect_threads;//保存多个io_context对象的运行子线程 public: //析构函数:RAII思想 ~Asio_Iocontext_Pool() { stop(); } //停止函数,方便停止所有io_context的异步事件循环,等待子线程运行结束,释放所有资源 void stop() { int core_num = std::thread::hardware_concurrency();//获取可以并行的线程个数,即并行的io_context的个数 //先关闭所有事件循环的io_context for (int i = 0; i < core_num; i++) { vect_work_guards.at(i).reset();//重置所有io_context的管理者,以告知io_context的异步事件结束 vect_io_contexts[i].stop();//关闭所有的io_context的异步事件循环 } //等待所有子线程运行结束 for (std::thread& t : vect_threads) if (t.joinable()) t.join(); } //提供可以访问到io_context对象引用的公共函数,以方便外部使用,采用io_context_number记录以顺序的方式使用io_context,保证平均分配 boost::asio::io_context& get_Io_context() { static unsigned short io_context_number = 0;//记录使用的io_context对象的序号,静态变量只在第一次使用时初始化,并且在生命周期和进程相同,防止不必要的重复创建 static unsigned short core_num = std::thread::hardware_concurrency();//获取并行线程数,即io_context的个数 boost::asio::io_context& temp = vect_io_contexts[io_context_number]; io_context_number = (io_context_number + 1) % core_num;//遍历方式平均使用io_context return temp;//引用方式返回io_context对象 } }; -
单 io_context、io_context 内并发 (
Asio_Thread_Pool):创建一个io_context和多个子线程,每个子线程执行该io_context的run方法,这样使每个子线程都能执行该io_context的异步事件循环的异步就绪事件,实现并发,但会造成读写的线程安全问题,还需要提供一个绑定该io_context的strand对象,使用boost::asio::bind_exector函数绑定异步操作的回调函数到该strand对象上,实现异步事件在io_context的串行执行,以牺牲并发效率实现线程安全。c++class Asio_Thread_Pool:public Singleton<Asio_Thread_Pool> { friend class Singleton<Asio_Thread_Pool>; private: //构造函数:RAII思想 Asio_Thread_Pool(int core_num = std::thread::hardware_concurrency()):Singleton<Asio_Thread_Pool>(), work_guard(io_context.get_executor()), bind_strand(io_context.get_executor()) { //创建多个子线程执行io_context的run函数 for (int i = 0; i < core_num; i++) vect_threads.emplace_back([this]() { io_context.run();//实现子线程并发执行io_context的异步事件循环 }); } //成员变量 boost::asio::io_context io_context;//asio多线程使用的同一个io_context异步事件循环 boost::asio::executor_work_guard<boost::asio::io_context::executor_type> work_guard;//管理io_context,防止在异步事件没有时直接退出循环 boost::asio::strand<boost::asio::io_context::executor_type> bind_strand;//绑定io_context异步事件到strand对象中,实现串行执行 std::vector<std::thread> vect_threads;//asio多线程模型中的子线程,用于执行run方法 public: //析构函数:RAII思想 ~Asio_Thread_Pool() { stop(); } //停止函数,停止事件循环,并等到线程运行结束,释放资源 void stop() { //停止io_context的事件循环 work_guard.reset(); io_context.stop(); //等待所有子线程运行结束 for (std::thread& t : vect_threads) if (t.joinable()) t.join(); } //获取asio线程池的io_context对象的引用,以提供给外界使用 boost::asio::io_context& get_Io_context() { return io_context; } //获取asio线程池的strand对象的引用,以提供给外界使用,实现绑定异步事件的回调函数在io_context的事件循环中串行执行 boost::asio::strand<boost::asio::io_context::executor_type>& get_Strand() { return bind_strand; } }; -
多 io_context 并发且 io_context 内并发 (
Asio_Iocontext_Thread_Pool,项目采用):多个 io_context × 每 io_context 多个线程,每个 io_context 配一个 strand;连接层用seq_num轮询选 io_context 与 strand。C++class Asio_Iocontext_Thread_Pool :public Singleton<Asio_Iocontext_Thread_Pool> { friend class Singleton<Asio_Iocontext_Thread_Pool>; private: //构造函数:RAII思想 Asio_Iocontext_Thread_Pool(int core_num = std::thread::hardware_concurrency()) :Singleton<Asio_Iocontext_Thread_Pool>(), vect_io_context(core_num), vvect_thread(core_num) { for (int i = 0; i < core_num; i++) { vect_work_guard.emplace_back(vect_io_context.at(i).get_executor()); vect_strand.emplace_back(vect_io_context[i].get_executor()); } for (int i = 0; i < core_num; i++) for (int j = 0; j < 2; j++) vvect_thread[i].emplace_back([this, i]() { vect_io_context[i].run();//vect_io_context[i]表示第i个io_context创建子线程运行run方法,默认创建两个子线程 }); } //成员变量 std::vector<boost::asio::io_context> vect_io_context;//保存多个io_context std::vector<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> vect_work_guard;//管理io_context在没有异步事件时不退出 std::vector<boost::asio::strand<boost::asio::io_context::executor_type>> vect_strand;//为每一个io_context提供一个strand进行管理异步事件串行执行 std::vector<std::vector<std::thread>> vvect_thread;//二维vector保存子线程,行数和io_context并发数相等,列数和io_context内并发线程数据相等 public: //析构函数:RAII思想 ~Asio_Iocontext_Thread_Pool() { stop(); } //停止函数,先停止io_context的异步事件循环,阻塞等待所有子线程的退出,最后释放资源 void stop() { //停止io_context的异步事件循环 for (boost::asio::executor_work_guard<boost::asio::io_context::executor_type>& work : vect_work_guard) work.reset(); for (boost::asio::io_context& ioc : vect_io_context) ioc.stop(); //阻塞等待子线程执行完毕 for (int i = 0; i < std::thread::hardware_concurrency(); i++) for (std::thread& t : vvect_thread[i]) if (t.joinable()) t.join(); } //提供给外部使用io_context的接口 boost::asio::io_context& get_Io_context(int i) { return vect_io_context.at(i);//返回第i个io_context,用于创建服务器与客户端通信的io_context } //提供给外部使用strand的接口 boost::asio::strand<boost::asio::io_context::executor_type>& get_Strand(int i) { return vect_strand[i];//返回第i个strand,用于绑定第i个io_context的异步事件 } };
2. Boost库实现异步高并发服务器
本服务器采用 分层架构(连接层 / 通信层 / 逻辑层) + TLV 应用层协议 + 多 io_context 多线程 Asio 模型,解决同步阻塞、粘包、字节序、发送有序性、对象生命周期、通信逻辑解耦等网络编程核心问题。
一、总体架构与分层设计
服务器按职责划分为三层,外加一个"多线程运行底座",各层职责单一、通过接口解耦:
| 层次 | 类 | 职责 |
|---|---|---|
| 服务器连接层 | Tcp_Server |
创建 acceptor 循环 async_accept;为每个客户端创建通信对象并登记到 map<uuid, shared_ptr> 统一管理 |
| 服务器通信层 | Server_Communicate_Client |
拥有通信 socket;异步接收/发送数据;在接收回调内做粘包切包;把完整消息投递给逻辑层 |
| 服务器逻辑处理层 | Logic_System(单例)+ LogicNode |
独立线程从队列取消息,按消息 ID 查表执行处理函数,处理结果通过通信层对象发回客户端 |
| 公共缓冲结构 | MsgNode |
TLV 收/发缓冲区:消息ID(2B)+消息长度(2B)+消息本身,记录总长/当前长实现收发完整性 |
| 运行底座(多线程模型) | Asio_Iocontext_Thread_Pool |
提供多个 io_context、每个 io_context 配 strand 和多线程事件循环,通信层按 seq_num 轮询选用 |
连接建立阶段
服务器先准备一个客户端连接器,然后异步等待接收客户端的TCP连接,在接受客户端连接的异步回调函数中,对连接成功的客户端通过该客户端连接器进行全双工通信。
Asio_Iocontext_Thread_Pool Tcp_Server(连接层) Server_Communicate_Client Tcp_Server.acceptor 客户端(任意) Asio_Iocontext_Thread_Pool Tcp_Server(连接层) Server_Communicate_Client Tcp_Server.acceptor 客户端(任意) #mermaid-svg-9kHjbYr6ShgR1WpI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9kHjbYr6ShgR1WpI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9kHjbYr6ShgR1WpI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9kHjbYr6ShgR1WpI .error-icon{fill:#552222;}#mermaid-svg-9kHjbYr6ShgR1WpI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9kHjbYr6ShgR1WpI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9kHjbYr6ShgR1WpI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9kHjbYr6ShgR1WpI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9kHjbYr6ShgR1WpI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9kHjbYr6ShgR1WpI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9kHjbYr6ShgR1WpI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9kHjbYr6ShgR1WpI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9kHjbYr6ShgR1WpI .marker.cross{stroke:#333333;}#mermaid-svg-9kHjbYr6ShgR1WpI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9kHjbYr6ShgR1WpI p{margin:0;}#mermaid-svg-9kHjbYr6ShgR1WpI .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-9kHjbYr6ShgR1WpI text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-9kHjbYr6ShgR1WpI .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-9kHjbYr6ShgR1WpI .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-9kHjbYr6ShgR1WpI .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-9kHjbYr6ShgR1WpI .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-9kHjbYr6ShgR1WpI #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-9kHjbYr6ShgR1WpI .sequenceNumber{fill:white;}#mermaid-svg-9kHjbYr6ShgR1WpI #sequencenumber{fill:#333;}#mermaid-svg-9kHjbYr6ShgR1WpI #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-9kHjbYr6ShgR1WpI .messageText{fill:#333;stroke:none;}#mermaid-svg-9kHjbYr6ShgR1WpI .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-9kHjbYr6ShgR1WpI .labelText,#mermaid-svg-9kHjbYr6ShgR1WpI .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-9kHjbYr6ShgR1WpI .loopText,#mermaid-svg-9kHjbYr6ShgR1WpI .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-9kHjbYr6ShgR1WpI .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-9kHjbYr6ShgR1WpI .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-9kHjbYr6ShgR1WpI .noteText,#mermaid-svg-9kHjbYr6ShgR1WpI .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-9kHjbYr6ShgR1WpI .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-9kHjbYr6ShgR1WpI .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-9kHjbYr6ShgR1WpI .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-9kHjbYr6ShgR1WpI .actorPopupMenu{position:absolute;}#mermaid-svg-9kHjbYr6ShgR1WpI .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-9kHjbYr6ShgR1WpI .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-9kHjbYr6ShgR1WpI .actor-man circle,#mermaid-svg-9kHjbYr6ShgR1WpI line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-9kHjbYr6ShgR1WpI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} altec == 0 连接成功ec != 0 失败 getInstance()->>get_Io_context(seq_num) + get_Strand(seq_num)1make_shared<Server_Communicate_Client>(ioc,this,seq_num)2acceptor.async_accept(comm->>get_socket(), accept_callback)3connect() 发起 TCP 连接4accept_callback(ec, comm_ptr)5comm_ptr->>communicate() 开始全双工通信6map_communicateuuid=comm_ptr (加锁)7seq_num=(seq_num+1)%core 轮询下一个 io_context8打印错误9circle_acceptor() 继续接受下一个连接10
一次数据请求处理(读 → 逻辑 → 写)
在客户端完成和服务器的连接并建立全双工通信后,服务器开始异步接收客户端发送的数据,在异步接收的回调函数中进行黏包处理,采用TLV格式先读取指定长度的包头获取消息ID和消息长度,然后根据消息长度进行读取分隔一个数据包体,得到一个完整到的TLV格式的数据包,并将该数据包提交给逻辑系统进行处理,逻辑系统根据消息ID调用对应注册的回调函数处理,将处理结果通过异步发送回调函数进行发送。
Server连接层 Server_Communicate_Client(send/write_callback) Logic_System 单例(逻辑线程) MsgNode(recv_head_node/recv_data_node) Server_Communicate_Client(receive/read_some_callback) Server_Communicate_Client.socket 客户端 Server连接层 Server_Communicate_Client(send/write_callback) Logic_System 单例(逻辑线程) MsgNode(recv_head_node/recv_data_node) Server_Communicate_Client(receive/read_some_callback) Server_Communicate_Client.socket 客户端 #mermaid-svg-4R8bBqe8BO9QQv8B{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4R8bBqe8BO9QQv8B .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4R8bBqe8BO9QQv8B .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4R8bBqe8BO9QQv8B .error-icon{fill:#552222;}#mermaid-svg-4R8bBqe8BO9QQv8B .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4R8bBqe8BO9QQv8B .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4R8bBqe8BO9QQv8B .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4R8bBqe8BO9QQv8B .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4R8bBqe8BO9QQv8B .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4R8bBqe8BO9QQv8B .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4R8bBqe8BO9QQv8B .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4R8bBqe8BO9QQv8B .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4R8bBqe8BO9QQv8B .marker.cross{stroke:#333333;}#mermaid-svg-4R8bBqe8BO9QQv8B svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4R8bBqe8BO9QQv8B p{margin:0;}#mermaid-svg-4R8bBqe8BO9QQv8B .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-4R8bBqe8BO9QQv8B text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-4R8bBqe8BO9QQv8B .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-4R8bBqe8BO9QQv8B .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-4R8bBqe8BO9QQv8B .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-4R8bBqe8BO9QQv8B .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-4R8bBqe8BO9QQv8B #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-4R8bBqe8BO9QQv8B .sequenceNumber{fill:white;}#mermaid-svg-4R8bBqe8BO9QQv8B #sequencenumber{fill:#333;}#mermaid-svg-4R8bBqe8BO9QQv8B #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-4R8bBqe8BO9QQv8B .messageText{fill:#333;stroke:none;}#mermaid-svg-4R8bBqe8BO9QQv8B .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-4R8bBqe8BO9QQv8B .labelText,#mermaid-svg-4R8bBqe8BO9QQv8B .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-4R8bBqe8BO9QQv8B .loopText,#mermaid-svg-4R8bBqe8BO9QQv8B .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-4R8bBqe8BO9QQv8B .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-4R8bBqe8BO9QQv8B .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-4R8bBqe8BO9QQv8B .noteText,#mermaid-svg-4R8bBqe8BO9QQv8B .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-4R8bBqe8BO9QQv8B .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-4R8bBqe8BO9QQv8B .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-4R8bBqe8BO9QQv8B .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-4R8bBqe8BO9QQv8B .actorPopupMenu{position:absolute;}#mermaid-svg-4R8bBqe8BO9QQv8B .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-4R8bBqe8BO9QQv8B .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-4R8bBqe8BO9QQv8B .actor-man circle,#mermaid-svg-4R8bBqe8BO9QQv8B line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-4R8bBqe8BO9QQv8B :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} "客户端发送 TLV 包 ID\|LEN\|DATA" "校验 data_len>MAX 或 data_id>MAX 则置 receive_error 丢弃" loop"while byte_num \> 0 (一次可能包含多个完整包)" altec != 0数据合法 "数据到达(可能粘包/半包)"1"async_read_some(bind_executor(strand, read_some_callback, shared_from_this()))"2"read_some_callback(ec, byte_num, self)"3"delete_map_communicate_member(uuid) / 投递错误消息"4"拷贝拼装头部(recv_head_node) → 解析 data_id/data_len"5"接收数据本身(recv_data_node) 凑齐完整包"6"post_LogicNode(make_shared<LogicNode>(shared_from_this(), recv_data_node))"7"继续处理本次剩余字节 / receive() 再次接收(保证一直接收)"8"逻辑线程 wait 队列被唤醒, pop LogicNode"9"map_id_funcmsg-\>\>get_ID() 找到处理函数并执行"10"communicate_ptr->>send(data,len,id) 发送处理结果"11"入发送队列 queue_send_data (加锁, 若队空才发起 async_write)"12"async_write(bind_executor(strand, write_callback, shared_from_this()))"13"write_callback(ec, n, self)"14"队首已发完"15"收到服务器响应数据"16
服务器关闭流程
#mermaid-svg-ylPMHO3Klzqj4HAX{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ylPMHO3Klzqj4HAX .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ylPMHO3Klzqj4HAX .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ylPMHO3Klzqj4HAX .error-icon{fill:#552222;}#mermaid-svg-ylPMHO3Klzqj4HAX .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ylPMHO3Klzqj4HAX .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ylPMHO3Klzqj4HAX .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ylPMHO3Klzqj4HAX .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ylPMHO3Klzqj4HAX .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ylPMHO3Klzqj4HAX .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ylPMHO3Klzqj4HAX .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ylPMHO3Klzqj4HAX .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ylPMHO3Klzqj4HAX .marker.cross{stroke:#333333;}#mermaid-svg-ylPMHO3Klzqj4HAX svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ylPMHO3Klzqj4HAX p{margin:0;}#mermaid-svg-ylPMHO3Klzqj4HAX .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ylPMHO3Klzqj4HAX .cluster-label text{fill:#333;}#mermaid-svg-ylPMHO3Klzqj4HAX .cluster-label span{color:#333;}#mermaid-svg-ylPMHO3Klzqj4HAX .cluster-label span p{background-color:transparent;}#mermaid-svg-ylPMHO3Klzqj4HAX .label text,#mermaid-svg-ylPMHO3Klzqj4HAX span{fill:#333;color:#333;}#mermaid-svg-ylPMHO3Klzqj4HAX .node rect,#mermaid-svg-ylPMHO3Klzqj4HAX .node circle,#mermaid-svg-ylPMHO3Klzqj4HAX .node ellipse,#mermaid-svg-ylPMHO3Klzqj4HAX .node polygon,#mermaid-svg-ylPMHO3Klzqj4HAX .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ylPMHO3Klzqj4HAX .rough-node .label text,#mermaid-svg-ylPMHO3Klzqj4HAX .node .label text,#mermaid-svg-ylPMHO3Klzqj4HAX .image-shape .label,#mermaid-svg-ylPMHO3Klzqj4HAX .icon-shape .label{text-anchor:middle;}#mermaid-svg-ylPMHO3Klzqj4HAX .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ylPMHO3Klzqj4HAX .rough-node .label,#mermaid-svg-ylPMHO3Klzqj4HAX .node .label,#mermaid-svg-ylPMHO3Klzqj4HAX .image-shape .label,#mermaid-svg-ylPMHO3Klzqj4HAX .icon-shape .label{text-align:center;}#mermaid-svg-ylPMHO3Klzqj4HAX .node.clickable{cursor:pointer;}#mermaid-svg-ylPMHO3Klzqj4HAX .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ylPMHO3Klzqj4HAX .arrowheadPath{fill:#333333;}#mermaid-svg-ylPMHO3Klzqj4HAX .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ylPMHO3Klzqj4HAX .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ylPMHO3Klzqj4HAX .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ylPMHO3Klzqj4HAX .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ylPMHO3Klzqj4HAX .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ylPMHO3Klzqj4HAX .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ylPMHO3Klzqj4HAX .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ylPMHO3Klzqj4HAX .cluster text{fill:#333;}#mermaid-svg-ylPMHO3Klzqj4HAX .cluster span{color:#333;}#mermaid-svg-ylPMHO3Klzqj4HAX div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ylPMHO3Klzqj4HAX .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ylPMHO3Klzqj4HAX rect.text{fill:none;stroke-width:0;}#mermaid-svg-ylPMHO3Klzqj4HAX .icon-shape,#mermaid-svg-ylPMHO3Klzqj4HAX .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ylPMHO3Klzqj4HAX .icon-shape p,#mermaid-svg-ylPMHO3Klzqj4HAX .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ylPMHO3Klzqj4HAX .icon-shape .label rect,#mermaid-svg-ylPMHO3Klzqj4HAX .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ylPMHO3Klzqj4HAX .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ylPMHO3Klzqj4HAX .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ylPMHO3Klzqj4HAX :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户按 Ctrl+C
signal_set 触发 SIGINT/SIGTERM
async_wait 回调
外层 io_context_guard.reset + io_context.stop
Asio_Iocontext_Thread_Pool 单例 stop
逐个 work_guard.reset
逐个 io_context.stop
join 所有子线程
外层 io_context.run 返回
执行到右花括号释放对象
服务器连接层实现
职责:监听端口、循环异步接受连接、管理所有通信对象、为每个连接轮询分配 io_context/strand。
cpp
class Tcp_Server {
boost::asio::io_context& io_context; // 外层事件循环引用
boost::asio::ip::tcp::acceptor acceptor; // 监听器
unsigned short listen_port;
boost::asio::executor_work_guard<io_context::executor_type> work_guard; // 防止外层 ioc 空转退出
std::map<std::string, std::shared_ptr<Server_Communicate_Client>> map_communicate; // 登记所有通信对象
std::mutex map_communicate_mutex; // 保护 map(多线程访问)
int seq_num; // 轮询:使用第几个 io_context/strand
};
构造函数(RAII):
cpp
Tcp_Server::Tcp_Server(io_context& ioc, unsigned short port)
: io_context(ioc), listen_port(port), acceptor(ioc),
work_guard(ioc.get_executor()), seq_num(0) {
acceptor.open(boost::asio::ip::tcp::v4()); // 打开 IPv4
acceptor.bind(boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), listen_port));
acceptor.listen(128); // 监听
circle_acceptor(); // 开始循环接受
}
循环接受(异步 + 智能指针保活 + 轮询分配 io_context):
cpp
void Tcp_Server::circle_acceptor() {
// 使用线程池第 seq_num 个 io_context 创建通信对象
std::shared_ptr<Server_Communicate_Client> ptr =
std::make_shared<Server_Communicate_Client>(
Asio_Iocontext_Thread_Pool::getInstance()->get_Io_context(seq_num), this, seq_num);
acceptor.async_accept(ptr->get_socket(),
std::bind(&Tcp_Server::accept_callback, this, std::placeholders::_1, ptr));
}
接受回调:
cpp
void Tcp_Server::accept_callback(boost::system::error_code ec,
std::shared_ptr<Server_Communicate_Client> ptr) {
if (ec.value() == 0) {
ptr->communicate(); // 1. 开始全双工通信
std::lock_guard<std::mutex> lock(map_communicate_mutex);
map_communicate.insert({ptr->get_uuid(), ptr}); // 2. 登记,防止对象被析构
seq_num = (seq_num + 1) % std::thread::hardware_concurrency(); // 3. 轮询
} else {
// 打印连接失败
}
circle_acceptor(); // 4. 继续接受下一个
}
移除通信对象(回调出错时只从 map 移除,不 delete):
cpp
void Tcp_Server::delete_map_communicate_member(std::string uuid) {
std::lock_guard<std::mutex> lock(map_communicate_mutex);
map_communicate.erase(uuid);
}
设计要点:
map<uuid, shared_ptr>保存通信对象 → 保证在服务器存活期间通信对象不会被析构,便于按 uuid 管理/查找。- map 被多线程访问 → 用
std::mutex保护。 - 出错回调只做"移除",配合
shared_from_this()保活 → 避免二次析构。
服务器通信层
职责 :持有一个通信 socket;循环异步接收;在接收回调里完成粘包切包与合法性校验;把完整消息投递给逻辑层;通过发送队列保证高并发下异步发送的有序与完整。
cpp
class Server_Communicate_Client : public std::enable_shared_from_this<Server_Communicate_Client> {
boost::asio::ip::tcp::socket socket;
std::string uuid; // 唯一标识(来自 boost::uuids)
std::queue<std::shared_ptr<MsgNode>> queue_send_data; // 发送队列
std::mutex queue_send_data_mutex; // 保护发送队列
std::shared_ptr<char[]> recv_data_ptr; // 原始接收缓冲(最大包长)
std::shared_ptr<MsgNode> recv_data_node;// 当前正在接收的数据本身
std::shared_ptr<MsgNode> recv_head_node;// 头部拼装/缓冲节点
bool head_finish; // 头部是否已拼完
bool receive_error; // 是否处于丢弃非法数据状态
Tcp_Server* server; // 反向引用连接层
int seq_num; // 记录所用 io_context/strand 序号
};
构造函数:初始化 socket、分配接收缓冲;创建头部节点;生成唯一 uuid:
cpp
Server_Communicate_Client::Server_Communicate_Client(io_context& ioc, Tcp_Server* server, int seq)
: socket(ioc), server(server), head_finish(false), receive_error(false), seq_num(seq) {
recv_data_ptr.reset(new char[MAX_MSG_LENGTH + HEAD_TOTAL_LENGTH]);
recv_head_node = std::make_shared<MsgNode>(HEAD_TOTAL_LENGTH);
boost::uuids::uuid uid = boost::uuids::random_generator()();
uuid = boost::uuids::to_string(uid);
}
接收入口(异步读 + 绑定 strand + 伪闭包):
cpp
void Server_Communicate_Client::receive() {
memset(recv_data_ptr.get(), 0, MAX_MSG_LENGTH + HEAD_TOTAL_LENGTH);
socket.async_read_some(
boost::asio::buffer(recv_data_ptr.get(), MAX_MSG_LENGTH + HEAD_TOTAL_LENGTH),
boost::asio::bind_executor(
Asio_Iocontext_Thread_Pool::getInstance()->get_Strand(seq_num),
std::bind(&Server_Communicate_Client::read_some_callback, this,
std::placeholders::_1, std::placeholders::_2, shared_from_this())));
}
接收回调核心逻辑(切包状态机):
#mermaid-svg-PSiBMQ500W6grR8a{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PSiBMQ500W6grR8a .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PSiBMQ500W6grR8a .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PSiBMQ500W6grR8a .error-icon{fill:#552222;}#mermaid-svg-PSiBMQ500W6grR8a .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PSiBMQ500W6grR8a .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PSiBMQ500W6grR8a .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PSiBMQ500W6grR8a .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PSiBMQ500W6grR8a .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PSiBMQ500W6grR8a .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PSiBMQ500W6grR8a .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PSiBMQ500W6grR8a .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PSiBMQ500W6grR8a .marker.cross{stroke:#333333;}#mermaid-svg-PSiBMQ500W6grR8a svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PSiBMQ500W6grR8a p{margin:0;}#mermaid-svg-PSiBMQ500W6grR8a .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PSiBMQ500W6grR8a .cluster-label text{fill:#333;}#mermaid-svg-PSiBMQ500W6grR8a .cluster-label span{color:#333;}#mermaid-svg-PSiBMQ500W6grR8a .cluster-label span p{background-color:transparent;}#mermaid-svg-PSiBMQ500W6grR8a .label text,#mermaid-svg-PSiBMQ500W6grR8a span{fill:#333;color:#333;}#mermaid-svg-PSiBMQ500W6grR8a .node rect,#mermaid-svg-PSiBMQ500W6grR8a .node circle,#mermaid-svg-PSiBMQ500W6grR8a .node ellipse,#mermaid-svg-PSiBMQ500W6grR8a .node polygon,#mermaid-svg-PSiBMQ500W6grR8a .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PSiBMQ500W6grR8a .rough-node .label text,#mermaid-svg-PSiBMQ500W6grR8a .node .label text,#mermaid-svg-PSiBMQ500W6grR8a .image-shape .label,#mermaid-svg-PSiBMQ500W6grR8a .icon-shape .label{text-anchor:middle;}#mermaid-svg-PSiBMQ500W6grR8a .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PSiBMQ500W6grR8a .rough-node .label,#mermaid-svg-PSiBMQ500W6grR8a .node .label,#mermaid-svg-PSiBMQ500W6grR8a .image-shape .label,#mermaid-svg-PSiBMQ500W6grR8a .icon-shape .label{text-align:center;}#mermaid-svg-PSiBMQ500W6grR8a .node.clickable{cursor:pointer;}#mermaid-svg-PSiBMQ500W6grR8a .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PSiBMQ500W6grR8a .arrowheadPath{fill:#333333;}#mermaid-svg-PSiBMQ500W6grR8a .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PSiBMQ500W6grR8a .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PSiBMQ500W6grR8a .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PSiBMQ500W6grR8a .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PSiBMQ500W6grR8a .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PSiBMQ500W6grR8a .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PSiBMQ500W6grR8a .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PSiBMQ500W6grR8a .cluster text{fill:#333;}#mermaid-svg-PSiBMQ500W6grR8a .cluster span{color:#333;}#mermaid-svg-PSiBMQ500W6grR8a div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PSiBMQ500W6grR8a .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PSiBMQ500W6grR8a rect.text{fill:none;stroke-width:0;}#mermaid-svg-PSiBMQ500W6grR8a .icon-shape,#mermaid-svg-PSiBMQ500W6grR8a .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PSiBMQ500W6grR8a .icon-shape p,#mermaid-svg-PSiBMQ500W6grR8a .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PSiBMQ500W6grR8a .icon-shape .label rect,#mermaid-svg-PSiBMQ500W6grR8a .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PSiBMQ500W6grR8a .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PSiBMQ500W6grR8a .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PSiBMQ500W6grR8a :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
是
否
是
未拼完
不足
够
非法
合法
未收完
已收完
是
否
read_some_callback 收到 byte_num 字节
ec.value==0?
出错: delete_map / 投递错误消息
receive_error 丢弃非法数据?
本次是否丢弃完非法字节
不够: 更新剩余非法字节继续 receive
相等: 恢复合法状态 receive
部分非法: 丢弃前段, 继续处理剩余
while byte_num>0
return
head_finish? 头部拼装
拼完 4 字节头?
拷入 recv_head_node 后 receive 继续收
解析 data_id/data_len 并转主机字节序
data_len>MAX 或 id>MAX?
投递错误消息到逻辑层 + 置 receive_error 丢弃后续
接收数据本身到 recv_data_node
本次是否收完整包
拷入节点后 receive 继续收
投递 LogicNode 到逻辑层
还有剩余字节?
再次 receive 继续监听
要点:读回调内永远再次调用 receive() → 保证服务器"一直接收"客户端数据,实现全双工。
发送入口(队列化串行异步发送):
cpp
void Server_Communicate_Client::send(char* send_data_ptr, int data_len, short data_id) {
bool send_nofinish = false;
std::lock_guard<std::mutex> lock(queue_send_data_mutex);
if (!queue_send_data.empty()) send_nofinish = true; // 上次还没发完
if (queue_send_data.size() > QUEUE_SEND_SIZE) return; // 队列已满则丢弃
queue_send_data.push(std::make_shared<MsgNode>(send_data_ptr, data_len, data_id));
if (send_nofinish) return; // 正在发,不能并发发起 async_write
boost::asio::async_write(
socket,
boost::asio::buffer(queue_send_data.front()->data, queue_send_data.front()->total_lenth),
boost::asio::bind_executor(Asio_Iocontext_Thread_Pool::getInstance()->get_Strand(seq_num),
std::bind(&Server_Communicate_Client::write_callback, this,
std::placeholders::_1, std::placeholders::_2, shared_from_this())));
}
发送回调(发完队首 → 若队列仍有数据继续发下一个):
cpp
void Server_Communicate_Client::write_callback(error_code ec, size_t byte_num, shared_ptr self) {
if (ec.value() == 0) {
std::lock_guard<std::mutex> lock(queue_send_data_mutex);
queue_send_data.pop(); // 队首已发完
if (!queue_send_data.empty()) // 继续发下一个节点
boost::asio::async_write(socket, buffer(queue_send_data.front()->...),
std::bind(&...::write_callback, this, _1, _2, self));
} else if (eof / connection_reset) {
server->delete_map_communicate_member(uuid);
} else {
Logic_System::getInstance()->post_LogicNode(/*SEND_ERROR*/);
server->delete_map_communicate_member(uuid);
}
}
为什么用队列 :同一 socket 若同时存在多个进行中的 async_write,底层发送会交错、无法保证时序。采用"队列缓存 + 每次只发队首 + 发完再发下一个 ",从机制上保证发送有序且完整。
TLV 数据缓冲区类
作用:自定义收发缓冲,把"原始数据"包装成 TLV 包(发送)或按长度承接接收数据(接收)。
字段:
char* data:数据首地址;short id:数据 ID(默认-1表示头部节点,业务 ID 1001--2048);short total_lenth:应收/应发总长;short cur_lenth:已收/已发当前长度。
发送构造(把裸数据封装成 TLV,头部转网络字节序):
cpp
MsgNode(char* msg, short msg_len, short id)
: total_lenth(msg_len + HEAD_TOTAL_LENGTH), cur_lenth(0), id(id) {
data = new char[total_lenth + 1]();
short network_id = boost::asio::detail::socket_ops::host_to_network_short(id);
memcpy(data, &network_id, ID_LENGTH); // [0..2) ID
short network_msg_len = boost::asio::detail::socket_ops::host_to_network_short(msg_len);
memcpy(data + ID_LENGTH, &network_msg_len, REAL_MSG_LENGTH); // [2..4) 长度
memcpy(data + HEAD_TOTAL_LENGTH, msg, msg_len); // [4..) 数据
data[total_lenth] = '\0';
}
接收构造:
cpp
MsgNode(short data_len, short id = -1)
: total_lenth(data_len), cur_lenth(0), id(id) {
data = new char[data_len + 1]();
data[data_len] = '\0';
}
常用方法 :Clear()(清零并重置 cur_lenth)、get_ID()、get_DATA()、get_LENGTH()、析构 delete[] data;友元重载 << 打印数据本身。
发送时数据从"主机字节序"转"网络字节序"写入头部;接收切包时用
network_to_host_short转回主机字节序再使用。
服务器逻辑处理层
CRTP 线程安全单例
cpp
template<typename T>
class Singleton {
static std::shared_ptr<T> instance; // 全局唯一实例
Singleton(const Singleton&) = delete; // 禁拷贝
Singleton& operator=(const Singleton&) = delete;
protected:
Singleton() = default;
public:
static std::shared_ptr<T> getInstance() {
static std::once_flag once; // 保证 call_once 只真正调用一次
std::call_once(once, [&]() { instance = std::shared_ptr<T>(new T); });
return instance;
}
};
template<typename T> std::shared_ptr<T> Singleton<T>::instance = nullptr;
特点 :懒汉式、std::call_once 保证多线程下只创建一次、子类继承获得单例能力(CRTP)。
逻辑数据节点
cpp
class LogicNode {
friend class Logic_System;
std::shared_ptr<Server_Communicate_Client> communicate_ptr; // 该消息来自哪个通信对象
std::shared_ptr<MsgNode> recv_message; // TLV 消息(含 ID 与数据)
public:
LogicNode(std::shared_ptr<Server_Communicate_Client> c, std::shared_ptr<MsgNode> m)
: communicate_ptr(c), recv_message(m) {}
};
逻辑层只依赖"通信对象 + 消息",从而与具体 socket 细节解耦。
逻辑处理系统
cpp
class Logic_System : public Singleton<Logic_System> {
friend class Singleton<Logic_System>;
std::queue<std::shared_ptr<LogicNode>> queue_logicnode; // 待处理队列
std::mutex queue_logicnode_lock;
std::condition_variable queue_logicnode_cond; // 队列空则阻塞逻辑线程
std::thread logic_thread; // 逻辑工作线程(单线程)
std::map<short, ID_CALLBACK_FUNC> map_id_func; // ID -> 处理函数
std::atomic<bool> server_stop;
// 构造函数: register_id_callback() + 启动 logic_thread
// 析构: server_stop=true; notify_all; join 逻辑线程
public:
void post_LogicNode(const std::shared_ptr<LogicNode> message); // 通信层调用
};
注册表类型 :typedef std::function<void(std::shared_ptr<LogicNode>)> ID_CALLBACK_FUNC;
逻辑线程循环:
cpp
void Logic_System::work_deal_message() {
while (1) {
std::unique_lock<std::mutex> lock(queue_logicnode_lock);
queue_logicnode_cond.wait(lock, [this]() {
return server_stop || !queue_logicnode.empty(); // 空队列+未停止 → 阻塞
});
if (server_stop) { while(!queue_logicnode.empty()){/* 处理剩余 */} break; }
auto node = queue_logicnode.front(); queue_logicnode.pop();
lock.unlock();
auto it = map_id_func.find(node->recv_message->get_ID());
if (it == map_id_func.end()) continue; // 未注册则忽略
it->second(node); // 执行对应处理
}
}
注册处理函数(示例):
cpp
map_id_func.insert({RETURN_MESSAGE, [](std::shared_ptr<LogicNode> node) {
node->communicate_ptr->send(node->recv_message->get_DATA(),
node->recv_message->get_LENGTH(), SEND_MESSAGE);
}});
// ERROR_MESSAGE_LENGTH / ERROR_ID_NUM / SEND_ERROR / RECEIVE_ERROR 等类似
投放消息接口:
cpp
void Logic_System::post_LogicNode(const std::shared_ptr<LogicNode> message) {
std::unique_lock<std::mutex> lock(queue_logicnode_lock);
queue_logicnode.push(message);
queue_logicnode_cond.notify_one(); // 唤醒可能阻塞的逻辑线程
}
为什么解耦 :通信层收到完整包后立即返回继续接收,逻辑处理交给单线程逻辑层 → ①通信不被慢逻辑阻塞;②单线程处理天然避免数据竞争;③便于扩展不同业务(只需注册新的 ID 处理函数)。
3.粘包与自定义应用层 TLV 协议
TCP 是字节流,需应用层定义包格式(本项目用 TLV = 消息ID(2B) + 消息长度(2B) + 消息本身,头部共 4 字节 HEAD_TOTAL_LENGTH)。
- 发送:封装成 TLV 再发(头部字节序转网络序)。
- 接收切包 两种思路:
async_read_some不定长读取 + 回调内手动切包(复杂);- 用
async_read/read先收固定长度头部,解析出 length 后再收固定长度消息体,循环(简单,本项目采用思路 2 于客户端,服务器则用思路 1 的完整切包实现)。
- 同时校验
data_len > MAX_MSG_LENGTH或data_id > MAX_LOGIC_ID丢弃非法包。