1. 为什么要用 WireShark 看 TCP 握手
做网络开发或者后端运维的时候,最头疼的问题不是代码报错,而是“代码看着没问题,但数据就是传不过去”。这时候你通常会想:到底是客户端没发出数据,还是服务端没收到?是中间防火墙拦了,还是对端压根没监听?全靠猜的话,效率极低,而且很难跟对方确认“真凶”到底在哪一层。
WireShark 就是用来解决这个问题的工具。它能把网卡上流过的每一个数据包原样抓下来,并且按协议层级拆开,让你直接看到 TCP 包头里的每一个字段。而 TCP/IP 的“三次握手”和“四次挥手”,恰恰是几乎所有网络连接建立和关闭时必经的过程。把这俩过程用 WireShark 完整抓一遍、看懂一遍,你对网络排障的理解就会从“代码层”下沉到“协议层”,很多线上问题一眼就能定位。
这篇文章我会带你逐步完成一次完整的抓包分析:先讲抓包前需要配置什么,再一个包一个包地拆解三次握手和四次挥手,最后把常见的异常包特征和排查思路整理出来。适合刚接触 WireShark 的开发者,也适合写过 socket 程序但没真正看过底层报文的同学。全程没有任何门槛,你只需要准备一台能上网的电脑,装个 WireShark 就行。
顺便说一句,很多人以为 WireShark 是给网管用的专业工具,其实它离我们一点都不远。你本地起的服务、前后端联调的接口、容器端口映射,全部都能用它来观察。学会抓包之后,你会发现自己写网络程序时心里“有底”了很多。
2. 抓包前的准备工作:过滤器和捕获选项
2.1 捕获过滤器 vs 显示过滤器,先用谁
打开 WireShark 之后会看到两个关键词:Capture Filter(捕获过滤器)和 Display Filter(显示过滤器)。名字相似,作用完全不同。
捕获过滤器是在抓包开始前就生效的,它决定了网卡上哪些包会被留下,哪些包直接从源头丢弃。它的语法比较古老,是基于 BPF(Berkeley Packet Filter)的一套规则,比如tcp port 80、host 192.168.1.100这种写法。好处是抓下来的包特别“干净”,文件体积小,如果你的抓包环境流量很大,捕获过滤器能帮你节省大量磁盘和内存。
显示过滤器是在抓到包之后,对已有的包进行筛选用的。它不会重新抓包,只是把列表里符合条件的行显示出来。语法更丰富,可以基于 TCP 标志位、序列号、IP 地址、端口等任意字段做组合过滤,比如tcp.flags.syn == 1、tcp.port == 443 && ip.addr == 127.0.0.1。日常分析过程中,我绝大多数时间都在用显示过滤器。
我的建议是:刚开始学抓包,不要设置捕获过滤器,直接全量抓取,然后用显示过滤器慢慢筛。因为你并不知道“不感兴趣”的包里会不会藏着一只关键的“妖”。等分析熟练了,再考虑用捕获过滤器控制线上环境抓包时的数据量。
2.2 三个最实用的过滤表达式
分析三次握手和四次挥手,你只需要记住三个过滤表达式,其他花里胡哨的都可以现查现用。
第一个:tcp.flags.syn == 1 && tcp.flags.ack == 0。这个表达式只筛选出“SYN 为 1 且 ACK 为 0”的包,也就是三次握手里的第一个包,即客户端发起的连接请求。为什么要加ack == 0这个条件?因为服务端回复的 SYN+ACK 包里 SYN 也是 1,不加这个条件就会混进来。同理,筛选握手第二个包用tcp.flags.syn == 1 && tcp.flags.ack == 1。
第二个:tcp.flags.fin == 1。这个表达式能筛出所有含 FIN 标志的包。四次挥手一共四个包,其中有两个包带 FIN,用这个表达式可以直接看到挥手的“起始”和“回应”。
第三个:tcp.port == 8080 || tcp.port == 443。指定端口过滤。比如你抓本地服务端 8080 端口的交互,就用这个表达式,注意左右两个||的前后不需要空格也可以,WireShark 会自动补全。如果你想只看某个 IP 的包,就写ip.addr == 192.168.1.10。
实际分析时,我经常把端口和标志位组合起来,比如tcp.port == 8080 && tcp.flags.syn == 1,这样定位速度非常快。
2.3 抓包时最容易忽略的三个设置
抓包之前有三个设置强烈建议先改掉,不然分析的时候会吃亏。
第一个是“相对序列号”。WireShark 默认开启 Relative sequence numbers,会把 TCP 的初始序列号显示成 0,后面的包从 1、2、3 这样顺序递增。这个功能在大多数情况下是友好的,方便肉眼比对增量。但如果你要精确对照 RFC 里面的真实 SEQ 值,或者和 tcpdump 的打印结果作比对,一定要去 Preferences 里取消勾选,让 WireShark 显示 Sequence Number (raw) 也就是原始序列号。我被人问过很多次“为什么我的包里序列号不是随机数,而是 0 开始”,十有八九就是这个默认设置在起作用。
第二个是“时间列显示精度”。抓包文件默认显示的时间是“从抓包开始到当前包的相对时间”,单位可能精确到秒。但分析 TCP 重传和延迟的时候,你需要看到毫秒甚至微秒级别的间隔。在数据包列表的“Time”列标题上右键,选择 Column Preferences,把时间列的精度改成“Seconds since previous displayed packet”或者“Seconds since epoch”,是一种常见做法。我个人的习惯是同时保留两个时间列:一个显示相对时间,一个显示与上一包的间隔,排查重传时非常直观。
第三个是“启用 TCP 流跟踪”。在你选中任意一个握手包之后,右键 -> Follow -> TCP Stream,可以把整个连接的所有数据拼成一个完整序列展示出来,包括握手、数据、挥手。初学阶段用它来“看整体”,能帮助你理解连接的生命周期。不过请注意,Follow TCP Stream 只展示抓到的包,如果一个包没被抓下来,它也不会替你“脑补”出来。
3. 三次握手的完整拆解
3.1 第一次握手:客户端发出 SYN
我们把“客户端”定义为主动发起连接的那一方,它不一定是平时浏览器里的前端,也可能是某个服务器上的进程去连另一个服务的端口。现在打开 WireShark,监听环回口或者局域网接口,然后在你本地执行一条最简单的测试命令,比如用nc或任意 socket 客户端去连一个本地服务端。
抓到第一个握手包后,点击它,在中间的 Packet Details 面板里依次展开 Transmission Control Protocol 层,会看到一堆很关键的字段。我们来逐个看得懂的部分。
第一个是 Source Port 和 Destination Port,也就是源端口和目的端口。一般的客户端都会使用一个临时分配的端口(通常叫 ephemeral port),范围在 32768-60999 左右;服务端则是固定监听端口,比如 80、443、8080。看到这个对,你就知道连接的方向了。
第二个是 Sequence Number (raw)。在未开启相对序列号的情况下,这里是一个很大的随机数,比如 622337480。注意,这不是从 1 开始的序列,而是操作系统随机生成的一个初始序列号,术语叫 ISN(Initial Sequence Number)。这个随机化非常关键,它防止了“TCP 序列号预测攻击”和安全扫描中的会话劫持风险。如果这个值每次都一样,那说明这台设备的随机算法很差,或者中间有人为修改。
第三个是 Flags 字段。展开后,你会看到 SYN 标志位变成高亮状态 1,其他标志位都是 0。在 Packet Details 里找到 Flags: 0x002 (SYN),这就是第一次握手的标志性特征。
此时括号里的 0x002 表示 TCP 头部 Flags 字段一共 16 位,SYN 在第 7 位(从右数左)。你对这个 16 进制值不用硬记,只需要在 WireShark 里看到 SYN 为 1、ACK 为 0,就能断定这是客户端发起的新连接请求。
提示:很多人在第一次看握手包时会灵魂发问,为什么客户端发 SYN 的时候自带的数据载荷是空?因为三次握手阶段,双方还没建立“可靠连接”,TCP 不会携带应用数据,只有纯粹的头部信息。这就像你拨通电话后不可能直接就聊正事,总得先确认对方是谁、线路通不通。
3.2 第二次握手:服务端回复 SYN+ACK
服务端收到客户端的 SYN 之后,如果监听端口存在且接受新连接,就会回复一个同时带有 SYN 和 ACK 标志的包。这个包就是第二次握手。
看这个包的字段时,注意两个核心字段的变化。
一是 Sequence Number (raw),服务端也会生成自己独立的随机 ISN,比如 391742586。这个值和客户端第一次的序列号没有任何数值关系,是两个独立的随机源。
二是 Acknowledgment Number (raw),它的值等于客户端的 ISN 加 1。这个加 1 是什么意思?TCP 规定,ACK 号表示“期望收到的下一个字节的序号”。客户端发送的 SYN 包虽然不携带数据,但它自身作为一个“虚拟字节”占用了序号空间。所以服务端收到 SYN 后,期望客户端下次发送数据的起始序号是 ISN+1,于是 ACK = ISN+1。
这一个加 1 的细节非常关键,很多人看抓包时困惑“数据都没发,为什么序号变了”,原因就在这里。在整个 TCP 生命周期里,累计确认模型要求每个字节都有序号,SYN 和 FIN 都会各“吃掉”一个序号。
第二次握手把 SYN 和 ACK 合在一起发,省了一次来回,体现了 TCP 设计的效率取向:既然服务端要回应客户端,又恰好需要一个方向相反的 SYN(表示服务端也愿意同步自己的序号),那就合包发。这也是三次握手被称为“一次半”交互的原因:一共 1.5 个 RTT 就能完成全部建立信息双向确认。
3.3 第三次握手:客户端最后确认 ACK
客户端收到服务端的 SYN+ACK 后,会回复一个纯 ACK 包,这是第三次握手。这个包的特征是 SYN 为 0、ACK 为 1,没有任何数据载荷。
看它的字段:
- Sequence Number (raw) = 客户端原先 ISN + 1
- Acknowledgment Number (raw) = 服务端原先 ISN + 1
也就是说,客户端完整确认了“服务端的初始序列号我已经收到,下次你发数据时我会期待从 ISN+1 开始”。这个时候,双方都完成了相互确认:
- 客户端确认了自己的发送能力(服务端确实收到了 SYN)
- 客户端确认了对端能收到自己的 ACK(通过什么?其实第三次 ACK 发出去后,如果服务端不回任何东西,客户端无法立刻知道。但这没关系,第四次交互如果正常,说明链路是全通的状态)
这就引出一个经典问题:第三次 ACK 丢了会发生什么?服务端收不到 ACK,会一直重传 SYN+ACK,而客户端此时认为自己已经连接建立了。如果客户端发数据,服务端能收到并回复,连接也会进入正常状态。但如果客户端不发数据,服务端会一直重传 SYN+ACK 直到超时。抓包时会看到一组重复的 SYN+ACK 包,很多刚学会看包的同学会误以为出现了“连接风暴”,其实就只是第三次握手 ACK 丢了。
3.4 为什么必须是三次,不是两次
这个问题面试背过多遍,但抓包后再理解会完全不一样。
如果只有两次握手,服务端在收到 SYN 后回复 SYN+ACK 就认为连接已建立,但客户端这个时候可能根本没准备好读取数据。更严重的是,如果一个“已失效的连接请求”突然到达服务端,服务端会为这段历史残留的消息建立毫无意义的资源占用。举生活例子:你发微信给朋友说“11 点见”,结果这条消息因故障迟到了半小时,你早就取消了这趟见面。朋友收到消息后如果立刻回复“好”,且双方都默认约定生效,那就会造成一次莫名其妙的等人事件。第三次握手就是让你朋友确认“你确实还在等”。
TCP 设计者用“三次”保证了最小的双向确认代价。再往上加四次确实安全一些,但多一次往返意味着多一次 RTT 开销,对广域网延迟很敏感的应用来说,这是不能接受的。
4. 四次挥手的完整拆解
4.1 四次挥手的完整过程
连接建立之后,数据传完就该关闭了。TCP 的关闭过程和建立过程不对称:建立需要三次,关闭需要四次,原因在于 TCP 是双工连接,每个方向都必须单独关闭。
我们假设客户端主动关闭连接,那整个过程是这样的:
- 客户端发送 FIN 包,表示“我的数据已经全部发送完毕,不再往这个方向发送数据了”。这个包的标志是 FIN=1,ACK 通常是 1(因为它在关闭前往往已经接收完所有数据,需要回执)。
- 服务端收到 FIN 后,回复一个 ACK 包,表示“我收到你的 FIN 了”。注意这时候服务端可能还有一些数据要发给客户端,所以它只是确认收到 FIN,并没有立即关闭自己那边的发送方向。
- 服务端把剩余数据发完,然后发送 FIN 包,表示“我这边也准备好关闭了”。
- 客户端收到服务端的 FIN 后,回复最后一个 ACK 包,然后经过一个 TIME_WAIT 周期后关闭连接。
抓包时,四次挥手最直观的表现是:四行记录,分别为 FIN、ACK、FIN、ACK(顺序取决于谁先发起)。但这里有一个容易迷惑的点:步骤 2 的 ACK 和步骤 3 的 FIN 有时候会合在一个包里(如果服务端在收到 FIN 后立刻没有剩余数据要发),那样你就只看到三次挥手记录。但严格意义上,TCP 仍然经历了“四次握手”的四个阶段:FIN 发出、对方确认、对方 FIN 发出、本端最终确认。抓包只能反映数据包的数量,不能代表协议实际流转的阶段数。
4.2 主动关闭方进入 TIME_WAIT
第四次握手发出后,主动关闭方(这里以客户端为例)不会立刻结束整个 socket 状态,而是进入 TIME_WAIT 状态,持续 2MSL(Maximum Segment Lifetime,报文最大存活时间)。通常取值为 60 秒或 2 分钟,Linux 上可以通过/proc/sys/net/ipv4/tcp_fin_timeout查看,默认值是 60。
这个等待的目的是什么?最核心的是防止最后一个 ACK 丢失后,服务端会重发 FIN,而客户端如果已经关闭,那服务端就永远收不到最后的 ACK,无法正常关闭。保留 2MSL 的时间,足够容纳一个报文在网络上来回存活的时间,服务端的重传 FIN 能被客户端收到,客户端能重新回复 ACK。
另一个容易被忽略的作用是,保证旧连接的延迟数据包已经彻底消亡,不会混入后续新建的相同四元组连接中。抓包时,你在主动关闭方会看到 FIN 之后还存在大量列表行,但内容为空或都是 ACK 重传,直到 60 秒后连接才能被回收。
这里有一个实操小技巧:如果你在服务端测试时发现端口一直被 TIME_WAIT 占用,导致新连接无法绑定,排查第一反应不要急着改tcp_tw_reuse,而是先通过抓包判断是不是存在大量高频短连接场景。如果是,可以优化应用层让它复用长连接;如果不是,才考虑调整内核参数。直接调参通常是“痛快治疗、痛后复发”,治标不治本。
4.3 被动关闭方进入半关闭状态
被动关闭方(这里的服务端)在收到 FIN 并回复 ACK 后,进入CLOSE_WAIT状态。在这个状态下,它仍然可以发送数据给客户端,因为自己那一侧发送方向还没有关闭。只有等到自己把所有该发的内容都发完,才能发出自己的 FIN,进入LAST_ACK状态。
CLOSE_WAIT是一个容易出问题的状态。如果你的服务端应用一直不调用 close,或者没有对连接关闭事件做出响应,那么服务端就永远停在CLOSE_WAIT,连接无法彻底回收。你可以在运行服务的机器上执行netstat -ant | grep CLOSE_WAIT查看数量。如果大量连接停留在 CLOSE_WAIT,通常不是网络问题,而是应用代码没有正确关闭 socket 文件描述符。这种问题用 WireShark 也能看出来:客户端已经发了 FIN,服务端回复了 ACK,但服务端迟迟不再发 FIN,甚至还会继续发应用数据。
之前遇到过一个线上问题:由于某框架的 Keep-Alive 机制和业务线程池的拒绝策略配合不当,每次客户端主动断开后,服务端线程池直接拒绝执行连接清理任务,导致 CLOSE_WAIT 堆积。抓包一看,服务端在收到 FIN 后明明返回了 ACK,但再也不出 FIN。问题瞬间从“网络层”被平移到了“应用层”,这就省去了大量绕弯排查的时间。
4.4 抓包时四次挥手常见误区
第一个误区:看到 FIN 就以为是对方主动断开。实际上要区分 FIN 是谁发的,重点看 TCP Stream 中到底是哪一侧先出现带 FIN 的包。比如 HTTP 长连接的 Keep-Alive 超时,服务端可能先发 FIN;客户端如果收到一个 RST,也会立即关闭连接,但那是异常断连,不一定会走完整的四次挥手。
第二个误区:漏看了半关闭状态下的“FIN 前的 ACK 混淆”。很多人在抓包文件中看到客户端发了 FIN,服务端回了一个 ACK,然后就没有后续了,就认为挥手结束了。其实这时服务端还没发 FIN,连接还挂状态。你需要在过滤框中执行tcp.flags.fin == 1重新检查是否遗漏了服务端的第二个 FIN。如果确实没有,那说明服务端不打算正常关闭,下一步去查应用层。
第三个误区:TIME_WAIT 被误认为“连接泄漏”。看抓包时,主动关闭那一侧可能会在最终的 ACK 后再出现一堆重复 ACK 或者重传的 FIN 包。这不一定是问题,可能是对端丢了这个 ACK,也可能只是抓包本身命中了延迟确认。判断真实问题的方法是看时间间隔和重传类型。如果只是零星的 ACK 重传,时间间隔很小,一般是链路小抖动;如果大量重传连续出现,那就要检查中间是否有设备干扰。
5. 抓包异常与排查实践
5.1 TCP 重传:你的第一个“异常”朋友
新手看抓包时最容易吓一跳的就是重传包。WireShark 会把检测到的重传包标记为灰色或者浅色背景,并且在 Info 列标注[TCP Retransmission]。
重传本身不是错误,是 TCP 的可靠性核心机制。它在发送方等待一定 RTO(超时重传时间)后还没收到 ACK,就重新发一次数据。抓包时看到少量重传是完全正常的,尤其在高延迟网络或有丢包抖动的情况下。但如果重传比例太高,就说明链路质量严重下降,或者接收端触发了很多快速重传事件。
排查重传时,我喜欢把时间列切换成“与上一包的间隔”,然后选中发生重传的流,看看重传发生的规律。如果每次都发生在几百毫秒间隔后,且重传次数持续递增,说明对端的 ACK 可能被中间设备丢了,或者是接收端 buffer 满了导致丢包。如果重传全部集中在连接建立的 SYN 阶段,那要考虑的是服务端端口不可达或者防火墙丢弃了 SYN+ACK。
另一个关键点:如果重传的次数暴增,但应用层体验没有明显问题,那可能只是重传包被 WireShark 识别为乱序重传,本质上是因为抓包点抓到了两个方向的数据,乱序是正常的。真正的快速重传(三次重复 ACK 触发)在 WireShark 里会显示为[TCP Fast Retransmission],含义完全不同。
5.2 突然冒出的 RST 包
RST(Reset)是 TCP 里“告诉对方别再继续了”的包。它不经过四次挥手,直接打断连接。抓包中看到 RST,需要有敏感度。
常见的 RST 出现在两个场景。
第一个场景:目标端口没有监听。你向一个未开放的端口发起 SYN,对端内核会立刻回 RST,然后连接失败。抓包时表现为第 1 行 SYN,第 2 行 RST+ACK。这种问题最简单,直接检查监听状态和防火墙放通情况。
第二个场景:连接已经建立,但因为某些原因一端主动放弃连接。例如应用进程崩溃、socket 被人为关闭、或者接收端确定了协议层无法处理的数据长度,都会构造 RST 包丢弃连接。这个时候抓包通常能看到 TCP 流的尾部出现一行 RST,且它前面的包可能是正常的 ACK。
排查 RST 的技巧是看 RST 包的接收方是谁。如果 RST 是服务端发过去的,那问题大概率在服务端;如果是从客户端发出来的,那客户端应用可能被系统或上层框架强行关闭。结合时间戳和前后几个包,你能比较准确地判断到底是哪一层进程干的。
另外,某些安全软件或特殊网络设备也可能会在中间调整 RST 注入,如果你在抓到的包里看到“本来不该有 RST 的位置出现了 RST”,就要考虑是否为中间设备行为。可以通过对比收发IP的 RST 方向来排查,但别把结论下得太快,多抓几处再确定。
5.3 连接建立成功后立即收到 FIN
这个现象特别容易出现在端口探测场景中。比如有人用扫描工具检查某个端口是否开放,会一次性发很多个 SYN,并选择性地对成功的 SYN 触发 RST 或者 FIN。你自己写测试脚本时也可能踩到:连上服务端,发送一个空数据,然后立刻调用 close,这时候就会看到握手完成后紧接着就出现 FIN。
如果你在线上环境看到大量“握手成功 -> 马上 FIN”的 TCP 流,但应用层几乎没有数据交换,基本可以断定有人在探测或做健康检查。不一定是恶意行为,也可能是负载均衡的健康检查机制。这种流一般不影响业务,但会导致大量短连接和 TIME_WAIT 堆积,需要重视连接池设计。
如果这种“握手后立刻 FIN”发生在真实业务请求前,那就要怀疑客户端逻辑是不是在收到 conn 建立事件后没有正确发送请求就关闭了。我曾经排查过一个接口超时问题,抓包发现服务端收到连接后一直等数据,客户端却直接 FIN 了。后来定位是客户端连接池预创建连接后并未真正调用请求方法,预创建和正式请求之间有一个超时窗口——典型的多线程协作导致的“白干活”场景,光看业务日志很难想起来。
5.4 三次握手不发第三次 ACK
收到 SYN+ACK,但始终看不到第三次 ACK,这在抓包现场不算罕见。常见原因有两种。
一,客户端的防火墙或者系统策略把这个 ACK 包拦截了。应用程序不感知,但它会一直等待服务端发送响应数据。服务端则因为没等到 ACK,会按 NAGLE 算法、超时重传机制重发 SYN+ACK。最终客户端可能会继续发数据,服务端也收得到,连接依然能“歪歪扭扭”地工作,但会导致首个数据包被额外延迟一个 RTT。
二,客户端在收到 SYN+ACK 前自己已经关闭了 socket,或者使用了非阻塞模式并超时退出。这种情况下,客户端可能直接发送一个 RST 来打断连接。你抓到的包历史中会看到 SYN、SYN+ACK、RST 三连组合。
处理这类问题,一定要同时在客户端和服务端两端同时抓包,观察同一个连接的四元组。只抓一端的话,你无法判断那个“缺失”的包是被丢弃了还是另外一端根本没发。
6. 动手实验:我建议你这样练一遍
说了这么多原理,最终还是要自己动手。我建议你按下面的流程练一遍,全程只需要五分钟,但能让你对前面所有内容产生肌肉记忆。
第一步,准备一个最简单的 TCP 服务端和客户端。不需要用复杂的框架,直接用命令行工具最靠谱。Linux 下用nc -l 127.0.0.1 8080监听;Windows 可以用 PowerShell 配合Test-NetConnection,或者装一个 netcat。如果你不想装额外工具,还可以用自带 Python 写一个几十行的服务器和客户端,但要注意别让服务端自动化关闭,尽量让流程可控。
第二步,WireShark 选择 Loopback 接口,也就是 lo 接口,开始抓包。因为本地回环流量不经过物理网卡,不选对应接口你会什么都抓不到。
第三步,打开一个终端执行服务端监听,再打开另一个终端执行客户端连接,随便发一行字符串,然后关闭客户端。重复做 3 到 5 次。抓完停止。
第四步,在 WireShark 过滤框输入tcp.port == 8080,你会看到几组完整的 TCP 流。选一组,右键 Follow TCP Stream,把三个握手和四次挥手从头到尾“看”一遍。对照本文说的标志位、序列号、确认号逐字段检查,再切换原始序列号显示,看看随机 ISN 和相对 ISN 之间的差异。
第五步,做一个“异常演练”:服务端监听后,客户端连接,然后直接杀死服务端进程。你会看到抓包里出现 RST 包,连接被异常中断,没有走 FIN/ACK 流程。感受一下“正常回收”和“异常中断”的区别,这是非常直观的体验。
建议你把每个步骤的截图留存一下,等你自己真正写程序时,遇到连接建立不上、数据一半卡住、连接关不掉,回来对照今天看到的基本模式,排查速度会快上不少。
7. 后续可以这样深入
如果你把三次握手和四次挥手已经看到透彻,接下来有几个方向可以顺路扩展,都很有意思。
第一个方向是研究 TCP 的延迟确认和 NAGLE 算法。抓包时你会看到一些 ACK 并不立刻发送,而是等一段时间,或者数据包和 ACK 合并。理解了延迟确认,你就能解释很多“为什么抓包看到的包顺序和逻辑预期不一致”的问题。
第二个方向是看 HTTP 和 TCP 的关系。HTTP 1.1 的 Keep-Alive 会让同一个 TCP 连接承载多个 HTTP 请求;HTTP2 则引入了多路复用。通过 WireShark 看这些协议在网络包层面的表现,能加深你对 Web 服务的理解。
第三个方向是抓包定位 MTU 问题。当 IP 层数据报过大需要分片时,WireShark 会显示相关分片包,TCP 有时候会触发 MSS 协商。很多“大包传输慢、小包正常”的故障,最终都是在这里面找到根因的。
最后一个我使用频率很高的技巧:WireShark 的 tshark 命令行工具,配合-w输出文件和-Y过滤表达式,可以在远程服务器上快速抓包再拉回本地分析。学会它之后,你不再需要为了抓包而给生产环境装图形界面,只需要一条命令:
tshark -i eth0 -f "tcp port 8080" -w /tmp/cap.pcapng -a duration:10等抓到 10 秒后收工,把文件传到本地慢慢看。
网络排障这件事,本质上就是“眼见为实”的过程。WireShark 给了你一双能穿透代码迷雾的眼睛,但前提是你得熟悉正常的包长什么样。三次握手和四次挥手作为两个最基础的交互模型,是最好的入门样本。多看几遍、多抓几次,你很快就能从“看到包里这些数字不知道是什么”过渡到“一眼就能从包里读出连接的生命周期和故障轨迹”。