资讯详情

TCP四次挥手详解:Wireshark抓包验证与常见异常排查

📅 2026/10/26 4:18:24 | 华诺云谱 👁 阅读
TCP四次挥手详解:Wireshark抓包验证与常见异常排查
1. 为什么一个“挥手”值得单独拎出来讲很多人学网络基础三次握手背得滚瓜烂熟一到四次挥手就开始含糊。我在带新人的时候做过一个小统计让他手画一遍TCP连接建立和释放的完整时序十个人里有七个能把SYN、SYN-ACK、ACK画对但到了FIN阶段要么少画一个ACK要么把半关闭状态搞混要么说不清为什么是四次而不是三次。这个现象挺有意思因为挥手在实际排障中的出场频率其实比握手更高——连接泄漏、端口耗尽、服务端大量CLOSE_WAIT堆积、抓包里看到莫名其妙的RST这些问题的根子往往都埋在四次挥手的某个环节里。这篇内容就围绕TCP四次挥手和Wireshark抓包验证这两件事展开。我会把挥手过程中每个报文的作用、状态迁移的来龙去脉讲透然后手把手带你用Wireshark把整个过程抓下来、看懂它。适合正在准备HCIA这类入门认证的朋友也适合已经上手但抓包时只会看“绿了红了”的运维和开发同学。你不需要有很深的协议栈功底但最好自己动手抓过一次包哪怕只是随便点开一个HTTP请求看看。我个人的习惯是讲挥手之前先把“连接”这件事的本质说清楚。TCP连接不是一根物理线而是两端各自维护的一套状态机双方通过交换报文来同步彼此的状态。所谓“挥手”本质上是两个方向的数据流各自独立关闭的过程。理解了“独立”这两个字四次挥手的很多疑惑就自动消解了。下面我按“设计思路—报文细节—抓包实操—问题排查”的顺序往下走中间会穿插我自己踩过的坑和几个容易忽略的细节。2. 四次挥手的整体设计与状态机拆解2.1 为什么关闭连接需要四次而不是三次先说结论因为TCP是全双工的两个方向的数据通道是独立的关闭一个方向不等于关闭另一个方向。三次握手能成立是因为建立连接时双方没有数据要传SYN和ACK可以合并在一个报文里捎带过去而关闭连接时主动关闭方发出FIN只代表“我这边没有数据要发了”不代表“我不能再收数据”。被动关闭方收到FIN后应用层可能还有数据没处理完、没发送完所以它的ACK和它自己的FIN通常不能合并这就多出了一次交互。用一个生活场景类比两个人打电话。A说“我这边说完了先不说了”FINB回答“好的我知道了”ACK但B可能还有话没讲完于是继续说了一会儿等B也讲完了B才说“那我也说完了”FINA再回一句“好挂了”ACK。这四句话对应四个报文缺一不可。如果B恰好也没话说了理论上ACK和FIN可以合并这就是所谓的“延迟确认”或者某些实现里的合并优化但标准流程和抓包中最常见的仍然是四次。这里有个容易被忽略的点主动关闭方在发出FIN之后仍然可以接收数据。这个状态叫半关闭half-close。很多新手以为发了FIN连接就断了实际上此时A到B的方向已关闭B到A的方向还开着。这个特性在某些协议设计里被刻意利用比如客户端发完请求后关闭发送方向但仍然等待服务端返回完整响应。理解半关闭是理解四次挥手的关键。2.2 四个报文与五个状态的对应关系把状态机和报文对应起来看逻辑会清晰很多。我习惯用一张表来记比纯背文字靠谱阶段发送方报文发送方状态变化接收方状态变化第一次挥手主动关闭方FIN1, sequESTABLISHED → FIN_WAIT_1ESTABLISHED → CLOSE_WAIT第二次挥手被动关闭方ACK1, acku1FIN_WAIT_1 → FIN_WAIT_2CLOSE_WAIT 保持第三次挥手被动关闭方FIN1, seqwCLOSE_WAIT → LAST_ACKFIN_WAIT_2 保持第四次挥手主动关闭方ACK1, ackw1FIN_WAIT_2 → TIME_WAITLAST_ACK → CLOSED这张表建议自己默画几遍。重点盯两个地方一是CLOSE_WAIT出现在被动关闭方二是TIME_WAIT出现在主动关闭方。实际排障中服务端出现大量CLOSE_WAIT几乎可以断定是应用层代码收到FIN后没有调用close()连接被晾在那儿了而大量TIME_WAIT则通常出现在主动发起关闭的一方是正常的协议行为只是数量过多时需要调优。这两个状态的方向搞反排查思路就会完全跑偏。关于序号FIN报文本身会消耗一个序列号所以确认号是seq1。这一点和SYN一样SYN也消耗一个序号。纯ACK报文不消耗序号。这个细节在抓包里看序号连续性时很有用如果你发现某个ACK的ack号对不上往往就是漏算了FIN或SYN占用的那一个序号。2.3 TIME_WAIT为什么必须等2MSLTIME_WAIT是四次挥手里被问得最多的状态。主动关闭方在发出最后一个ACK后不会立刻进入CLOSED而是要等2MSLMaximum Segment Lifetime报文最大生存时间才真正关闭。MSL在不同实现里取值不同常见的是30秒、60秒或120秒所以2MSL通常是1到4分钟。为什么要等两个原因。第一保证最后一个ACK能到达对端。如果这个ACK丢了被动关闭方会重传FIN此时主动关闭方还处于TIME_WAIT能重新回一个ACK如果已经CLOSED了收到重传的FIN会回RST对端就会认为连接异常终止。第二让本次连接产生的旧报文在网络中自然消亡避免它们干扰后续使用相同四元组建立的新连接。第二个原因更微妙涉及序列号回绕的问题入门阶段知道“等是为了清理残留”就够了。我见过有人为了减少TIME_WAIT数量直接把等待时间改得很短结果在压测环境里偶发连接异常。这个参数不是不能调但要知道代价。后面排查章节我会细说。3. Wireshark抓包前的准备与过滤技巧3.1 抓包环境怎么搭才不白抓抓四次挥手最怕的是抓了半天只抓到一半——要么没抓到连接建立要么FIN发出去了但没抓到对端的响应。我的建议是抓包点尽量靠近你想观察的那一端并且保证双向流量都能被捕获。如果你在客户端本机抓看到的是客户端视角的进出流量如果在服务端抓看到的是服务端视角。两端同时抓当然最理想可以对照时间戳分析延迟。具体操作上本机抓包用Wireshark直接选对应网卡即可。如果是抓本机与远程服务器的通信注意选对网卡——有虚拟网卡、回环网卡、无线网卡多个选项时选实际承载业务流量的那个。我踩过的坑是在装了虚拟化软件的机器上默认选了虚拟网卡抓了半天全是背景广播业务报文一个没有。判断方法很简单看抓包界面的包速率如果每秒几十上百个无关包多半选错了。抓包前先想清楚过滤条件。四次挥手涉及的是TCP连接最基础的过滤是tcp.port 目标端口。如果知道对端IP加上ip.addr x.x.x.x更精准。但要注意过滤表达式在抓包阶段和显示阶段是两回事。抓包阶段的过滤器capture filter语法是BPF写错了直接抓不到显示过滤器display filter语法是Wireshark自己的写错了只是不显示。新手常犯的错是在抓包过滤器里写tcp.port80这种带空格的表达式导致抓包启动失败。稳妥做法是抓包阶段不加过滤或者只加最简单的tcp抓完再用显示过滤器精筛。3.2 几个必会的显示过滤器抓完包面对成百上千个报文不会过滤等于白抓。下面这几个过滤器是我每次分析挥手都会用到的tcp.flags.fin 1只显示FIN报文快速定位挥手起点。tcp.flags.reset 1找RST判断连接是否异常终止。tcp.port 8080按端口筛把无关流量排除。tcp.stream eq 3按TCP流编号筛把一条完整连接的所有报文拎出来。这个最实用右键任意报文选“Follow → TCP Stream”也能达到类似效果。tcp.time_delta 1找相邻报文间隔超过1秒的用来发现挥手过程中的异常延迟。其中tcp.stream这个过滤器值得单独说。Wireshark会给每条TCP连接分配一个流编号同一条连接的所有报文编号相同。分析挥手时先用FIN过滤器找到目标连接看它的流编号然后用tcp.stream eq N把整条连接过滤出来从SYN一直看到最后的ACK时序一目了然。这比手动一个个找报文高效得多。3.3 抓包时机与复现技巧四次挥手不是随时都能碰上的尤其是短连接可能你刚点开始抓包连接已经关完了。我的做法是主动制造一次可控的关闭。最简单的方式是用一个短连接请求比如命令行发一个HTTP请求然后让它自然结束或者用脚本建立TCP连接后主动close。如果你想观察服务端主动关闭的场景可以找一个配置了短超时的服务或者自己写个简单的服务端程序处理完请求就关闭连接。还有一种情况是抓“异常挥手”比如RST。RST通常在连接被强制终止时出现比如向一个已关闭的端口发数据、或者应用层直接abort。抓RST相对容易随便连一个没监听的端口就能触发。把正常四次挥手和RST终止放在一起对比看对理解TCP的优雅关闭和强制关闭的区别很有帮助。提示抓包文件建议及时保存为.pcapng格式并命名规范比如“日期_场景_端口.pcapng”。我吃过亏抓完没存回头想复现发现环境变了只能重来。4. 逐报文拆解一次完整的四次挥手抓包4.1 从FIN_WAIT_1到CLOSE_WAIT第一次和第二次挥手假设我们抓到了客户端主动关闭的一条连接。在Wireshark里用流过滤器筛出来后找到第一个FIN报文。展开TCP层你会看到几个关键字段Flags里FIN置1Seq是当前序列号假设为uAck是上一次收到的序列号。这个报文发出后客户端进入FIN_WAIT_1。紧接着应该能看到对端的ACK报文Ack号等于u1因为FIN消耗了一个序号。这个ACK到达后客户端从FIN_WAIT_1进入FIN_WAIT_2服务端进入CLOSE_WAIT。在抓包里这两个报文的时间间隔通常很短因为ACK由内核协议栈自动回复不涉及应用层处理。如果你看到FIN和对应ACK之间隔了很久那就要怀疑网络延迟或者中间设备的问题了。这里有个抓包细节Wireshark的“Time”列默认显示相对第一个包的时间分析挥手间隔时建议把时间显示格式调成“Seconds Since Previous Displayed Packet”这样相邻报文的时间差直接可见。在菜单里 View → Time Display Format 里改。我一开始不知道这个对着绝对时间戳心算差值效率极低还容易算错。另外FIN报文里如果带了数据FIN可以和payload一起发Seq和Ack的计算会稍微复杂一点因为数据也占序号。不过大多数场景下FIN是单独发的入门阶段先掌握纯FIN的情况即可。4.2 CLOSE_WAIT的停留时间说明了什么第二次挥手之后服务端处于CLOSE_WAIT等待应用层调用close()。这个停留时间在抓包里体现为ACK报文和后续FIN报文之间的间隔。这个间隔可长可短取决于应用层什么时候处理完并关闭socket。这个间隔是排查问题的金矿。如果间隔很短毫秒级说明应用层处理很快属于正常短连接如果间隔很长几秒甚至几十秒说明应用层在收到FIN后迟迟没有关闭连接可能是业务逻辑在处理耗时任务也可能是代码里压根忘了close。后者就是典型的CLOSE_WAIT泄漏。我在实际项目里见过一个服务因为异常分支里没有正确释放连接导致CLOSE_WAIT数量持续上涨最终把文件描述符耗尽。当时就是靠抓包看ACK到FIN的间隔结合代码定位到具体分支的。所以抓包时别只看“有没有FIN”要看“FIN来得有多晚”。这个时间差往往比报文本身更能说明问题。4.3 LAST_ACK与最后的ACK挥手收尾服务端应用层关闭连接后发出自己的FIN第三次挥手Seq为w此时服务端从CLOSE_WAIT进入LAST_ACK。客户端收到后回复ACKAck号为w1客户端进入TIME_WAIT服务端收到这个ACK后进入CLOSED。在抓包里第三次挥手的FIN和第四次挥手的ACK通常也是紧挨着的。但要注意如果第四个ACK丢失服务端会重传FIN此时你在抓包里会看到重复的FIN报文。Wireshark会把重传标记为“[TCP Retransmission]”或者“[TCP Fast Retransmission]”。看到FIN重传基本可以判断最后一个ACK没送达这时候TIME_WAIT的存在就体现出价值了——客户端还在等待期内能重新应答。还有一个容易混淆的点抓包里有时会看到FIN之后跟着一个RST。这通常发生在连接状态已经不一致的情况下比如一端已经CLOSED另一端还在发FIN收到的一方回RST。这种情况在长连接保活、中间设备超时清理等场景下比较常见看到RST不要慌先看它出现在挥手的哪个阶段再结合两端状态判断。4.4 用Wireshark的统计功能看全局单看报文是微观视角Wireshark的统计功能能给你宏观视角。分析挥手时我常用两个Statistics → Flow Graph和Statistics → TCP Stream Graphs → Time-Sequence (tcptrace)。Flow Graph会把一条连接的所有报文按时间顺序画成箭头图谁发给谁、什么标志位一目了然。分析四次挥手时Flow Graph能直观展示四个报文的往返关系特别适合给新人讲解或者写文档时截图。Time-Sequence图则把序列号和时间的对应关系画出来能看出数据传输和挥手的时间分布判断连接是短连接还是长连接、挥手是否及时。这两个功能不需要额外配置选中目标流后直接打开即可。我习惯在排查完一个问题后用Flow Graph截个图存档下次遇到类似问题直接对照比翻文字记录快得多。5. 常见异常与排查思路实录5.1 只看到三次挥手是怎么回事抓包里只看到三个报文FIN、ACK、FIN最后的ACK没抓到有两种可能。第一种是抓包点位置问题最后一个ACK没有经过抓包网卡比如抓包点在中间设备上回程路径不对称。第二种是ACK确实发了但被合并或延迟某些实现里最后的ACK可能和后续数据捎带或者抓包工具漏抓。判断方法是两端同时抓包对照如果一端抓到了完整四个报文另一端只抓到三个那就是抓包点的问题不是协议异常。还有一种“三次挥手”是ACK和FIN合并的情况被动关闭方收到FIN后如果应用层立刻也关闭了内核可能把ACK和自己的FIN合并成一个报文发出。这在抓包里表现为FIN报文同时带ACK标志Ack号确认了对端的FIN。这种情况是合法的不算异常只是把第二和第三次挥手压缩了。看到这种报文不用惊讶理解成“对方也没话说了一起回复”即可。5.2 大量CLOSE_WAIT和大量TIME_WAIT分别意味着什么这两个状态是面试和排障的高频考点我把判断逻辑整理成表现象出现方根因方向处理思路大量CLOSE_WAIT被动关闭方通常是服务端应用层收到FIN后未调用close()查代码连接释放逻辑重点看异常分支大量TIME_WAIT主动关闭方通常是客户端或短连接服务端主动关闭后等待2MSL属正常行为数量过大时考虑连接复用、调整内核参数大量FIN_WAIT_2主动关闭方对端一直不发FIN半关闭卡住查对端应用是否卡住或未关闭大量SYN_RECV服务端握手未完成可能是SYN Flood或网络问题结合安全防护和队列参数排查CLOSE_WAIT的排查核心在应用层抓包只能告诉你“FIN来了但没关”具体哪行代码没关得看程序。TIME_WAIT则要区分场景如果是客户端主动关闭短连接TIME_WAIT多是正常的如果是服务端主动关闭且数量巨大可以考虑让客户端主动关闭或者启用连接复用减少连接创建频率。调内核参数比如缩短TIME_WAIT时间、开启重用要谨慎改之前先想清楚代价别为了指标好看引入新问题。5.3 抓包时看不到FIN的几种可能有时候你明明知道连接关了抓包里却找不到FIN。常见原因有几个一是连接被RST终止没有走正常挥手流程这时候找RST报文而不是FIN二是抓包开始得太晚连接关闭发生在抓包启动之前三是过滤条件把FIN滤掉了比如只过滤了特定长度的报文四是连接被中间设备静默清理两端都没收到FIN后续通信才发现连接已失效。排查这类问题我的顺序是先去掉所有显示过滤器看全量再用tcp.flags.reset1找RST然后看连接的最后几个报文是什么。如果最后是数据报文后突然没了可能是抓包中断或连接被中间设备清理如果最后是RST那就是强制终止。这个顺序能覆盖大部分“找不到FIN”的场景。注意分析抓包时不要只看单个报文一定要结合上下文和两端状态。同一个RST在不同阶段出现含义可能完全不同。6. 我踩过的坑和几条实操建议先说一个我早期犯的错抓包时只抓了服务端看到客户端发来FIN、服务端回了ACK然后就没了以为服务端没发FIN是异常。后来两端对照才发现服务端的FIN其实发了只是抓包网卡选错了方向回程报文没经过那块网卡。从那以后我养成了习惯分析挥手异常前先确认抓包点是否覆盖双向流量。这个前置检查能省掉大量误判。第二个坑是关于时间戳的。有次排查一个“挥手很慢”的问题看抓包发现FIN到ACK隔了好几秒一度怀疑网络问题。后来发现是Wireshark的时间显示设置成了绝对时间我误读了。改成相对时间后实际间隔只有几十毫秒。所以分析延迟类问题先把时间显示格式调对这是基本功。第三个经验是关于过滤器的组合使用。单独用tcp.flags.fin1可能筛出很多条连接的FIN定位不到目标。我的做法是先按端口或IP缩小范围再叠加FIN过滤最后用流编号锁定。三步走比一步到位更稳。另外Wireshark的“Apply as Filter”右键功能很好用选中一个报文的某个字段右键就能快速生成过滤表达式比手写快还不容易错。最后分享一个我觉得很值的小习惯每次分析完一个挥手案例把关键报文的时间线、状态变化、异常点整理成三五行的笔记存起来。积累多了你会发现大部分挥手问题就那么几类排查时直接对号入座比每次从头分析快得多。协议这东西看十遍不如抓一次包抓十次包不如认真分析透一次。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