【计算机网络】TCP粘包和拆包

你这段内容讲的是:TCP 传输时不会帮你区分"一个完整业务消息"的边界,所以应用层要自己设计规则来拆分消息。

可以按下面这条主线理解:

TCP 只管可靠传输字节,不管你一次发的是几条消息。


1. 为什么会有粘包和拆包?

假设客户端连续发送两条消息:

text 复制代码
消息1:hello
消息2:world

你以为服务端一定会收到:

text 复制代码
hello
world

但 TCP 是字节流,服务端实际可能收到:

text 复制代码
helloworld

这就是粘包

也可能收到:

text 复制代码
hel
lowor
ld

这就是拆包

注意:这里的"包"不是 TCP 真正意义上的包,而是我们应用层自己认为的一条完整消息。


2. 粘包是什么?

粘包:多个应用层消息被一次性读到了。

比如发送方发了两条消息:

text 复制代码
[消息1][消息2]

接收方一次 read() 读到:

text 复制代码
[消息1消息2]

接收方如果不知道消息边界,就不知道哪里是第一条结束,哪里是第二条开始。

粘包可能来自两个地方:

发送方导致

发送方为了提高效率,可能会把多个小数据合并后一起发出去,减少网络传输次数。

比如:

text 复制代码
send("hello")
send("world")

底层可能合并成一次发送:

text 复制代码
helloworld

接收方导致

即使发送方分两次发,接收方也可能一次从 TCP 缓冲区里读出多条数据。

比如缓冲区里已经有:

text 复制代码
hello world

接收方一次读取 1024 字节,就可能把两条消息都读出来。


3. 拆包是什么?

拆包:一条应用层消息被拆成多次接收。

比如发送方发送一条大消息:

text 复制代码
[很长很长的一条消息]

由于 TCP 分段、MSS 限制、缓冲区大小等原因,接收方可能分多次读到:

text 复制代码
第1次:[消息前半部分]
第2次:[消息后半部分]

这时候接收方不能拿到一半就解析,否则会解析失败。


4. 根本原因是什么?

根本原因不是 TCP 出错,而是:

TCP 是面向字节流的协议,没有消息边界。

TCP 看见的是一串连续字节:

text 复制代码
010101010101010101...

它不关心你应用层原本是:

text 复制代码
消息1 + 消息2 + 消息3

所以 TCP 只保证:

  1. 数据可靠到达;
  2. 数据按顺序到达;
  3. 数据不重复、不丢失。

但它不保证:

text 复制代码
你 send 几次,对方 recv 就收到几次

这一点非常重要。


5. UDP 为什么没有粘包问题?

因为 UDP 是面向数据报的。

你发送一次 UDP 数据报:

text 复制代码
sendto("hello")

接收方收到的就是这一整个数据报:

text 复制代码
hello

UDP 保留消息边界。

所以 UDP 的特点是:

text 复制代码
一次发送,对应一次接收

但 UDP 不保证可靠性,可能丢包、乱序。

TCP 的特点是:

text 复制代码
可靠、有序,但没有消息边界

6. 怎么解决 TCP 粘包和拆包?

核心思想就是:

应用层自己定义消息边界。

常见方案有三种:定长、分隔符、长度字段/TLV。


方案一:定长消息

约定每条消息固定长度,比如每条都是 1024 字节。

发送方发送:

text 复制代码
hello

长度不够 1024,就补齐:

text 复制代码
hello + 填充字符

接收方每次固定读取 1024 字节,就知道这是一条完整消息。

优点:实现简单。

缺点:浪费空间,不灵活。

比如你只发 5 个字节,也要占 1024 字节。


方案二:分隔符

给每条消息后面加一个特殊分隔符,比如:

text 复制代码
hello\n
world\n

接收方按 \n 切分:

text 复制代码
hello
world

优点:简单,适合文本协议。

缺点:如果消息内容本身也包含分隔符,就要做转义处理。

比如你用 \n 做分隔符,但正文中本身就有换行,就容易出问题。

FTP、Redis 协议中都有类似思想。


方案三:长度字段 / TLV

这是最常用、最可靠的一种方式。

你图里的结构就是 TLV:

