TCP/IP四层模型实战:抓包验证与网络故障分层定位
1. 为什么我坚持用TCP/IP四层模型来理解网络TCP/IP四层模型这五个字我在带新人、写故障复盘、跟运维对线的时候几乎每周都要提一次。它能干的事情很具体把一次网络通信拆成网络接口层、网络层、传输层、应用层四段让你在出问题的时候知道该往哪个方向看而不是抱着一句“网不通”干瞪眼。它解决的问题是定位效率——同一个“页面打不开”的现象可能是网线没插好也可能是路由黑洞可能是防火墙拦了端口还可能是应用层返回了502四层模型给的就是一套排查顺序。这篇内容适合三类人看刚入行的开发同学写代码时会把connect timeout和read timeout混为一谈的做后端、运维、测试需要独立定位线上问题的以及准备面试、被问到“三次握手为什么不是两次”时只能背答案的。就算你只会敲几行 ping看完也能把四层这套框架用起来。我不打算把它写成教科书。市面上的分层图已经够多了缺的是“这一层到底管什么、不管什么”“参数为什么是这个数”“抓包看到的东西对应哪一层”这类落地的东西。下面我按自己的理解顺序来讲先说分层的本质和取舍再逐层拆解然后走一遍完整的数据旅程最后上手抓包验证附一份我自己常用的排查速查表。1.1 从一次“网不通”的排查说起先说个真实场景。某个内部服务突然报连接超时值班的同学第一反应是“服务挂了”去看日志发现日志里干干净净一行请求都没进来。第二反应是“服务器宕机”ssh 上去一看机器活得好好的CPU 空闲。第三反应开始玄学重启服务、重启机器、换客户端全都没有用。后来用分层的方式五分钟定位先ping对端 IP通的——网络层没问题路由可达再telnet目标端口卡住不返回——传输层的端口不通最后看本机防火墙规则发现一条新下发的策略把该端口拦了。整个过程没有任何“猜”每次只验证一层验证完就排掉一层剩下的范围越来越小。这就是分层最朴素的价值把一个大问题切成一串小问题每层只回答一个是非题。反过来如果没有这套框架排查就会变成“把能想到的办法都试一遍”运气好碰上了运气不好浪费两小时还说不清原因在哪。1.2 四层模型到底切开了什么TCP/IP四层模型的分法本质是按“职责边界”切不是按“代码模块”切。每一层只关心自己那一小段对上层提供接口对下层提出要求。层级核心职责数据单位典型协议出问题时的表现应用层业务语义、报文格式消息 / 报文HTTP、DNS、SSH、SMTP返回码异常、字段错、超时但连接正常传输层端到端可靠性与端口寻址段TCP/ 数据报UDPTCP、UDP端口不通、连接被重置、重传多网络层主机到主机的寻址与路由包PacketIP、ICMP、ARP部分实现归此层ping 不通、跨网段不通、TTL 超时网络接口层同一链路上的帧传输帧Frame以太网、Wi-Fi、PPP网线/网卡、MAC 冲突、链路丢包跟 OSI 七层对照TCP/IP 是把 OSI 的会话层、表示层合并进应用层把数据链路层和物理层合并成网络接口层。很多教材会强调“OSI 是理论模型、TCP/IP 是工程模型”我的看法更直接OSI 切得细适合教学TCP/IP 切得粗适合干活。真实项目里没人会问“这是会话层问题还是表示层问题”但所有人都会问“这是应用的问题还是网络的问题”。注意四层模型里的“网络接口层”不是一个具体协议而是一个集合名词。以太网、Wi-Fi、PPPoE、4G/5G 的链路层实现都在这里但它们的行为差异极大。所以排查到这一层时问“是不是链路层问题”意义不大得具体问“是不是这个网卡、这个 VLAN、这条专线的问题”。1.3 分层带来的边界与代价分层不是白拿的它有两个代价必须清楚。第一个代价是头部开销。每下一层加一个头一个 TCP 段出去以太网头 14 字节 IP 头至少 20 字节 TCP 头至少 20 字节光头部就 54 字节。MTU 1500 的情况下真正能装的业务数据只有 1460 字节这就是后面要展开的 MSS 的来源。如果你做的是高频小包的业务比如实时对战的帧同步这个比例就非常难受这也是很多游戏协议宁可在 UDP 上自己实现可靠性也不要 TCP 的原因之一。第二个代价是跨层信息丢失。上层看不到下层的细节下层也不知道上层在干什么。比如 TCP 只知道“对端确认了这些字节”但它不知道这些字节是登录请求还是图片数据应用层也不知道自己这条连接中间穿过了一个 NAT、一个限速策略和一个老旧的中间设备。很多“明明服务没问题但用户就是连不上”的疑难杂症根因都在这种跨层信息不对称上。我自己的习惯是设计阶段按四层去思考排查阶段一定要跨层去怀疑。分层是为了降低认知负担不是为了画地为牢。2. 逐层拆解每一层究竟管什么、不管什么教科书的问题是只告诉你“这一层做什么”不告诉你“这一层不做什么”。而实际工作里知道一层的边界在哪比知道它的功能更重要因为越界的问题你在这层是永远找不到的。2.1 网络接口层最容易被忽视的起点这一层干的事很单纯把一串字节变成能在物理介质上传输的信号或帧并且在同一链路内把帧送到正确的邻居手里。以太网里靠的就是 MAC 地址48 位前 24 位是厂商标识后 24 位由厂商分配。同一广播域内设备之间靠 ARP 把 IP 地址问出 MAC 地址然后按 MAC 直接投递。这一层最关键的参数是MTU最大传输单元以太网默认 1500 字节。它决定了上层 IP 包的最大尺寸超过这个尺寸就必须分片而分片是性能杀手任一分片丢失整个原始包都要重传。实际网络里 MTU 不一致是极常见的坑——本地 1500隧道封装之后变成 1400 甚至更小如果路径上没人做 MTU 探测或者 ICMP 被拦了表现就是“小请求正常、大请求卡死”非常典型。我见过太多人把“链路层”和“物理层”混为一谈觉得网线松了才是链路层问题。实际上交换机端口协商成半双工、网卡 offload 导致抓包看到的分段和实际发出的对不上、VLAN 标签配错、无线信道干扰导致重传率高这些全在这一层而且全都会表现成上层“偶发超时”。你要是只在应用层加日志永远看不见它们。实操心得抓包时如果看到大量“TCP Previous segment not captured”或者重传先别急着骂服务端。在交换机上看看接口的 CRC 错误计数和丢包计数很多时候是链路质量问题跟 TCP 一点关系都没有。2.2 网络层寻址与路由的全部逻辑网络层的核心是 IP它做两件事给每台主机一个全局可路由的地址以及决定这个包下一步往哪送。注意 IP 是“尽力而为”的它不保证送达、不保证顺序、不保证不重复这些全甩给了上面。很多人对这一点没概念以为 IP 是可靠的结果在 UDP 上写业务代码时理所当然地认为包不会丢线上就出事了。IP 头里几个字段值得单独说TTL生存时间每过一个路由器减 1减到 0 就丢弃并回一个 ICMP 超时。这个设计原本是为了防止老式网络里的包永久打转现在它的主要用途变成了traceroute的原理基础也成了很多排查手段的抓手。协议号标明上层是 TCP6、UDP17还是 ICMP1。防火墙做策略时除了看 IP 和端口常常也要看这个字段。分片相关字段标识、标志、片偏移IPv4 里字段齐全IPv6 干脆把分片挪到了端到端中间路由器不再分片这是 IPv6 一个很大的行为变化很多老排查经验在 IPv6 下直接失效。路由这块主机只关心一条这个目的地址是不是我直连网段的是就 ARP 找邻居直接发不是就丢给默认网关。所以“能不能通”这个问题在网络层其实可以拆成两个子问题——默认网关可达吗、网关知道怎么去目的地吗。这两个问题的排查手段完全不同前者靠ping 网关后者靠traceroute。2.3 传输层TCP 和 UDP 的真实分工传输层解决的是“进程到进程”的通信靠端口号区分同一台机器上的不同服务。这里最常见的误解是“TCP 可靠、UDP 不可靠”这话没错但太粗。更准确的表述是TCP 提供的是有序的、带确认的字节流它保证你收到的字节和发出的字节一模一样UDP 提供的是带校验的数据报它只保证“尽力送到”其余一概不管。维度TCPUDP连接需要握手建立、挥手释放无连接发送即结束可靠性确认 重传 排序无丢了就丢了边界字节流无消息边界保留消息边界一次发一次收头部开销20 字节起8 字节拥塞控制有慢启动、拥塞避免、快速重传等无需应用自己实现典型用途HTTP/1.1、SSH、数据库连接DNS、音视频、实时对战、QUIC 底层关于“字节流没有消息边界”这点值得多讲两句因为它导致的 bug 我见过太多次。TCP 是流你发两次 100 字节对面可能一次收到 200 字节也可能分三次收到。所以任何基于 TCP 的自定义协议都必须自己定义长度字段或者分隔符否则解析一定出错。这不是“偶尔出问题”而是“早晚出问题”在高并发和网络抖动下概率会放大。三次握手和四次挥手我建议不要只背流程要理解它背后的动机。三次握手的核心目的是双方都确认对方的收发能力正常同时交换初始序列号。两次握手只能确认“我发你收”无法确认“你发我收”服务端会为一个可能根本收不到响应的连接分配资源。四次挥手则是因为 TCP 是全双工的两个方向要各自关闭被动方还可能还有数据没发完所以不能合并。2.4 应用层协议设计与最容易被甩锅的一层应用层是业务最熟悉的一层也是问题最容易堆积的一层。HTTP 的状态码、DNS 的解析顺序、SSH 的密钥交换这些都在这一层。它不管字节怎么传只关心“这段字节代表什么含义”。这里有个特别常见的甩锅模式接口报错开发说“网络问题”运维说“应用问题”来回踢皮球。我的做法是拿一个判断标准卡住——如果 TCP 连接建立成功、数据能双向收发那基本就是应用层问题。因为传输层能建连说明路是通的能收发说明没有被拦剩下的只能是协议格式、超时配置、证书、鉴权这些应用层的事。应用层设计里我最看重的一点是超时策略。很多服务默认不设超时或者设一个 30 秒的超时结果上游早就断了下游还在傻等一个慢接口拖垮整个线程池。合理做法是分层设置连接超时短1~3 秒读超时长按业务 P99 的 2~3 倍并且总超时不能超过上游的超时预算。这个数字没有标准答案但一定要算过不能拍脑袋。3. 一次完整的数据旅程从发起请求到收到响应光看分层图不够直观走一遍完整的流程你就明白每一层实际加了什么、减了什么。假设客户端要访问一个 HTTP 接口域名已经解析成 IP。3.1 封装每一层往包里加什么封装是自顶向下的过程。应用层生成 HTTP 报文比如 GET 请求几百字节交给传输层传输层加上 TCP 头源端口、目的端口、序列号、确认号、窗口、标志位等 20 字节变成 TCP 段网络层加上 IP 头源 IP、目的 IP、TTL、协议号等 20 字节变成 IP 包链路层加上以太网头源 MAC、目的 MAC、类型14 字节和尾部校验4 字节变成帧最后变成电信号发出去。算一下开销1500 的 MTU 减去 20 字节 IP 头、20 字节 TCP 头得到MSS 1460 字节。这就是单个 TCP 段能携带的最大业务数据量。如果你的业务一次要传 10KB 数据那就得切成至少 8 个段这 8 个段能不能连续发出去取决于拥塞窗口和接收窗口。反过来接收端是解封装网卡收到帧校验通过后剥掉以太网头交给 IP 层IP 层看目的 IP 是不是自己或者需要转发剥掉 IP 头交给 TCPTCP 按序列号重排、去重、确认把有序字节流交给应用应用按自己的协议格式解析。注意抓包工具如 tcpdump默认看到的是某一层的视图-e才显示链路层头不加就只到 IP 层。很多人抓包后说“看不到 MAC 地址”其实是参数没加不是没抓到。3.2 连接状态的维护不只是三次握手从“发起连接”到“关闭连接”中间的状态变化比大多数人以为的复杂。客户端侧典型状态流转是 CLOSED → SYN_SENT → ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT → CLOSED服务端侧是 CLOSED → LISTEN → SYN_RCVD → ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED。这里面最值得关注的两个状态一个是TIME_WAIT一个是CLOSE_WAIT。TIME_WAIT 是主动关闭方在等 2MSL目的是保证最后一个 ACK 能到达以及让旧连接的迷路包在网络中消散。高并发短连接场景下 TIME_WAIT 堆积是常态不是故障但数量太大确实会耗尽本地端口需要从连接复用keep-alive或内核参数上解决。CLOSE_WAIT 则是被动关闭方收到了 FIN 但应用没调 close堆积说明代码里有连接没关这是真 bug必须查代码。我踩过最典型的一个坑是某服务用连接池但异常分支里漏了归还连接跑几天之后 CLOSE_WAIT 上千新连接建不上报的错却是“连接超时”看日志完全看不出跟连接泄漏有关。从那以后我养成习惯排查超时问题一定先ss -tan state close-wait看一眼。3.3 关键参数窗口、RTT 与带宽的三角关系传输层的性能本质上被三个量卡住带宽、RTT、窗口大小。三者关系是理论吞吐上限 窗口大小 / RTT举个例子跨国链路 RTT 200ms窗口 64KB那么单连接吞吐上限约 64KB / 0.2s 320KB/s也就是 2.5Mbps 左右。哪怕你买的是 1Gbps 带宽单条 TCP 连接也只能跑这么快。这就是为什么大文件传输要开多连接也是为什么长肥管道高带宽高延迟场景必须开窗口缩放。反过来的算法也常用如果想跑满 100MbpsRTT 50ms那么需要窗口 12.5MB/s × 0.05s ≈ 625KB。这个数字直接决定了你要不要调内核的接收/发送缓冲区。还有一个常被忽略的量是重传率。netstat -s里的重传段数和总发送段数之比正常应该远低于 1%。如果超过 2%说明链路质量有问题此时的“慢”不是带宽不够而是有效带宽被重传吃掉了。我曾经遇到一个机房内网重传率 5%业务方一直以为是代码慢加了无数日志最后是交换机一个端口光模块老化。4. 实操用抓包和命令亲手验证四层结构理论说再多不如自己抓一次包。这一节给的是能直接复现的操作环境是任意 Linux 机器不需要特殊权限之外的东西。4.1 环境与工具准备需要的东西很基础ping、traceroute或tracepath用于网络层验证ss现代系统自带替代 netstat用于传输层状态tcpdump用于抓包dig或nslookup用于应用层 DNS 验证curl用于构造应用层请求# 确认工具是否齐全缺哪个装哪个 which ping traceroute ss tcpdump dig curl # 查看本机路由表确认默认网关 ip route show # 查看网卡与 MTU ip link show第一件事是确认自己的网络底子默认网关是谁、本机 IP 是多少、网卡 MTU 是不是 1500。这三条信息决定了后面所有排查的起点。很多人跳过这步直接抓包结果抓了半天发现抓的是错的网卡多网卡机器很常见白忙一场。4.2 抓一次请求对照四层看开两个终端。第一个终端抓包# -nn 不做域名和端口解析-i 指定网卡port 80 过滤 sudo tcpdump -i eth0 -nn -A tcp port 80 -c 20第二个终端发请求curl -v http://example-target/health抓到的输出里你能清晰看到几件事三次握手的 SYN、SYN-ACK、ACK 三个包HTTP 请求的明文内容-A参数把载荷打成 ASCII响应的状态行。如果加上-e还能看到 MAC 地址加上-v能看到 TTL 和窗口值。重点看三个数字源端口随机高位端口说明客户端用的是临时端口、TTL能反推经过了多少跳常见初始值 64/128/255、win接收窗口反映对端还能收多少。这三个值平时没人注意但排查时极其有用比如 TTL 异常小说明路由绕远了win 一直是 0 说明接收方处理不过来。4.3 用命令观察各层状态不想抓包的时候用几条命令也能把四层看个大概# 网络层连通性与路径 ping -c 4 目标IP traceroute -n 目标IP # 传输层端口监听与连接状态统计 ss -tlnp # 本机监听端口 ss -tan state established | wc -l # 已建立连接数 ss -tan state time-wait | wc -l # TIME_WAIT 数量 ss -s # 汇总统计 # 传输层重传与异常 netstat -s | grep -iE retrans|reset|timeout # 应用层DNS 解析链路 dig trace 目标域名dig trace这条特别值得跑一次它会把从根到权威的整条解析链路打出来。很多人排查“域名解析慢”只看本地 DNS 返回时间其实慢可能发生在链路的任何一段。跑完你就明白为什么 DNS 缓存这么重要也明白 TTL 设得太短会带来什么代价。4.4 手工验证 MTU 与分片这是我最推荐做一次的实验因为它能把“MTU 不一致”这个抽象概念变成可感知的现象。# 1472 8(ICMP头) 20(IP头) 1500正好不分片 ping -M do -s 1472 目标IP # 1473 就会触发Message too long ping -M do -s 1473 目标IP-M do的意思是禁止分片。1472 能通、1473 不通就说明路径 MTU 是 1500。如果两个都不通但你ping小包是通的说明路径 MTU 小于 1500可能是中间有隧道封装。这时候用-s二分法往下试很快就能测出真实值。这个实验的现实意义在于如果你遇到“大文件传一半卡住、小请求正常”的现象八成就是路径 MTU 比两端都小而路径上的 ICMP 又被拦了导致分片永远拼不回来。解决办法要么调小本端 MTU要么让路径放行必要的 ICMP。提示做这个测试前先确认目标允许 ICMP很多云环境默认禁 ping测试结果会误导你。可以先ping小包确认基础连通性再逐步加大。5. 常见问题与排查技巧实录这一节是我自己的速查表按“现象 → 先怀疑哪层 → 用什么手段”组织遇到问题可以直接对号入座。5.1 典型故障速查表现象优先怀疑层快速验证手段常见根因完全 ping 不通网络层 / 接口层ip link、ip route、ping 网关网卡 down、网关错、路由缺失ping 通但端口连不上传输层ss -tlnp、telnet IP 端口服务未监听、防火墙拦截、监听在 127.0.0.1连接建立后立刻断开应用层 / 传输层抓包看 RST 时机协议不匹配明文连 TLS 端口、鉴权失败偶发超时重试就好接口层 / 网络层netstat -s看重传、看接口错误计数链路丢包、MTU 不一致、无线干扰大响应卡死小响应正常接口层 / 网络层ping -M do -s二分测 MTU路径 MTU 更小且 ICMP 被拦连接数上不去传输层ss -s、看 TIME_WAIT 与本地端口范围端口耗尽、未复用连接服务端 CLOSE_WAIT 堆积应用层ss -tan state close-wait代码未关闭连接、连接池泄漏域名解析慢或失败应用层dig trace、cat /etc/resolv.confDNS 配置错、上游超时、TTL 过短5.2 分层定位法从哪一层开始怀疑我的固定顺序是自下而上理由很实际下面的层不通上面所有层的表现都是假的。链路断了你在应用层看日志只会看到一堆超时毫无信息量。具体动作是四步第一步ping目标 IP。通说明网络层没问题直接跳到第三步不通进入第二步。第二步ping网关。网关通说明本机链路正常问题在网关之外用traceroute看在哪一跳断的网关不通说明本机链路或 IP 配置有问题查ip link、ip addr、ARP 表。第三步测目标端口。能连上说明传输层通问题在应用层连不上说明被拦或服务没起查监听状态和防火墙规则。第四步应用层验证。用curl -v看完整的请求响应重点看状态码、响应头、耗时分解DNS、连接、传输各花多久。这套顺序的价值在于每一步都能排除一大片不会来回反复。我见过有人一上来就翻应用日志翻了两小时最后发现是安全组没放行这种时间浪费完全可以避免。5.3 几个反直觉的坑第一个坑ping通不代表端口通。ICMP 和 TCP 走的是完全不同的处理路径很多环境禁 ping 但端口正常也有环境允许 ping 但业务端口全拦。只用 ping 判断“网络好不好”是非常不靠谱的。第二个坑telnet卡住不等于端口关闭。如果是被防火墙 DROP表现是一直卡着直到超时如果是 REJECT表现是立刻拒绝。这两种情况对应的处理方式完全不同抓包看有没有回 RST 就能区分。第三个坑抓包看到重传不一定是网络丢了。如果抓包点在本机网卡上而网卡开了大量 offload你看到的分段可能和实际发出的不一致甚至会出现“假重传”。正确做法是在对端或中间设备上抓或者临时关掉 offload 再验证。第四个坑TIME_WAIT 多不一定是问题。它是正常的连接关闭流程产物只要本地端口没耗尽、不影响新建连接就不用管。真正要处理的是主动减少短连接、开启连接复用而不是简单调小 TIME_WAIT 时间那会破坏协议的可靠性保证。第五个坑窗口为 0 不等于对端挂了。接收窗口为 0 说明对端应用层没及时读数据缓冲区满了TCP 会进入持续探测。这时候该查的是对端应用的处理能力而不是网络。5.4 一份可直接抄的排查脚本骨架最后给一段我自己常用的最小排查脚本出问题时跑一遍基本信息全有了#!/bin/bash TARGET$1 PORT$2 echo 1. 本机网络配置 ip -brief addr ip route show echo 2. 网络层连通性 ping -c 3 -W 2 $TARGET echo 3. 路径追踪 traceroute -n -w 2 -q 1 $TARGET | head -15 echo 4. 传输层端口 timeout 3 bash -c echo /dev/tcp/$TARGET/$PORT echo 端口 $PORT 可连接 || echo 端口 $PORT 不可连接 echo 5. 连接状态概览 ss -s echo TIME_WAIT: $(ss -tan state time-wait | wc -l) echo CLOSE_WAIT: $(ss -tan state close-wait | wc -l) echo 6. 重传与异常 netstat -s 2/dev/null | grep -iE retransmit|reset|failed这段脚本没什么高深技巧就是把上面四步固化成命令避免慌乱时漏步骤。我建议每个团队都留一份类似的新人值班时直接用比口头指导靠谱得多。我个人在这些年踩坑的经验里最看重的一点是分层模型最大的价值不在“知道有四层”而在“知道自己现在看的是哪一层”。同样一条报错有人看了十分钟就知道该查防火墙有人查了一整天还在翻业务日志差别不在技术深度而在有没有一套稳定的定位顺序。四层这套框架就是那个顺序。你不需要把它背得滚瓜烂熟但遇到问题时能条件反射地问一句“这是哪一层的事”基本上就已经赢了大半。至于剩下的细节抓几次包、改几次 MTU、看几次重传率自然就熟了。