1. 先搞清楚四种访问方式本质上是四份不同的“寻址清单”1.1 一个让我印象深刻的排障案例前阵子系统测试环境出了个怪问题把我拉去一起看了半天服务明明已经在 8080 端口起来了用curl http://127.0.0.1:8080访问完全正常浏览器打开也是欢迎页但同一个小伙伴在另一台设备上用curl http://localhost:8080却直接Connection refused再换成服务器的公网 IP 访问又是超时页面根本打不开。当时项目里几个人第一反应都是“服务没起来吧”但ps一看进程活着ss -lnt也显示端口在监听。后来折腾了一圈才发现这三个访问方式背后走的根本不是同一条数据链路localhost被解析成了 IPv6 的::1而服务只监听了 IPv4 的127.0.0.1所以本地访问直接失败公网 IP 那个则纯粹是安全组策略没放行。类似的事我在不同项目里遇到过不止一次。HTTP 请求数据包从客户端到服务端表面上看就是“连一下、发个请求、收个响应”但你只要把localhost、127.0.0.1、公网 IP、公网域名这四种访问形式摊开来对比就会发现每个词背后对应的是不同的地址解析路径、不同的路由决策、不同的 NAT 处理逻辑甚至抓包时能看到的数据包轨迹都不一样。这篇就把这条链路完整拆开讲一遍。1.2 数据包流转前你必须先理解“封装”和“路由决策”这两件事在进入四种访问方式之前需要先建立两个底层概念否则后面讲路径的时候很容易晕。第一个是封装。一个 HTTP 请求本质上是应用层的一串文本。这段文本进入协议栈后会被逐层包起来传输层给它加上 TCP 头包含源端口、目标端口、序列号网络层再加上 IP 头包含源 IP、目标 IP、TTL、协议号到了链路层再根据目标 IP 查 ARP 表得到下一跳的 MAC 地址组装成真正的以太网帧。你可以把整个过程类比成寄快递HTTP 报文是信纸TCP 端口是收件人姓名IP 地址是门牌号MAC 地址是快递员当前手上用来找路的分拣编号。每一层都只关心自己那一层的“地址”这也是为什么网络排障要分层看的根本原因。第二个是路由决策。数据包从本机发出之前内核要拿着目标 IP 去查路由表决定这个包该从哪个网卡出去、下一跳是谁、还是直接投递给本地进程。Linux 的路由表里有一张特殊的local表优先级最高专门用来匹配“目标地址是本机某个网卡地址”的情况。只要目标 IP 是自己身上的地址这个包根本不会走到物理线路上而是直接在协议栈内部被转交给本地进程。把这两个概念记住后面无论是看localhost的回环路径还是看公网 IP 的 NAT 转换都不会再觉得混乱。2. localhost 和 127.0.0.1看似是同一件事其实是两条微妙的路径2.1 localhost 背后的解析顺序hosts、IPv6 优先的坑很多人直接就把localhost当成127.0.0.1来用它们确实都指向“本机”但解析过程不一样而且这个差异坑过无数人。操作系统解析localhost时会先查/etc/hosts文件再去查系统配置的 DNS。在大多数主流 Linux 发行版里默认的/etc/hosts通常写着两行127.0.0.1 localhost ::1 localhost也就是说localhost这个名称同时绑定了 IPv4 回环地址和 IPv6 回环地址。问题就出在这儿现代系统的getaddrinfo()在做地址解析时默认会优先尝试 IPv6 地址。于是当你访问localhost时很多程序实际先去连接::1只有::1连接失败后才回退到 IPv4 的127.0.0.1。如果服务端进程只监听了127.0.0.1:8080那你用curl http://localhost:8080就会看到程序先显示Trying ::1...然后Connection refused紧接着再尝试Trying 127.0.0.1...才成功。我当时排障时用curl -v一眼就看到了这个细节。所以如果你的服务只想被本机访问建议监听地址直接明确写127.0.0.1:8080同时也要清楚localhost和127.0.0.1在客户端视角并不总是等价。2.2 回环接口的真实流转路径从 socket 到协议栈投递无论最终走的是::1还是127.0.0.1只要是回环地址数据包的流动范围都局限在本机内部。拿http://127.0.0.1:8080为例客户端程序发起connect()后传输层构造 TCP SYN 段目标 IP 填127.0.0.1目标端口填8080IP 层拿着这个目标 IP 查路由表发现它在local表里命中了本机回环地址于是内核直接把数据包交给lo回环接口处理。回环接口没有物理介质不需要 ARP 查询不经过交换机也不需要外部链路数据包从协议栈的一个进程出来再从这个回环接口原样送进协议栈最终投递给监听在 8080 端口的目标进程。你可以用tcpdump -i lo抓到真实证据回环接口上能看到源 IP 和目标 IP 都是127.0.0.1的 TCP 包。有个细节值得一提回环包虽然没有真实以太网头但抓包工具还是会以虚拟的链路层头部展示它们不要被这个假象迷惑它并没有真正离开主机。2.3 为什么服务端监听地址必须写对写错就触发“localhost 通、IP 不通”回环通了不代表所有本机访问都能通。服务端在监听时绑定地址决定了谁能连上来。常见写法有三种127.0.0.1:8080只监听回环地址只能在服务器本机访问0.0.0.0:8080监听本机所有 IPv4 地址外部网络也能访问[::]:8080监听本机所有 IPv4/IPv6 地址取决于配置是更通用的通配监听。我在很多项目里见过一种典型误配服务监听了127.0.0.1然后告诉用户“在本机用 localhost 访问没问题”可用户换成服务器公网 IP 立刻超时。原因很简单公网请求要进到进程前提是内核能收到发往公网 IP 的包并把它投递给那个 socket但如果 socket 只挂在回环地址上即使外部包到达了网卡也没有任何监听 socket 与之匹配连接自然被拒。排查这类问题最快的方法就是ss -lnt看输出里Local Address那一列到底是什么地址。如果只看到127.0.0.1:8080那外部访问必然失败这是服务端监听问题如果不是监听问题再往下走看路由和防火墙。3. 公网 IP 访问出口路由、NAT 和“你在访问自己”的三种剧本3.1 剧本一访问远程服务器的一个公网 IP最常见的链路这是最标准的场景你的电脑在国内某个办公网络目标是一台位于云厂商机房的服务器它有一个公网 IP比如203.0.113.10。请求发出时客户端程序先通过 DNS 或者你手写的 IP 直接拿到目标地址然后 TCP 开始三次握手。第一个 SYN 包从应用层一路封到链路层后本机路由表会告诉你目标不在直连网段走默认网关。于是包被封装成以太网帧源 MAC 是你的网卡 MAC目标 MAC 是你路由器内网口的 MAC从网卡发出。包到达路由器后这里大多会发生一次关键转换你的内网设备 IP 一般是私有网段比如192.168.1.100路由器会把源 IP 改写成自己的公网 IP同时记下一条 NAT 映射表项内网 IP 加端口号对应哪个外网目标 IP 加端口。这样包才能在公网上被正确送回。到达云服务器机房后还会经过云平台的安全组和主机侧的防火墙规则只要策略放行TCP SYN 才能进入服务器的内核协议栈最终和监听进程完成握手。值得注意的是这段链路中每一跳的 TTL 都会递减所以traceroute可以看到路径上的路由节点NAT 设备的存在意味着源 IP 已经被改写抓包服务器收到的源地址是路由器的公网 IP而不是你电脑的内网 IP。这就解释了一个常见现象为什么服务端日志里永远看不到客户端的真实内网 IP除非专门让 HTTP 头带上X-Forwarded-For或者在路由器上配置端口转发日志。3.2 剧本二访问本机网卡上绑定的公网 IP很多人会想当然地认为访问“自己的公网 IP”数据包一定是从网卡出去绕一圈再回来。这个直觉得分情况讨论。如果那个公网 IP 真的绑定在这台机器的网卡上比如有些物理机托管直接把公网 IP 配置在 eth0 上那么和数据包访问127.0.0.1的流程几乎一样。内核路由查找时发现目标 IP 属于local表就把这个包直接投递给本机进程压根不会从物理网卡发出去。想验证也很简单ip addr看网卡上有没有显示这个公网 IPip route get 203.0.113.10看内核给的答案是local还是普通的 via 网关。这种情况下用tcpdump -i eth0是抓不到任何包的因为包没离开过协议栈。但云服务器通常不是这个模式。绝大多数云厂商的“公网 IP”其实并没有体现在服务器的网卡配置上而是在云平台底层通过 NAT 方式映射。你curl http://你的弹性公网IP时包会真的从 eth0 发出去进入云的网关设备网关再把目标地址转换成对应的内网 IP转发回这台虚拟机。所以同样是“访问自己的公网 IP”物理机和云主机之间会表现出完全不同的抓包结果。3.3 剧本三访问弹性公网 IP / 端口映射时的 NAT loopback剧本三经常在家用网络和云服务器上同时出现你的网络出口有一个公网 IP内网有一台服务器映射了这个公网 IP 的某个端口你在内网去访问“公网 IP:端口”。普通家用路由器大多默认不支持 NAT loopback也就是内网设备访问自己出口公网 IP 的映射端口时会失败。因为路由器不知道这个包该送回内网哪台机器或者干脆丢弃了。这也是一个很常见的排障剧情外网手机用流量能打开服务家里电脑用同一个公网 IP 反而打不开。云平台上类似但表现形式更隐蔽。判断方法是ip addr看公网 IP 是否在网卡上如果不在说明所有“访问自己公网 IP”的流量都要出网口绕一圈那就要考虑云底层 NAT 网关的处理逻辑。另外在 Linux 内核层面有些系统开启了严格的反向路径过滤rp_filter本来是想防止 IP 欺骗的在某些 NAT 场景下反而可能导致这类回环包被丢弃。遇到说不清的通断问题时sysctl net.ipv4.conf.all.rp_filter值得看一眼。4. 公网域名访问DNS 解析只是前半场后半场才是重头戏4.1 域名解析完整链路域名访问比 IP 访问多出来的是“间接寻址”这一步浏览器并不知道www.example.com的 IP得先问 DNS。完整链路一般是这样的浏览器先查自己的 DNS 缓存没命中就查操作系统缓存再没命中就查/etc/hosts之后才轮到系统配置的 DNS 服务器。本机 DNS 服务器通常是运营商或公共 DNS收到递归查询请求后会从根域名服务器开始一级一级往下查根服务器告诉你.com的权威服务器在哪.com服务器告诉你example.com的权威服务器在哪最后example.com的权威服务器把具体的 A 记录或 AAAA 记录返回回来。整个过程一般走 UDP 53 端口数据量超过阈值时也可能切到 TCP 53。在实际排障中我建议你关注三个点TTLDNS 记录自带生存时间解析结果会被各级缓存层层保留。改了 DNS 记录后旧 IP 可能还存活好几个小时所以“为什么域名还指向老服务器”往往不是没改对而是缓存没到期。本地 hosts 优先级/etc/hosts里的条目会优先于 DNS 解析这也是很多模拟环境改本地域名映射的手段。如果线上域名异常先确认是不是本机 hosts 被改过。解析结果不唯一同一域名在不同地区、不同网络下可能返回完全不同的 IP这是 GeoDNS 做地域调度的结果不是故障。4.2 HTTPS 时代SNI、证书和 Host 头在数据包里的位置拿到域名对应的 IP 之后数据包流转路径和直连公网 IP 并无区别但域名不会就此消失它还会继续影响后续的 HTTP / HTTPS 通信。以 HTTPS 为例TCP 三次握手完成后客户端会发送 TLS ClientHello。这个握手包里的 SNIServer Name Indication字段会把域名明文带过去服务端靠着 SNI 来挑选对应的证书和虚拟主机配置。这也是为什么多个 HTTPS 站点可以共用一个 IP因为 SNI 帮你区分了站点。到应用层HTTP 请求的 Host 头里同样要带上域名Nginx 之类的 Web 服务器再用它做路由匹配。如果你用 IP 直接访问一个只配置了域名的 HTTPS 站点大概率会收到证书错误因为服务端拿不出匹配这个 IP 的证书这就是“域名参与了整个请求生命周期”的最好例子。抓包时TLS ClientHello 里的 SNI 是明文的在开启 ECH 之前所以运维排查时可以从抓包里直接确认客户端真正想要访问的域名。4.3 CDN 和负载均衡如何改变“IP 对应关系”传统的“一个域名解析到一个服务器 IP”已经越来越少见了。大型站点通常在域名层做了多层收敛。常见的是域名先解析出一个 CNAME然后 CNAME 再解析到 CDN 或负载均衡服务的 A 记录。CDN 服务商会根据请求来源 IP 的地理位置、运营商、当前节点健康情况返回一个就近的节点 IP。也就是说同一个域名你在北京和上海拿到的解析结果可能完全不同上午和下午也可能因为节点调度而变化。理解这一点对排查很有用当你觉得某个域名访问变慢不要假设域名始终对应同一个 IP先dig一下当前解析结果再对比不同地区工具查到的结果往往能定位是不是 CDN 节点调度问题。反过来如果某个服务要求客户端必须访问固定 IP就不要让它走域名或者干脆在 hosts 里锁定解析。5. 把四种方式放进一张比较表里排查用起来最顺手5.1 数据包流转路径的横向对比我把这四种访问方式最核心的差异整理成一张表排查时对着看会快很多访问方式是否涉及 DNS 解析关键地址处理数据包实际流动范围物理网卡能否看到该包典型故障类型localhost查 hosts 或 DNS可能优先得到::1无地址转换仅在协议栈内部走回环接口tcpdump -i lo能看eth0看不到IPv6/IPv4 监听不一致127.0.0.1不涉及无地址转换仅在协议栈内部走回环接口tcpdump -i lo能看eth0看不到服务端监听地址绑定过窄公网 IP 访问不涉及远程场景经过 NAT/安全组端到端跨网络设备tcpdump -i eth0能看到防火墙/安全组未放行、NAT loopback 不支持公网域名访问完整 DNS 递归查询DNS 解析 可能 CNAME 调度DNS 阶段走 UDP 53业务阶段和公网 IP 一样能同时看到 DNS 包和业务包DNS 缓存污染、CNAME 解析异常、证书域名不匹配这张表你仔细看就会发现localhost和127.0.0.1真正的区别不在数据包路径而在“名称解析时选哪个地址族”公网 IP 和公网域名的区别则主要在于“先查一次电话簿”后续数据流转本质上没有区别。5.2 实际开发调试的选型建议结合我在项目里的习惯再给一些选型和排查建议本机联调、和外部环境无关时优先用127.0.0.1而不是localhost。前者永远走 IPv4 回环不会踩 IPv6 优先解析的坑。localhost虽然看起来更“语义化”但排查成本往往更高。需要验证服务对外可达性时不要只在本机 curl 自己要从另一台设备、最好是从和目标服务器不同网络段的机器去访问。本机能通说明不了任何问题因为数据包可能根本没出网卡。像云服务器绑弹性公网 IP 的场景我会直接用一台外部跳板机curl http://公网IP:端口同时登录目标服务器执行tcpdump -i eth0 port 8080看包有没有真正到达网卡。能用 IP 排查连通性就不要一开始用域名。域名接入 DNS 之后一旦解析出问题你很难分清是 DNS 的锅、路由的锅还是后端服务的锅。正确姿势是先ping 公网IP或者curl http://公网IP:端口确认链路通再切到域名访问如果 IP 通而域名不通问题基本锁定在 DNS 解析或域名调度上。6. 用 tcpdump 亲手验证一遍两条抓包命令、两份输出解读6.1 案例一抓 localhost 回环包理论说再多不如自己抓一次包。先开一个最简单的 HTTP 服务比如用python3 -m http.server 8080起在 8080 端口然后在另一个终端执行sudo tcpdump -i lo -nn port 8080再执行一次访问curl http://127.0.0.1:8080你会看到类似下面的输出16:22:33.123456 IP 127.0.0.1.53124 127.0.0.1.8080: Flags [S], seq 123456789, win 43690, length 0 16:22:33.123457 IP 127.0.0.1.8080 127.0.0.1.53124: Flags [S.], seq 987654321, ack 123456790, win 43690, length 0 16:22:33.123458 IP 127.0.0.1.53124 127.0.0.1.8080: Flags [.], ack 987654322, win 43700, length 0 16:22:33.123459 IP 127.0.0.1.53124 127.0.0.1.8080: Flags [P.], seq 123456790:123457200, ack 987654322, win 43700, length 410这就是 TCP 三次握手加 HTTP 请求数据的完整过程。注意源 IP 和目标 IP 都是127.0.0.1而且接口是lo这说明数据包全程没有离开主机协议栈。抓包端口是 8080对应的是传输层端口TCP 用端口来区分进程所以应用层里表面上只是一个访问底层却已经完成了连接建立、数据发送、连接释放等一系列动作。有个细节如果你在eth0上抓同一个访问多半什么都抓不到。这不是 tcpdump 出问题而是这个包真的没有从物理网卡经过。明白这一点你就不会再犯“在公网网卡上抓本机回环包然后怀疑服务器出了问题”的低级错误。6.2 案例二抓公网域名访问的全过程抓公网访问要换成物理网卡。假设网卡名是eth0访问http://example.com先不要开业务请求单独抓 DNS 包sudo tcpdump -i eth0 -nn udp port 53这时用另一个终端执行dig short example.com就能看到 DNS 查询和响应的数据包里面包含目标 DNS 服务器 IP、问题记录里的域名、回答记录里的 A 地址。这一步验证的就是“域名先翻译成 IP”的过程。然后重新抓 TCP 流量sudo tcpdump -i eth0 -nn host 203.0.113.10 and tcp port 80访问http://example.com就能看到源 IP 是你当前网络的出口 IP、目标 IP 是203.0.113.10的三次握手包和 HTTP 请求包。如果请求的是 HTTPS端口改成 443同时在抓包参数里加上-A你还能看到 TLS ClientHello 里的 SNI 字段明文显示了客户端实际访问的域名。我自己抓过几轮之后最有感触的一点是“连通”并不等于“路径正确”。同样的目标用127.0.0.1通、用localhost通、用公网 IP 通背后的路径完全不同。排障时与其靠猜不如直接抓包看接口、看源地址、看目标地址、看 TTL数据包走到哪一步断开问题就定位到哪一层。这也是我一直建议团队里每个开发同学都掌握一点 tcpdump 基本功的原因——它能帮你把网络黑盒变成透明管道四种访问方式的流转路径看一次就再也不会混了。
阅读完成 · 觉得有帮助?