当前位置: 首页 > news >正文

【JavaSE - 网络部分07】TCP 收尾:面向字节流(粘包问题)与异常场景处理【传输层】

✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
🎯你正在阅读「网络原理续命手册」系列文章🎯
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨

🔥弹简特 个人主页

❄️个人专栏直通车:

  • 💻软件测试入门记
  • 📱野生测试修炼手册 | APP 专项测试笔记
  • 🔌接口测试从入门到跑路
  • 一个后端的 JavaEE 续命指南
  • 🛜网络原理续命手册
  • 🐍Python 从零摸索日记

靠热爱去书写自己,靠勇敢去书写生活!
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨


🌟 博主简介:



文章目录:

  • 前言
  • 一、TCP其他核心机制,确保程序正常运转
    • 1、TCP核心机制9,面向字节流
      • 1.1 粘包问题引入
      • 1.2 解决粘包问题
        • 方法一:引入分隔符
        • 方法二:引入数据长度
      • 1.3 粘包问题总结
    • 2、TCP核心机制10,异常情况
      • 2.1 进程崩溃
      • 2.2 主机关机
      • 2.3 主机掉电
        • 2.3.1 如果掉电的是接收方
        • 2.3.2 如果是发送方断电的情况
      • 2.4 网络断开
  • 二、补充
    • 1、TCP标志位补充说明
    • 2、TCP选项部分
    • 3、TCP与UDP对比总结
  • 三、写在最后

前言

上期吃透TCP延迟应答与捎带应答两大优化机制✨,高效缩减冗余报文、节约网络带宽。本期迎来TCP内容收尾环节🔎,详解字节流特性催生的粘包问题,剖析丢包、断连等异常处置方案,补齐传输层关键要点。

一、TCP其他核心机制,确保程序正常运转

1、TCP核心机制9,面向字节流

我们面向字节流他本身理解起来比较简单,我们可以把它比作源源不断流动的水流。但在字节流传输的过程中,有一个比较难理解的问题,那就是粘包问题。

这时不少小伙伴都会产生疑问,粘包到底粘连的是什么数据包呢?
其实粘包粘连的就是应用层数据包

1.1 粘包问题引入

因为我们TCP 协议是以字节流的形式传输数据,这种传输方式没办法划分独立数据包的界限,很容易让各个数据包之间的边界变得模糊,接收设备也就没办法分辨,一段数据从起始位置到结束位置,哪一部分才是单独完整的应用层数据包。

就像图中展示的这样,应用程序会调用 read 方法来读取字节流数据,读取操作没有固定的读取规范与范围限制。图里一共存在三个应用层数据包,这些数据都会统一存放到接收缓冲区当中,我们没办法直观区分界限,自然也就判断不出读取到哪个位置,才算获取到一个完整的应用层数据包。


我们期望的是得到一个完整的应用层数据包,如果不加任何限制的读取,此时得到的大概率就不是一个完整的数据,所以我们对粘包问题提供了一些解决方案👇


1.2 解决粘包问题

出现了粘包问题之后,那我们该用什么样的方式去解决这个问题呢?

想要解决粘包问题,归根结底就是要把每一个应用层数据包之间的边界划分清楚,只要能够精准区分开相邻的数据包,就能规避粘包带来的影响。日常开发中主要有两种常用的解决方式👇

方法一:引入分隔符

第一种解决办法就是自定义分隔符来划分数据包边界。
我们可以选用\n这类符号当作分隔标记,程序在读取字节流数据的时候,只要读取到预先设定好的分隔符,就代表当前这一份应用层数据包已经读取结束。

不过这种方式也存在对应的缺陷,如果我们选定\n作为分隔符,一旦实际传输的数据内容里面,本身就包含了\n这个字符,系统就会错误判定数据包结束位置。
正是因为存在这样的隐患,我们在使用分隔符方案时,挑选的分隔符号一定不能出现在业务数据当中。具体选择哪种符号作为分隔标识,要结合实际传输的数据内容来判断,核心原则就是分隔符和真实业务数据不会出现重合。


方法二:引入数据长度

第二种解决办法是在数据包头部新增长度字段,依靠这个长度数值,界定出单个数据包的数据起止范围。

按照这个规则读取数据时,会先读取固定个数的字节,从中解析出数据包的实际大小。举个例子,解析得到长度数值为 3,那就说明紧随其后的 3 个字节数据,共同构成一份完整的应用层数据包。


