惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Full Disclosure
博客园 - 三生石上(FineUI控件)
MyScale Blog
MyScale Blog
Apple Machine Learning Research
Apple Machine Learning Research
L
LINUX DO - 最新话题
T
The Blog of Author Tim Ferriss
P
Proofpoint News Feed
宝玉的分享
宝玉的分享
小众软件
小众软件
Hugging Face - Blog
Hugging Face - Blog
GbyAI
GbyAI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
V
Visual Studio Blog
爱范儿
爱范儿
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园_首页
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
月光博客
月光博客
博客园 - 叶小钗
D
Docker
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
Tailwind CSS Blog
D
DataBreaches.Net
酷 壳 – CoolShell
酷 壳 – CoolShell
B
Blog RSS Feed
量子位
美团技术团队
Vercel News
Vercel News
Y
Y Combinator Blog
IT之家
IT之家
Martin Fowler
Martin Fowler
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
S
SegmentFault 最新的问题
腾讯CDC
Recent Announcements
Recent Announcements
Google DeepMind News
Google DeepMind News
罗磊的独立博客
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
G
Google Developers Blog
Microsoft Azure Blog
Microsoft Azure Blog
The Register - Security
The Register - Security
博客园 - 司徒正美
N
Netflix TechBlog - Medium
S
Schneier on Security
博客园 - 聂微东
U
Unit 42
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
雷峰网
雷峰网
Latest news
Latest news

蛮荆

如何获取更多的免费服务器 Kubernetes 调度器队列 - 设计与实现 Kubernetes 调度器 - 核心流程 Kubernetes Networking Model & CNI Kubernetes 控制器管理总结 Kubernetes CronJob 设计与实现 Kubernetes Job 设计与实现 Kubernetes HPA 设计与实现 Kubernetes Deployment 滚动更新实现原理 Kubernetes GC 设计与实现 Kubernetes Pod 驱逐 - 设计与实现 Kubernetes Daemonset 设计与实现 Kubernetes ReplicaSet 设计与实现 Kubernetes EndPoint 设计与实现 Kubernetes Informer 设计与实现 降本增效之应用优化 (三) 日志存储与检索 Kubernetes Pod 设计与实现 - 创建流程 Kubernetes 探针设计与实现 Unix 编程艺术名句摘录 Kubernetes - CRI 概述 Golang 编译速度为什么这么快? Kubernetes Pod 设计与实现 - Pause 容器 Kubernetes - kube-proxy 代理模式工程优化 Kubernetes 应用最佳实践 - 优雅关闭长连接 Kubernetes Service 类型和会话亲和性 Kubernetes 为什么需要 Ingress Kubernetes 架构 - 控制平面和数据平面 降本增效之应用优化 (二) 大报表 Go 语言如何获取 CPU 利用率 降本增效之应用优化 (一) Redis 业务规则引擎演变过程简述 微服务中的熔断算法 漏桶算法和令牌桶算法 jsonparser 为什么比标准库的 encoding/json 快 10 倍 ? zap 高性能设计与实现 HTTP Router 算法演进 fastcache 高性能设计与实现 Web 常见的三个安全问题 ants Code Reading Go 线程安全 map 方案选型 布隆过滤器 死锁、活锁、饥饿、自旋锁 sync.Pool Code Reading Go 内存管理概述 Go netpoll Code Reading goroutine 泄漏与检测 time/Timer Code Reading GMP Scheduler Code Reading Go channel 的 15 条规则和底层实现 为什么 Linux “一切皆文件” context.Context Code Reading runtime/HACKING.md Goland 最佳实践 互联网开发与金庸武学 为什么 Redis 6.0 引入多线程模型? Kubernetes 应用最佳实践 - 金丝雀发布 容器中如何正确配置 GOMAXPROCS ? singleflight Code Reading sync.Map Code Reading sync.Cond Code Reading sync.WaitGroup Code Reading sync.RWMutex Code Reading sync.Mutex Code Reading sync.Once Code Reading Go 无锁编程 sync/atomic Code Reading goroutine 交替打印奇偶数 GODEBUG Go 并发模式 Go 汇编 UUID 通用技术选型 Kubernetes 应用最佳实践 - 水平自动伸缩 Go 高性能 Tips fasthttp 为什么比标准库 net/http 快 10 倍 ? 技术文章配图指南 ChatGPT 初体验 Docker 网络原理概览 iptables 的五表五链 Kubernetes 应用最佳实践 - 亲和性和污点容忍度 Go 的反射与三大定律 Docker 官方提供的最佳实践 Go 语言内置的设计模式 HTTP1 到 HTTP3 的工程优化 Kubernetes 应用最佳实践 - Sidecar 模式 Kubernetes 应用最佳实践 - init 容器和钩子函数 为什么 recover 必须在 defer 中调用? 为什么 defer 的执行顺序和注册顺序不同? Go map 设计与实现 Go 切片扩容底层实现 Go 语言中的零拷贝 Go Delve 云原生和边缘计算简介 Kubernetes Pod 服务质量等级 Kubernetes 应用最佳实践 - 探针 Kubernetes 应用最佳实践 - 资源请求和限制 CDN 原理 Kubernetes 应用最佳实践 - 开篇 缓存策略和模式 Go 内存模型 Kubernetes 核心概念
为什么 TCP 粘包 是正常现象
2018-12-16 · via 蛮荆

