1. 抓包之前先想清楚:TCP实验到底在验证什么
很多人做Wireshark的TCP实验,打开软件就开始抓,抓完对着一堆密密麻麻的包发呆,不知道从哪看起。我刚开始接触的时候也是这样,抓了上百个包,最后只看出“哦,有三次握手”,然后就没了。问题出在哪儿?出在动手之前没有想清楚这个实验到底要验证什么。
TCP协议的核心机制其实就那么几个:连接建立(三次握手)、数据传输(序号与确认号)、连接释放(四次挥手)、流量控制(窗口大小)、拥塞控制(慢启动、拥塞避免)、重传机制(超时重传、快速重传)。Wireshark实验的目的,就是让你在真实的网络流量中亲眼看到这些机制是怎么运作的,而不是停留在课本上的时序图里。
这个实验适合谁?网络方向的在校学生、准备网络相关认证的考生、刚入行的运维工程师,以及任何想真正搞懂TCP而不是背概念的人。你不需要有很深的编程基础,但需要了解IP地址、端口号、报文这些基本概念。如果连“客户端”和“服务端”谁先发包都分不清,建议先补一下计算机网络的基础课。
我个人的习惯是:每次做TCP实验之前,先在纸上画一遍预期的时间线——哪个包应该是SYN,哪个应该是SYN+ACK,哪个应该是ACK,数据传输阶段序号应该怎么递增。画完之后再去抓包对照,效率会高很多。这个习惯看起来笨,但实测下来比漫无目的地翻包快得多。
提示:做TCP实验不需要真的去搭建一个复杂的网络环境。一台电脑,一个能访问的远端服务,加上Wireshark,就够了。关键是你要能控制变量——比如你想观察三次握手,就找一个干净的连接;你想观察重传,就人为制造丢包。
2. 实验环境搭建与抓包策略
2.1 工具准备与网卡选择
Wireshark的安装没什么好说的,官网下载对应系统的版本,一路下一步就行。Windows下安装的时候会提示你装Npcap,这个必须装,它是底层抓包驱动,不装的话Wireshark找不到网卡。安装过程中有一个选项叫“Restrict Npcap driver's access to Administrators only”,如果你的电脑只有你自己用,可以不勾选,这样普通权限也能抓包,省得每次都右键“以管理员身份运行”。
装好之后打开Wireshark,你会看到一列网卡接口。这里有个坑:不要选“any”或者“所有接口”,除非你明确知道自己要抓什么。选“any”会把所有网卡的流量混在一起,噪音极大。正确的做法是选你实际用来上网的那块网卡——有线就选以太网,无线就选WLAN。怎么确认?看哪个网卡的流量曲线在跳动,或者直接看IP地址,和你ipconfig结果里一致的的那块就是。
选好网卡之后,先不要急着点开始。Wireshark的捕获选项里有一个“Capture filter”,这个和后面要讲的“Display filter”是两码事。Capture filter是在抓包阶段就过滤,不满足条件的包直接不抓,好处是减少文件体积,坏处是抓错了就没了。Display filter是抓完之后在界面上过滤,不影响原始数据。对于TCP实验,我建议抓包阶段不加过滤,抓完再用Display filter筛选,这样不会漏掉关键包。
2.2 抓包时机的把握
抓包时机这件事,说起来简单,但实际操作中很多人会错过关键帧。比如你想抓三次握手,结果等你想起来点开始的时候,连接已经建立完了。我的做法是:先点开始抓包,再去触发连接。具体来说,如果你要访问某个网站,先在Wireshark里点开始,然后在浏览器里输入地址回车。这样SYN包一定能抓到。
如果你要观察的是已经建立的连接上的数据传输,那就在传输动作之前开始抓。比如你要观察文件下载的TCP流,先点开始抓包,再去点下载按钮。听起来是废话,但我见过太多人抓完之后发现第一个包就是PSH+ACK,SYN根本没抓到,就是因为顺序反了。
还有一个细节:抓包文件要及时保存。Wireshark默认把抓到的包放在内存里,如果你抓了很久不保存,万一软件崩了或者你不小心关了,数据就没了。我一般抓完一段就File -> Save As存成pcapng格式,这个格式比pcap支持更多元数据,后续分析更方便。
2.3 用Display Filter精准定位TCP流量
抓完包之后,面对成百上千个包,第一步就是过滤。Wireshark的Display filter语法很灵活,下面这几个是我做TCP实验时最常用的:
| 过滤表达式 | 作用 | 使用场景 |
|---|---|---|
tcp | 只显示TCP包 | 排除ARP、DNS等干扰 |
tcp.port == 80 | 只显示80端口的TCP包 | 观察HTTP流量 |
tcp.port == 443 | 只显示443端口的TCP包 | 观察HTTPS流量 |
ip.addr == 192.168.1.100 | 只显示与指定IP相关的包 | 锁定特定主机 |
tcp.flags.syn == 1 | 只显示SYN标志位置1的包 | 快速定位握手包 |
tcp.flags.fin == 1 | 只显示FIN标志位置1的包 | 快速定位挥手包 |
tcp.analysis.flags | 显示Wireshark标记的异常包 | 排查重传、乱序等问题 |
这些过滤器可以组合使用,比如tcp.port == 80 && tcp.flags.syn == 1就能精确找到80端口上的SYN包。注意逻辑运算符:Wireshark用的是&&(与)、||(或)、!(非),和很多编程语言一样。但如果你写tcp.port == 80 or 443,这是错的,必须写成tcp.port == 80 || tcp.port == 443。
提示:Display filter输入框变绿表示语法正确,变红表示语法有误。如果你不确定某个字段名怎么写,可以在包详情面板里右键某个字段,选择“Apply as Filter”,Wireshark会自动帮你生成正确的过滤表达式。这个功能对新手特别友好。
3. 三次握手:从SYN到ACK的完整拆解
3.1 三次握手的包长什么样
三次握手是TCP连接建立的必经过程,也是Wireshark实验里最容易观察到的现象。找一个干净的HTTP网站(比如某个不设防的静态页面),在浏览器里访问它,同时用Wireshark抓包。抓完之后用tcp.flags.syn == 1 || tcp.flags.ack == 1过滤,你会看到类似这样的序列:
No. Source Destination Protocol Info 1 192.168.1.100 93.184.216.34 TCP 49152 → 80 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 2 93.184.216.34 192.168.1.100 TCP 80 → 49152 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=1460 3 192.168.1.100 93.184.216.34 TCP 49152 → 80 [ACK] Seq=1 Ack=1 Win=64240 Len=0这三行就是三次握手的全部内容。第一行是客户端发起的SYN包,第二行是服务端回的SYN+ACK包,第三行是客户端再回的ACK包。注意看Info列里的Seq和Ack值:第一个包的Seq=0(相对序号),第二个包的Ack=1,第三个包的Seq=1、Ack=1。这个递增关系就是TCP可靠传输的基础。
点开第一个SYN包的详情面板,展开“Transmission Control Protocol”这一层,你会看到很多字段。其中几个关键字段需要重点关注:
- Source Port:客户端随机选择的端口,通常是49152到65535之间的一个值。这个范围叫“动态端口”或“私有端口”,IANA规定的。
- Destination Port:服务端监听的端口,HTTP是80,HTTPS是443。
- Sequence Number:初始序号(ISN),Wireshark默认显示相对序号,所以是0。如果关掉相对序号显示,你会看到一个很大的随机数。
- Flags:这里显示的是
0x002 (SYN),表示SYN标志位置1。 - Options:展开这一层,你会看到MSS、SACK Permitted、Window Scale等选项。这些是TCP的可选参数,在握手阶段协商。
3.2 为什么是三次而不是两次或四次
这个问题面试经常问,但很多人背答案,没有真正理解。从Wireshark的抓包结果来看,三次握手的本质是双方各自确认自己和对方的收发能力都正常。
第一次握手:客户端发SYN,服务端收到。服务端能确认“客户端发送能力正常,我的接收能力正常”。但客户端还不知道服务端能不能收到、能不能发。
第二次握手:服务端回SYN+ACK,客户端收到。客户端能确认“我的发送能力正常,服务端的接收能力正常,服务端的发送能力正常,我的接收能力正常”。到这里客户端已经全确认了,但服务端还缺一个确认——它不知道客户端有没有收到自己的SYN+ACK。
第三次握手:客户端回ACK,服务端收到。服务端确认“我的发送能力正常,客户端的接收能力正常”。至此双方全确认。
两次不够,因为服务端缺最后一次确认;四次多余,因为第二次握手已经把SYN和ACK合并了。在Wireshark里你能直观看到这个合并:第二个包的Flags字段同时有SYN和ACK两个标志位,这就是“捎带确认”的体现。
3.3 握手阶段的常见异常
正常情况下三次握手一气呵成,但在Wireshark里你可能会看到一些异常情况。下面这张表是我在实际抓包中遇到过的几种:
| 异常现象 | Wireshark表现 | 可能原因 | 排查方向 |
|---|---|---|---|
| SYN无响应 | 只有SYN包,没有SYN+ACK | 服务端未监听、防火墙拦截 | 检查目标端口是否开放 |
| SYN被拒绝 | 收到RST+ACK | 服务端端口关闭 | 确认服务是否启动 |
| 重复SYN | 同一个SYN发多次 | 第一次SYN丢失或ACK丢失 | 检查网络丢包率 |
| SYN+ACK重传 | 服务端重发SYN+ACK | 客户端ACK丢失 | 检查客户端网络 |
| 半开连接 | 只有SYN和SYN+ACK,无最终ACK | 客户端异常退出 | 检查客户端状态 |
其中RST包值得单独说一下。RST是TCP的复位标志,当一方收到不期望的包时就会发RST。比如你访问一个没有服务监听的端口,服务端会直接回RST+ACK,而不是忽略。在Wireshark里看到RST,基本可以确定连接建立失败了。
注意:有些防火墙会伪造SYN+ACK来欺骗客户端,让客户端以为连接建立了,但实际上后续数据都被拦截。这种情况在Wireshark里表现为握手正常但数据传输阶段没有响应。如果你遇到“能握手但传不了数据”的情况,可以往这个方向排查。
4. 数据传输:序号、确认号与窗口的协同
4.1 序号与确认号的递增规律
三次握手之后,连接进入数据传输阶段。这个阶段最核心的观察点就是序号(Sequence Number)和确认号(Acknowledgment Number)的变化。TCP把数据看成字节流,每个字节都有一个序号。发送方告诉对方“我这次发的数据从第N个字节开始”,接收方回“我已经收到第N+M个字节了,下次给我发第N+M+1个”。
在Wireshark里,默认显示的是相对序号,也就是相对于初始ISN的偏移量。比如握手时ISN是0,那么第一个数据字节的序号就是1。假设客户端发了100字节的数据,Seq=1,Len=100,那么下一个包的Seq就是101。服务端回的ACK包里,Ack=101,表示“1到100我都收到了,下次发101”。
这里有一个容易混淆的点:Len=0的包不消耗序号。纯ACK包、SYN包、FIN包的Len都是0,它们的序号不递增。只有携带数据的包才会让序号增加。你在Wireshark里看到Seq不变但Ack在变的包,基本都是纯ACK。
我建议在Wireshark里打开“Statistics -> Flow Graph”,选择“TCP flow”,这样能直观看到双方交互的时序图。每个包的Seq和Ack都标在箭头上,比在包列表里逐个看要清楚得多。
4.2 窗口大小与流量控制
TCP的流量控制靠的是滑动窗口机制。接收方在每个ACK包里告诉发送方“我还能接收多少字节”,这个值就是窗口大小(Window Size)。发送方根据这个值调整发送速率,确保不会把接收方的缓冲区撑爆。
在Wireshark的TCP详情面板里,Window Size字段显示的就是当前通告的窗口大小。但要注意,如果握手时协商了Window Scale选项,实际窗口大小是“Window Size值 × 2^Scale因子”。比如Window Size显示65535,Scale是7,实际窗口就是65535 × 128 = 8388480字节。
窗口大小的变化能反映很多信息。如果窗口突然变小,说明接收方处理不过来,缓冲区快满了;如果窗口变成0,说明接收方缓冲区完全满了,发送方必须停止发送,这就是零窗口。零窗口之后,发送方会定期发窗口探测包,询问接收方“现在能收了吗”。在Wireshark里,零窗口和窗口探测都有特殊的标记,用tcp.analysis.zero_window可以过滤出来。
4.3 实操:观察一次完整的文件传输
光看理论不够,我带你走一遍完整的观察流程。找一个提供文件下载的网站,或者自己用Python起一个简单的HTTP服务,放一个大一点的文件(比如几十MB),然后:
- 在Wireshark里选好网卡,点开始抓包。
- 在浏览器里访问那个文件,开始下载。
- 下载完成后停止抓包。
- 用
tcp.port == 80过滤(假设是HTTP)。 - 找到三次握手的第一个SYN包,右键选择“Follow -> TCP Stream”。
Follow TCP Stream会把整个TCP连接的所有包按顺序展示出来,左边是客户端发的,右边是服务端发的。你能看到HTTP请求头、响应头、以及文件数据的传输过程。注意观察数据传输阶段的包大小:通常会是MSS的整数倍,比如1460、2920、4380这样。最后一个包可能小于MSS,因为文件大小不一定刚好是MSS的整数倍。
在Follow TCP Stream的窗口里,你还能看到Wireshark自动计算的吞吐量、丢包率等统计信息。这些数据对于分析传输性能很有帮助。如果丢包率高,说明网络质量差;如果吞吐量远低于带宽,可能是窗口太小或者有拥塞。
提示:Follow TCP Stream默认显示的是ASCII格式,如果传输的是二进制文件,会显示乱码。这时候可以切换到“Raw”或“Hex Dump”视图。不过对于分析TCP行为来说,内容是什么不重要,重要的是包的序号、确认号和窗口变化。
5. 四次挥手与连接释放
5.1 四次挥手的标准流程
数据传输完成后,连接需要释放。TCP的释放过程是四次挥手,因为TCP连接是全双工的,每个方向都需要单独关闭。标准流程如下:
- 主动关闭方发FIN,表示“我没有数据要发了”。
- 被动关闭方回ACK,表示“我知道你没数据要发了”。
- 被动关闭方发FIN,表示“我也没有数据要发了”。
- 主动关闭方回ACK,表示“我知道你也没数据要发了”。
在Wireshark里用tcp.flags.fin == 1过滤,你能看到FIN包。注意FIN包也消耗一个序号,虽然它的Len是0。这是因为FIN标志位在逻辑上占一个字节的位置,接收方回的ACK会确认这个FIN。
为什么需要四次而不是三次?因为被动关闭方收到FIN后,可能还有数据没发完,不能立即发FIN。它先回一个ACK,等数据发完了再发FIN。如果被动关闭方也没有数据要发了,ACK和FIN可以合并,变成三次挥手。在Wireshark里这种情况也能看到,第二个包同时有ACK和FIN标志。
5.2 TIME_WAIT状态的意义
四次挥手的最后一步,主动关闭方发完ACK后,会进入TIME_WAIT状态,等待2MSL(Maximum Segment Lifetime,报文最大生存时间)后才真正关闭连接。在Wireshark里你抓不到TIME_WAIT本身,因为它是内核状态,不是网络包。但你能通过端口复用的行为间接观察到。
TIME_WAIT的存在有两个原因:一是确保最后一个ACK能到达被动关闭方,如果ACK丢了,被动关闭方会重发FIN,主动关闭方还能响应;二是让本次连接的所有报文在网络中消失,避免影响后续使用相同端口的新连接。
在服务器上netstat -an | grep TIME_WAIT能看到大量TIME_WAIT状态的连接,这是正常现象。但如果TIME_WAIT过多导致端口耗尽,就需要调整内核参数了。不过这是运维层面的问题,Wireshark实验里不需要深入。
5.3 异常释放:RST包的使用场景
除了正常的四次挥手,TCP还支持用RST包强制释放连接。RST包的特点是不需要确认,收到即生效。常见的使用场景包括:
- 访问未监听的端口,服务端回RST。
- 连接超时,一方主动发RST。
- 应用程序异常退出,操作系统代发RST。
- 收到不期望的包,比如没有握手就收到数据。
在Wireshark里,RST包的Flags字段显示为0x004 (RST)。如果RST包同时带ACK,显示为0x014 (RST, ACK)。RST包通常意味着连接异常终止,如果你在抓包中看到大量RST,说明网络或应用层面有问题。
注意:有些中间设备(如负载均衡器)会主动发RST来关闭空闲连接。这种情况下的RST是正常行为,不需要排查。判断方法是看RST的发送方是谁——如果是你认识的服务器IP,可能是应用问题;如果是未知IP,可能是中间设备。
6. 重传与异常分析:从抓包中定位问题
6.1 超时重传与快速重传
TCP保证可靠传输的核心机制是重传。当发送方在一定时间内没有收到ACK,就会重传数据。这个时间叫RTO(Retransmission Timeout),是根据RTT(往返时间)动态计算的。在Wireshark里,超时重传的包会被标记为[TCP Retransmission],颜色通常是黑色背景红色文字。
除了超时重传,还有快速重传。当接收方收到乱序的包时,会立即发送重复ACK(Duplicate ACK),告诉发送方“我期望的序号是X,但我收到了X之后的包”。发送方连续收到3个重复ACK,就认为X丢了,立即重传X,不用等超时。快速重传在Wireshark里标记为[TCP Fast Retransmission]。
这两种重传的区别很重要:超时重传说明网络状况很差,RTT波动大;快速重传说明有零星丢包,但网络整体还行。在Wireshark的“Expert Information”里能看到重传的统计,如果重传率超过1%,就需要关注了。
6.2 重复ACK与乱序
重复ACK是接收方对乱序包的响应。假设发送方发了Seq=1、Seq=1461、Seq=2921三个包,但Seq=1461丢了,接收方收到Seq=1后回Ack=1461,收到Seq=2921后发现不是期望的1461,又回Ack=1461。这个重复的Ack=1461就是重复ACK。
在Wireshark里,重复ACK会被标记为[TCP Dup ACK]。如果你看到大量重复ACK,说明网络有乱序或丢包。乱序和丢包的区别在于:乱序时后续的包最终会到达,只是顺序不对;丢包时某个包彻底没了,必须重传。
Wireshark有一个很好用的功能叫“TCP Stream Graphs -> Time Sequence (Stevens)”,能把序号随时间的变化画成图。正常传输是一条斜向上的直线,如果有丢包或重传,线上会出现平段或回退。这个图对于快速判断网络质量非常直观。
6.3 常见问题速查表
下面这张表是我在做TCP实验和实际运维中总结的常见问题速查表,遇到异常时可以对照排查:
| Wireshark标记 | 含义 | 常见原因 | 解决方向 |
|---|---|---|---|
| [TCP Retransmission] | 超时重传 | 网络拥塞、链路质量差 | 检查带宽和丢包率 |
| [TCP Fast Retransmission] | 快速重传 | 零星丢包 | 检查链路稳定性 |
| [TCP Dup ACK] | 重复确认 | 乱序或丢包 | 区分乱序和丢包 |
| [TCP Out-of-Order] | 乱序 | 多路径转发 | 通常无需处理 |
| [TCP ZeroWindow] | 零窗口 | 接收方缓冲区满 | 检查接收方性能 |
| [TCP Window Full] | 发送方窗口满 | 接收方通告窗口小 | 调整窗口大小 |
| [TCP Keep-Alive] | 保活探测 | 连接空闲过久 | 正常现象 |
| [TCP RST] | 连接复位 | 端口关闭、异常退出 | 检查应用状态 |
这张表建议收藏,下次抓包遇到不认识的标记,先查表再深入分析。
6.4 我踩过的坑与实操心得
说几个我在做TCP实验时踩过的坑,希望能帮你省点时间。
第一个坑:抓包位置不对。我一开始在虚拟机上抓包,结果发现抓到的包和真实网络行为对不上。后来才明白,虚拟机的网卡是虚拟的,流量经过NAT转换后,源IP和端口都变了。如果你要观察真实的TCP行为,尽量在物理机上抓,或者把虚拟机网卡设成桥接模式。
第二个坑:忽略了时间戳。Wireshark默认显示的是相对时间,第一个包是0秒,后面的包显示相对于第一个包的时间。这个设置对于分析RTT很方便,但如果你想看绝对时间,需要改一下视图设置。我建议在分析重传时用相对时间,分析连接建立时间时用绝对时间。
第三个坑:过滤太早。有一次我想观察三次握手,就在Capture filter里写了tcp.flags.syn == 1,结果只抓到了SYN包,SYN+ACK和ACK都没抓到。因为Capture filter是在抓包阶段过滤的,不满足条件的包直接丢弃。正确的做法是抓包阶段不过滤,抓完再用Display filter筛选。
第四个坑:忘了关相对序号。Wireshark默认显示相对序号,这对于初学者很友好,但有时候你需要看真实的ISN。在“Edit -> Preferences -> Protocols -> TCP”里可以关掉“Relative sequence numbers”。关掉之后,你会看到真实的随机ISN,对于理解TCP的安全性设计有帮助。
第五个坑:抓包文件太大。如果你抓了一个小时的全量流量,文件可能有好几个GB,Wireshark打开会很卡。我的做法是用Capture filter限制抓包范围,比如只抓特定IP或特定端口。虽然前面说抓包阶段不过滤,但那是对初学者而言。等你熟练了,知道自己在抓什么,就可以用Capture filter提高效率了。
提示:Wireshark的“Statistics -> Conversations”能看到所有TCP会话的统计信息,包括每个会话的包数、字节数、持续时间。这个功能对于快速定位异常会话很有用。比如你发现某个会话的重传率特别高,就可以重点分析那个会话。
7. 从实验到实战:TCP分析能力的迁移
做完Wireshark的TCP实验,你获得的不只是几个截图和一份报告,而是一种从原始流量中还原协议行为的能力。这种能力在实际工作中有很多应用场景。
比如线上服务响应变慢,你可以抓包分析是不是TCP重传导致的;比如用户反馈下载速度慢,你可以看窗口大小是不是被限制了;比如怀疑有网络攻击,你可以看SYN包的数量是不是异常。这些场景的核心分析方法,和你在实验里做的是一样的:抓包、过滤、看序号和标志位、定位异常。
我个人的经验是,TCP分析能力的关键不在于记住多少字段,而在于建立一套排查思路。遇到问题先问自己:连接建立了吗?握手有没有异常?数据传输阶段有没有重传?窗口有没有变化?连接是怎么释放的?沿着这条线走下来,大部分TCP层面的问题都能定位。
最后分享一个小技巧:如果你经常做TCP分析,可以把自己常用的Display filter保存成按钮,放在Wireshark的过滤栏旁边。这样下次用的时候一键就能过滤,不用每次手敲。具体操作是Edit -> Preferences -> Filter Expressions,添加你常用的表达式并命名。我保存了“握手包”“重传包”“零窗口”“RST包”四个按钮,用起来很顺手。
这个实验后续还可以这样扩展:把抓包环境换成无线网络,观察无线环境下的TCP行为有什么不同;或者用tc命令人为制造延迟和丢包,观察TCP的拥塞控制响应;再或者分析HTTPS流量的TCP行为,虽然内容加密了,但TCP层的序号、确认号、窗口还是可见的。这些扩展方向都能让你对TCP的理解更深一层。