手写 C++ TCP 服务器:自定义协议与粘包处理实战
基于字节流的 TCP 通信中,数据边界是一个经典难题。UDP 基于数据报传输,天然具有消息边界;而 TCP 是流式协议,发送方写入的数据在接收端可能以任意片段形式返回。如果应用层不定义规则,接收方很容易读到半包、粘包或错误顺序的数据。
TCP 通信的本质挑战
TCP 负责控制何时发送、发送多少以及出错重传,但具体数据如何组织由应用层决定。我们调用 write 将数据放入内核缓冲区后,TCP 会将其拆分成多个报文段发送。接收端的 read 同样受限于内核缓冲区和网络状况,可能出现以下情况:
- 发送方发了一个长报文,接收方只读到了前半部分。
- 发送方连续发了两个短报文,接收方一次性读到了合并后的数据。
这就像发送方先放了一个整形,又放了一个浮点数,而接收方按照字符串去解析,必然导致数据不一致。因此,一旦涉及结构化数据传输,必须制定应用层协议来明确边界。
自定义应用层协议设计
我们以一个简单的网络版计算器为例,演示如何通过协议解决上述问题。
架构概览
Client
│
│ request (请求)
▼
TcpServer
│
│ callback (回调处理)
▼
CalculatorServer
│
│ result (结果)
▼
response (响应)
主要模块包括 Socket 封装、TCP 服务器框架、协议封装及计算逻辑。
协议格式
为了区分消息边界,我们采用 长度头 + 内容 的格式:
len\n content\n 例如发送
10 + 5,编码后为:
6\n10 + 5\n
这种设计让接收方能先读取头部获取长度,再根据长度精确截取后续内容,从而彻底解决粘包和半包问题。
核心实现细节
协议封装(Encode/Decode)
我们需要将业务数据序列化为符合协议的字符串,并在接收端反序列化。
#include <string>
#define blank_sep " "
#define protocol_sep "\n"
// 编码:添加长度头和分隔符
std::string Encode(std::string &content) {
std::string s;
size_t len = content.size();
s += std::to_string(len);
s += protocol_sep;
s += content;
s += protocol_sep;
return s;
}
// 解码:从缓冲区提取完整报文
bool Decode(std::string &s, std::string *content) {
left_pos = s.(protocol_sep);
(left_pos == std::string::npos) ;
std::string content_len = s.(, left_pos);
len = std::(content_len);
(s.() < content_len.() + len + ) ;
*content = s.(left_pos + , len);
s.(, content_len.() + len + );
;
}