2018-12-16 计算机网络

  • TCP 粘包现象
  • TCP 粘包/拆包 原因
    • 1. 面向字节流的工作特性
    • 2. 缓冲机制
    • 3. Nagle 算法
  • 解决方案
  • 不同应用场景的下的应对方案
    • 完全禁止 粘包/拆包 的场景
    • 可以忽视 粘包/拆包 的场景
  • UDP 有粘包/拆包 问题吗?

TCP 粘包现象

TCP 粘包是指发送方发送了若干数据包,但是到达接收方之后,数据包的内容全部 “粘连” 在了一起,每个数据包的数据结尾直接和下个数据包的数据连在了一起,无法正常区分。

下面的图片展示了一个 TCP 粘包现象,正常的 3 个数据包因为粘包,被连在了一起,看起来像是一个数据包,结果就很感人了 …

TCP 粘包/拆包 原因

TCP 粘包 并不是因为协议本身有 “问题”,而是一种 “正常现象”, 因为 TCP 是面向字节流的协议,数据之间没有所谓 “边界”,所以数据粘包之后的 “拆包” 工作应该 (也必须) 由应用层完成。

TCP 采用 “异步” 方式发送应用数据,也就是说,当应用程序中调用 Send(packet) 发送数据时,虽然 Send 函数会立即返回,但是数据并不一定已经到达通信对方了。应用数据具体什么时候发,由应用层下面的传输层 TCP 说了算,TCP 使用了 3 个主要机制 (确认与重传、滑动窗口流量控制、拥塞控制) 来实现可靠性传输,保证应用数据的可靠传输和 应用层数据发送顺序和到达顺序一致的语义保证

下面来展开说一下可能导致 粘包/拆包 问题的原因。

1. 面向字节流的工作特性

TCP 作为传输层,并不了解 (也不关心) 应用层数据的上下文含义,它只会根据通信双方约定的 MSS 对发送缓冲区的数据包进行拆分。

所以在应用层的视角来看,一个完整的数据包 (可能是一段聊天文字、一个图片、一个视频) 可能会经历不同的发送过程:

  • 如果 MSS 较小,一个完整的数据包会被 TCP 拆分成多个更小的 数据包进行发送,产生拆包现象
  • 如果 MSS 较大,多个完整的数据包会被 TCP 合并为一个更大的 数据包进行发送,产生粘包现象

2. 缓冲机制

TCP 在发送数据时,会将数据放入发送缓冲区;在接收数据时,会将数据放入接收缓冲区。TCP 会尽可能地将发送缓冲区中的数据打包成一个或多个数据包发送出去,而接收方在读取数据时,也会尽量将接收缓冲区中的数据全部读取出来。这种机制可能导致发送方一次发送的多个数据包被接收方一次性读取,从而引发粘包问题。

  • TCP 发送方会将数据放入 发送缓冲区
  • TCP 接收方会将数据放入 接收缓冲区