text 复制代码
Type | Length | Value

含义是:

text 复制代码
Type:这条消息是什么类型
Length:Value 有多长
Value:真正的数据内容

比如:

text 复制代码
[T1][L1][V1][T2][L2][V2]

接收方读取时:

  1. 先读 Type,知道消息类型;
  2. 再读 Length,知道消息体有多长;
  3. 然后根据 Length 读取指定长度的 Value;
  4. 读完后,就知道一条完整消息结束了;
  5. 继续读取下一条消息。

所以即使 TCP 把两条消息粘在一起:

text 复制代码
[T1][L1][V1][T2][L2][V2]

接收方也能根据 Length 正确切出来:

text 复制代码
第一条:[T1][L1][V1]
第二条:[T2][L2][V2]

这就是图中想表达的意思。


7. Content-Length 为什么也算一种解决方案?

HTTP/1.1 中请求头或响应头里有:

text 复制代码
Content-Length: 100

意思是:

text 复制代码
后面的请求体/响应体长度是 100 字节

接收方看到这个长度后,就知道应该继续读取 100 字节作为 body。

这本质上就是:

text 复制代码
Length + Value

只不过 HTTP 的 Type 信息在请求行、状态行、Header 里面。


8. Protobuf 和 WebSocket 怎么解决?

Protobuf

Protobuf 本身主要负责数据序列化,它会把对象编码成二进制数据。

但在 TCP 传输时,通常还需要在 Protobuf 数据前面加一个长度字段:

text 复制代码
Length + ProtobufData

接收方先读长度,再读对应长度的 Protobuf 数据。

WebSocket

WebSocket 自己定义了帧格式,帧头里包含长度信息,所以接收方知道一帧数据的边界。

所以 WebSocket 底层虽然也是基于 TCP,但它在应用层协议中解决了消息边界问题。


9. 面试里可以这样回答

TCP 是面向字节流的协议,它只保证数据可靠、有序地传输,但不保证应用层消息边界。所以发送方多次写入的数据,接收方可能一次读到,形成粘包;发送方一次写入的大数据,接收方也可能多次读到,形成拆包。

本质原因不是 TCP 的缺陷,而是应用层没有定义清楚消息边界。解决方式一般有三种:定长消息、分隔符、长度字段。实际开发中最常用的是长度字段方案,比如 TLV、HTTP 的 Content-Length、WebSocket 帧格式、Protobuf 前置长度等。

可以简单记成:

text 复制代码
TCP 只管字节流,不管消息边界;
应用层必须自己定义边界。
相关推荐
2301_7807896615 分钟前
DDoS 攻击溯源:DNS 水印标记 + 区块链存证的双保险
运维·服务器·网络·云原生·ddos
2601_949499946 小时前
芯瑞科技 DT-1414 光模块工程实测:完美兼容博通(安华高) HFBR-1414PTZ,VCSEL 替代方案
大数据·网络·人工智能·科技·光模块
2501_915918416 小时前
iOS 怎么抓包?抓包鹰系统级 网卡 应用层三种方式对比,不越狱抓 iPhone 流量
网络协议·计算机网络·网络安全·ios·adb·https·udp
云边云科技_云网融合6 小时前
连锁门店 POS、监控、IoT 设备网络不稳定怎么解决?
网络·物联网
XR1234567888 小时前
学校宿舍区网络方案对比:多终端高并发下的认证、管理与体验
网络
March.s8 小时前
IP 存储核心 iSCSI:原理拆解 + 无报错实战指南
网络·网络协议·tcp/ip
星恒讯工业路由器8 小时前
远程访问技术解析:从内网穿透到安全隧道的三种主流方案
网络·信息与通信·内网穿透·远程访问·vpn隧道
超智算科技9 小时前
超智算领衔协办“NVIDIA创业企业展示·西安站”,全栈AI服务赋能行业生态协同发展
大数据·网络·人工智能·物联网·百度
空杆推不起9 小时前
企业数据加密实战:传输链路与存储层全场景落地指南
网络·安全·web安全
️学习的小王9 小时前
基于UDP实现简易聊天室(基础版)——Python实现
网络·python·udp