TCP中的粘包、拆包问题产生原因及解决方法

简介: TCP中的粘包、拆包问题产生原因及解决方法

 目录

粘包/拆包 问题产生原因:

解决 粘包/拆包 问题:

为什么TCP有粘包?

为什么UDP没有粘包?

发生在网络的哪些层上?


粘包/拆包 问题产生原因:

发生TCP粘包或拆包有很多原因,现列出常见的几点:

    1. 要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包。
    2. 待发送数据大于MSS(TCP报文长度 - TCP头部长度 > MSS最大报文长度),TCP在传输前将根据MSS大小进行拆包分段发送。
    3. 要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据包合并为一次发送,将发生粘包(Nagle算法优化,避免tcp报文头重脚轻的情况发生)
    4. 接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包。

    补充:

    MTU 最大传输单元 (链路层):

    数据链路层传输的帧大小是有限制的,不能把一个太大的包直接塞给链路层,这个限制被称为「最大传输单元(Maximum Transmission Unit, MTU)」

    以太网的帧最大的帧是 1518 字节,除去 14 字节头部和 4 字节 CRC,有效荷载最大为 1500,这个值就是以太网的 MTU。

     

    MSS TCP最大段大小 Maximum Segment Size(传输层,可传输tcp数据包大小):

    TCP 为了避免被发送方分片,会主动把数据分割成小段再交给网络层,最大的分段大小称之为 MSS(Max Segment Size)。

    这样一个 MSS 的数据恰好能装进一个 MTU 而不用分片。

    一般,在以太网中 TCP 的 MSS = 1500(MTU) - 20(IP 头大小) - 20(TCP 头大小)= 1460

    image.gif编辑

    总结:IP 数据包长度超过链路的 MTU 时,在发送之前需要分片,而 TCP 层为了 IP 层不用分片主动将包切割成 MSS 大小。

    解决 粘包/拆包 问题:

    解决问题关键在于如何给每个数据包添加边界信息用于区分不同数据包

      1. 消息分为tcp首部和tcp消息体,tcp首部中应保存数据包的长度TCP的首部原本是没有表示数据长度的字段,因为可由IP层计算出:TCP数据包长度 = IP首部的数据包长度 - IP首部长度(20字节 )- TCP包首部长度(20字节 )。这样接收端在接收到数据后,通过读取包首部的长度字段,就知道每个数据包的实际长度了。
      2. 发送端在每个包的末尾使用固定的分隔符(如 \r\n),这样接收端通过这个边界就可以将不同的数据包拆分开(如 FTP协议)。
      3. 发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。不推荐这种方式,尤其是在高并发大流量的业务场景下,会消耗不必要的资源...

      补充:TCP报文格式图

      image.gif编辑

      为什么TCP有粘包?

      TCP协议粘包拆包问题是因为TCP协议数据传输是基于 "字节流" 的,它不包含消息、数据包等概念,需要应用层协议自己设计消息边界。日常网络应用开发大都在传输层进行,因此粘包拆包问题大都只发生在TCP协议中。

      为什么UDP没有粘包?

        1. UDP有消息保护边界,不会发生粘包拆包问题
        2. UDP发送的时候,不经过Nagle算法优化,不会将多个小包合并一次发送出去。
        3. UDP协议的接收端,采用了链式结构来记录每一个到达的UDP包。UDP是基于报文发送的,从UDP的帧结构可以看出,在UDP首部采用了16bit字段来指示UDP数据报文的长度,因此在应用层能很好的将不同的数据报文区分开,从而避免粘包和拆包的问题。

        补充:"消息保护边界",就是指传输协议把数据当作一条独立的消息在网络上传输,接收端只能接收独立消息。也就是说存在保护消息边界,接收端一次只能接收发送端发出的一个数据包。而面向字节流是指无消息保护边界的,如果发送端连续发送数据,接收端有可能在一次接收动作中,会接收两个或者更多的数据包。

        发生在网络的哪些层上?

        粘包拆包问题在数据 “链路层、网络层、传输层” 都可能发生。

        目录
        相关文章
        |
        Linux
        一个进程最多可以创建多少个线程基本分析
        一个进程最多可以创建多少个线程基本分析
        746 1
        |
        边缘计算 网络协议 网络架构
        DoIP看这篇就够了,吐血整理
        DoIP看这篇就够了,吐血整理
        DoIP看这篇就够了,吐血整理
        |
        8月前
        |
        存储 编译器 C++
        【c++】多态(多态的概念及实现、虚函数重写、纯虚函数和抽象类、虚函数表、多态的实现过程)
        本文介绍了面向对象编程中的多态特性,涵盖其概念、实现条件及原理。多态指“一个接口,多种实现”,通过基类指针或引用来调用不同派生类的重写虚函数,实现运行时多态。文中详细解释了虚函数、虚函数表(vtable)、纯虚函数与抽象类的概念,并通过代码示例展示了多态的具体应用。此外,还讨论了动态绑定和静态绑定的区别,帮助读者深入理解多态机制。最后总结了多态在编程中的重要性和应用场景。 文章结构清晰,从基础到深入,适合初学者和有一定基础的开发者学习。如果你觉得内容有帮助,请点赞支持。 ❤❤❤
        1084 0
        |
        人工智能 NoSQL C语言
        程序又崩了?一招精准定位段错误!
        在C/C++开发中,程序崩溃(如段错误)是常见问题,但快速定位崩溃原因却颇具挑战。本文介绍了一种精准定位崩溃问题的方法:通过捕获异常信号(如SIGSEGV),结合`backtrace()`和`abi::__cxa_demangle()`打印堆栈信息,从而快速定位问题接口。相比增加日志或生成coredump文件,此方法更高效且无副作用。实现时需注意编译选项(如`-O0 -g -rdynamic`)以保留符号信息,并处理C++名称修饰问题。
        379 0
        |
        9月前
        |
        存储 网络协议 Linux
        【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
        本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
        403 34
        |
        消息中间件 中间件 Kafka
        分布式事务最全详解 ,看这篇就够了!
        本文详解分布式事务的一致性及实战解决方案,包括CAP理论、BASE理论及2PC、TCC、消息队列等常见方案,助你深入理解分布式系统的核心技术。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
        分布式事务最全详解 ,看这篇就够了!
        |
        NoSQL 关系型数据库 MySQL
        排行榜系统设计:高并发场景下的最佳实践
        本文由技术分享者小米带来,详细介绍了如何设计一个高效、稳定且易扩展的排行榜系统。内容涵盖项目背景、技术选型、数据结构设计、基本操作实现、分页显示、持久化与数据恢复,以及高并发下的性能优化策略。通过Redis与MySQL的结合,确保了排行榜的实时性和可靠性。适合对排行榜设计感兴趣的技术人员参考学习。
        1728 7
        排行榜系统设计:高并发场景下的最佳实践
        |
        SQL 关系型数据库 MySQL
        【MySQL】脏读、不可重复读、幻读介绍及代码解释
        【MySQL】脏读、不可重复读、幻读介绍及代码解释
        |
        负载均衡 安全 Java
        【C++ 并发 线程池】轻松掌握C++线程池:从底层原理到高级应用(一)
        【C++ 并发 线程池】轻松掌握C++线程池:从底层原理到高级应用
        1917 2
        |
        网络协议 Python
        面试题:三次握手,为什么要三次而不是两次四次?
        字节跳动面试题:三次握手,为什么要三次而不是两次四次?
        424 0