1.3 粘包问题总结

粘包问题,本质是我们设计应用层通信协议时,必须提前规划和处理的问题。
粘包并不是 TCP 协议单独存在的问题,只要数据是以字节流的形式完成传输,就都有可能出现粘包现象,日常的文件读取场景,同样也会碰到这类问题。


2、TCP核心机制10,异常情况

这里边我们谈的一些异常情况如下:

  • 进程崩溃
  • 主机关机
  • 主机掉点
  • 网线断开

如果这几种情况出现,我们tcp会怎么做呢?

2.1 进程崩溃

当程序进程崩溃(比如程序自己挂掉了或被强制终止)时,操作系统会帮它做清理工作:

  1. 自动发出结束信号(FIN)
    就像程序正常关闭时主动说“我这边没数据要发了”一样,进程崩溃后,操作系统会自动替它向对方发出同样的结束信号,然后开始四次挥手流程。所以从网络上看,崩溃和正常关闭的效果是一样的。

  2. 连接不会马上消失
    发出结束信号后,TCP连接并不会立刻释放。它会进入一个“半关闭”的等待状态,给对方一点时间来处理。如果对方一直没有回应,系统会等待一段时间(比如几十秒到几分钟),超时后才彻底释放这个连接。这就是“等待一定时间之后才会释放”的原因。

  3. 对方怎么知道这边崩溃了?
    对方程序会感觉到连接出了问题:比如尝试读取数据时发现读不到新内容(就像对方挂断了电话),或者尝试发送数据时收到错误提示(类似“对方已不在线”)。这样对方就知道连接已经失效,可以做相应处理(比如重连或报错)。

它本质上和我们正常的状态连接过程是一样的,也就是我们的四次挥手是一样的。
我们调用close方法就会四次挥手,触发fin,
那么我们进程退出它也会触发fin。

我们进程崩溃它不代表我们tcp连接就释放了,tcp连接仍然存在,等待一定时间之后才会释放。

总结
1)进程崩溃时,操作系统内核会自动清理进程持有的 TCP 套接字,触发 FIN 报文,启动四次挥手,这和主动调用close()的核心 TCP 行为一致。
2)进程崩溃后 TCP 连接不会立即释放,是因为连接会进入半关闭状态(如 FIN_WAIT_2),内核会等待对端回应或超时后才彻底释放。
3)对端(客户端)会通过读写异常(如BrokenPipeError、ConnectionResetError)感知到进程崩溃,从而知道连接已失效。


2.2 主机关机

正常关机时,操作系统会先强制结束所有用户进程。这个操作和进程崩溃本质上是一样的——内核会自动清理进程占用的TCP套接字,从而触发四次挥手,发出FIN报文。

下图展示了这一过程:以咱们客户端关机为例

不过,关机情况下的四次挥手可能挥完,也可能挥不完。原因在于电脑关机是一个有时间限制的过程(系统会等待一段时间让进程退出,但不会无限等待)。

如果四次挥手非常快:在网络状况良好、对端响应及时的情况下,整个四次挥手可以在关机完成之前顺利结束。此时连接正常关闭,就像我们主动调用close()一样。

如果四次挥手没那么快:可能出现挥手还没结束,电脑就已经关机断电的情况。例如下图所示:

在这种情况下,对端(仍在运行的那台机器,比如服务器)会收不到ACK回应。因为客户端这边已经关机了,无法回复任何报文。

收不到ACK怎么办?对端(服务器)就会启动超时重传机制。它会多次尝试重传FIN,等待对方的回应。这个过程可以参考下图:

如果重传几次之后仍然得不到任何回应,对端(服务器)就知道对方已经不可达了。此时,对端服务器会单方面释放自己保存的连接信息

这里需要理解一个关键概念:断开连接,本质上是通信双方各自删除对方的信息。正常四次挥手完成后,双方都会删除对方的连接记录。但在关机这种异常情况下,规则无法完整执行。既然对方已经关机,这一端就不再等待了——至少保证把自己保存的信息删掉。这样,虽然挥手没完成,但连接在逻辑上已经断开。

总结:主机关机时,会强制结束进程并触发FIN,开始四次挥手。如果挥手在关机前完成,则正常关闭;如果挥手未完成就断电,则关机方不再参与,而对端会因收不到回应而超时重传,最终单方面释放自己记录的连接信息。


