一、什么是 TCP 粘包?
TCP 是面向字节流(byte stream)的协议,它只保证数据按序传输、不丢不重,但不保证消息边界。
也就是说,TCP 看到的是一串连续的字节流,而不是一条条独立的“消息”。
因此,在应用层上发送的数据,到了接收方时,可能会出现以下几种情况:
发送方 接收方

二、为什么会发生粘包/拆包?
TCP 在传输数据时,底层会根据各种因素动态决定包的发送时机。
粘包原因:
-
发送方缓冲区合并
-
如果应用层连续多次调用
send(),数据可能被 TCP 缓冲区合并为一个包一起发送。
-
-
Nagle 算法
-
TCP 默认启用 Nagle 算法,小包会等待前一个包的 ACK 再合并发送,以提高效率。
-
-
接收方缓冲区一次性读取多个包
-
recv()调用时,如果缓冲区中有多个包的数据,会一次性读出。
-
拆包原因:
-
应用层消息过大
-
一条消息超过了 TCP 缓冲区大小或 MTU,TCP 必须拆分为多个包。
-
-
接收方读取不及时
-
recv()一次只读了部分数据 -
粘包本身是正常现象,根本问题是如何在接收端正确“分包”解析。
-
粘包本身是正常现象,根本问题是如何在接收端正确“分包”解析。
✅ 方法一:固定长度协议
每条消息的长度固定,例如 128 字节。
-
优点:简单。
-
缺点:浪费空间,不适合变长数据。
-
// 每条消息固定128字节
recv(sock, buffer, 128, 0);
✅ 方法二:特殊分隔符
在每条消息末尾加上特定分隔符(如 \n、#END# 等),接收方按分隔符切割。
// 发送端
send(sock, "Hello#END#", ...);
send(sock, "World#END#", ...);
// 接收端解析
std::string msg = recv_data();
split(msg, "#END#");
✅ 方法三:消息头 + 消息体(推荐)
在消息前加一个固定长度的“包头”,记录后续数据的长度。
例如:前 4 个字节表示消息长度。
// 发送端
uint32_t len = htonl(data.size());
send(sock, &len, 4, 0);
send(sock, data.data(), data.size(), 0);
// 接收端
uint32_t len;
recv(sock, &len, 4, MSG_WAITALL);
len = ntohl(len);
std::vector<char> buf(len);
recv(sock, buf.data(), len, MSG_WAITALL);
优点:
-
通用、可靠、广泛使用(HTTP、MQTT、RPC 协议都类似)。
-
能有效避免粘包和拆包问题。
🔬 四、实际开发建议

五、小结

C++未处理粘包示列:
Sever.cpp
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
int main() {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = INADDR_ANY;
bind(server_fd, (sockaddr*)&addr, sizeof(addr));
listen(server_fd, 1);
std::cout << "Server listening on 8080..." << std::endl;
int client_fd = accept(server_fd, nullptr, nullptr);
char buffer[1024];
while (true) {
int len = recv(client_fd, buffer, sizeof(buffer)-1, 0);
if (len <= 0) break;
buffer[len] = '\0';
std::cout << "[Server recv] " << buffer << std::endl;
}
close(client_fd);
close(server_fd);
}
client.cpp
#include <iostream>
#include <string>
#include <unistd.h>
#include <arpa/inet.h>
int main() {
int sock = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
connect(sock, (sockaddr*)&addr, sizeof(addr));
for (int i = 0; i < 5; ++i) {
std::string msg = "Message_" + std::to_string(i);
send(sock, msg.c_str(), msg.size(), 0);
usleep(1000); // 故意短间隔
}
close(sock);
}
[Server recv] Message_0Message_1Message_2Message_3Message_4
说明:五条消息“粘”在一起了!
✅(正确示例)—— 包头 + 包体协议
在每个消息前添加一个 4 字节整数表示消息长度。
server.cpp
#include <iostream>
#include <vector>
#include <unistd.h>
#include <arpa/inet.h>
ssize_t read_n(int fd, void* buf, size_t n) {
size_t total = 0;
while (total < n) {
ssize_t ret = recv(fd, (char*)buf + total, n - total, 0);
if (ret <= 0) return ret;
total += ret;
}
return total;
}
int main() {
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = INADDR_ANY;
bind(server_fd, (sockaddr*)&addr, sizeof(addr));
listen(server_fd, 1);
std::cout << "Server listening on 8080..." << std::endl;
int client_fd = accept(server_fd, nullptr, nullptr);
while (true) {
uint32_t len_net;
ssize_t n = read_n(client_fd, &len_net, 4);
if (n <= 0) break;
uint32_t len = ntohl(len_net);
std::vector<char> buf(len + 1, 0);
read_n(client_fd, buf.data(), len);
std::cout << "[Server recv] " << buf.data() << std::endl;
}
close(client_fd);
close(server_fd);
}
client.cpp
#include <iostream>
#include <string>
#include <unistd.h>
#include <arpa/inet.h>
void send_packet(int sock, const std::string& msg) {
uint32_t len = htonl(msg.size());
send(sock, &len, 4, 0);
send(sock, msg.c_str(), msg.size(), 0);
}
int main() {
int sock = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
connect(sock, (sockaddr*)&addr, sizeof(addr));
for (int i = 0; i < 5; ++i) {
std::string msg = "Message_" + std::to_string(i);
send_packet(sock, msg);
usleep(1000); // 仍然很短的间隔
}
close(sock);
}
运行结果:
[Server recv] Message_0
[Server recv] Message_1
[Server recv] Message_2
[Server recv] Message_3
[Server recv] Message_4
每条消息完整解析,粘包问题完美解决。
| 方法 | 优点 | 缺点 |
|---|---|---|
| 固定长度 | 简单 | 不灵活 |
| 特殊分隔符 | 易实现 | 不适合二进制数据 |
| 包头 + 包体 | 通用可靠 | 需要协议定义 |
448

被折叠的 条评论
为什么被折叠?