为了尽可能提升发送数据和接受处理数据的性能,作为发送方来讲:

  • 如果要发送的数据小于发送缓冲区大小,TCP 会将多次要发送的数据,全部写入发送缓冲区,然后一起发送,产生粘包现象
  • 如果要发送的数据大于发送缓冲区大小,TCP 会将要发送的数据进行切分,满足写入发送缓冲区的条件,然后发送,产生拆包现象

作为接收方来讲:

  • 如果应用层没有及时处理接收缓冲区的数据,产生粘包现象

3. Nagle 算法

Nagle 算法原理: 发送方已经发送数据还未被接收方确认之前,期间如果又有小数据生成,先把小数据收集起来,凑满一个 MSS (最大报文段大小) 或者收到接收方 Ack 后再一起发送。通过将小数据包积累成较大的数据包后再发送,从而提高网络效率。

很显然,根据 Nagle 算法的工作机制,在频繁发送小的数据包时 (例如 Telnet, SSH 终端),会产生粘包现象。

下面是在 Go 语言中关闭 TCP Nagle 算法的示例代码。

package main

func main() {
    conn, _ := net.Dial("tcp", "dbwu.tech:443")

    fd := conn.(*net.TCPConn).File()
    // 关闭 Nagle 算法
    syscall.SetsockoptInt(int(fd.Fd()), syscall.IPPROTO_TCP, syscall.TCP_NODELAY, 1)
}

❓ 单纯关闭 Nagle 算法并不能解决粘包问题,读者思考一下为什么?

💡 综上所述,因为应用层和传输层工作方式上的差异,所以就导致了所以的 “TCP 粘包/拆包” 问题。


解决方案

既然拆包必须由应用层来完成,那么按照数据处理的思路,只要应用程序对 TCP 的字节流数据能够区分单个消息的标志和边界,那么应用程序就可以将粘包后的数据分割为正常的单个消息,粘包问题自然迎刃而解。

目前业界主要采用的解决方案:

  1. 设置消息固定长度: 发送方可以将每个消息设置为固定的长度 (对于长度不够的消息可以使用 0 进行填充),接收方从缓冲区读取数据时,每次都读取固定的长度,这样很自然就把单个消息拆分出来

  1. 设置消息分隔符: 发送方在将单个消息末尾追加分隔符,用来分区单个消息,接收方从缓冲区读取数据后,根据分隔符将读取到的数据切割成一个个单条消息

当然,分隔符很容易出现在要发送的应用数据中,这样就产生了冲突,无法进行正常拆包,所以实际项目中很少使用这种方式。

  1. 设置消息头部格式: 将消息分为消息头部和消息体,消息头中包含表示消息总长度(或者消息体长度,例如 HTTP 中的 Content-Length)字段,接收方首先读取消息头部,然后根据消息长度字段 读取具体的消息体内容,这也是最常用的方式

  1. 特定消息格式: 例如将单个消息固定为 JSON 格式,接收方从缓冲区读取数据后,根据读取到的数据能否被解析成合法的 JSON 来判断消息是否结束,当然,实际项目中很少使用这种方式

💡 注意,以上提到的解决方案通常由网络编程框架 (例如 Java 的 Netty, Golang 的 gnet) 来实现,而不是由应用程序中的业务代码来实现。

不同应用场景的下的应对方案

完全禁止 粘包/拆包 的场景

  1. 低延迟要求: 在线游戏、股票交易
  2. 小数据频繁传输,例如 Telnet, SSH 终端

可以忽视 粘包/拆包 的场景

  • 小数据传输,且两次传输之间的时间间隔很大,例如 5 秒/次 的心跳
  • 使用已经处理了粘包问题的应用层协议,例如 HTTP 使用响应头中的 Content-Length 作为消息分隔符
  • 传输数据只需要保证顺序即可,无需区分边界,例如大文件传输,每个数据包都是文件的一小部分而已,因为 TCP 保证了传输顺序性,所以接收方只需要读取数据,然后追加到已有数据的后面即可

UDP 有粘包/拆包 问题吗?

UDP 是无连接的协议,每个数据报都是独立传输的,接收方收到的数据报和发送方发送的数据报是一一对应的 (但是报文到达时间上可能会出现乱序),不需要建立连接,也不需要维护连接的状态,换句话说,选择 UDP 协议时,应用层必须完成数据的拆包工作,所以自然也就不存在 粘包/拆包 问题了。