Boost TCP Server远程接收消息时随机断包问题求助
问题描述
本地同系统向Boost TCP服务器发送消息时,服务器能正常接收;但远程跨系统客户端通过TCP/IP发送消息时,部分消息会随机断行。例如客户端发送"THIS IS A MESSAGE",服务器会读取为:
"THIS IS A ME
SSAGE"
以下是Server类代码:
#pragma once #include <boost/asio/io_context.hpp> #include <boost/asio/ip/tcp.hpp> #include <boost/asio/spawn.hpp> #include <boost/asio/steady_timer.hpp> #include <boost/asio/write.hpp> #include <iostream> #include <global.h> #include <memory> #include <fstream> #include <fstream> #include <iomanip> #include <iostream> #include <memory> #include <queue> 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() { auto self(shared_from_this()); // dispatch not strictly necessary for single-threaded contexts dispatch( socket_.get_executor(), [this, self] { do_read(); }); } private: void handleCommand() { enqueueAnswer(); } void enqueueAnswer() { if (stdqueAnswers.size() == 1) { do_write(); } } void do_read() { 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) { if (length > 0) { // In case the message has a leading 1 than we have to send a answer back to the client. if (data_[0] == '1') { std::string stdstrCmd(data_); stdstrCmd.erase(0, 2); wavefrontAccess->ReceiveCommandExternalGet(stdstrCmd); handleCommand(); } else { std::string strData(data_, length); if(!strData.empty() || strData.find_first_not_of(' ') != std::string::npos) { // There's a non-space. commandsQueue.push(strData); // this is std Queue } } } do_read(); } }); } void do_write() { if (stdqueAnswers.empty()) return; auto self(shared_from_this()); async_write( socket_, boost::asio::buffer(stdqueAnswers.front()), [this, self](boost::system::error_code ec, size_t) { if (!ec) { stdqueAnswers.pop(); do_write(); } }); } tcp::socket socket_; enum { max_length = 12000 }; char data_[max_length]; }; class server { public: server(boost::asio::io_context& io_context, std::uint16_t port) : acceptor_{ io_context, tcp::endpoint(tcp::v4(), port) } { acceptor_.listen(); do_accept(); } private: void do_accept() { acceptor_.async_accept( make_strand(acceptor_.get_executor()), [this](boost::system::error_code ec, tcp::socket socket) { if (!ec) { std::make_shared<session>(std::move(socket))->start(); do_accept(); } }); } tcp::acceptor acceptor_; };
问题原因及修复方案
核心问题
TCP是流式协议,不保证一次async_read_some能完整读取客户端发送的整段消息。本地环境下数据传输延迟极低,通常会一次性到达;但远程网络中,数据可能被TCP拆分成多个分段传输,导致服务器分多次读取到完整消息的不同部分,最终被当作多条独立消息存入队列。
具体代码问题
do_read中每次调用async_read_some后,直接把当前读到的片段存入commandsQueue,没有处理消息拼接逻辑- 开头为
'1'的命令处理存在隐患:直接用std::string stdstrCmd(data_)构造字符串,会读取到data_缓冲区中未被覆盖的旧数据(async_read_some仅写入length字节)
修复步骤
1. 为session添加消息缓冲区
在session类的私有成员中添加:
std::string incomplete_msg_; // 存储未完成的消息片段
2. 修改do_read逻辑,实现消息拼接
需要先明确消息边界规则(比如换行符、特定分隔符、固定长度或长度前缀),以下以换行符作为消息结束标记为例修改do_read:
void do_read() { 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) { if (length > 0) { // 将新读取的数据追加到未完成消息缓冲区 incomplete_msg_.append(data_, length); size_t pos; // 循环查找消息边界(这里用'\n'作为分隔符,可根据实际协议调整) while ((pos = incomplete_msg_.find('\n')) != std::string::npos) { // 提取完整消息 std::string full_msg = incomplete_msg_.substr(0, pos); // 移除已提取的部分(包括换行符) incomplete_msg_.erase(0, pos + 1); // 处理完整消息 if (!full_msg.empty() && full_msg.front() == '1') { // 避免越界,确保消息长度足够截取 if (full_msg.size() >= 2) { std::string cmd = full_msg.substr(2); wavefrontAccess->ReceiveCommandExternalGet(cmd); handleCommand(); } } else { // 过滤空消息或全空格消息 if (!full_msg.empty() && full_msg.find_first_not_of(' ') != std::string::npos) { commandsQueue.push(full_msg); } } } } do_read(); } }); }
3. 关键说明
- 必须根据实际应用的通信协议确定消息边界:
- 文本协议常用换行符
\n或\r\n作为分隔 - 二进制协议通常用固定长度的消息头(包含消息长度)标识消息结束
- 文本协议常用换行符
- 永远不要依赖
async_read_some一次读取完整消息,这不符合TCP流式传输的特性
内容的提问来源于stack exchange,提问作者Summit
相关产品推荐
相关产品推荐

