头图

引言

许多开发者都曾面对过这样一个的问题:明明分别调用两次send()发送了"Hello""World",接收方却可能在一个recv()调用中读到完整的"HelloWorld";或是发送了一个完整的 JSON 对象,接收端却需要多次读取才能拼凑出完整数据。这种现象被中文技术社区广泛称为“TCP 粘包”

然而,若我们深入 TCP 协议的设计本质,会发现一个令人困惑的矛盾——RFC 文档中从未定义过“粘包”概念,国际权威技术文献也鲜少提及这一术语。 所以,谓的“粘包”究竟是 TCP 协议的固有缺陷,还是开发者对网络编程模型的误解?为何该术语几乎只存在于中文技术社区?

TCP “粘包”定义和解决方案

TCP 是一种面向连接的、可靠的、基于字节流的传输层通信协议

而中文技术社区对“TCP 粘包”的典型定义:因为 TCP 本身不保留数据包的边界信息,多个小的数据包被组合成一个大的数据包进行传输,导致接收端无法直接区分原始的数据包结构

原因分析
  • TCP 的字节流特性:TCP 将数据视为连续的字节流,发送端多次写入的数据可能在传输中被组合成一个 TCP 段发送(如 Nagle 算法优化),而接收端可能一次性读取多个数据包,或分多次读取一个完整数据包
  • 缓冲区机制:发送端和接收端的操作系统内核均有缓冲区。数据在发送前可能被缓冲合并,接收端也可能从缓冲区中一次读取多个包
  • 网络传输不确定性:数据包可能因网络波动被拆分或重组,进一步模糊了数据包的边界。
解决方案

应用层自行设计协议来界定消息边界

  • 固定长度消息:每个数据包长度固定,不足部分填充空字符。不过灵活性差
  • 分隔符标识:用特定字符(如换行符\n)分隔数据包。接收方按分隔符切分数据(如 HTTP 头)
  • 长度字段:在数据包头部添加长度字段,接收方先读长度,再按长度读取内容。如 HTTP 协议通过Content-LengthTransfer-Encoding: chunked处理粘包;Redis 协议使用长度前缀。
分析

看起来好像是 TCP 的问题?TCP 没有划分数据边界,导致应用层要给它“擦屁股”。但是这真的是 TCP 的问题吗?这锅真的是 TCP 该背的吗?

我的观点是:TCP 不存在“粘包”,处理数据边界本来就是应用层的工作!

论证 TCP 不存在“粘包”

传输层与应用层的职责界限

我们再来看看应用层和传输层的职责是什么:

  • 传输层:为进程提供端到端的通信服务。常见协议有 TCP、UDP、QUIC
  • 应用层:负责消息语义解析。常见协议有 HTTP、FTP、DNS

TCP/IP 四层模型的意义,就是将各层解耦和标准化,把复杂的网络通信过程划分为独立的层级,每层专注于特定功能。希望开发者只需关注某一层的实现,无需理解其他层的细节。网络协议的价值不在于隐藏复杂性,而在于明确各层的责任边界。比如,传输层不需要关心路由和转发,因为这是网络层的工作,只需要网络层处理

TCP 作为传输层,已经完成的它的职责——为进程提供端到端的通信服务。TCP 不需要关心用户关注什么样的数据、如何处理数据边界。因为这些事情都是应用层的事情。所以,TCP 没有“粘包”问题。因为所谓的“粘包”问题(数据边界处理)并不是 TCP 的职责

对数据边界的划分是应用层的需求,如果应用层都不需要对数据边界划分,又何谈“粘包”呢?

职责混淆的后果

错误实践:尝试通过设置TCP_NODELAY禁用 Nagle 算法,或调整内核缓冲区大小来"解决粘包"

本质矛盾:这些操作仅影响字节流的传输效率,无法保证应用层消息的原子性,并没有解决应用层的「数据边界不明确」的问题

流式传输

首先区分一下 TCP 流和 TCP 段:

  • TCP 流:从应用层视角看,TCP 流是连续的、无结构、无边界的字节序列。是逻辑整体。应用程序通过 TCP 发送或接收数据时,感知到的是一股“数据流”,无需关心底层如何分段或传输(例如:发送端写入多次的数据,接收端可能一次读完)
  • TCP 段:从传输层视角看,TCP 段是实际传输的数据单元。是物理分块。TCP 将应用层的字节流分割为适合网络传输的若干块,每个块添加 TCP 头部(包含序列号、确认号、窗口大小等控制信息),形成 TCP 段,最终封装成 IP 包进行传输。

