资讯动态

WireShark抓包实战:TCP三次握手与四次挥手详解

发布时间:2026/10/19 5:20:26 来源:尧图企业网站定制
1. 抓包前的准备工作与思路说实话干网络这一行早晚都得跟 WireShark 打交道。不管是排查应用响应慢、接口超时还是定位数据包丢失、TCP 重传最终都得落到抓包分析这一步。而在所有抓包场景里TCP 三次握手和四次挥手又是最基础、也最值得亲手抓一次的东西——因为这两个过程把 TCP 状态机的核心逻辑全演出来了。我之前带过不少新人让他们做的第一个实操练习就是抓一次完整的 HTTP 请求然后从第一个 SYN 开始一路看到最后一个 FIN整个过程走一遍很多之前看书看不懂的概念立刻就通了。这次分享就以“使用 WireShark 分析 TCP/IP 三次握手和四次挥手”为主线把我自己的抓包步骤、报文解读方法、以及踩过的几个坑都写出来。适用于刚入门 TCP/IP 协议的开发者、运维人员也适合那些想彻底搞清楚 seq、ack、标志位到底怎么变化的朋友。我不打算罗列 WireShark 的所有菜单只挑真正用得上的操作来聊。先说说环境。抓包最怕的是网络环境嘈杂办公室局域网里广播包、ARP、其他主机的流量一大堆抓下来的包虽然没几个是你要分析的反而干扰判断。所以我一直推荐一个思路尽量在隔离环境里抓包。最省事的是抓本机回环loopback流量也就是访问 127.0.0.1 上的服务这样产生的 TCP 流量完全不受物理网卡和交换机的影响干净得一塌糊涂。另一个选择是两台虚拟机之间通信或者用一台测试服务器单独跑一个 Web 服务然后从客户端抓包。我自己最常用的组合是这样本地起一个最简单的 HTTP 服务然后用浏览器或者 curl 访问一次同时在 WireShark 里选择回环接口抓包。这种方式的优点是流量纯净、可控你能清晰地看到三次握手、HTTP 请求响应、四次挥手的过程不会有杂七杂八的包干扰视线。如果你需要用远程服务器做测试那就在抓包机上加上过滤条件比如tcp.port 8080只看目标端口的包。打开 WireShark 后第一步是选择抓包接口。我通常会在主界面先看一眼各接口的流量趋势选择一个网卡或者 Loopback 接口然后点击开始捕获。注意WireShark 在 Windows 上抓 Loopback 用的是 Npcap 的虚拟接口名字一般是“Npcap Loopback Adapter”在 Linux 上则是lo。抓包开始后再去触发 HTTP 请求然后回到 WireShark 点停止这一步别搞反了不然啥都抓不到。等抓完包要做的就是过滤。这里给新手一个建议不要一上来就盯着全部报文看先用过滤表达式把范围缩小。比如你要分析 TCP 握手直接输入tcp就能只看 TCP 报文如果你想精确到某个连接可以通过tcp.stream 0来过滤出第一个 TCP 流的全部报文这是我最常用的操作。右键任意一个 TCP 包选择“Follow” - “TCP Stream”WireShark 会自动帮你筛选出该连接的所有双向报文而且会弹出一个窗口显示应用层的数据内容特别适合配合 HTTP 请求一起分析。2. 三次握手抓包过程与报文逐包拆解2.1 如何抓到一次完整的三次握手我拿一次最简单的 HTTP 访问举例。假设我在本机 127.0.0.1 的 8000 端口跑了一个 Web 服务然后使用 curl 命令发请求curl http://127.0.0.1:8000/抓包完成后在 WireShark 的过滤栏输入tcp.port 8000就能看到这一次连接产生的所有 TCP 报文。为了分析方便最好再输入tcp.stream eq 0或者右键 Follow TCP Stream确保只看这一条连接。正常的三次握手会呈现出四个包第一个是客户端发出的 SYN第二个是服务端回复的 SYNACK第三个是客户端发出的 ACK紧接着往往还有一个 HTTP GET 请求包这个属于三次握手完成后的数据包。我见过不少人把 GET 请求当作第三次握手的一部分其实不对——第三次握手的 ACK 包是纯 ACK不携带应用层数据。但在实际抓包里因为 TCP 的延迟确认Delayed ACK机制第四个包可能会把 ACK 和 HTTP 请求一起发出去这种现象在抓包中很常见一会儿我会单独说。2.2 第一个包SYN 报文怎么看点开第一个 SYN 包在中间的面板区域展开 TCP 协议详情。你会看到几行关键信息依次是 Source Port源端口、Destination Port目的端口、Sequence Number序列号、Acknowledgment Number确认号、Flags标志位。这里我直接说结论第一次握手客户端发送一个 TCP 报文段标志位只有 SYN 被置 1seq 被设置为一个随机初始值。这个初始值在抓包里通常显示为一个很大的数字比如 3867294321。很多人第一次看到会觉得奇怪为什么不是从 0 开始因为 TCP 为了防止旧连接的报文干扰新连接使用随机初始序列号ISN这是 RFC 规范要求的。你不用记住具体数值但要知道这是客户端选择的起始序列号后续所有从客户端发往服务端的字节都会以这个数字为起点进行编号。在 WireShark 的报文详情里你还会看到一行 “Sequence Number: 0” 之类的内容。注意WireShark 默认开启了相对序列号功能也就是说它会把抓到的第一个包的 seq 显示为 0方便阅读。如果你要看真实序列号可以在 WireShark 的偏好设置里关闭“Enable sequence number analysis”或者启用“Relative sequence numbers”的开关。我个人建议新手先保持相对序列号因为分析起来更直观。顺便说一句这个 SYN 包本身不携带任何应用层数据它的负载几乎为零TCP header 后面直接跟可选的 MSS、窗口扩展等 TCP Options。2.3 第二个包SYNACK 回应了什么第二个报文是服务端返回的 SYNACK。在 Flags 区域你会看到 SYN 和 ACK 两个标志位同时为 1。它的作用有两层第一确认收到了客户端的 SYN第二同步服务端自己的初始序列号。所以这个包里的 Acknowledgment Number 是多少它是客户端初始 seq 加 1。为什么加 1因为 SYN 标志位本身要占用一个序列号。即使 SYN 包没有携带数据字节它也被视为占用了一个序列号空间。同理后续的 FIN 标志位也要占一个序列号。这是初学者最容易忽略的点。打个比方就像你在排队时取了一个号虽然你还没开始办业务但这个号已经属于你了。TCP 里 SYN 和 FIN 都是“虚拟字节”它们的 seq 会被消耗。同时服务端会在自己的 SYN 字段里设置另一个随机初始序列号比如 1846912345。这个值会在 WireShark 里显示出来。你注意到没这个包同时包含 seq 和 ack 两个序号因为一个报文里可以既确认对方又同步自己。扩展一下知识SYNACK 包里的窗口大小、MSS 等参数也很关键它告诉对方自己所能接收的报文段大小上限这会影响后续传输的报文分片情况。2.4 第三个包ACK 确认以及可能合并的 HTTP 请求第三个包是客户端发出的 ACK。它的标志位只有 ACKseq 是客户端的初始序列号加 1ack 是服务端的初始序列号加 1。这个包发出后连接进入 ESTABLISHED 状态双方开始正式传输数据。但在实际抓包里第三个包往往不是单独的 ACK它经常会和 HTTP GET 请求合并成一个包。这是因为 TCP 协议栈做了优化客户端等收到 SYNACK 后如果紧接着有数据要发送它会直接把 ACK 和数据一起发出去而不是干等一个定时器。从 WireShark 上看这个包在 TCP 层标志位是 ACK但往下展开 HTTP 层能看到GET / HTTP/1.1。我一般就跟新人说第三个 ACK 和第一个应用数据包“搭车”了你只要认准 seq 和 ack 都对得上就行至于它是单独 ACK 还是携带数据并不影响连接建立。到这里三次握手的三个包就解析完了。为了让你更清楚 seq 的递增逻辑我把关键字段整理如下以相对序列号为例方向标志位seqack说明客户端 → 服务端SYN00初始序列号占用一个序号服务端 → 客户端SYNACK01服务端初始序列号确认客户端 SYN客户端 → 服务端ACK11确认服务端 SYN完成握手你有没有发现在这个表格里SYN 包的 seq 显示为 0但它在逻辑上占用了序号 0所以第二个包的 ack 就变成了 1。第三个包发出去时客户端的 seq 也从 0 变成了 1。这条规则贯穿整个 TCP 生命周期后面分析四次挥手时同样适用。2.5 三次握手背后的关键设计思想聊完报文我想多说几句设计层面的东西。为什么需要三次为什么两次不行因为 TCP 想要解决一个核心问题让双方确认彼此的收发能力都正常。第一次握手服务端知道客户端能发第二次握手客户端知道服务端能收能发第三次握手服务端知道客户端能收。这样双方才达成一致——“你能收我也能收可以开始传数据了”。如果只用两次握手服务端在收到 SYN 后直接认为连接建立可万一客户端已经超时重传了一个 SYN或者客户端并未真正准备好接收数据服务端就会白白建立一堆半连接。三次握手相当于给服务端一个验证机会如果客户端不回应最终的 ACK服务端就不认为连接建立成功。这也是为什么那些 SYN Flood 攻击会让服务端满内存都是半连接的原因——攻击者只发 SYN不回应 ACK服务端只能一直等。另外一个小细节第三次握手是可以携带数据的。也就是说客户端可以在发送 ACK 的同时携带 HTTP 请求这种优化对性能有明显帮助减少了交互轮次。抓包时看到的三次握手后立刻就是 GET 请求就是这个原理。3. 四次挥手的抓包流程与报文状态解读3.1 挥手的发起方不一定总是客户端三次握手一定是客户端先发起 SYN但四次挥手不一定。主动关闭连接的一方可以是客户端也可以是服务端。HTTP/1.1 默认开启 Keep-Alive连接不会在每次请求后立即断开。当客户端决定不再复用连接时通常由客户端主动发送 FIN但如果服务端配置了超时关闭也可能是服务端先发送 FIN。抓包分析时不要理所当然地认为第一个 FIN 一定是客户端发出来的。在我上面的 curl 场景里由于 curl 默认行为是请求结束后主动关闭连接所以你抓到的挥手动作发起方基本都是客户端。而在浏览器场景里因为浏览器会复用连接你可能会看到多个 HTTP 请求复用一个 TCP 流直到空闲超时后某一方才发起关闭。为了抓到四次挥手我建议先用 curl 做一次性的短请求抓包结果干净又清晰。3.2 第一次挥手客户端发送 FIN抓到一个 HTTP 请求完成后在 WireShark 里继续看这条 TCP 流的后续报文。第一个挥手报文一般是客户端发出一个 FIN标志位为 FIN同时可能带着一个 ACK如果它还有未确认的数据需要确认。这个包的作用是告诉服务端“我的数据已经发送完了我这边不会再有新的数据发给你了但我还能接收数据。”注意 FIN 包本身的 seq 也需要占用一个序号。这个包里的 seq 通常是最后一个数据字节序号加 1。你可能会看到 FIN 和 ACK 同时置位这并没什么好奇怪的因为 TCP 标志位本来就是独立的可以随意组合。主动关闭的一方发出 FIN 后它的 TCP 状态从 ESTABLISHED 变成 FIN_WAIT_1。3.3 第二次挥手服务端回应 ACK服务端收到 FIN 后需要先确认这个 FIN。于是它发送一个 ACK 包ack 值为客户端 FIN 的 seq 加 1。这个 ACK 的语义是“我收到你的关闭请求了但我这边可能还有数据要发你先等一下。”这个包就是第二次挥手。从状态机看服务端此时从 ESTABLISHED 进入 CLOSE_WAIT 状态而客户端从 FIN_WAIT_1 进入 FIN_WAIT_2 状态。在抓包里这个 ACK 包和第一次 FIN 之间通常隔得极短。但如果服务端还有数据没发完那么在 FIN_WAIT_2 之后可能会看到服务端陆续发来一些数据包这些都属于关闭连接前的最后数据传输。3.4 第三次挥手服务端发送 FIN当服务端把所有剩余数据都发完并且确认客户端已经接收完毕后它才发送自己的 FIN 包。这个包可以单独是 FIN也可以携带 ACK 标志。它的 seq 是服务端最后一个数据字节序号加 1ack 是客户端 FIN 的 seq 加 1。这个包发出后服务端进入 LAST_ACK 状态。这里我想特别强调一个常见误区很多人以为挥手必须是“FIN、ACK、FIN、ACK”这样严格两两对应。其实第三次挥手不一定要等第二次挥手之后很久如果服务端在收到 FIN 时已经没有数据要发了它完全可以立刻回复一个 FINACK 包把第二次和第三次挥手合并成一个报文。所以抓包里你有时会看到三次挥手FIN、FINACK、ACK而不是四次。TCP 规范并不强制要求 ACK 和 FIN 分开发送只是很多实现为了性能会把它们合并。如果你看到只有三个挥手包不要怀疑抓包出错这属于正常的优化行为。3.5 第四次挥手客户端发送最后的 ACK服务端发出 FIN 后客户端需要回应一个 ACK确认收到服务端的 FIN。这个包的 ack 是服务端 FIN 的 seq 加 1。发完这个 ACK 后客户端并不立即关闭而是进入 TIME_WAIT 状态等待 2MSLMaximum Segment Lifetime最大报文生存时间后才最终关闭连接。TIME_WAIT 是很多人容易忽略但非常关键的机制。为什么需要 TIME_WAIT两个原因第一保证最后的 ACK 如果丢失服务端能够重发 FIN客户端还有机会再补发 ACK否则服务端会一直收不到确认而导致关闭异常第二让旧连接的延迟报文在网络中自然消失避免它们干扰新连接。从这个角度看四次挥手其实也是一次“可靠关闭”的协商过程。我还是用一个表格来展示挥手过程的序号变化方向标志位seqack状态变化客户端 → 服务端FIN101201客户端进入 FIN_WAIT_1服务端 → 客户端ACK201102服务端进入 CLOSE_WAIT客户端进入 FIN_WAIT_2服务端 → 客户端FIN201102服务端进入 LAST_ACK客户端 → 服务端ACK102202客户端进入 TIME_WAIT服务端进入 CLOSED这里的 seq 和 ack 只是示意值但你发现规律没有每次 FIN 的确认号都是对端 seq 加 1。只要把这个规律记牢哪怕不看标志位单看 seq 增长都能猜出来当前是哪一步。3.6 抓包验证 TIME_WAIT如果你想亲眼看到 TIME_WAIT 状态可以在抓包结束后用命令行查看连接状态。Linux 下执行ss -tan或netstat -tan如果过滤127.0.0.1:8000能看到类似TIME_WAIT的条目。Windows 下用netstat -ano | findstr 8000也能看到。我建议你在抓包结束后尽快执行这个命令因为 TIME_WAIT 默认持续 60 秒左右等太久就看不到了。我印象很深的一次是帮某开发者排查一个高并发短连接服务发现服务器上出现了海量 TIME_WAIT 连接原因是服务端主动关闭了连接而客户端也走了完整的四次挥手。大量短连接导致 TIME_WAIT 堆积占满了端口资源进而引发新连接无法建立。后来通过调整 keep-alive 和复用连接解决了问题。这就是抓包分析之外延伸出来的实战经验也是理解四次挥手价值的地方。4. 常见问题与排查技巧实录4.1 抓包只看到 SYN没有 SYNACK这种情况通常意味着客户端发起的连接根本没到达服务端或者服务端没回应。先看 IP 和端口对不对再看服务端口是否处于监听状态。如果被测服务没有启动客户端会收到一个 RST 包连接被拒绝而不是 SYNACK。如果连 RST 都看不到那可能是防火墙把包丢了。我排查过不少案例最后发现是云服务厂商的安全组规则没有放行对应端口服务器本机 netstat 显示 LISTEN但外部死活连不上。抓包就能看出来SYN 发出去了但服务端没回复也没有 RST。这种时候去检查防火墙策略比盯着 WireShark 有用得多。4.2 大量 TCP Retransmission 出现如果抓包窗口里出现很多黑底红字的 Retransmission或者 Fast Retransmission说明网络丢包比较严重或者接收方窗口满了。你可以配合 TCP 层的[Analysis Flags]查看丢包原因。也可能是发送方设置的 TCP 窗口太小导致吞吐量上不去。如果是在本地回环抓包基本不会出现重传如果是在跨网段测试中出现先检查是否开启了网卡 offload 功能。我在某些 Linux 虚拟机上遇到过抓包工具显示很多异常校验和错误但实际业务却正常后来发现是网卡 TCP 校验和卸载Checksum Offloading导致的误报。解决办法是在抓包时选择关闭该网卡的校验和卸载或者直接忽略校验和错误。4.3 抓不到四次挥手包很多人用浏览器访问一个网址后发现只抓到三次握手和 HTTP 请求响应之后就一直没有 FIN 包。这很正常因为浏览器为了复用连接不会在请求结束后立刻关闭。你可以强制用Connection: close请求头或者在服务端设置不使用 keep-alive。用 curl 加一个参数也行curl -H Connection: close http://127.0.0.1:8000/另外如果你是用 WireShark 的tcp.stream过滤器逐条看但在多条连接复用时抓包会显得特别乱。这种情况下建议先抓完包再过滤这样能看到整个会话的完整脉络。4.4 如何根据状态排查连接卡住对付连接卡住还有一个技巧在 WireShark 里配合 TCP 状态分析选中某个包后右下角的 Expert Info 会提示异常比如 Window Full、ACKed lost segment、Out-of-order 等。这些提示在快速定位问题时有奇效。注意不要被红字吓到TCP 存在乱序和重传是网络正常现象需要结合出现频率和业务表现综合判断。如果你需要查看某个 TCP 连接当前处于什么状态可以在 Linux 上用ss -tanp | grep 8000看状态但抓包更直观。把三次握手和四次挥手的抓包过程走一遍其实你已经把 TCP 状态机的绝大部分路径都验证了一遍。4.5 时间戳与选项字段不要忽略在报文详情的 TCP Options 里你会看到TCP Timestamps。这个选项用于计算往返时间 RTT 和防止序列号回绕。如果多台设备时间不同步那么在分析 RTT 和重传时可能会产生误导。我一般会先确认抓包时间和系统时间基本一致。另外在分析高延迟链路时一定要看 Timestamp 值而不要光看包到达时间的间隔因为网络抖动会让包的到达时间不规律。我还遇到过一个情况某服务和客户端之间经过了一个代理抓包时看到 SYN 的 MSS 是 1460但服务端回复的 MSS 只有 1400说明链路上某个设备限制了报文长度。这种细节在抓包中一眼就能看到而在业务层面根本察觉不到。所以每次抓包都养成看 TCP Options 的习惯会有意想不到的收获。5. 实操扩展完整抓取一次 HTTP 请求的连接生命周期把基础握手和挥手都弄明白后不如再做一次综合练习抓取一次完整的 HTTP 请求把所有 TCP 段按顺序列出来观察三次握手、HTTP 请求、HTTP 响应、四次挥手这些事件在整个连接中是怎么穿插的。我建议的步骤如下启动 WireShark选择回环接口开始抓包。用 curl 访问本机服务加参数-v可以看到 TCP 连接过程的时间点curl -v -H Connection: close http://127.0.0.1:8000/ -o /dev/null停止抓包过滤tcp.port 8000。把时间排序从第一条 SYN 开始数记录每个报文的相对时间、seq、ack 和标志位。你最终看到的报文顺序应该类似第 1 包客户端 SYNseq0第 2 包服务端 SYNACKseq0ack1第 3 包客户端 ACKseq1ack1第 4 包客户端 HTTP GET通常和 ACK 合并第 5 包服务端 HTTP 200 OK 响应分多个数据包发送第 6 包客户端 ACK 确认服务端数据第 7 包客户端 FINseq某值第 8 包服务端 ACKack客户端 seq1第 9 包服务端 FINseq某值第 10 包客户端 ACKack服务端 seq1如果你用-H Connection: close服务端发完响应后会在响应头里带上Connection: close然后主动或被动触发关闭。每个包的相对时间也可以观察正常情况下握手三个包的时间间隔都在毫秒级甚至微秒级如果超过 200 毫秒就要怀疑中间设备有延迟。这个练习做完后你还可以尝试用浏览器访问一个大型网页观察 TCP 连接如何复用、多个 HTTP 请求如何在同一个连接上排队传递、服务端什么时候发起 keep-alive 超时关闭。这些都是面试里常问的“TCP 长连接”话题抓几次包之后你就能用自己的话讲清楚。我还想补充一个技巧WireShark 的 “Statistics” - “Flow Graph” 功能可以生成时间序列图展示每个包的方向和标志位非常直观。不过我的建议是先用基础列表自己手动分析一遍再用图表验证这样印象才深。别一上来就依赖可视化工具否则你会发现换个环境、换个抓包工具就什么都看不懂了。6. 一些个人体会与建议抓包这事看着简单但真正要熟练得多动手。我建议每个做网络相关工作的同学都亲自抓一次三次握手和四次挥手并且尝试在抓包文件里逐字段解释每个包的 seq、ack、flags、窗口大小、MSS、时间戳。这个过程可能只需要半小时但它带给你的收益远比看十篇理论文章大。我第一次抓包时也闹过笑话以为三次握手后的 HTTP GET 是第三次握手的一部分后来对照文档才发现那是第四个包。也曾经因为只看 IP 包而忽略 TCP 连接的概念导致分析半天不知道这个流是哪个进程发起的。后来我总结出一条经验抓包分析一定要有“流”的概念WireShark 里的 stream 就是一条完整的 TCP 双向会话。看问题不要只看单个包要把它放在整个流转过程中去理解。如果非要说一个最值得记住的抓包技巧那就是先过滤后分析。过滤字段用好了抓包效率能提升一大截。我最常用的是tcp.stream过滤器它能让你瞬间从嘈杂的全局流量里抽出一条完整连接。还有http、dns、tcp.flags这些常见过滤器建议都练一练。另外提醒一句WireShark 版本之间菜单位置会有差异但核心功能差异不大。如果你用的版本和我描述的路径不一样直接在过滤栏输入表达式即可不一定非得通过菜单操作。抓包文件不要急着删留着以后遇到类似问题可以对比着看。我自己就留着好几个典型的抓包文件正常握手、带重传的握手、RST 拒绝连接、半关闭、TIME_WAIT 堆积都是不同场景下的宝贵样本。最后我想说的是TCP/IP 协议栈本身很复杂但三次握手和四次挥手是理解它的最好起点。把这十个包看懂你对 TCP 可靠传输、连接状态、超时控制的理解就建立起来了。后面再去研究拥塞控制、窗口滑动的原理会轻松得多。希望这篇基于我实际抓包经验的分享能帮你少走一些弯路。

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

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

免费获取报价 →
↑