【计算机网络】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 只管字节流,不管消息边界;
应用层必须自己定义边界。
相关推荐
志栋智能1 小时前
轻量级 vs. 重平台:巡检超自动化的两种路径选择
运维·网络·人工智能·自动化
Bruce_kaizy1 小时前
c++网络编程——解析主机字节序、网络字节序以及深入剖析tcp编程中万恶的结构体(爆肝)
linux·服务器·网络·tcp/ip·ubuntu
TE-茶叶蛋1 小时前
Next.js中App Router 全部特殊文件一览
开发语言·javascript·网络
酉鬼女又兒2 小时前
零基础入门计算机网络:物理层核心知识全解——传输方式分类、编码调制原理与信道极限容量计算
网络·计算机网络·考研·职场和发展·分类·数据挖掘·php
程序猿阿伟2 小时前
《从TCP到WebSocket:Discord静默断流的七层排查指南》
websocket·网络协议·tcp/ip
Flash.kkl2 小时前
C++基于websocketpp的多用户网页五子棋项目
开发语言·网络·数据库·c++·websocket·mysql
10WTW012 小时前
计网实验 模拟器的配置与使用
网络·智能路由器
酉鬼女又兒2 小时前
零基础入门计算机网络物理层:核心概念、传输媒体、传输方式、编码调制与信道极限容量完整知识点总结
开发语言·网络·计算机网络·考研·职场和发展·php·信息与通信
逆境不可逃2 小时前
【WebSocket 01】 入门原理剖析,手写群发消息、私聊会话功能
网络·websocket·网络协议