TCP 流由多个 TCP 段按顺序拼接而成,段是流在传输过程中的物理表现形式。那么它们是否会“粘包”呢?

  • TCP 流不会“粘包”,因为它是无边界的,没有“包”的概念,自然不会“粘包”
  • TCP 段不会“粘包”,因为 TCP/IP 四层模型决定了每层只做自己分内的事情。从传输层角度看,它并不关心「数据」字段,也就是应用层,要如何组织数据。也可以认为应用层对传输层是不可见的
典型误解场景
# 发送端
sock.send(b"Hello")  # 第1次发送
sock.send(b"World")  # 第2次发送

# 接收端可能的表现
data = sock.recv(1024)  # 收到 b"HelloWorld"(开发者误认为"粘包")

开发者错觉:认为send()recv()应存在一一对应的"消息包"映射关系。

而实际上:

  • 发送端的send()次数 ≠ 接收端的recv()次数
  • 单次send()的数据可能被拆分为多个 TCP 段(Segment)传输
  • 多次send()的数据可能被合并为一个 TCP 段传输

国内外术语定义

上面两点已经解释了为什么 TCP 没有“粘包”问题,接下来就来看看国内“粘包”术语的来源,和国外使用的术语

“TCP 粘包”术语传播溯源?

一种可能的原因是,早期中文网络资料并不丰富,可能因为少数人对「应用层消息边界问题」的误解,错误地使用「TCP 粘包」术语来描述这个问题。之后这个术语广泛传播,成为了大家的“共识”

先回顾一下“数据”在各层的叫法
  • 应用层:消息(Message)
  • 传输层(TCP):段(Segment)
  • 网络层(IP):分组(Packet)
  • 数据链路层:帧(Frame)
再来看看国外是如何描述
  • StackOverFlow 提问: What is a message boundary
  • 《TCP/IP Illustrated》: "Applications using TCP must implement their own message framing."
  • AWS 开发者文档:"When using TCP, your application must handle message framing to detect complete messages。"
  • Microsoft Azure 技术指南:"Design protocol headers to handle message boundaries in stream-oriented transports。"

不难看出,它们用的的都是应用层数据 message 这个词

如何正确表达“粘包”术语

如果要用精确的术语表达“粘包问题”,可以使用消息边界问题(Message Boundary Problem)、消息边界处理(Message Delineation)、消息分帧(Message Framing)

通过术语的精准化,让开发者能更清晰地认识到:TCP 的流式特性不是需要解决的"问题",而是需要应用层协议来处理消息的边界

所以我们应该抛弃“TCP 粘包”这个术语?

我认为不应该抛弃这个术语

因为「TCP 粘包」这个术语已经在国内社区根深蒂固了,大家都知道它描述的是「数据边界问题」。可以看做中文社区的“方言”,方便交流

我们在使用 wireshark 的时候,国内外也都会统一使用“包”描述所有抓到的数据。比如应用层、传输层、网络层等数据,我们都统称为“包”,方便描述数据,而不关心它位于哪一层

其他“错误”的术语

以下术语并没有错误,也并不影响我们理解其本身的含义,只是我认为有更好的选择

  • Robustness,鲁棒性。虽然“鲁棒性”是正确的音译,但我认为“健壮性”更符合中文习惯
  • Default Value,缺省值。“缺省”为文言用法(“缺失”+“省略”),现代汉语中较少使用。使用“默认值”更直白
  • 心跳包。将应用层的心跳机制(Heartbeat)错误地理解为 TCP 层的“数据包”。心跳是应用层维持连接的机制,可通过空闲报文、定时消息或 TCP 自身的 Keep-Alive 实现,并非严格依赖显式的“包”。同时,因为是在应用层来实现心跳,可能使用「心跳消息」更加准确点
  • Parent Delegation,双亲委派。字面上可能会认为会有一个“父”和一个“母”?个人认为更贴切的译法应为「父级委派」

结语

最后总结一下“粘包”这一术语背后的认知偏差:

  • 它混淆了传输层职责(进程数据传输)与应用层职责(消息边界解析)的界限
  • 它本质是开发者对 TCP 字节流传输特性的误读,而非协议缺陷
  • 它折射出中文技术社区在术语翻译与传播过程中形成的独特“方言现象”

虽然术语「TCP 粘包问题」本不应该存在,但「数据边界问题」依旧是存在的,不过它是应用层的职责。我们真正需要解决的从来不是“粘包”,而是如何设计健壮的应用层协议,来处理数据边界问题

在这里我给大家留几个小小的问题:TCP 是否存在半包?UDP 是否存在粘包和半包?


如果大家感觉有帮助,欢迎点赞收藏+关注,有问题可以在评论区评论哦!

公众号【牛肉烧烤屋】

B 站【爱烤猪蹄的乔治】

参考资料

https://zh.wikipedia.org/wiki/%E4%BC%A0%E8%BE%93%E6%8E%A7%E5%...

封面:环保节能小夜灯


牛肉烧烤屋
1 声望0 粉丝