「网络挂了」不是诊断结论。一次连接要走过域名解析、路由、目标 port、TCP、通常还有 TLS,最后才是应用层协议。按这个顺序一个个测,笼统的「故障」就会收敛成某一步的失败。
🎙️ 发布并录制于: ·
你的应用把字节写进一个 socket。TCP 把这条字节流切成 segment,负责顺序,也负责重传丢掉的数据。IP 把这些 segment 装进 packet,让每个 packet 朝着目标地址走。以太网或者 Wi-Fi 在当前这一跳上搬运帧。路由器只关心 IP,它看不懂你的 JSON 错误。
HTTP request bytes
↓ application
TLS records # when HTTPS is used
↓
TCP segments # ordered, reliable byte stream
↓
IP packets # source and destination addresses
↓
Ethernet / Wi-Fi frames # one local hop at a time
DNS 把一个主机名翻译成一个或多个 IP 地址。IPv4 长得像四个十进制数字;IPv6 是十六进制,空间大得多。解析完成之后,IP 路由再挑一条通往这个地址的路。同一个主机名可能因为地区、网络或时间返回不同的地址,所以要记下出问题的那个客户端实际拿到的地址。
# Resolve without making a TCP connection
nslookup api.example.com
# or
dig api.example.com A +short
dig api.example.com AAAA +short
# Inspect the route, not the application
tracert api.example.com # Windows
traceroute api.example.com # Linux/macOS
别把生产环境的 IP 直接敲进一个 HTTPS URL,然后管它叫干净的 DNS 测试。TLS 选证书和 HTTP 的虚拟主机都需要主机名。用 curl --resolve 钉住一个地址,同时保留真实的 host 名。
IP 地址把流量送到一台机器或者一个网络接口。port 把它送到某个服务。一个监听中的 socket 绑在本地的某个地址和 port 上。一条已连接的 TCP socket 是由源地址、源 port、目标地址、目标 port 四元组标识的,所以几千个客户端可以同时跟同一个服务端 port 说话。
client 192.0.2.44:53182 → 203.0.113.10:443 server
temporary port HTTPS listener
# See listeners
ss -lntp # Linux
netstat -ano | findstr LISTEN # Windows
lsof -nP -iTCP -sTCP:LISTEN # macOS/Linux
Error: listen EADDRINUSE: address already in use 0.0.0.0:3000。第一步:用 ss、lsof 或者 netstat -ano 看一下三千这个 port。第二步:把 PID 对到具体进程上。第三步:优雅地停掉那个残留实例,或者换一个 port。第四步:重启,然后确认只有一个预期中的监听者。把所有叫 Node 的进程全杀掉,只会盖住进程管理上的 bug,还可能顺手停掉无关的应用。应用字节流动之前,客户端先发 SYN,服务端回 SYN-ACK,客户端再回 ACK。这一来一回协商了初始序列号,也确认了两个方向都有通路。之后 TCP 给你的是一条有序的字节流。它没有「消息」这个概念:两次写可能被一次读全拿到,一次写也可能要读好几次。
client server
| -------- SYN, seq=x --------------> |
| <----- SYN-ACK, seq=y, ack=x+1 ---- |
| -------- ACK, ack=y+1 ------------> |
| ===== application byte stream ===== |
| -------- FIN ---------------------> | # orderly close
TCP 的可靠性不等于无限的耐心。重传发生在你的应用底下,但应用自己仍然需要连接超时、读超时和整体截止时间。一次成功的 handshake 只能证明:那个地址和 port 上有东西接了 TCP。它证明不了 TLS、鉴权,或者你请求的那条 HTTP 路由是好的。
Refused 是一个很快的回答:目标主机,或者某条主动拒绝规则,明确说了没人接这个连接。Timeout 是沉默:packet 被丢了、被路到了没用的地方,或者回包回不来。把两者都当成「服务挂了」,等于浪费了网络已经交给你的信息。
Error: connect ECONNREFUSED 127.0.0.1:5432
Error: connect ETIMEDOUT 203.0.113.10:443
# Test only TCP reachability to the destination port
Test-NetConnection api.example.com -Port 443 # PowerShell
nc -vz api.example.com 443 # Linux/macOS
curl -v --connect-timeout 5 https://api.example.com/
127.0.0.1。第二步:到服务端确认那个服务真的在跑。第三步:看一下监听者绑的地址和 port。第四步:先在服务端本机测,再从客户端测。如果本机通、远程被拒,就去查 bind 地址、容器端口发布,以及主动拒绝规则。127.0.0.1 和 ::1 是回环地址。流量永远不会离开那个网络命名空间。绑在回环上的服务只接受本地客户端。绑到 0.0.0.0 的意思是「在所有 IPv4 接口上监听」,它不是客户端该去连的目标地址。在容器里,localhost 指的是这个容器,不是宿主机,也不是另一个容器。
# Reachable only inside the same host or container
server.listen(3000, "127.0.0.1")
# Reachable on the container's interfaces; publish separately
server.listen(3000, "0.0.0.0")
docker run -p 8080:3000 my-app
# Verify the actual bind
ss -lntp | grep :3000
LISTEN 0 511 127.0.0.1:3000 ...
curl http://127.0.0.1:3000。第二步:看 ss -lnt;如果显示的是 127.0.0.1:3000,就把应用的 bind 改成 0.0.0.0。第三步:确认容器把 port 发布出来了,比如宿主机八千零八十映射到容器三千。第四步:测宿主机上的那个 port。第五步:到这一步再去看宿主机或云上的 firewall。把 port 暴露出去,修不了一个绑在容器回环上的应用。私有 IPv4 段可以在不同网络里重复使用,公网上不会路由它们。出向连接经过 NAT 网关时,私有的源地址和 port 会被改写成一个公网地址,回包再按映射还原回来。没有映射的入向流量进不来,除非你加了端口转发或者一个负载均衡器。
10.0.0.24:53182 → NAT → 198.51.100.7:62014 → internet
private host public mapping
Private IPv4:
10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
# Public-looking address from outside
curl https://ifconfig.example # use a trusted endpoint you control
NAT 不是安全策略。有状态的映射恰好挡住了大多数未经请求的入向流量,但授权该由 firewall 和应用来做。另外记住运营商级 NAT:可能有两层路由器在做转换,家里想做端口转发就不可能,除非运营商支持、改用 IPv6、打隧道,或者走中继。
一条 firewall 规则通常会看协议、源、目标、port、方向、接口和连接状态。云上的网络在主机 firewall 之前还有安全组和网络 ACL。Kubernetes 又加了 Service 和 NetworkPolicy。如果另一层照样丢掉同一个 packet,那某个面板上的一条绿规则说明不了什么。
# Minimum question, stated precisely
Allow TCP from 198.51.100.0/24
to 10.0.2.15 destination port 443
with return traffic for established connections
# Linux host checks vary by system
sudo nft list ruleset
sudo ufw status verbose
# Windows
Get-NetFirewallProfile
Get-NetFirewallRule -Enabled True
一次 HTTPS 请求是一叠依赖。DNS 返回一个 IP。TCP 连上一个 port。TLS 验证主机名并建立加密。HTTP 发出方法、路径、请求头和 body。就按这个顺序测。我的态度很明确:ping 是很弱的可用性测试。很多健康的服务器会屏蔽 ICMP,而一台能回 ping 的机器,应用可能根本没起来。
# 1. DNS
nslookup api.example.com
# 2. TCP port
Test-NetConnection api.example.com -Port 443
# 3 and 4. TLS plus HTTP, with verbose evidence
curl -v --connect-timeout 5 https://api.example.com/health
# Bypass DNS while preserving hostname for TLS and HTTP
curl -v --resolve api.example.com:443:203.0.113.10 \
https://api.example.com/health
# Listener ownership
ss -lntp | grep :443
netstat -ano | findstr :443
lsof -nP -iTCP:443 -sTCP:LISTEN
Could not resolve host 是 DNS。Failed to connect 是 TCP。certificate verify failed 是 TLS 的身份或信任问题。一个 HTTP 404 说明 DNS、TCP,通常还有 TLS 都通了;这时候去看 Host 请求头和路径。证书出错,永远不要拿 -k 当生产环境的修法。在真正出问题的那台机器或者那个容器里跑测试。从你笔记本上请求成功,证明的是你笔记本的路径,不是生产 worker 的路径。
1. Record exact HOST:PORT and failing client location
2. Resolve A and AAAA; note the returned addresses
3. Test TCP to that exact port with a short deadline
4. On server: verify process + bound address + listener
5. Check container/service port publication
6. Check route, NAT, cloud rules, and host firewall
7. Test TLS with the real hostname
8. Send the smallest HTTP request; read status and headers
ECONNREFUSED → host answered; no listener or active reject
ETIMEDOUT → dropped path, wrong route, or missing return path
EADDRINUSE → another socket already owns address and port
404 → network stack worked; inspect host/path/routing
TLS error → TCP worked; inspect name, chain, date, trust
localhost → this network namespace only
0.0.0.0 → listen on all IPv4 interfaces; not a client address
ping works → only ICMP replied
ping fails → server may still be healthy
Best evidence: timestamp, client, resolved IP, HOST:PORT,
listener output, curl -v trace, and firewall packet counters.
值得留住的规则很简单:改任何东西之前,先说出是哪一层失败了。解析名字,摸到地址,连上 port,验证 TLS,然后再说 HTTP。当每次测试只问一个问题、下一步动作由那个确切答案决定时,「网络」这件事就变得可控了。