TCP/IP五层模型这东西,很多人第一次接触是在课本上,背完"物理层、数据链路层、网络层、传输层、应用层"就完事了,考试一过脑子就清空。可真正开始做项目、调网络问题的时候,你会发现几乎所有疑难杂症最后都要回到这五层上来定位:明明 ping 得通却访问不了服务,多半卡在传输层;能访问但时快时慢,问题可能藏在链路层的 MTU 上;换了网段就断,八成是路由或 NAT 没配对。我写这篇东西的出发点很朴素——把"一个数据包从你按下回车到对方收到"这个过程,按五层一段一段拆开讲清楚,每一步加了多少字节、换了什么地址、谁在做判断,全部落到实处。刚入门网络的朋友可以顺着读,建立完整的心智模型;已经有几年经验、平时靠"重启大法"兜底的同行,也可以拿它当一份排查清单来对照。下面讲的内容,全部围绕一次真实的网络传输展开,从封装到解封装,从同网段到跨网段,配合能直接复现的命令和抓包观察,尽量做到看完就能上手验证。
1. 五层模型到底在解决什么实际问题
1.1 分层设计的核心动机:把一个巨型问题切成五段
我在带新人的时候喜欢问一个问题:如果没有分层,让你设计一套"全世界所有电脑互相通信"的方案,你会怎么写?绝大多数人第一反应是写下"定义一种格式,包含对方地址、内容长度、内容本身、校验值",然后发现这个格式根本没法统一——有的链路是有线、有的是无线、有的中间要跨好几种介质,有的应用需要可靠传输、有的只要快。分层本质上是一种工程上的"甩锅机制":每一层只解决自己那一段的确定性问题,把剩下的不确定性打包丢给下一层,并且约定好层与层之间的接口。
打个生活化的比方。你给外地朋友寄一箱瓷器,这件事拆开是:你决定寄什么(应用层)、你选择顺丰还是邮政(传输层的可靠性选择)、快递公司规划中转路线(网络层的选路)、每一段公路由哪个司机开哪辆车(链路层)、最后车轮压在柏油路上(物理层)。你完全不需要知道货车走的是哪条高速,快递公司也不需要知道你箱子里装的是茶叶还是瓷器——只有装车的人需要关心"这箱子能不能塞进车厢",这就是 MTU 的含义。
分层带来的第一个直接好处是替换成本极低。你把家里的网线换成 Wi-Fi,浏览器、微信、你的代码一行都不用改,因为链路层和物理层换了实现,往上层的接口没变。第二个好处是故障可以被隔离。出问题时你能快速判断是"这一段"坏了还是"那一段"坏了,而不是面对一个黑盒抓瞎。第三个好处是并行演进。新的物理介质(比如更高速的光模块)和新的应用协议可以各自独立发展,互不阻塞。
反过来说,分层也有代价:每一层都要加自己的头部,带来额外开销;层与层之间的抽象有时候会掩盖真实问题,比如你以为"发送成功了",其实数据只是进了内核缓冲区。理解这一点很关键——五层模型是分析工具,不是物理实体,真实的内核协议栈实现里各层边界是模糊的,很多优化(比如校验和卸载、TSO/GSO)会把本该分层的活合并做。所以别把模型当教条,把它当"排查问题的坐标系"来用,效果最好。
1.2 五层与 OSI 七层的对照,以及工程上为什么更认五层
教科书上还有一套 OSI 七层模型,多出来的是会话层和表示层。这两层在实际工程里几乎没有独立的协议实现——会话管理被应用层自己做了(比如 HTTP 的 Cookie、连接的 keep-alive),数据的编码压缩加密也被应用层或传输层做掉了(比如 TLS 就横跨在传输层和应用层之间,你很难说清它属于第七层还是第六层)。所以工业界的抓包工具、排障流程、文档描述基本都用五层或 TCP/IP 四层来讲,因为这才是实际代码里能对上号的东西。
| 五层模型 | OSI 对应 | 数据单元名称 | 这一层的地址 | 典型协议/设备 |
|---|---|---|---|---|
| 应用层 | 应用层 + 表示层 + 会话层 | 报文(Message) | 域名、URL、端口号(逻辑标识) | HTTP、DNS、SSH、SMTP |
| 传输层 | 传输层 | 段(Segment)/ 数据报(Datagram) | 端口号 | TCP、UDP、QUIC |
| 网络层 | 网络层 | 包(Packet) | IP 地址 | IP、ICMP、路由表、路由器 |
| 数据链路层 | 数据链路层 | 帧(Frame) | MAC 地址 | 以太网、ARP、交换机、网卡 |
| 物理层 | 物理层 | 比特(Bit) | 无(电信号/光信号) | 网线、光纤、无线射频、集线器 |
看这张表要抓住一个核心:地址是分层的。应用层认域名和端口,网络层认 IP,链路层认 MAC。一次传输过程中,IP 地址从头到尾基本不变(除非过 NAT),MAC 地址却会一跳一跳地换。这个差别是后面所有内容的主线,先记住它。
1.3 每一层到底管什么:职责边界速查
物理层管的是"0 和 1 怎么变成电信号/光信号发出去",涉及电压、编码方式、速率、接口形态。这一层出问题的表现通常是"灯不亮""速率协商不对""网线被压断了"。数据链路层管的是"在同一个广播域内,怎么把帧交给正确的邻居",核心是 MAC 寻址、差错检测(FCS)、介质访问控制。网络层管的是"跨网络怎么找到目的地",核心是 IP 寻址、路由选择、分片。传输层管的是"端到端的数据怎么可靠地、按序地、不拥挤地送达",核心是端口复用、连接管理、重传与流控。应用层管的是"这些字节代表什么业务含义",核心是协议语义。
这五层的职责边界,直接决定了排障时的提问顺序:先问物理层通不通(链路状态、灯、速率),再问链路层邻居找不找得到(ARP 有没有解析),再问网络层路由走不走得通(ping 网关、traceroute),再问传输层端口开不开(telnet 端口、抓 SYN),最后才怀疑应用层逻辑(日志、返回码)。从下往上问,能避免大量无效猜测。我见过太多人一上来就怀疑代码有 bug,折腾半天最后发现是网线松了——顺序错了,时间就白花了。
2. 一次请求发出去,数据到底被"包"了多少层
2.1 从应用层到物理层的封装全过程
假设你在浏览器里输入一个地址,要请求一个页面。应用层先是根据域名去查 IP(这一步本身也是一次完整的五层传输,走的是 UDP 或 TCP 的 53 端口),拿到 IP 之后构造出一条 HTTP 请求,比如GET /index.html HTTP/1.1加上若干请求头。这条文本就是应用层的"报文",此时它只是一串字节,没有任何网络相关的头部。
接下来交给传输层。TCP 会在这一串字节前面加上自己的头部:源端口、目的端口、序列号、确认号、标志位、窗口大小、校验和等。加上头部之后,这一坨数据被称为段(Segment)。这一步最关键的动作是绑定端口——内核靠"源 IP + 源端口 + 目的 IP + 目的端口"这个四元组来区分不同的连接,所以同一台机器上几百个连接互不干扰。
再往下交给网络层。IP 协议在 TCP 段前面再加一个 IP 头部:版本、头长度、总长度、标识、分片标志、TTL、上层协议号(TCP 是 6,UDP 是 17,ICMP 是 1)、校验和、源 IP、目的 IP。加完之后叫包(Packet)。注意这里出现了本层最重要的判断:目的 IP 在不在我所在的网段?不在就把包交给网关,在就直接发给对方。这个判断是用子网掩码做按位与算出来的,后面会详细算一遍。
然后交给数据链路层。以太网会在 IP 包前面加 14 个字节的帧头:目的 MAC 6 字节、源 MAC 6 字节、类型字段 2 字节(0x0800 表示里面装的是 IPv4),尾部再加 4 字节的帧校验序列 FCS。如果不知道目的 MAC 是谁,就得先发一个 ARP 请求去问。加完之后叫帧(Frame)。
最后到物理层,网卡把这串帧按位拆成电信号或者光信号,按协商好的速率打到线路上。到了这一步,"数据"这个概念已经完全消失了,只剩电平的高低变化。
2.2 逐层加头部的字节账,算清楚才不吃亏
很多人对"开销"没概念,觉得加几个头无所谓。真算一遍就知道为什么 MTU 和 MSS 这么重要。
| 层级 | 头部组成 | 字节数(最小/典型) |
|---|---|---|
| 以太网帧头 | 目的 MAC(6) + 源 MAC(6) + 类型(2) | 14 |
| 以太网帧尾 | FCS 校验 | 4(不计入 MTU) |
| IP 头 | 固定部分 20 字节,含选项可到 60 | 20 |
| TCP 头 | 固定部分 20 字节,含选项可到 60 | 20(带时间戳等选项常为 32) |
| UDP 头 | 源端口(2)+目的端口(2)+长度(2)+校验和(2) | 8 |
一个最常见的以太网 MTU 是 1500 字节,指的是IP 包的总长度上限,不含帧头和 FCS。那么留给 TCP 的最大载荷就是:
- TCP 段的最大长度 MSS = MTU − IP 头 − TCP 头 = 1500 − 20 − 20 =1460 字节
- 如果 TCP 带了 12 字节的选项(时间戳),实际 MSS 就只有 1448
- 换成 UDP,最大载荷是 1500 − 20 − 8 =1472 字节
这个账必须会算,因为一旦算错,症状特别隐蔽:小请求一切正常,一传大文件就卡死或者超时。原因就是某个环节的 MTU 比你以为的小,包被丢弃又没收到"需要分片"的通知(也就是所谓的黑洞),TCP 一直重传,表现成"卡住"。我在一个跨机房的项目里就栽过:链路上有一层封装把 MTU 压到了 1400 出头,本地 MSS 还是 1460,结果握手正常、登录正常、传大图必挂。后来把 MSS 钳到 1360 才彻底消停。
2.3 分片与路径 MTU 发现:为什么"能连上但传不动"
IPv4 允许中间路由器对超大的包做分片,把一个大包切成几个小包分别转发,到目的端再重组。听起来很美好,实际有两个坑:一是分片后任意一片丢了,整个原始包都要重传,效率极低;二是很多设备出于安全策略直接丢弃需要分片的包,并且不返回 ICMP 的"分片需要"消息——这就是黑洞。
现代做法是路径 MTU 发现(PMTUD):发送方先把 IP 头的 DF(不分片)标志置 1,如果路上遇到 MTU 更小的链路,路由器会回一个 ICMP 差错报文告诉你"下一条链路 MTU 是 X",发送方据此调小自己的包长。这套机制依赖 ICMP 能被放行,一旦 ICMP 被全面拦截,PMTUD 就失效了。这也是老运维常说"别把 ICMP 全禁掉"的原因之一。
实操上更稳的做法是在网关或服务端直接钳制 MSS:
# Linux 上对经过某网卡的 SYN 包改写 MSS,值 = 目标 MTU - 40 iptables -t mangle -A FORWARD -p tcp --syn -j TCPMSS --set-mss 1360注意:MSS 钳制只影响 TCP 握手时协商出来的值,对 UDP 无效。UDP 场景要靠应用自己控制包大小,一般建议单包载荷不超过 1200 字节,给各种隧道封装留足余量。
2.4 接收侧的解封装:一层层剥回去
接收端做的事情正好反过来,但有一个细节很多人忽略:剥的顺序和加的顺序是镜像的,但判断发生在每一层剥之前。网卡先看帧头的目的 MAC 是不是自己(或者是广播/组播地址),不是就直接丢弃,CPU 根本不知道这件事发生过——这就是网卡硬件过滤,也是为什么"抓包抓不到别人的流量"(除非网卡处于混杂模式)。MAC 匹配上了,检查 FCS,没问题就剥掉帧头,根据类型字段知道里面是 IPv4,交给网络层。
网络层看目的 IP:如果是自己,剥掉 IP 头,根据协议号交给 TCP 或 UDP;如果不是自己,且本机没开转发,直接丢弃。传输层看目的端口,找到监听在这个端口上的进程,把数据放进这个连接的接收缓冲区,剥掉 TCP 头,按序列号排序、去重、确认。应用层被唤醒,读到一串字节,按自己的协议解析。
这一路下来,任何一个环节的判断失败都会导致"包到了但没人处理"。抓包时看到"包明明到了本机却没有响应",八成就是后面这几层里某一层的判断没过——端口没监听、防火墙拦了、目的 IP 配错了。
3. 逐层拆开看:每一层的真实工作细节
3.1 物理层与链路层:MAC 地址、ARP 与交换机的一天
MAC 地址是网卡出厂时烧进去的 48 位标识,前 24 位是厂商代码,后 24 位是序列号。它在同一个广播域内唯一,跨广播域就没有意义了。这一点非常关键:MAC 地址的作用范围只有"一跳",也就是从一台设备到它直连的下一台设备。
那怎么知道下一跳的 MAC 是谁?靠 ARP。假设主机 A(192.168.1.10)要发包给同网段的 192.168.1.20,它先查本地 ARP 缓存,如果没有对应条目,就发一个 ARP 请求:源 MAC 是自己的,目的 MAC 是全 F(广播),内容是"谁是 192.168.1.20,请告诉 192.168.1.10"。这个广播帧会被同一广播域内所有设备收到,只有 192.168.1.20 会单播回应自己的 MAC。A 收到后把这条映射写进缓存(Linux 默认有老化时间,一般几分钟),然后才真正开始发数据。
交换机在这一层干活。它维护一张 MAC 地址表,记录"哪个 MAC 在哪个端口后面"。收到帧时先看目的 MAC:表里有就只从对应端口转发出去(单播),没有就向除来源端口外的所有端口泛洪。这张表是靠"看源 MAC + 记下入端口"自学出来的,不需要任何配置。所以交换机是"透明"的,插上就能用。
排查链路层问题,我常用的三条命令:
# 看 ARP 缓存,确认邻居解析是否正常 ip neigh show # Linux arp -a # Windows / 部分 Linux # 看网卡链路状态、速率、双工、丢包 ip -s link show eth0 # 手动触发一次 ARP 解析并观察 arping -I eth0 192.168.1.20实操心得:ARP 缓存里出现
incomplete状态,说明请求发出去了但没人应答——要么对方没开机,要么中间有隔离(比如 VLAN 划错、无线客户端隔离开着),要么对方防火墙把 ARP 拦了(少见但存在)。这种时候别急着怀疑路由,问题还在二层。
3.2 网络层:一次路由判断的完整推演
网络层最核心的动作是判断目的地在哪,然后决定从哪个口发出去。这个判断在 Linux 上叫路由查找,规则是"最长前缀匹配"——在所有匹配的路由条目里,选掩码最长(最具体)的那条。
举个具体例子。本机 IP 是 192.168.1.10,掩码 255.255.255.0,默认网关 192.168.1.1,要访问 203.0.113.5。判断过程是:
- 拿本机掩码和目的 IP 做按位与:203.0.113.5 & 255.255.255.0 = 203.0.113.0
- 拿本机 IP 和掩码做按位与:192.168.1.10 & 255.255.255.0 = 192.168.1.0
- 两者不相等,说明目的不在本网段,需要走网关
- 再查路由表,发现有一条
default via 192.168.1.1,于是决定把包发给 192.168.1.1 - 但发出去需要一个 MAC 地址,于是对 192.168.1.1 发起 ARP,拿到网关的 MAC
- 最终发出的帧,目的 MAC 是网关的,目的 IP 还是 203.0.113.5
第 6 步是整篇文章最值得记住的一句话:跨网段通信时,MAC 地址指向下一跳,IP 地址指向最终目的地。数据包在网络里一跳一跳往前走,每一跳都重新封装帧头(换 MAC),但 IP 头里的源和目的地址始终不变(NAT 场景除外)。这就是"IP 端到端、MAC 逐跳"。
TTL 也是这一层的重要字段。每经过一个路由器,TTL 减 1,减到 0 就丢弃并回一个 ICMP 超时报文。这个设计防止了路由环路导致包永远转圈。traceroute 正是利用它工作的:先发 TTL=1 的包,第一个路由器减到 0 回超时,就暴露了它的地址;再发 TTL=2 的包,暴露第二个……依次递推画出整条路径。
# Linux / macOS traceroute -n 203.0.113.5 # Windows tracert -d 203.0.113.5NAT 是网络层另一个绕不开的话题。家用路由器把内网地址转换成公网地址出去,靠的是改写源 IP 和源端口,并在内存里维护一张转换表。返回的包根据目的端口反查这张表,改回内网地址再送进去。所以内网主机对外呈现的是同一个公网 IP 的不同端口,这也是为什么外部无法主动连进来——没有对应的表项。
3.3 传输层:端口、三次握手与可靠传输的代价
传输层解决的是"一台机器上的哪个程序"这个问题。IP 地址只能定位到主机,端口号才能定位到进程。端口是 16 位,0 到 65535,其中 1024 以下是知名端口,普通用户进程绑不了(需要特权)。
TCP 和 UDP 的分工很清楚:TCP 要可靠、有序、有连接、有流控和拥塞控制,代价是握手、确认、重传带来的延迟和开销;UDP 只做端口复用和可选的校验,其他什么都不管,快但不可靠。选型上的经验是:能容忍丢包且对延迟敏感的用 UDP(音视频、实时游戏、DNS 查询),其余一律 TCP。现在有个折中方案 QUIC 跑在 UDP 上,把可靠性做到用户态,兼顾了两者,新项目值得考虑。
TCP 建立连接是三次握手,过程是这样的:
- 客户端发 SYN,带上自己的初始序列号 seq=x,同时附带 MSS、窗口缩放、SACK 等选项
- 服务端回 SYN+ACK,seq=y,ack=x+1,同时把自己的 MSS 和选项带上
- 客户端回 ACK,seq=x+1,ack=y+1,连接建立
为什么是三次不是两次?因为要让双方都确认对方的收发能力正常。两次握手下,服务端发完 SYN+ACK 就认为连接建立了,但如果这个包在路上丢了,客户端根本没收到,服务端却已经开始分配资源等待数据——历史上这种"半开连接"被用来做资源耗尽攻击。第三次 ACK 让服务端确认"客户端确实收到了我的序列号",双方的状态才真正同步。
序列号的作用远不止"编号"。它是可靠传输的基石:接收方靠它排序、去重、发现空洞;发送方靠 ACK 号知道哪些数据已被确认可以释放缓冲区;重传靠它定位丢的是哪一段。窗口字段则告诉对方"我还能收多少",实现流量控制——接收方处理不过来时把窗口调小,发送方就自动降速,不需要任何额外协商。
# 观察本机 TCP 连接状态分布 ss -tan state established | head -20 ss -s # 查看某个端口的监听与队列情况 ss -lnt # 查看 TCP 重传、丢包等统计 nstat -az | grep -i -E "retrans|drop"注意:
ss输出的 Recv-Q 和 Send-Q 含义随状态不同而不同。在 LISTEN 状态下,Recv-Q 是当前已完成握手但还没被应用 accept 的连接数,Send-Q 是 backlog 上限。这个值持续接近上限,说明应用的 accept 速度跟不上,跟网络无关,是代码的问题。这个坑我踩过,当时一屋子人查网络,最后发现是服务端处理线程池满了。
3.4 应用层:协议语义与端口的约定
应用层是最"自由"的一层,只要双方约定好字节的含义,什么都能传。但自由也意味着这里是 bug 最多的地方——编码问题、长度字段解析错、粘包、超时设置不合理,全都出在这一层。
端口是应用层和传输层的接口。约定俗成的端口有 HTTP 80、HTTPS 443、SSH 22、DNS 53、SMTP 25,但完全可以改,只要客户端知道。服务端绑定端口时有个常见误区:绑0.0.0.0表示监听所有网卡,绑127.0.0.1表示只接受本机访问。很多"外网访问不了"的问题,根源就是服务只监听了回环地址。
# 确认服务监听在哪个地址上,这一条能解决一半的"连不上" ss -lntp | grep 8080 # 从另一台机器测端口可达性 nc -zv 192.168.1.10 8080应用层协议设计里我最想强调的一点是超时要分层设置。连接超时、读写超时、整体请求超时,三个值要递增且互相匹配。如果连接超时设成 30 秒,整体请求超时也是 30 秒,那么一旦连接阶段卡住,你连"是连不上还是读不到"都分不清。合理的做法是连接 3 秒、读写 10 秒、整体 30 秒,日志里分别打点,出问题时一眼就能定位。
4. 动手实测:把一次完整传输抓出来看
4.1 环境与工具准备
光看文字容易飘,我建议所有人都亲手抓一次包。你需要的工具并不多:系统自带的ping、traceroute、ip、ss,加上一个抓包工具。命令行抓包用tcpdump,图形化用 Wireshark,两者本质一样,前者适合在服务器上跑,后者适合分析。
# 抓取指定网卡上与某个主机相关的所有流量,写文件便于分析 tcpdump -i eth0 -nn -s 0 -w trace.pcap host 203.0.113.5 # 只抓 TCP 且只看握手阶段 tcpdump -i eth0 -nn 'tcp[tcpflags] & (tcp-syn|tcp-ack) != 0' # 抓 ICMP,观察 ping 的完整往返 tcpdump -i eth0 -nn icmp参数说明一下,这几个都很实用:-nn表示不把 IP 反解成域名、不把端口反解成服务名,避免 DNS 查询干扰观察;-s 0表示抓完整包不截断;-w写文件。在容器里抓包要记得先进到对应 netns,否则抓的是宿主机网卡的流量,什么也看不到。
4.2 同网段访问:观察 ARP 与 ICMP 的配合
先做最简单的实验:从 192.168.1.10 ping 同网段的 192.168.1.20。抓包会看到这样的顺序:
- ARP 请求:广播帧,目的 MAC 是 ff:ff:ff:ff:ff:ff,内容是"谁有 192.168.1.20"
- ARP 应答:单播帧,192.168.1.20 回自己的 MAC
- ICMP Echo Request:源 MAC 是 .10,目的 MAC 是 .20,源 IP 是 .10,目的 IP 是 .20
- ICMP Echo Reply:方向反过来,MAC 和 IP 都对调
这里能验证一个关键结论:同网段通信,源和目的 MAC、源和目的 IP 都直接对应两台主机,中间没有第三者改写。如果这个实验里只看到 ARP 请求没看到应答,说明二层就断了,往上查都是浪费时间。
4.3 跨网段访问:看 MAC 怎么逐跳变化
再做一个实验:ping 一个不同网段的地址(比如文档示例段里的 203.0.113.5)。抓包会看到:
- 本机先 ARP 查询网关的 MAC,而不是目的 IP 的 MAC——这是很多新手第一次抓包时最困惑的地方
- 发出的 ICMP 包,目的 MAC 是网关的,目的 IP 是 203.0.113.5
- 如果能在网关之外再抓一次,会发现同一个包出去时,源 MAC 已经变成了网关外网口的 MAC,源 IP 也被 NAT 改写(如果启用了 NAT),但目的 IP 一直没变
- 回程包同理,MAC 一路上换了好几次,IP 只在 NAT 那一处被改写
把这两次抓包对比着看,"IP 端到端、MAC 逐跳"这句话就再也不会忘了。我在给团队做内训时,这个实验的留存率最高,比讲十遍理论管用。
4.4 抓一次完整的 TCP 握手与数据传输
最后抓一次 TCP。用nc或者curl访问一个 HTTP 服务,重点看这几个包:
| 序号 | 方向 | 标志位 | 关键字段 | 说明 |
|---|---|---|---|---|
| 1 | C→S | SYN | seq=0, MSS=1460, WS=7, SACK_PERM | 客户端发起,通告自己的接收能力 |
| 2 | S→C | SYN,ACK | seq=0, ack=1, MSS=1440 | 服务端回应,双方取较小的 MSS |
| 3 | C→S | ACK | seq=1, ack=1 | 握手完成,可以发数据 |
| 4 | C→S | PSH,ACK | seq=1, len=200 | 发出第一段数据 |
| 5 | S→C | ACK | ack=201 | 确认收到 |
| 6 | S→C | PSH,ACK | seq=1, len=1500 | 服务端返回数据 |
| 7 | C→S | ACK | ack=1501 | 客户端确认 |
看这张表要抓三个点。第一,MSS 是双方各报各的,取小的那个,所以链路上有个窄口子会立刻限制整体吞吐。第二,数据包的 len 加上 IP 头和 TCP 头正好不超过 MTU,超过就要分段。第三,ack 号永远是"我期望收到的下一个字节序号",等于"已收到的最后序号 + 长度",这个规则搞懂了,分析重传和乱序就很简单。
Wireshark 里可以直接看Statistics → Flow Graph或者右键Follow → TCP Stream,把整条流还原出来,比一个个包看效率高得多。分析重传时用过滤器tcp.analysis.retransmission,一抓一个准。
5. 常见问题与排查思路实录
5.1 自下而上的分层排查法
我处理网络问题的固定套路是五步,按层推进,绝不跳步:
第一步,看物理层。网卡是不是 up,灯亮不亮,速率和双工协商对不对(ethtool eth0能看到)。无线场景看信号强度和重传率。这一层的问题占了实打实的一小半,但最容易被跳过。
第二步,看链路层。同网段能不能 ping 通,ARP 能不能解析,交换机端口有没有 VLAN 划错。跨网段时这一层关注的是"能不能解析到网关的 MAC"。
第三步,看网络层。ping 网关通不通,traceroute 到哪一跳断了,路由表里有没有去目的网段的路由(ip route get 203.0.113.5这一条命令能直接告诉你内核会从哪个口、经哪个网关发出去,非常好用)。
第四步,看传输层。端口有没有监听、防火墙有没有放行、SYN 发出去有没有 SYN+ACK 回来。只有 SYN 没有回应,通常是中间设备拦了或者服务没起;有 SYN+ACK 但立刻 RST,通常是端口没监听。
第五步,才是应用层。看日志、看返回码、看协议格式。
这个顺序的价值在于每一步都能给出确定的结论,不会让你在五个可能原因里瞎猜。我带的项目里有个规矩:任何网络故障,排查记录必须先写"物理层结论:xxx",写不出来就说明你还没开始查。
5.2 常见问题速查表
| 现象 | 最可能的层 | 排查命令 | 常见原因 |
|---|---|---|---|
| 网卡显示 down | 物理层 | ip link、ethtool | 网线/光模块问题、对端未启用 |
| 同网段 ping 不通 | 链路层 | ip neigh、arping | ARP 解析失败、VLAN 隔离、无线客户端隔离 |
| 能 ping 通 IP 但域名解析失败 | 应用层 | dig、cat /etc/resolv.conf | DNS 配置错误、53 端口被拦 |
| ping 通但端口连不上 | 传输层 | nc -zv、ss -lntp | 服务未监听、防火墙规则、监听地址不对 |
| 小请求正常、大文件卡死 | 网络层/链路层 | ping -M do -s 1472 | MTU 不匹配、PMTUD 被 ICMP 拦截 |
| 连接偶发超时,重传率高 | 传输层 | nstat、抓包看 retransmission | 链路丢包、拥塞、接收窗口过小 |
| 服务端 CPU 不高但吞吐上不去 | 传输层/应用层 | ss -ti看 cwnd 和 rtt | 窗口太小、RTT 太大、accept 队列满 |
| 换网络环境后全部超时 | 网络层 | ip route get、traceroute | 路由缺失、NAT 未配置、安全组未放行 |
用ping验证 MTU 有个小技巧:ping -M do -s 1472 目标IP,-M do表示禁止分片,-s 1472是 ICMP 载荷,加上 8 字节 ICMP 头和 20 字节 IP 头正好 1500。能通说明路径 MTU 至少 1500,报"Message too long"就往小调,每次减 8,直到能通为止,再加 28 就是实际路径 MTU。
5.3 几个我踩过的坑,写下来给后来人
第一个坑:以为ping通就没问题。ICMP 走的是网络层,它通只证明三层可达,完全不能证明四层端口开着、五层逻辑正确。早期我排查一个"服务不可用",ping 通了就以为是代码问题,折腾两小时才发现是安全组没放行 8080。后来我养成习惯,ping 通之后立刻补一条nc -zv测端口,两步都过才往下查。
第二个坑:忽略 conntrack 表满。NAT 环境下所有连接都要在连接跟踪表里占一条记录,表满了新连接直接建不起来,现象是"时好时坏、偶发超时"。查法是cat /proc/sys/net/netfilter/nf_conntrack_count对比nf_conntrack_max。这个问题在高并发短连接场景特别常见,把连接复用做好能大幅缓解。
第三个坑:TIME_WAIT 数量被误判为故障。主动关闭连接的一方会进入 TIME_WAIT 状态,等 2 倍 MSL 才释放,这是协议的正常设计,为了保证最后一个 ACK 能重传、防止旧连接的残留包干扰新连接。看到几千个 TIME_WAIT 不代表有问题,只有当它耗尽了本地端口范围(net.ipv4.ip_local_port_range)才是真问题。解决办法是让客户端复用连接、或者调整端口范围,而不是简单粗暴地开tcp_tw_reuse了事。
第四个坑:改 MTU 只改了一头。链路上任何一台设备的 MTU 变小,都需要端到端对齐,只改一侧会导致单向大包不通、小包正常这种极其迷惑的现象。改之前先确认整条路径,改完用带-M do的 ping 双向验证。
第五个坑:在容器里排查网络,抓错了命名空间。容器有自己的网络栈,宿主机上的ss、tcpdump看不到容器内的连接。正确做法是nsenter -t <pid> -n ss -lntp或者直接进容器执行。这个坑我踩过不止一次,每次都要愣一下才想起来。
6. 一点个人体会
五层模型最大的价值不在于你能背出五层的名字,而在于它给了你一个稳定的提问顺序。网络问题的难点从来不是知识点不够,而是可能性太多、无从下手。有了分层这个坐标系,你每次只需要回答"这一层通不通",答完一层再走下一层,问题自然会收敛到一个很小的范围里。我自己这些年处理过的线上故障,绝大多数在第二层和第三层就定位到了,真正需要翻到应用层的比例并不高。
另外一个建议是:别怕抓包。很多人觉得抓包是"高手技能",其实门槛比想象的低得多。你只要会看三个东西——源和目的地址、标志位、长度,就能解决八成问题。剩下的细节,抓得多了自然会认出来。我现在的习惯是,任何一个网络相关的工单,先抓 30 秒包再说,哪怕最后用不上,也比凭猜测强。真要说有什么诀窍,就一条:先确认事实,再构建假设。抓包给的是事实,猜测给的是假设,两者混在一起,排查就会变成玄学。