很多人一开始接触 Wireshark 都是从“抓包”两个字开始的,装好软件,打开,点一下开始,然后盯着满屏乱跳的列表发呆。数据包在滚,什么都认识,又什么都不认识。我最早也是这样,后来真正把 OSI 模型、TCP 三次握手和 Wireshark 的过滤规则这三样东西串在一起,才算突然开了窍。
这篇文章想做的,就是帮你把这三块拼图放到同一张桌子上。我会先讲 OSI 七层模型在真实报文里到底长什么样,再讲 TCP 三次握手在抓包列表里如何一条一条认出来,最后给你一套 Wireshark 过滤规则的实用操作,顺便把常见问题、排障技巧一起聊完。适合刚入门的网络学习者、运维和开发,也适合那些已经装了 Wireshark 但一直只会看热闹的朋友。
1. 为什么搞懂 OSI 模型,抓包才不算白抓
1.1 七层模型不是考试题,是报文的“解剖图”
很多人对 OSI 七层模型的印象停留在面试题和选择题上,什么“网络中各结点具有相同的层次”“同一结点内相邻层之间通过接口通信”,背得滚瓜烂熟,但一问到实际抓包就懵了。原因很直接:OSI 模型是理论框架,而 Wireshark 是现实世界的解剖刀,两者中间隔着一层“怎么对应”的窗户纸。
这层窗户纸其实不难捅破。你抓到一个数据包,Wireshark 的“协议栈”面板从上到下展开,就是活生生的分层结构。最上面是应用层数据,比如 HTTP 的请求行、Host 字段;再往下是 TCP 层,有源端口、目的端口、序号、确认号;再往下是 IP 层,有源地址、目的地址、TTL;最底层是以太网帧,有 MAC 地址和类型字段。这四条从上到下的信息,对应的就是 OSI 模型中的 L7、L4、L3、L2。你不需要在脑海里背层次表,只要抓一个包,看协议栈面板,层与层的关系自然就清楚了。
我记得自己第一次用 Wireshark 打开一个 pcap 文件时,印象最深的就是那个树状展开列表。展开一行,看到 Ethernet II、Internet Protocol Version 4、Transmission Control Protocol、Hypertext Transfer Protocol,一层套一层,像剥洋葱一样。从那一刻起,七层模型就不再是抽象概念,而是一个具体的数据结构。
1.2 每一层都在做“封装”:可以想象成寄快递
操作系统发送一段数据时,不是把数据整个扔到网线上,而是每一层都会在数据前面加一个“头部”,个别层还会加尾部,这个过程叫封装。我在给同事讲的时候经常打一个比方:把应用层的数据当成你要寄的货物,TCP 层是快递单,IP 层是地址贴在快递单外面的包装箱标签,以太网层是物流车上的装卸单。每一层只关心自己这一层的信息,不越界。
所以你抓到的每一个包,其实是一个“套娃”。Wireshark 的列表默认只展示几个关键字段,但真正的内容藏在“封套”里。这时候你就明白为什么 Wireshark 叫协议分析器而不叫“抓包器”了——它真正的价值是把套娃一层一层拆开给你看。而理解了封装,你也就自然理解了为什么不同层级有不同的过滤字段:过滤 http 和过滤 tcp.port 的语义根本不是一回事,因为它们操作的对象在不同层。
从项目实战的角度讲,学会看分层结构最大的好处是“快速定位问题在哪一层”。应用报错,先看有没有 TCP 连接;TCP 正常,再看应用层数据对不对;应用层正常,再往底层查丢包和延迟。这种从高层往低层排查的思路,是 OSI 模型教给我们的最实用技能,比背概念有用得多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. TCP 三次握手:一条连接是怎么“谈拢”的
2.1 三次握手到底在交换什么
TCP 是面向连接的协议,通俗说,就是正式开始传数据之前,双方要先把话说清楚,确认“你准备好了”“我也准备好了”。三次握手就是这个确认过程。
第一次:客户端发送一个 SYN 报文,告诉你“我想建立连接”,同时带上一个初始序号 ISN(Initial Sequence Number,通常标记为 seq=0 或一个随机值,取决于 Wireshark 的相对序号显示设置)。
第二次:服务器收到 SYN,回复一个 SYN+ACK 报文。ACK 的意思是对客户端序号表示确认,SYN 的意思是服务器也说“我这边也准备好了”。这个报文里会带上自己的初始序号,还会带上确认号,值一般是客户端序号加 1。
第三次:客户端收到 SYN+ACK,再发一个 ACK 报文。这个报文的意义在于让服务器知道“你的回复我收到了”,避免服务器一直在等确认。
很多人问为什么不是两次握手表?因为 TCP 连接是双向的。第一次和第二次解决了“客户端到服务器”方向的同步,第二次和第三次解决了“服务器到客户端”方向的同步。少了第三次,服务器无法确定客户端是否收到自己的 SYN,那么服务器可能一直保持半开连接,浪费资源。网络上所有关于“为什么是三次不是两次”的问题,归根结底都是这个“双向同步确认”的问题。
2.2 在 Wireshark 里认出三次握手
抓包列表里认三次握手非常简单。找三条连续报文,依次是 SYN、SYN, ACK、ACK,而且它们的 TCP 端口完全一致。这就是标准的三次握手。第一次握手源端口一般是某个高位随机端口,目的端口是服务器服务端口,比如 80、443、22、3306 这些。第二次握手方向反过来。第三次又反过来。
这里有一个 Wireshark 的老用户偏好问题:默认情况下,Wireshark 会把 TCP 序号显示为相对序号(Relative Sequence Number),所以第一次握手的 seq 显示为 0,而不是那个巨长的原始序号。这样方便人看,但有时候会让人误以为“怎么都是 0”。如果你想看真实的原始序号,可以在右键菜单里选择 Protocol Preferences,把 TCP 的 Relative Sequence Numbers 关掉,或者查看报文详情里的 raw sequence number 字段。
更直观的方法是直接套用 Wireshark 自带的着色规则。默认配色里,SYN 报文常常是淡红色,SYN+ACK 是淡黄色,纯 ACK 是淡绿色。初次上手时我是靠颜色找握手的,后来才学会看 Flags 字段。
2.3 SYN、ACK、SEQ、ACK 号这些字段怎么读
我见过不少初学者,看到一排 seq=0, ack=1 就晕。这里其实只要记住两个方向和两个数字。
先看方向。客户端发给服务器的报文,源端口是客户端端口,目的端口是服务器端口;服务器回复的报文,源端口和目的端口正好反过来。方向确定后,再看 seq 和 ack。
- seq(Sequence Number):表示我这个报文里携带的数据是数据流中的第几个字节。不带数据的纯 ACK 包,seq 往往不变化。
- ack(Acknowledgment Number):表示“我期望对方下一次发送的序号是多少”,本质上就是“我已经收到哪个字节了”。
第一次握手,客户端 seq=0(相对值),ack 无效或为 0。第二次握手,服务器 seq=0,ack=1,意思是“你的 0 号字节收到了,下一次请发 1”。第三次握手,客户端 seq=1,ack=1,意思是“你的 0 号字节也收到了,下一次服务器请发 1”。这三个来回,双方把彼此的初始序号都确认过了,之后就可以有序传数据。
至于确认号为什么都是加 1,因为 SYN 报文本身会消耗一个序号。Wireshark 在相对模式下,为了让你看得舒服,把三次握手里的 seq 和 ack 都简化成 0 和 1,实际网络上的原始数据是随机大数,但这个“加 1”的语义是固定的。
3. Wireshark 过滤规则:从“什么都看不懂”到“只看想说的事”
3.1 显示过滤器和捕获过滤器,别搞混
这是一个非常经典的新手误区。Wireshark 顶部那个绿色的编辑框是“显示过滤器”,作用是“已经抓到一大堆包之后,我只想让你显示其中符合条件的一部分”。另外还有一个设置捕获选项时的“捕获过滤器”,作用是在抓包之前就过滤掉不需要的流量,没被捕获的包根本不会进入结果。
对绝大多数日常调试来说,显示过滤器完全够用。因为抓包本身的开销在现代设备上可以接受,先把包全抓下来,再用显示过滤慢慢筛选,更灵活。捕获过滤器的优势是省资源和减噪音,适合流量很大的生产环境,但它用起来语法不一样,而且是 BPF 语法,不支持字段过滤,只支持 host、port、tcp 这类简单条件。我建议新手先把显示过滤器玩明白,真的遇到大规模抓包时再学 BPF 也不迟。
显示过滤器的核心语法就三样:字段名、比较符、值。比如 tcp.port == 80,就是只看 TCP 端口为 80 的包。ip.addr == 192.168.1.1,就是只看这个 IP 的包。字段名可以在包详情面板里右键选择 “Apply as Filter”,Wireshark 会自动帮你生成,这是最不容易出错的方式。
3.2 最常用的过滤表达式,直接抄作业
我整理了一份自己工作中使用频率最高的过滤表达式,按场景分类给你。
| 场景 | 过滤表达式 | 说明 |
|---|---|---|
| 只看某主机 | ip.addr == 192.168.1.100 |
源或目标为该 IP 的包都显示 |
| 只看两个主机之间的流量 | ip.addr == 192.168.1.100 && ip.addr == 10.0.0.1 |
两个地址同时出现才算命中 |
| 只看某个端口的 TCP 流量 | tcp.port == 443 |
源或目的端口为 443 都显示 |
| 只看 HTTP 协议 | http |
简洁写法,所有 HTTP 报文 |
| 只看三次握手中的 SYN 包 | tcp.flags.syn == 1 && tcp.flags.ack == 0 |
筛选纯 SYN 报文 |
| 只看 SYN+ACK | tcp.flags.syn == 1 && tcp.flags.ack == 1 |
同时满足两个 flag |
| 只看纯 ACK | tcp.flags.ack == 1 && tcp.flags.syn == 0 |
排除握手时的 ACK 干扰 |
| 只看 RST 报文 | tcp.flags.reset == 1 |
排查连接异常时非常有用 |
| 只看重传 | tcp.analysis.retransmission |
出现重传说明网络有问题 |
| 只看 DNS | dns |
DNS 查询和响应都很显眼 |
| 排除某个 IP | !ip.addr == 192.168.1.100 |
注意 ip.addr 是集合字段,取反时逻辑要小心 |
| 只看某个协议栈中的明文请求 | http.request |
只看请求头 |
在这些表达式的后面,你还可以继续叠加条件。比如要分析某个小程序或网页的接口请求,可以写 http && ip.addr == 你的服务器IP,一下子就过滤掉其他噪音。
3.3 几个很容易踩的过滤坑
第一个坑是 ip.addr 的“或”语义问题。ip.addr == 1.1.1.1 && ip.addr == 2.2.2.2 看着像“我要同时等于两个地址”,实际是“我要源地址是前者且目标地址是后者,或者反过来”。因为 ip.addr 同时包含源地址和目的地址两个可能的值,只要其中一个满足就命中。如果你只想过滤“从 A 到 B”的单向流量,应该写 ip.src == A && ip.dst == B,而不是用 ip.addr。
第二个坑是取反。很多人想“除了某个 IP 都看”,写 !ip.addr == 1.1.1.1,结果发现某些包还是显示出来了。原因还是 ip.addr 是集合,只要包的源地址不是 1.1.1.1 就算命中,可如果包的源地址是 1.1.1.1、目的地址是别的 IP,那这个包里有一个等于 1.1.1.1,逻辑上 ! (ip.addr == 1.1.1.1) 会把它过滤掉,但 ip.addr != 1.1.1.1 则可能因为目的地址不是 1.1.1.1 而把它保留。所以要排除一个地址,建议写成 !(ip.src == 1.1.1.1 || ip.dst == 1.1.1.1),或者按需要只排除源或只排除目的。
第三个坑是把显示过滤器当成搜索框用。你一输入 tcp,它会把所有 TCP 包显示出来,可以,但当你输入不完整的字段名时,Wireshark 会显示绿色、黄色、红色背景。红色说明有语法错误,黄色说明可能表达的不是你想要的意思。我建议任何时候输入完过滤条件,都看一眼输入框的背景颜色,红色时先修再回车。
4. 用 Wireshark 完整分析一次 TCP 连接:实操记录
4.1 抓包环境准备与抓包
我这次演示用的是自己电脑上的 Wireshark 4.0,系统是 Windows,抓一个访问本地或远程 HTTP 服务的流量。你也可以直接用浏览器打开任意一个网站,然后抓包看三次握手,效果一样。
第一步,选择网卡。Wireshark 启动后会列出所有网卡,带波形符号的表示当前有流量活动。选错网卡是新手最常见的问题,比如物理网卡和虚拟网卡同时存在,抓了半天发现没有想要的包。我一般是先不点开始,盯着网卡列表看哪个的流量波动明显,再双击进入。
第二步,设置临时过滤。如果当前网络环境很嘈杂,可以在捕获选项中加一个捕获过滤器 tcp port 80 或 tcp port 443,只抓 HTTP 或 HTTPS 的流量。这里如果目标是 HTTPS,TCP 握手一样能看到,只是应用层内容是加密的,你看不到明文 HTTP 数据。想分析明文 HTTP,可以抓本机的 localhost 或企业内部测试接口。
第三步,开始抓包后,立刻去浏览器访问目标地址。建议访问一个你熟悉的页面,多刷新几次,然后回到 Wireshark 点停止捕获。此时你已经拥有了一份包含多次 TCP 连接的数据包集合。
4.2 从 pcap 里还原三次握手和挥手
停止抓包后,先在显示过滤器里输入 tcp && ip.addr == 目标服务器IP,去掉无关流量。然后把视图切到 TCP 流。最简单的办法是右键任意属于你访问过程的 TCP 报文,选择 “Follow -> TCP Stream”,Wireshark 会自动帮你把这条连接的所有报文筛出来。
这时候你看列表,应该能清晰地看到一条连接从建立到断开的过程。连接的开始是 [SYN]、[SYN, ACK]、[ACK] 三连,连接过程中是大量内容长度不等的 ACK、PSH+ACK(数据包通常标记为 PSH,表示推送数据),最后断开时是 [FIN, ACK]、[ACK]、[FIN, ACK]、[ACK] 这样的四次挥手。
这里我要多说一句四次挥手。很多资料喜欢背“四次挥手”的字眼,但实际抓包中,挥手次数不一定是严格的四段,因为 TCP 允许延迟合并 FIN 和 ACK。比如客户端发 FIN+ACK,服务器回 ACK,然后服务器也发 FIN+ACK,客户端再回 ACK,外观上还是四段。但如果服务器直接发送 FIN+ACK 合并包,次数可能会少一些。不要看到“五次、四次”就以为协议有问题,关键还是看 seq 和 ack 的对应关系。
4.3 结合 OSI 模型分析一次真实请求
拿一次访问网页的过程举例。抓到一个 TCP 报文,展开列表里的树状结构,从上到下依次是:
- Frame:这是 Wireshark 自己加的元信息,包含帧号、时间戳、帧长度等,不属于 OSI 层,只是辅助信息。
- Ethernet II:第二层,里面有源 MAC 和目标 MAC。这一步是局域网内的转发依据,跨网段时目标 MAC 往往不是最终服务器,而是网关的 MAC。
- Internet Protocol Version 4:第三层,里面有源 IP、目标 IP、TTL、协议号(TCP 是 6,UDP 是 17)。这决定了数据包从哪台设备到哪台设备,跨路由时依赖这层。
- Transmission Control Protocol:第四层,里面有源端口、目标端口、seq、ack、flags、Window 等。端口是应用层进程的“门牌号”,没有端口,IP 只能把数据送到主机,不能送到进程。
- Hypertext Transfer Protocol:第七层,里面是请求方法、路径、Host、User-Agent 等明文内容。只有 HTTP 这类明文协议能在 Wireshark 里看到这层;HTTPS 的话这里就是“TLS”层,里面是加密后的内容。
通过这种方式,一次抓包就把 OSI 层次从下到上全部串起来了。以后再有人问你 OSI 各层功能,你不用背,直接打开 pcap 对着看就行。
5. 常见问题与排障技巧实录
5.1 Wireshark 只显示 520 字节,怎么看到 2090 字节
有人遇到过这种情况:明明应用层数据有 2090 字节,但 Wireshark 里只看到 520 字节,剩下的不见了。这通常不是丢包,而是 MSS(Maximum Segment Size,最大报文段长度)或 MTU(最大传输单元)限制导致的 IP 分片或 TCP 分段。
在 TCP 建立连接时,双方会在 SYN 里协商 MSS。以太网 MTU 常见值 1500,减去 IP 头 20 字节和 TCP 头 20 字节,MSS 一般就是 1460 字节。如果一个 HTTP 响应有 2090 字节,那么数据会被拆成两个或更多 TCP 段:第一段大约 1460 字节,第二段剩余 630 字节。你在 Wireshark 里看到某个 TCP 报文长度显示为 520 字节,很可能是因为 IP 层又因为路径 MTU 限制做了分片,或者你的显示过滤器把不相关的报文过滤掉了。
排查方法很简单。选中那个疑似不完整的 TCP 段,看 IP 层的 Total Length 字段和 TCP 层的 Segment Length。如果是分片,你会看到两个帧的 IP 标识符(Identification)相同,而且第一个分片的 flags 里 MF(More Fragments)为 1。如果是 TCP 分段,你会看到两个不同的 TCP 序号,Wireshark 的 “IPv4 defragment” 以及 TCP sequence 分析会帮你重建整个流。如果实在看不到,就在显示过滤器里输入 tcp.analysis.fragments 或 ip.flags.mf == 1,Wireshark 会直接列出所有分片相关的报文。
5.2 过滤规则不生效的几个原因
过滤规则不生效,90% 都是方向理解错了。你写 tcp.port == 80,抓包结果里可能并没有 80 端口的包,因为目的端口和源端口都可以是 80。如果你只想看客户端访问服务器的请求,写成 tcp.dstport == 80。反过来,只看服务器响应的包,写 tcp.srcport == 80。很多人嫌麻烦一直用 tcp.port,结果在分析双向流量时分不清方向,误以为规则有问题。
另一个原因是大小写和字段名拼写问题。Wireshark 的字段名不区分大小写吗?其实很多字段对大小写敏感。比如 tcp.flags.syn == 1 和 TCP.Flags.SYN == 1 可能都能用,但某些自定义字段就不一定。最稳妥的方法还是右键包详情里的字段,选择 “Apply as Filter”,让 Wireshark 生成标准语法,而不是手敲。
还有一个常见问题:过滤表达式没写完整,比如 tcp.port == 后面没写值,或者值不是数字而是 IP 地址,Wireshark 会拒绝。输入框变红时,鼠标悬停上去会显示错误原因,我的习惯是先看红色提示,再百度错误原因,基本都能解决。
5.3 端口占用和 connect 超时怎么定位
工作里经常会遇到服务起不来,报错类似“Error: listen tcp 127.0.0.1:11434: bind: only one usage of each socket address”,或者代码里报“dial tcp ... connectex: a connection attempt failed”。这些问题的本质是端口或地址不可用,用 Wireshark 也能配合排查。
先看端口占用。Windows 可以用 netstat -ano | findstr 11434,Linux 用 ss -lntp | grep 11434,查出 PID 后去任务管理器或 ps 里看是哪个进程占用了端口。如果确认真有进程占用,要么换端口,要么杀掉旧进程。
再看 connect 超时。这类问题往往是对方没有监听端口、防火墙丢包或网络不通。抓包时你可以观察到:如果 SYN 发出去没有回应,说明包可能在途中被丢弃或服务器根本没监听;如果收到 RST,说明端口是关闭的,对方拒绝连接;如果收到了 SYN+ACK 但客户端迟迟不再发 ACK,可能是本地防火墙拦截或应用没继续走。
Wireshark 里判断连接失败,重点看三点:有没有 SYN、有没有对应的 SYN+ACK、有没有 RST。常见情况是只看到 SYN,没有后续,那就是网络层问题;看到 SYN+ACK 后紧接着 RST,可能是应用层主动断开;看到第一次握手就 RST,一般端口未监听。
6. 几个值得收藏的实操细节
很多文章到上面就结束了,但我还是想把自己踩过的几个细节补上,对日常工作很有帮助。
第一个是充分利用 Wireshark 的着色规则。默认规则里,TCP 重传包会被标成淡红色,你抓包时如果看到一大片红色,说明网络有丢包或拥塞,不用等高深分析。手动自定义规则也很好用,比如把 RST 标成深红,把 HTTP 4xx/5xx 响应标成橙色,排查问题效率提升很明显。
第二个是把 “Follow TCP Stream” 当成万能钥匙。不管请求是 HTTP、WebSocket、Modbus TCP 还是其他 TCP 承载协议,都可以通过这个功能快速重建双向数据流。尤其调试嵌入式设备或工业协议时,比如有人问的 Modbus TCP 自由协议通信,抓包后直接 Follow TCP Stream,你就能看到一帧一帧的请求响应对应关系,比一个一个找包快得多。
第三个是导出特定报文的技巧。有时候需要把某个 pcap 片段发给同事,可以在过滤完流量后,File -> Export Specified Packets,只导出当前过滤器的结果,注意选择显示过滤后的报文,而不是全部。这样文件小,定位快,对方打开也不会被无关流量干扰。
第四个是关于 Wireshark 可视化的问题。新版 Wireshark 自带很多统计图,比如 Statistics -> Flow Graph,可以直接画出 TCP 连接的时序图,三次握手、数据交换、挥手过程一目了然。做实验报告、写故障复盘文档时,这个功能比截图列表好使得多。
老实说,网络协议这东西,看十遍书不如抓一次包。我当年学三次握手,看了几篇博客都没记牢,后来自己在 Wireshark 里抓了一个百度首页的访问过程,把 SYN 到 ACK 从头到尾点了一遍,从此再没忘过。OSI 模型也一样,你在协议树里把那几个字符和层次对应起来,天然就记住了。把这些工具和方法变成自己的习惯之后,你会发现自己排障时不再瞎猜,而是每一步都有数据支撑,这种掌控感是看多少教程都换不来的。
