文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

为什么 TCP 粘包是正常现象

2024-11-29 18:30

关注

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

二、TCP 粘包/拆包 原因


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


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

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

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

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

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

2. 缓冲机制

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 进行填充),接收方从缓冲区读取数据时,每次都读取固定的长度,这样很自然就把单个消息拆分出来

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

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

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

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

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

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

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

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

五、UDP 有粘包/拆包 问题吗?

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

来源:洋芋编程内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