手写一个 C++ TCP 服务器实现自定义协议(顺便解决粘包问题)
在网络编程的实践中,我们往往容易忽略 TCP 和 UDP 在传输机制上的本质区别。UDP 基于数据报,发送即接收;而 TCP 是面向字节流的,它只保证数据的可靠传输,却不关心消息的边界。
这意味着,当客户端通过 connect 建立连接并调用 write 写入数据后,服务端通过 read 读取时,并不能保证一次读取就能拿到完整的逻辑报文。如果发送方先发了一个整形,再发一个浮点数,而接收方按顺序去解析却读到了不完整的片段,数据结构就会错乱。

TCP 主要负责控制何时发送、发送多少以及出错重传。应用层将数据拷贝到内核缓冲区后,具体何时发送由 TCP 协议栈决定。这导致接收端面临不确定性:发送方发长报文,接收方可能只读到一部分就返回给用户了。
这就好比发微信道歉,原本想说的是'我不后悔我爱你',结果因为网络分片或缓冲问题,对方只收到了'我不后悔'。这种语义偏差在程序里就是数据不一致。因此,必须制定应用层协议来界定消息边界,确保收发双方对数据的理解一致。
为什么需要自定义协议?
想象一下群聊场景,你发送'哈哈哈',实际传输的不止这三个字,还有昵称、头像、时间戳等元数据。看似简单的文本,背后是一系列结构化信息的序列化传输。接收方收到字节流后,需要反序列化才能还原出原始对象。
为了直观理解序列化和反序列化,我们来实现一个简单的网络版计算器。
整体架构
Client
│
▼ request
TcpServer
│
▼ callback
CalculatorServer
│
▼ result
response
主要模块包括 Socket 封装、TCP 服务器框架、协议封装以及计算逻辑。
自定义应用层协议设计
由于 TCP 是字节流协议,一次 read() 可能读到半个数据,也可能读到多个数据拼接在一起。例如客户端连续发送两个请求:
10 + 20 5 * 6
服务器可能一次性读到:
10 + 20\n5 * 6\n
或者只读到前半部分:
10 +
为了解决这个问题,我们采用长度前缀(Length-Prefix)协议。
协议格式
协议结构非常简单:
len\n content\n
例如发送 10 + 5,编码后变为:
7\n10 + 5\n
核心逻辑是:先发送内容长度,换行符,再发送具体内容,最后再加一个换行符作为结束标记。
| 字段 | 说明 |
|---|---|
| len | 内容长度(十进制字符串) |
| \n | 分隔符 |
| content | 实际业务数据 |
| \n | 结束符 |
协议封装实现
Encode(封装报文)
std::string {
std::string s;
len = content.();
s += std::(len);
s += ;
s += content;
s += ;
s;
}