2.3 主机掉电

主机掉电属于瞬间关机,操作系统来不及做任何动作(比如发送FIN、保存状态等)。那么TCP在这种情况下会怎么处理呢?我们分两种情况讨论。

2.3.1 如果掉电的是接收方

假设服务器(接收方)突然掉电了,之前客户端发送的数据,服务器本来都会回复ACK。但现在服务器瞬间断电,无法发出任何回应。

此时客户端发现:发出去的数据迟迟收不到ACK。于是客户端就会触发超时重传机制——它会多次重传相同的数据,等待服务器的回应。

下图示意了这一过程:

如果重传达到一定的次数之后仍然没有任何ACK回来,客户端就知道对方已经不可达了。这时客户端会单方面释放自己这边保存的连接信息,并且主动向对方发送一个复位报文(RST),表示这个连接异常终止。

总结接收方掉电:发送方收不到ACK → 超时重传 → 重传失败 → 单方面释放连接 + 发送RST。


2.3.2 如果是发送方断电的情况

假设客户端(发送方)突然断电了,它正在发送的数据戛然而止,也不会再发送任何后续报文。

那么接收方(服务器)会怎么做?它会等。

接收方会等待发送方继续发送数据,但它不是一直傻等。TCP协议中有一个机制叫做保活机制(Keep-Alive),也就是我们常说的心跳包

这个心跳包的作用是探测对方是否还在正常工作。为什么叫“心跳”呢?因为心跳停了,就代表对方“挂了”。

具体过程是这样的:接收方在等待一定时间后(这个时间可以配置),会定期向发送方发送一个不携带任何数据载荷的探测包(其实就是一种特殊的TCP报文)。然后它等待对方回复ACK。

  • 如果对方回复了ACK:说明对方的工作状态正常,连接可以继续保持。
  • 如果对方一直没有回复ACK:说明对方已经出问题(比如掉电、崩溃、网络断开),此时接收方就可以单方面释放这个连接了。

下图示意了发送方断电的情况:

总结发送方掉电:接收方收不到数据 → 等待一段时间 → 定期发送心跳包 → 收不到ACK → 单方面释放连接。


2.4 网络断开

网络断开的情况,可以理解为发送方断电接收方断电两种情况的结合——因为断开的是网络,所以双方都无法正常收到对方的数据。

我们分别从发送方和接收方的视角来看:

站在发送方的视角:

  • 发送方像往常一样发送数据,但一直收不到对方的ACK确认
  • 于是发送方会触发超时重传机制,多次重传数据。
  • 重传一定次数后仍然没有回应,发送方就会触发复位(RST),即单方面释放自己这边保存的连接信息

站在接收方的视角:

  • 接收方原本能持续收到发送方的数据,但突然间对方发来的数据没有了
  • 接收方不会一直傻等,而是会定期给对方发送心跳包(也就是TCP的保活探测报文),用来探测对方是否还在正常工作。
  • 如果心跳包发出去后一直没有反应(收不到ACK),接收方也会触发复位(RST)单方面释放连接

总结:网络断开时,发送方因收不到ACK而超时重传后复位,接收方因收不到数据而用心跳探测后复位。双方最终都会单方面释放连接,不再走正常的四次挥手流程。


二、补充

1、TCP标志位补充说明

在前面我们学过TCP的6个标志位,已经知道其中4个是:ACK、PSH、RST、SYN、FIN,这里补充剩下的一个URG,以及进一步解释PSH

标志位名称作用说明
URG紧急指针有效用来表示当前数据段中有紧急数据,需要优先处理。可以通俗理解为**“插队”**,让接收方跳过普通数据先处理这段紧急内容。
PSH催促标志位告诉接收方:收到这个报文段里的数据之后,尽快把这个数据交给上层应用程序,不要等缓冲区满了再交。可以理解为**“催你赶紧去处理这个数据”**。

其余四个标志位的简单回顾:

  • ACK:确认号有效,用于确认收到数据。
  • RST:复位连接,用于异常终止。
  • SYN:同步序号,用于建立连接。
  • FIN:结束标志,用于正常关闭连接。

2、TCP选项部分

