资讯动态

TCP四次挥手全解析:Wireshark抓包逐包拆解与异常排查

发布时间:2026/10/26 12:39:24 来源:尧图企业网站定制
1. 为什么“四次挥手”值得单独拎出来讲搞网络的人都有一个共同的体会三次握手背得滚瓜烂熟四次挥手却总是含糊。面试被问到“为什么关闭连接要四次而建立只要三次”很多人能背出“因为TCP是全双工的”但再往下追问一句“TIME_WAIT到底等多久、为什么是2MSL”就开始支支吾吾了。我自己刚接触HCIA那会儿也是这样书上的状态迁移图看了一遍又一遍FIN、ACK、FIN、ACK四个包画得清清楚楚可一到Wireshark里抓包看到满屏的黑色、红色条目就发懵根本对不上号。这篇内容就是把我自己从“背概念”到“真看懂”这个过程里踩过的坑、总结的方法完整摊开。核心围绕TCP四次挥手这个连接拆除过程配合Wireshark抓包做逐包拆解把状态机、报文细节、异常现象一次性讲透。它解决的是“知道结论但不会验证”的问题——你不仅要知道挥手有四个包还要能在抓包文件里一眼定位每个包、读懂每个字段、判断这次关闭是否正常。适合正在备考HCIA/CCNA这类入门认证的朋友也适合日常做运维、开发、测试需要排查连接异常的同学。哪怕你之前完全没碰过抓包工具跟着走一遍也能上手。我始终认为四次挥手是理解TCP可靠性设计的一把钥匙。它不像握手那样只是“建立”挥手涉及资源释放、延迟确认、半关闭、异常终止等一堆边角情况把这些搞明白了你对TCP的理解会上一个台阶。下面就从设计思路开始一层层往下拆。2. 四次挥手的整体设计与思路拆解2.1 全双工连接为什么必须拆两次要理解四次挥手先得接受一个前提TCP连接是全双工的意思是A到B和B到A这两个方向的数据通道是相互独立的。你可以把它想象成两个人打电话你说的话走一条线对方说的话走另一条线两条线互不干扰。当一方说“我说完了”发送FIN只代表他这个方向不再发数据了但对方可能还有话没说完所以对方要先回一个“知道了”ACK等自己那边也说完了再单独发一个“我也说完了”FIN最后这边再回一个“知道了”ACK。这就是四个包的根本来源。对比三次握手就很好理解了。握手时双方都没有任何数据要传SYN和ACK可以合并成一个包发出去所以三次就够。挥手时因为两个方向的数据流是分别关闭的FIN和ACK在大多数情况下没法合并除非对方恰好也没有数据要发且开启了延迟确认的某些优化于是就成了四次。这里有个容易被忽略的点四次挥手不是协议强制规定的固定次数而是“两个方向各关一次、各确认一次”这个逻辑下的自然结果。实际抓包里偶尔能看到三次甚至五次的变体后面讲异常的时候会细说。2.2 半关闭状态容易被忽视的中间态四次挥手过程中有一个特殊状态叫半关闭Half-Close。当A发出FIN并收到对方的ACK之后A到B这个方向就关闭了A不能再发数据但B到A方向还开着B仍然可以继续给A发数据。这个设计非常有用——比如一个客户端把请求发完了发个FIN告诉服务器“我的请求发完了”但服务器还要把大量响应数据传回来这时候连接就处于半关闭状态客户端能收但不能发。很多初学者以为发了FIN连接就断了其实不是。真正断开要等双方都完成关闭。这个认知差异在排查问题时特别关键如果你在抓包里看到一方发了FIN但之后还有数据从另一方传过来不要惊讶这是完全合法的半关闭行为。Wireshark里判断半关闭看的就是FIN之后、对方FIN之前这段时间里是否还有单向的数据传输。2.3 状态迁移的完整链路四次挥手涉及的状态比握手多我把主动关闭方和被动关闭方分开列这样对照抓包时不容易乱。主动关闭方假设是客户端的状态路径是ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED。被动关闭方服务器的路径是ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED。这里有两个状态是重点。第一个是FIN_WAIT_2主动方发了FIN、收到ACK后就进入这个状态此时它在等对方的FIN。如果对方一直不发FIN主动方就会卡在FIN_WAIT_2这个状态本身没有超时机制要靠应用层或系统参数干预是很多“连接泄漏”问题的根源。第二个是TIME_WAIT主动方收到对方FIN并回完最后一个ACK后进入要等2MSL才真正关闭。这两个状态在抓包分析里出现频率极高后面会专门展开。2.4 为什么主动关闭方要进TIME_WAIT这是面试高频题也是实操中最容易困惑的点。TIME_WAIT存在的理由有两个缺一不可。第一是保证最后一个ACK能到达对方。假设主动方回完最后一个ACK就直接关闭而这个ACK在网络中丢了被动方会以为自己的FIN没被确认于是重传FIN。可这时候主动方已经关了收到重传的FIN只会回一个RST被动方就会以异常方式关闭而不是正常的CLOSED。有了TIME_WAIT主动方在2MSL内还能收到重传的FIN并再次回ACK保证对方正常关闭。第二是让本次连接的旧报文在网络中自然消亡。一个TCP报文在网络中的最大生存时间叫MSLMaximum Segment Lifetime2MSL就是往返一趟的最大时间。等够2MSL本次连接的所有报文都从网络中消失了新连接就不会收到旧连接的残留报文避免数据错乱。这个设计思路很朴素但很严谨是TCP可靠性哲学的典型体现。注意不同系统的2MSL取值不同常见的是60秒即MSL30秒也有系统用更短的值。抓包里看到TIME_WAIT持续一分钟左右是正常的不要误判为异常。3. 核心报文细节与Wireshark实操要点3.1 四个包的标志位与序号变化四次挥手的四个包标志位组合和序号变化是有规律的记住这个规律抓包时一眼就能认出来。假设客户端主动关闭初始序号相关字段如下包序方向标志位关键字段含义第1包客户端→服务器FIN, ACKSeqx, Acky客户端说“我没数据了”第2包服务器→客户端ACKSeqy, Ackx1服务器说“知道了”第3包服务器→客户端FIN, ACKSeqz, Ackx1服务器说“我也没数据了”第4包客户端→服务器ACKSeqx1, Ackz1客户端说“知道了”这里有几个细节值得抠。第一FIN包本身会消耗一个序号所以第2包的Ack是x1而不是x。这一点和SYN一样SYN也消耗一个序号。第二第3包的Seq是z而不是y1因为服务器在第2包之后可能还发过数据z是它实际发到的位置。第三第4包的Ack是z1同样因为FIN消耗了一个序号。这些序号关系是判断挥手是否正常的第一道关卡如果Ack对不上说明中间有丢包或乱序。3.2 Wireshark过滤器的精准用法抓包文件动辄几千个包不会过滤等于大海捞针。针对四次挥手我常用的过滤器有这么几个按使用场景分。最直接的是按TCP标志位过滤tcp.flags.fin 1这条能筛出所有带FIN的包四次挥手里的第1包和第3包都会出来。如果想看某个具体连接的完整挥手过程用tcp.stream eq NN是流编号先把整条流的所有包拉出来再顺着看。更精准一点可以组合过滤tcp.flags.fin 1 or tcp.flags.reset 1这样连异常终止的RST包也能一起看到排查问题时很有用。还有一个我特别推荐的技巧用tcp.port 端口号 tcp.flags.fin 1锁定特定服务的挥手。比如排查某个Web服务的连接关闭把端口限定住避免被其他连接的FIN干扰。另外Wireshark的“Follow TCP Stream”功能配合过滤器使用效果很好——先过滤出目标流右键Follow能看到应用层数据的完整交互再回到包列表对照挥手位置上下文一目了然。提示过滤器里的和eq等价但写tcp.flags.fin 1比写tcp.flags.fin更明确后者在某些版本里行为不一致建议养成写全的习惯。3.3 逐包拆解从字段到状态光看标志位还不够真正要吃透得把每个包的字段展开看。以第1包客户端FIN为例展开TCP层重点看这么几个字段Sequence Number是当前发送位置Acknowledgment Number是对对方数据的确认Flags里FIN和ACK都置1Window Size反映接收窗口。这个包的Window Size往往能反映客户端当时的接收缓冲情况如果窗口很小可能说明客户端处理不过来。第2包服务器ACK相对简单Flags只有ACKAck等于第1包的Seq1。但这里要注意一个现象如果服务器在第2包之后还有数据要发你会看到ACK包之后跟着一串数据包然后才是第3包的FIN。这段“ACK到FIN之间”的数据传输就是半关闭状态的直观体现抓包里非常值得观察。第3包服务器FIN和第1包结构类似Flags是FINACKSeq是服务器当前发送位置Ack仍然是x1因为客户端在FIN之后没再发数据。第4包客户端ACK是纯ACKAck等于第3包Seq1。看完这四个包再回到Wireshark的“TCP stream graph”里看时序图四个包的先后关系和方向一目了然比纯看列表直观得多。3.4 用“专家信息”快速定位异常Wireshark有个“Expert Information”面板分析菜单里会把抓包文件里的异常自动归类比如重传、乱序、零窗口、RST等。排查挥手问题时先扫一眼这个面板能快速发现线索。比如看到大量“TCP Retransmission”说明有丢包挥手可能不完整看到“TCP RST”说明连接被异常终止不是正常四次挥手。我个人的习惯是打开抓包文件先看Expert Information再看过滤器筛出的FIN包最后逐包展开。这个顺序能保证既不漏掉异常又能聚焦到挥手本身。Expert Information里的条目按严重程度分级Error和Warning优先看Note级别的可以稍后。这个面板是Wireshark里被低估的功能很多人只盯着包列表其实专家信息能省大量时间。4. 完整抓包实操与关键环节实现4.1 抓包环境搭建与准备要复现一次干净的四次挥手环境不用复杂一台机器上跑客户端和服务器就行但有几个准备动作不能省。首先是选抓包点如果客户端和服务器在同一台机器抓loopback接口Windows上是\Device\NPF_Loopback或Npcap Loopback AdapterLinux上是lo如果分处两台机器抓靠近主动关闭方的那张网卡这样四个包都能抓到不会因为抓包点位置导致某个方向的包缺失。其次是选工具。Wireshark是首选图形化、字段解析全、过滤器强大。命令行场景可以用tshark适合服务器上没有图形界面的情况。抓包权限方面Linux下需要root或给dumpcap加capabilityWindows下装Npcap时要勾选支持抓包的模式。这些准备工作看着琐碎但抓包抓不全、权限不够是最常见的翻车点提前搞定能省很多事。最后是构造一个可控的关闭场景。最简单的办法是用一个短连接请求比如用curl访问一个HTTP服务请求完成后连接自然关闭就能抓到标准的四次挥手。如果想控制哪一方主动关闭可以用ncnetcat手动建连再手动断开或者写个几行的Python脚本用socket的close()触发。脚本方式最灵活能精确控制关闭时机适合反复练习。4.2 一次标准挥手的抓包实录我用一个Python小脚本做演示客户端连上服务器、发一条消息、然后主动关闭。服务器端用nc监听。整个过程在loopback上抓包抓到的四个包非常干净。import socket s socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((127.0.0.1, 9999)) s.sendall(bhello) s.close() # 触发四次挥手客户端主动关闭抓包结果按时间顺序排列四个包的标志位和序号完全符合前面讲的规律。第1包客户端发FINACK第2包服务器回ACK第3包服务器发FINACK第4包客户端回ACK。在Wireshark里选中第1包展开TCP层能看到Flags字段里FIN和ACK都打了勾Seq和Ack的数值和预期一致。这里有个实操细节第2包和第3包之间往往有时间间隔。因为服务器收到FIN后应用层可能还要处理一下才关闭自己的发送方向这个间隔在抓包里表现为两个包的时间戳差了几毫秒到几十毫秒。如果间隔特别长比如几秒说明服务器应用层关闭逻辑有问题值得排查。这个观察点是纯看理论看不出来的只有实际抓包才会注意到。4.3 用IO Graph观察挥手节奏Wireshark的IO Graph统计菜单里能把抓包文件里的包按时间画成曲线对观察挥手节奏很有帮助。设置过滤条件为tcp.flags.fin 1看FIN包在时间轴上的分布能直观看出连接关闭是集中发生还是分散发生。如果某个时间段FIN包密集说明有大量连接在关闭结合业务日志能定位到具体操作。更进一步可以用tcp.analysis.flags相关的过滤条件观察重传和乱序配合IO Graph看异常包的时间分布。这个技巧在排查“为什么挥手很慢”这类问题时特别有效——如果FIN之后紧跟着重传说明ACK丢了挥手被拖长了。IO Graph不是必需工具但它能把“时间”这个维度可视化比盯着时间戳列强得多。4.4 对照状态机验证抓包结果抓完包、看完字段最后一步是拿状态机做交叉验证。在Wireshark里选中某个包后可以在TCP层看到Wireshark自己推断的连接状态部分版本支持或者手动对照第1包发出后主动方进FIN_WAIT_1第2包到达后进FIN_WAIT_2第3包到达后进TIME_WAIT第4包发出后仍在TIME_WAIT等待2MSL。被动方则是第1包到达进CLOSE_WAIT第3包发出进LAST_ACK第4包到达进CLOSED。这个对照过程能帮你发现很多隐藏问题。比如如果抓包里第2包之后迟迟没有第3包被动方就一直卡在CLOSE_WAIT这就是典型的“CLOSE_WAIT堆积”通常是应用层忘了调用close()。再比如主动方发完第4包后如果很快又收到一个FIN对方重传说明第4包可能丢了这时候TIME_WAIT的价值就体现出来了。把抓包和状态机对上号才算真正看懂了挥手。5. 常见异常与排查技巧实录5.1 挥手只有三个包是怎么回事实际抓包里经常看到“三次挥手”别慌这通常是正常的优化。当被动关闭方收到FIN时如果它恰好也没有数据要发且开启了延迟确认它可以把ACK和随后的FIN合并成一个包发出去于是第2包和第3包合二为一四次变三次。判断依据是合并后的那个包Flags同时有ACK和FINAck等于对方FIN的Seq1。这种合并是协议允许的不是异常。真正需要警惕的是另一种“三包”情况主动方发了FIN对方回了ACK然后就没有然后了既没有FIN也没有数据。这说明被动方应用层没有正确关闭连接连接卡在CLOSE_WAIT。这种时候抓包里看不到第3包但用tcp.flags.fin 1过滤只能看到一个FIN结合netstat看CLOSE_WAIT数量就能确认。两种情况表面都是三个包性质完全不同一定要区分开。5.2 TIME_WAIT过多到底要不要处理这是运维里被问得最多的问题之一。先说结论TIME_WAIT是正常状态数量多本身不是故障。它是主动关闭方的必经状态高并发短连接场景下TIME_WAIT堆积是必然的。真正要关注的是它有没有影响到新连接的建立——如果本地端口耗尽导致无法建连那才需要处理。处理手段上常见的有调整系统参数缩短TIME_WAIT时间、开启端口复用等但这些都有副作用不能无脑上。缩短2MSL会削弱TIME_WAIT的两个保护作用端口复用可能带来连接混淆风险。我的经验是先确认是不是真的端口耗尽看netstat的统计和错误日志如果是优先从架构上减少短连接用连接池、长连接而不是直接改内核参数。参数调整是最后手段且要经过测试。注意抓包里看到TIME_WAIT持续约60秒是正常的不要因为“等太久”就去改参数。先搞清楚业务是否真的受影响。5.3 RST代替FIN的异常终止有时候抓包里看不到四次挥手取而代之的是一个带RST标志的包连接直接断了。RST是“重置”表示异常终止不走正常挥手流程。常见触发场景有向一个已关闭的端口发数据、应用层调用带SO_LINGER且超时为0的close、接收缓冲区有未读数据时关闭连接、防火墙拦截等。排查RST要看它出现在哪个方向、什么时机。如果RST紧跟在某个数据包之后可能是对端认为这个数据非法如果RST出现在连接空闲很久之后可能是中间设备清理了会话表。Wireshark的Expert Information会把RST标出来结合前后包看上下文基本能定位原因。RST不是“错误”它是TCP的一种合法机制但频繁出现RST说明连接管理有问题值得深挖。5.4 常见问题速查表把上面这些整理成一张表排查时对着看能省不少时间。现象可能原因排查方向只有FIN没有后续被动方应用层未关闭查CLOSE_WAIT数量、应用日志四次变三次ACK与FIN合并看合并包Flags是否含FINACK大量TIME_WAIT短连接高并发评估端口耗尽风险考虑连接池出现RST异常终止看RST前后包、SO_LINGER设置、防火墙FIN后紧跟重传ACK丢失看重传包、网络质量卡在FIN_WAIT_2对方不发FIN查对端应用是否挂起这张表是我自己排查时反复用的覆盖了入门阶段八成以上的挥手异常。真正遇到复杂情况还是要回到抓包逐包看表格只是快速定位的起点。5.5 几个只有实操才会知道的坑第一个坑是抓包点选错导致只看到半个挥手。如果抓包点在中间设备上可能只抓到单向的包看起来像挥手不完整其实是抓包位置问题。解决办法是尽量在端点抓或者两端同时抓对比。第二个坑是虚拟化环境的时钟问题。在虚拟机或容器里抓包时间戳可能因为时钟同步出现跳变导致包的先后顺序看起来混乱。遇到这种情况先确认时钟源别急着下结论说协议异常。第三个坑是Wireshark的“相对序号”默认开启。默认情况下Wireshark显示的是相对序号从0开始和理论上的绝对序号对不上初学者容易懵。可以在TCP协议首选项里关掉相对序号看绝对序号或者干脆习惯相对序号的视角——反正Ack和Seq的加减关系是不变的。第四个坑是延迟确认干扰判断。接收方可能等一小会儿再发ACK导致抓包里FIN和ACK之间有明显间隔看起来像卡住了。这通常是正常的除非间隔超过几百毫秒才需要怀疑。理解延迟确认机制能避免大量误判。6. 从抓包到理解我的几点体会把四次挥手和Wireshark抓包结合起来练最大的收获不是记住了四个包而是建立了一种“用抓包验证理论”的习惯。书本上的状态机是静态的抓包里的状态是动态的两者对上号的那一刻理解才真正落地。我建议每个学网络的人都亲手抓一次完整的挥手逐包展开字段看一遍再对照状态机走一遍这个过程的收获远超背十遍书。另外别把TIME_WAIT、CLOSE_WAIT这些状态当成“要消灭的敌人”。它们是TCP设计的一部分存在即合理。排查问题的正确姿势是先判断“这个状态是否影响了业务”而不是看到数量多就动手改参数。很多所谓的“优化”反而破坏了TCP的可靠性保证得不偿失。最后分享一个我常用的练习方法用脚本批量建立和关闭连接同时抓包然后用过滤器统计FIN包和RST包的比例。比例正常说明关闭流程健康RST偏多就要查原因。这个练习成本很低但能把挥手、抓包、异常判断串成一条线练几轮下来四次挥手就真的成了肌肉记忆。后续如果想深入可以接着研究TCP的拥塞控制、窗口缩放这些话题抓包工具都是同一套迁移成本很低。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