2024 年,我把一个新服务部署到机器 B 上,让机器 A 去调用它。结果 A 这边连接直接失败。我的第一反应,是先确认两台机器网络通不通——于是在 A 上 ping 了一下 B,几个回包干净利落地回来了,延迟 0.2 毫秒,漂亮得很。网络明明是通的,可服务就是连不上。这个矛盾把我卡了很久:ping 都通了,还能有什么网络问题?我下意识地认定"网络没问题",转头一头扎进了服务本身的代码和配置里,翻了很久,什么也没翻出来。真正让我跳出僵局的,是一个迟来的醒悟:ping 通,和"服务连得上",根本就不是同一件事——它们测的,是网络里完全不同的两层。我一直把"能 ping 通"当成"网络一切正常"的总开关,可它其实只点亮了那么一小段。这件事逼着我把 Linux 的网络分层、ping/telnet/ss、路由与防火墙这一整套彻底理清了。本文复盘这次实战。
问题背景
环境:CentOS 7,机器 A(10.0.0.21)调机器 B(10.0.0.31)的 8080
事故现象:
- A 连 B 的 8080 服务,连接失败
- 在 A 上 ping B,通,延迟 0.2ms,毫无问题
- "网络是通的",于是一头扎进服务配置,查了半天没结果
现场排查:
# 1. A 上 ping B —— 通
$ ping -c3 10.0.0.31
64 bytes from 10.0.0.31: icmp_seq=1 ttl=64 time=0.21 ms # ★ 通
# 2. ★ 但是 telnet 那个端口 —— 不通
$ telnet 10.0.0.31 8080
Trying 10.0.0.31...
(长时间卡住,最后 Connection timed out) # ★ 端口不通!
# 3. ★ 跑到 B 上,看 8080 到底有没有在监听
$ ss -lnt | grep 8080
LISTEN 0 128 127.0.0.1:8080 *:* # ★ 监听在 127.0.0.1!
# ^^^^^^^^^ 只绑了本地回环地址
# 4. B 上的防火墙也顺手看一眼
$ firewall-cmd --list-ports
(8080/tcp 不在里面) # ★ 防火墙也没放行
根因(后来想清楚的):
1. ★ ping 通,只能说明 A 和 B 之间【第三层(IP 层)】
是通的 —— 数据包能在两台机器间路由往返。
2. ★ 但"连上一个服务"是【第四层(TCP)】的事,
它还要求:目标端口有进程在监听 + 防火墙放行 +
服务绑的地址是对的。ping 一个都管不着。
3. 真相一:B 的服务绑在 127.0.0.1:8080 —— 只有 B
自己能连,外面的 A 根本够不着。
4. 真相二:就算改成绑 0.0.0.0,B 的 firewalld 也
没放行 8080,A 的 TCP 握手包会被防火墙丢掉。
5. ★ 我把"ping 通"误当成"网络全通",于是跳过了
端口、绑定地址、防火墙这三个真正出问题的环节。
ping 通只代表 L3 通,服务连不上要一层层往上测。
修复 1:一次连接要穿过几层——ping 通只点亮了一段
# === ★ "A 连上 B 的服务",不是一件事,是【一串】事 ===
# 它要从下到上,一层层都成立:
# 第 ① 层 - 网线/网卡通:物理链路、网卡 UP
# 第 ② 层 - IP 可达:A 的包能路由到 B,B 的包能回来
# ★ ping 测的就是到这一层为止
# 第 ③ 层 - 端口可达:B 上 8080 端口的 TCP 握手能完成
# 第 ④ 层 - 服务就绪:8080 后面真有个进程在正常处理
# === ★ 关键认知:ping 通 = 第 ② 层通,仅此而已 ===
# ping 走的是 ICMP 协议,它验证的是"IP 包能不能往返"。
# 它【完全不碰】端口,不碰 TCP,不碰具体某个服务。
# ★ 所以"ping 通"能告诉你的,只有一句话:
# "A 和 B 之间,IP 层的路是通的。"
# 它【不能】告诉你:8080 有没有人听、防火墙放没放行、
# 服务进程是死是活 —— 这些全在它的视野之外。
# === ★ 正确的排查姿势:从下往上,一层层确认 ===
$ ping B_IP # ② IP 层通不通
$ telnet B_IP 端口 # ③ 端口通不通(TCP 握手)
$ curl http://B_IP:端口/ # ④ 服务应用层正不正常
# ★ 在哪一层第一次失败,问题就在那一层 —— 别越级。
# 我这次的错,就是②通了就直接跳到④,漏掉了③。
# === 一个形象的比喻 ===
# ping 通 = "这栋楼的地址是真的、路能开到楼下"。
# 但你要找的那个房间(端口)有没有人、
# 门卫(防火墙)放不放你进 —— ping 一概不知道。
修复 2:ping 到底测了什么——ICMP 与 L3 可达性
# === ★ 把 ping 这个工具本身看清楚,才不会高估它 ===
# === ping 用的是 ICMP 协议,不是 TCP/UDP ===
$ ping -c4 10.0.0.31
64 bytes from 10.0.0.31: icmp_seq=1 ttl=64 time=0.21 ms
# ^^^^^^^^^^ ★ ICMP,跟"端口"毫无关系
# ping 发 ICMP echo request,对方回 ICMP echo reply。
# 整个过程【没有端口的概念】—— 这就是它只能测 L3 的原因。
# === ping 的几个有用参数 ===
$ ping -c4 IP # -c4 发 4 个就停(别无限刷)
$ ping -W1 IP # -W1 单个回包最多等 1 秒
$ ping -s 1400 IP # -s 指定包大小(可测 MTU 问题)
# === ★ 一个大坑:ping 不通 ≠ 网络不通 ===
# 很多服务器、防火墙会【主动屏蔽 ICMP】(安全策略)。
# ★ 所以 ping 不通,也可能只是"对方不理 ICMP",
# 而 TCP 服务其实好好的。
# 反过来,ping 通也不代表 TCP 能通。
# ★ 结论:ping 只能【单向证明】L3 通;它的"不通",
# 和服务的"通不通",都不能直接划等号。
# === ttl 字段能看出"中间过了几跳" ===
# 回包的 ttl,从一个初始值(64/128/255)每过一个
# 路由器减 1。ttl=64 基本是同网段直连;
# ttl 明显变小 -> 中间过了好几跳路由。
# === 看 IP 层路由通不通,还能用 traceroute ===
$ traceroute 10.0.0.31 # 列出到对方经过的每一跳
$ mtr 10.0.0.31 # ★ traceroute + ping 的合体,
# 能看每一跳的丢包率,更强
# ★ 这些都还在 L3 这一层 —— 它们解决"路通不通",
# 解决不了"端口通不通"。
修复 3:端口通不通——telnet/nc 测 L4,ss 看监听
# === ★ ping 之后必做的一步:测【端口】通不通 ===
# === 在客户端 A 上:telnet 测 TCP 端口 ===
$ telnet 10.0.0.31 8080
Trying 10.0.0.31...
Connected to 10.0.0.31. # ★ 出现 Connected = 端口通!
# - Connected -> TCP 握手成功,端口是通的
# - Connection refused -> ★ 包到了,但没人监听这个端口
# - Connection timed out -> ★ 包石沉大海(防火墙丢包/路由不到)
# ★ refused 和 timeout 是两种【完全不同】的故障,要分清。
# === 更专业的工具:nc(netcat)===
$ nc -zv 10.0.0.31 8080 # -z 只测不发数据 -v 显示结果
Ncat: Connected to 10.0.0.31:8080. # 通
$ nc -zv 10.0.0.31 8000-8100 # 还能扫一个端口范围
# === ★ 在服务端 B 上:ss 看端口到底有没有在监听 ===
$ ss -lnt
State Recv-Q Send-Q Local Address:Port
LISTEN 0 128 127.0.0.1:8080 # ★ 看这一行
LISTEN 0 128 0.0.0.0:22
# -l 只看 LISTEN 的 -n 不解析名字 -t 只看 TCP
# ★ 如果 grep 8080 啥都没有 -> 服务根本没起来/没监听这个口。
# === 看某端口是被哪个进程监听的 ===
$ ss -lntp | grep 8080 # -p 显示进程
LISTEN ... 127.0.0.1:8080 users:(("java",pid=9001,fd=5))
# === ★ 把 refused / timeout 对应到原因 ===
# telnet 报 refused:
# -> 端口没进程监听,或服务挂了 -> 上 B 用 ss -lnt 查
# telnet 报 timed out:
# -> 包被防火墙丢了,或绑定地址不对 -> 查防火墙 + 绑定地址
# ★ 这次我 telnet 报的是 timed out,顺着它,
# 果然挖出了"绑定 127.0.0.1"和"防火墙没放行"两个根因。
修复 4:服务绑在哪个地址——127.0.0.1 的经典坑
# === ★ 这次根因之一:服务绑在了 127.0.0.1,外面够不着 ===
# === ss 输出里那个 "Local Address" 是关键 ===
$ ss -lnt | grep 8080
LISTEN 0 128 127.0.0.1:8080 *:* # ★ 绑 127.0.0.1
LISTEN 0 128 0.0.0.0:8080 *:* # ★ 绑 0.0.0.0(对比)
# === 127.0.0.1 和 0.0.0.0 的天壤之别 ===
# 监听 127.0.0.1:8080
# -> 只有【本机】能连这个服务。
# -> 别的机器(比如 A)来连,根本到不了 —— 内核直接不睬。
# -> ★ 在 B 本机 curl 127.0.0.1:8080 是通的,
# 这会给你"服务正常"的【错觉】!
# 监听 0.0.0.0:8080
# -> 本机【所有网卡/所有 IP】上都能被连。
# -> 这才是"想让别的机器连进来"该用的。
# === ★ 这个坑最迷惑人的地方 ===
# 你在 B 上自测:curl localhost:8080 -> 通!服务没问题啊!
# 可 A 上死活连不上。
# ★ 因为你自测走的是 127.0.0.1,而 A 走的是 10.0.0.31 ——
# 服务只在 127.0.0.1 上听,自测当然通,A 当然不通。
# 排查"别人连不上"时,自测一定要用【对外 IP】:
$ curl http://10.0.0.31:8080/ # 用对外 IP 自测,别用 localhost
# === 怎么改:让服务绑 0.0.0.0 ===
# 这是【应用配置】的事,各家不同,常见的:
# nginx listen 8080; (默认就是所有地址)
# Spring server.address=0.0.0.0
# 通用 配置里把 bind/host 从 127.0.0.1 改成 0.0.0.0
# 改完重启服务,再 ss -lnt 确认变成了 0.0.0.0:8080。
# === ★ 安全提醒:0.0.0.0 是"对所有人开门" ===
# 绑 0.0.0.0 后,这个端口对能路由到这台机的人都可见,
# 务必靠【防火墙】来限定"只允许哪些来源 IP" —— 见下节。
修复 5:防火墙这道暗门——firewalld 与 iptables
# === ★ 这次根因之二:B 的防火墙没放行 8080 ===
# 服务监听对了,防火墙这关没过,A 的握手包照样被丢。
# === CentOS 7 默认是 firewalld ===
$ systemctl status firewalld # 看防火墙开没开
$ firewall-cmd --state # running / not running
# === ★ 看当前放行了哪些端口/服务 ===
$ firewall-cmd --list-all
services: ssh dhcpv6-client
ports: # ★ ports 是空的 -> 8080 没放行
# 8080 既不在 services 也不在 ports 里 -> 它是被挡着的。
# === ★ 放行一个端口(持久)===
$ firewall-cmd --add-port=8080/tcp --permanent # --permanent 持久
$ firewall-cmd --reload # ★ 重载才生效
$ firewall-cmd --list-ports # 确认 8080/tcp 在了
# === ★ 更安全:只放行给特定来源 IP ===
$ firewall-cmd --permanent --add-rich-rule=\
'rule family=ipv4 source address=10.0.0.21 port port=8080 protocol=tcp accept'
$ firewall-cmd --reload
# 这样只有 A(10.0.0.21)能连 8080,别的来源照样挡 ——
# 比 --add-port 对全世界开放安全得多。
# === 如果系统用的是 iptables ===
$ iptables -L -n --line-numbers # 看现有规则
$ iptables -nvL INPUT # 看 INPUT 链,关注 DROP/REJECT
# === ★ 别忘了云服务器还有一层"安全组" ===
# 云主机(阿里云/腾讯云等)在【机器之外】还有一层
# "安全组"防火墙,在云控制台配。
# ★ 经典坑:系统内 firewalld 放行了,云安全组没放行 ->
# 还是 timeout。排查云上的端口不通,这一层必查。
# === 怎么确认"就是防火墙在丢包" ===
# 在 A telnet B 的同时,在 B 上抓包:
$ tcpdump -i any -nn tcp port 8080
# ★ 抓到了 A 的 SYN 包、但 B 没回 -> 包到了内核被防火墙丢了。
# 连 SYN 都没抓到 -> 包在更外层(云安全组/路由)就没了。
修复 6:网络分层排查纪律
# === 这次事故暴露的认知盲区,定几条纪律 ===
# === 1. ★ ping 通只代表 L3 通,绝不等于"服务能连" ===
# ping 走 ICMP,不碰端口、不碰 TCP、不碰具体服务。
# === 2. ★ 从下往上分层测,在哪层失败问题就在哪层 ===
$ ping B_IP # ② IP 层
$ telnet B_IP 端口 # ③ 端口/TCP 层
$ curl http://B_IP:端口/ # ④ 应用层
# === 3. ★ telnet 的 refused 和 timeout 是两种病 ===
# refused = 端口没人监听 -> 上服务端 ss -lnt 查
# timeout = 包被丢了 -> 查防火墙、查绑定地址
# === 4. ★ 服务端必查:监听地址是不是 127.0.0.1 ===
$ ss -lntp | grep 端口 # 绑 127.0.0.1 外面连不上,要绑 0.0.0.0
# === 5. ★ 自测要用对外 IP,别用 localhost ===
$ curl http://对外IP:端口/ # 用 localhost 会给你"服务正常"的错觉
# === 6. 防火墙:系统 firewalld + 云安全组,两层都要查 ===
$ firewall-cmd --list-all # 系统这层
# 云主机还要去控制台看安全组那层。
# === 7. 排查"连不上"的命令链 ===
$ ping B_IP # ① L3 通不通
$ telnet B_IP 端口 / nc -zv # ② 端口通不通
$ ss -lntp | grep 端口 # ③(服务端)有没有监听、绑哪
$ firewall-cmd --list-all # ④(服务端)防火墙放行没
$ tcpdump -nn tcp port 端口 # ⑤ 抓包看 SYN 到没到/有没有回
# 按这个顺序,"连不上"基本能定位到层。
命令速查
需求 命令
=============================================================
测 IP 层(L3)可达 ping -c4 IP
看到对方的每一跳路由 traceroute IP / mtr IP
测某个 TCP 端口通不通 telnet IP 端口 / nc -zv IP 端口
看本机监听了哪些端口 ss -lnt
看端口被哪个进程监听 ss -lntp | grep 端口
看防火墙放行了什么 firewall-cmd --list-all
放行一个端口 firewall-cmd --add-port=8080/tcp --permanent
重载防火墙规则 firewall-cmd --reload
用对外 IP 自测服务 curl http://对外IP:端口/
抓某端口的包看 SYN tcpdump -nn tcp port 端口
口诀:ping 通只是 L3 通,要 ping->telnet->curl 一层层往上测
telnet refused 是没监听,timeout 是被丢包(防火墙/绑错地址)
避坑清单
- ping 通只代表 IP 层(L3)可达,完全不能说明服务端口连得上
- 排查连不上要从下往上分层测:ping 测 L3、telnet 测端口、curl 测应用
- ping 走 ICMP 不碰端口,很多机器屏蔽 ICMP,ping 不通也未必网络不通
- telnet 报 refused 是端口没人监听,timeout 是包被丢,两者根因不同
- 服务绑 127.0.0.1 只有本机能连,要让别人连得绑 0.0.0.0
- 服务端自测要用对外 IP,用 localhost 会因走回环而产生服务正常的错觉
- ss -lntp 看端口的监听地址和监听进程,是服务端排查第一命令
- CentOS 7 防火墙是 firewalld,firewall-cmd --list-all 看放行情况
- 放行端口加 --permanent 后必须 firewall-cmd --reload 才生效
- 云服务器在系统防火墙之外还有一层安全组,两层都放行才通
总结
这次"ping 明明通了、服务却死活连不上"的事故,纠正了我一个用了很多年、却从没认真审视过的思维习惯——把 ping 当成判断"网络好不好"的总开关。在我过去的认知里,排查一个网络问题,流程几乎是条件反射式的:先 ping 一下。ping 不通,那就是网络的事;ping 通了,我心里就"咔哒"一声,给"网络"这一项画上一个大大的对勾,然后心安理得地掉头,去服务的代码里、配置里找原因。ping 通,在我心里几乎就等于"网络这块,放心,没问题"。正是这个根深蒂固的等号,让我这次在错误的方向上消耗了大量时间——ping 通了,我便深信不疑地认定问题在服务本身,于是对着服务的配置翻来覆去,而真正的两个病根,自始至终都安安静静地待在我已经画了对勾的那个"网络"里。复盘到根上,我才真正想清楚一件事:"A 连上 B 的服务"这句话,听起来像一个动作,可它实际上是一长串前后相扣的条件,缺一不可。最底下,是物理链路和网卡要通;往上一层,是 IP 包能在 A 和 B 之间被正确地路由、往返;再往上,是 B 上那个具体的端口,要能完成 TCP 的握手;最顶上,才轮到那个端口背后,真有一个健康的进程在处理请求。这是层层叠叠的一摞条件。而 ping 这个工具,它走的是 ICMP 协议,这个协议的世界里压根就没有"端口"这个概念——它能验证的,从头到尾就只有那么一层:IP 包能不能在两台机器间往返,也就是这摞条件里靠下的那一层。ping 通,它诚实地、且仅仅地告诉了我这一层是好的。可我却把这一层的"好",慷慨地、错误地,推广成了整摞条件的"好"。它上面那几层——端口有没有进程在监听、防火墙这道暗门放不放行、服务到底绑在了哪个地址上——ping 一层都没碰过,它根本没有能力对那几层发表任何意见。而这次的两个真凶,恰恰齐刷刷地藏在 ping 视野之上的那几层里:一个,是 B 的服务把自己绑死在了 127.0.0.1 上,这个地址是机器的"自言自语"频道,只有 B 自己听得见,外面的 A 永远够不着——更阴险的是,我在 B 上用 localhost 自测时它一切正常,这份"正常"反过来又麻痹了我;另一个,是 B 的 firewalld 这道门卫,压根没把 8080 端口列进放行名单,A 递过来的 TCP 握手包,在门口就被悄无声息地丢掉了。这两个真凶,没有一个属于服务的代码逻辑,它们全都属于我那个被 ping 一通就轻率盖章的"网络"。这次从一个"ping 通却连不上"的死结里走出来,我最大的收获,是把脑子里那个浑然一块的"网络"概念,拆成了清清楚楚的一层一层。ping 通是个好消息,但它是一句范围极其有限的好消息,它只为最底下那层背书。真正可靠的排查,是放弃寻找"网络好不好"这个根本不存在的总开关,转而老老实实地从下往上,一层一层地问过去:IP 层通吗?端口层通吗?应用层通吗?在哪一层第一次得到"不通"的回答,真正的问题,就端端正正地坐在那一层里等着你。
—— 别看了 · 2026