TCP头部除了固定的20字节之外,还有可选的选项字段,用于扩展TCP的功能。常见的选项包括:

  • 最大报文段长度(MSS):告诉对方自己能接收的最大数据段大小。
  • 窗口扩大因子:用于支持更大的窗口(超过64KB)。
  • 时间戳:用于计算往返时间(RTT)和防止回绕的序号。
  • SACK(选择性确认):允许只重传丢失的特定数据,而不是全部重传。

下图展示了TCP选项在报文中的位置(了解即可):

注:此图仅作示意,具体选项格式和内容需要时可以查阅TCP协议标准文档。

3、TCP与UDP对比总结

到这里,我们的TCP协议就告一段落了。最后用一句话对比TCP和UDP的核心区别:

协议特点可靠性是否支持广播
TCP面向连接、可靠传输、有拥塞控制和流量控制可靠性要求高不支持广播(是一对一的)
UDP无连接、尽力交付、没有拥塞控制传输效率要求高支持广播(可以一对多)

简单记忆:

  • TCP:可靠但慢,不丢数据,适合文件下载、网页访问等。
  • UDP:快但可能丢包,适合视频直播、语音通话、广播场景。

三、写在最后

至此,TCP协议的十大核心机制——从确认应答、超时重传、连接管理、滑动窗口、流量控制、拥塞控制,到延迟应答、捎带应答、面向字节流(含粘包处理),以及本文详述的四大异常场景处理——已全部讲解完毕。

TCP通过精妙的设计,在不可靠的IP网络上构建了可靠的传输服务。理解这些机制,不仅能帮你应对面试和笔试中的高频考点,更能让你在实际网络编程中做到“知其然,知其所以然”。

如果你觉得本文对你有帮助,欢迎点赞👍、收藏⭐、关注👀,后续将继续解锁更多网络原理与编程实战知识。我们下期再见!

http://www.rkmt.cn/news/1396715.html

相关文章:

  • 叠氮酸介绍
  • ChatGPT辅助定量研究:Stata/Python代码生成、回归结果解读、稳健性检验提示链(附GitHub可验证代码库)
  • Windows虚拟光驱终极指南:开源免费的ISO文件挂载工具完整解析
  • FreeRTOS临界区失效剖析:从vPortExitCritical卡死到中断优先级配置陷阱
  • 告别熬夜改 PPT!Okbiye AI PPT 一键搞定毕业论文答辩,小白也能零失误通关
  • Win10/Win11下雷云3驱动打不开?别急着重装系统,试试手动修复这两个关键服务
  • 联邦学习与对比学习融合:破解隐私保护下的社交关系预测难题
  • Redis RDB解析工具完整指南:轻松掌握内存数据分析技巧
  • 如何快速配置OpenCore EFI:智能简化工具的终极指南
  • CodeX++这工具确实不赖,强驱DeepSeek官方或第三方API到CodeX里使用(踩坑记录)
  • 从ASK到QAM:数字调制技术全景解析与实战选型指南
  • AUTOSAR AP 详解
  • 关于大学专业课如何去正确学习
  • RK3588 适配 WiFi 模组 (SDIO)
  • Matlab肺结节分割(肺结节提取)源程序,也有GUI人机界面版本。使用传统图像分割方法,非深度学习方法。使用LIDC-IDRI数据集
  • Prompt工程×前端渲染×实时协同,Lovable写作助手开发全流程解析,含GitHub可运行代码库
  • 通过curl命令快速测试Taotoken多模型API连通性与响应
  • 使用taotoken聚合api后,c语言程序调用大模型的延迟与稳定性体验观察
  • 用 AI 复刻潮语深情,声线 App 让人人会念 “阿嬷的情书”
  • 网络层——ip地址
  • RK3588 适配 WiFi 模组 (USB)
  • 智慧农业农机农用机器设备检测数据集VOC+YOLO格式7376张7类别
  • 遇到带宽太小不好往服务器里面上传或下载数据,利用bypy工具
  • 大模型 RAG 技术演进图谱:2026 年 GEO 优化的核心技术路径解析——附5 家头部服务商
  • cmd命令行启动独立的chrome浏览器
  • 0.9V写入电压与万亿次耐久性:BEOL兼容AOS-FEFET如何革新嵌入式缓存
  • 3步掌握Pyfa:为什么这是EVE玩家必备的离线装配神器?
  • 构建生产级RAG流水线:从架构设计到性能优化的实战指南
  • AI拐点已至:2026年,这三大趋势将重塑智能产业
  • 15. Python 类型提示与静态检查 深度解析