这是一个基于 Boost.Asio 的异步 (Asynchronous) TCP 客户端实现。它被设计为与 v2_FullDuplex 服务器配合使用,支持全双工通信和自定义消息协议。
AsyncClient/
├── AsyncClient.h # 客户端类声明
├── AsyncClient.cpp # 客户端类实现
├── main.cpp # 主程序入口
└── README.md # 说明文档
-
全双工通信:
- 发送和接收互不干扰。
- 使用
io_context在后台线程处理网络 I/O。 - 独立的发送线程 (
send_thread) 模拟高频业务请求。
-
发送队列 (
_send_queue):- 解决了 Boost.Asio 不允许并发
async_write的问题。 Send()函数是线程安全的,通过boost::asio::post将任务投递到 I/O 线程执行。
- 解决了 Boost.Asio 不允许并发
-
消息协议:
- 采用
Header (2 bytes) + Body的格式。 - 解决了 TCP 粘包问题。
- 与服务器端的
MsgNode协议保持一致。
- 采用
本客户端采用 多线程 + 异步 I/O 的架构,通过 boost::asio::io_context 调度所有网络操作。程序运行时存在三个并发执行流,它们的分工与协作如下:
主线程负责程序的生命周期管理,不直接参与网络 I/O。
- 初始化: 调用
WSAStartup初始化 Windows Socket 环境,创建核心对象io_context。 - 启动连接: 实例化
AsyncClient,构造函数中立即调用async_connect发起非阻塞连接请求。 - 创建线程:
- 创建
t线程运行ioc.run(),启动异步引擎。 - 创建
send_thread线程运行业务逻辑。
- 创建
- 等待退出: 调用
join()阻塞等待子线程结束,防止主程序过早退出。
该线程运行 ioc.run(),是所有异步回调函数(Handlers)的执行场所。它负责实际的“脏活累活”。
- 接收循环 (Read Loop):
- 连接成功后,立即发起
async_read读取 2 字节头部。 - 头部读取完成后,回调函数解析出消息长度,再次发起
async_read读取包体。 - 包体读取完成后,打印消息,并立即回到第 1 步读取下一个头部。
- 机制: 这是一个无限链式回调,确保只要有数据到达就能被处理。
- 连接成功后,立即发起
- 发送逻辑 (Write Logic):
- 响应
post投递过来的发送任务。 - 将消息压入
_send_queue。 - 检查当前是否正在发送(
write_in_progress)。如果空闲,则调用async_write发送队首消息。 - 发送完成的回调中,弹出队首,如果队列不空,继续发送下一条。
- 机制: 保证了即使业务层疯狂调用 Send,底层的 Socket 也永远是串行写入,不会崩溃。
- 响应
该线程模拟高频产生数据的业务场景。
- 死循环: 包含一个
while(true)循环。 - 频率控制: 使用
std::this_thread::sleep_for(2ms)控制发送频率。 - 跨线程投递: 调用
client.Send("hello world!")。- 关键点:
Send函数内部并没有直接操作 Socket,而是使用boost::asio::post将一个 Lambda 表达式“扔”给 IO 线程去执行。这就像是把信件丢进邮筒,发送线程不需要等待信件寄出就可以继续执行。
- 关键点:
sequenceDiagram
participant User as Send Thread
participant Client as AsyncClient
participant IO as IO Thread (io_context)
participant Socket as tcp::socket
Note over User, IO: 1. 初始化与连接
User->>Client: AsyncClient(host, port)
Client->>Socket: async_connect
User->>IO: Start IO Thread (ioc.run)
IO->>Socket: Connect Complete
Socket-->>Client: Callback (do_connect)
Client->>Socket: async_read (Header)
Note over User, IO: 2. 高频发送消息 (线程安全)
loop Every 2ms
User->>Client: Send("hello world!")
end
Client->>IO: post(Task)
Note right of Client: 切换到 IO 线程执行
IO->>Client: Task Execution
Client->>Client: Push to Queue
alt Queue was empty
Client->>Socket: async_write
end
Note over IO, Socket: 3. 接收消息 (循环)
Socket-->>Client: Read Header Complete
Client->>Socket: async_read (Body)
Socket-->>Client: Read Body Complete
Client->>User: Print Reply
Client->>Socket: async_read (Next Header)
void AsyncClient::Send(const string& msg) {
// 使用 post 将任务切到 io_context 线程,避免多线程竞争 socket
boost::asio::post(_socket.get_executor(), [this, msg]() {
bool write_in_progress = !_send_queue.empty();
// ... 封装消息 ...
_send_queue.push(send_data);
if (!write_in_progress) {
do_write();
}
});
}- 读取:
do_read_header->do_read_body->do_read_header... (无限循环,直到出错) - 写入:
do_write-> 回调中检查队列 -> 若不空则再次do_write。
g++ -o AsyncClient.exe main.cpp AsyncClient.cpp -lws2_32 -lboost_system -std=c++20- 确保服务器 (
AsyncServer.exe) 正在运行。 - 运行客户端:
./AsyncClient.exe
- 客户端启动后会自动开启一个发送线程,每隔 2ms 发送一条 "hello world!"。
- 控制台将持续打印服务器的回显消息。
以下时序图展示了主线程(用户输入)与 IO 线程(网络处理)之间的交互:
sequenceDiagram
participant Main as Main Thread
participant IO as IO Thread (io_context)
participant Client as AsyncClient
participant Socket as tcp::socket
Note over Main, IO: 1. 初始化与连接
Main->>Client: AsyncClient()
Client->>Socket: async_connect
Main->>IO: thread(ioc.run)
IO-->>Client: Handle Connect (回调)
Client->>Socket: async_read (Header)
Note over Main, IO: 2. 数据发送 (Send)
Main->>Client: Send(msg)
Client->>IO: post(Task)
Note right of Client: 切换到 IO 线程以保证安全
IO-->>Client: Execute Task
Client->>Client: Push Queue
alt Queue was Empty
Client->>Socket: async_write
end
IO-->>Client: Handle Write (回调)
Client->>Client: Pop Queue
opt Queue not Empty
Client->>Socket: async_write
end
Note over Main, IO: 3. 数据接收 (Receive)
IO-->>Client: Handle Read Header (回调)
Client->>Socket: async_read (Body)
IO-->>Client: Handle Read Body (回调)
Client->>Socket: async_read (Header)