1. 从按下回车到页面出现:一次完整的HTTP旅行
很多人面试前会把“浏览器输入URL后发生了什么”背得滚瓜烂熟,可真到自己动手排查网络问题的时候,反而连从哪一步开始查都不知道。原因很简单:背八股和真正理解这条链路是两回事。
我先用一句话概括整件事的脉络——当你在地址栏输入一个网址并按下回车,浏览器要做四件大事:解析URL、查询DNS拿到服务器IP、通过TCP三次握手建立连接、发送HTTP请求并接收响应。页面关闭或连接闲置时,再通过四次挥手优雅地断开连接。
这篇文章就围绕这四件事展开,把每一环的细节、原理、以及实际排查中会用到的知识都掰开揉碎。适合正在准备网络方向面试的同学,也适合后端开发、运维、测试同学把它当作排查问题时的底层参考。
先说整体链路,后面逐段拆解:
输入URL -> URL解析(确定协议、域名、端口、路径) -> DNS解析(域名 -> IP,期间可能经过多个层级缓存) -> TCP三次握手(客户端与服务端建立可靠连接) -> 发送HTTP请求(请求行、请求头、请求体) -> 服务器处理并返回HTTP响应 -> 浏览器解析渲染页面 -> TCP四次挥手(连接关闭)这条链路里的每一步,都有大量可以深挖的细节。下面我从第一步开始,按真实发生的顺序走一遍。
2. URL解析:并不是所有输入都叫网址
在浏览器发出任何网络请求之前,它首先得搞清楚你输入的到底是个什么东西。
2.1 URL的组成部分与浏览器如何拆解
URL(Uniform Resource Locator,统一资源定位符)的标准结构是:
协议://用户名:密码@主机名:端口/路径?查询参数#片段实际使用中,大部分字段是可省略的,比如你输入www.example.com时,完整的URL实际上是http://www.example.com:80/。
拿https://www.example.com:443/products?id=1#reviews举例,浏览器会把它拆成:
| 组成部分 | 值 | 说明 |
|---|---|---|
| 协议(Scheme) | https | 决定使用HTTP还是HTTPS协议,也决定默认端口 |
| 主机名(Host) | www.example.com | 服务器的域名,后续交给DNS解析 |
| 端口(Port) | 443 | HTTPS默认443,HTTP默认80,可省略 |
| 路径(Path) | /products | 服务器上资源的路径 |
| 查询参数(Query) | id=1 | 传给服务器的额外参数,用?开始 |
| 片段(Fragment) | reviews | 页面内的锚点定位,不会发送给服务器 |
这里有一个常被忽略的细节:#后面的片段(Fragment)根本不会出现在HTTP请求中。我在实际抓包调试时见过很多次,开发人员以为改了锚点就会触发新的网络请求,结果抓包发现请求根本没发出去。锚点跳转是浏览器纯本地行为,属于页面内定位。
2.2 非标准输入的兜底处理
如果你输入的不是完整URL,浏览器会做智能补全。比如输入example.com,浏览器自动补成http://example.com/;输入localhost:8080,浏览器把它理解为主机名localhost、端口8080,协议默认http。
还有一个比较冷门但面试偶尔会问到的点:如果输入的内容既不像域名也不像IP,比如hello world,浏览器会默认走搜索引擎,而不是当作网址处理。这背后的逻辑是浏览器的“地址栏即搜索框”设计。
URL解析完成后,浏览器就拿到了三个关键信息:协议类型、服务器域名、端口号。接下来要解决的是:这个域名对应的服务器IP地址是什么?
3. DNS域名解析:互联网的通讯录是怎么查的
DNS(Domain Name System,域名系统)是整个链路中最容易被低估的一环。很多人以为DNS解析就是“把域名换成IP”,但实际上一次看似简单的解析,背后可能经过多次递归查询和迭代查询。
3.1 为什么不能直接用IP访问,非要有域名
IP地址是一串数字,比如93.184.216.34,人类记这种东西很费劲,而且IP地址可能因为服务器迁移、负载均衡调整而变化。域名相当于给IP起了一个好记而且稳定的名字。
更重要的是,一个域名可以对应多个IP。大型网站(比如视频平台、电商平台)会在DNS里配置多条A记录,DNS解析时会根据请求来源、服务器负载等因素返回不同的IP,实现最基本的流量分散。你和其他人访问同一个域名,拿到的IP可能完全不同。这也是为什么排查问题时要先确认自己实际解析到了哪个IP。
3.2 完整的DNS解析链路:从浏览器缓存到根服务器
DNS查询是一个逐级向上、逐级返回的过程。以一个全新域名、完全没有任何缓存的情况为例,完整链路是这样的:
浏览器DNS缓存 -> 操作系统DNS缓存(hosts文件也在这层) -> 本地DNS服务器(通常是路由器或运营商分配的) -> 根域名服务器 -> 顶级域名服务器(.com / .net 等) -> 权威域名服务器每往下一级,负责的“范围”就更具体。类比一下:
- 根域名服务器:13组服务器(实际节点很多),它不关心你的域名具体指向哪个IP,只告诉你“
.com域的服务器在哪”。 - 顶级域名服务器:管
.com、.cn这类后缀,它知道example.com这个域名的权威服务器是谁。 - 权威域名服务器:真正存储域名和IP映射关系的地方,比如你域名商提供的DNS服务器。
每次查询,本地DNS服务器会拿着“我是谁、我要找谁”的问题,一级一级往上问,拿到答案后再逐级返回,同时把结果缓存下来。
3.3 递归查询与迭代查询的区别
这两个概念面试常问。简单说:
- 递归查询:客户端(浏览器/操作系统)只发出一次请求,要求DNS服务器“你必须给我最终答案”。中间的多次查询由DNS服务器代劳。
- 迭代查询:DNS服务器并不直接给最终答案,而是返回“我不知道,但你可以去问下一级”。
实际场景中,客户端到本地DNS服务器是递归查询,本地DNS服务器到根/顶级/权威服务器是迭代查询。理解了这个,就能解释为什么你 ping 一个域名第一次很慢、后面就快了——本地DNS服务器有缓存了。
3.4 常用DNS记录类型(不只是A记录)
| 记录类型 | 作用 | 使用场景 |
|---|---|---|
| A | 域名指向IPv4地址 | 最常见,example.com -> 93.184.216.34 |
| AAAA | 域名指向IPv6地址 | IPv6环境 |
| CNAME | 域名别名 | www.example.com -> example.com |
| MX | 邮件服务器 | 配置企业邮箱必用 |
| NS | 指定域名服务器 | 域名解析的授权配置 |
| TXT | 任意文本记录 | 域名验证、SPF/DKIM邮件认证 |
| PTR | 反向解析,IP指向域名 | 反垃圾邮件等场景 |
3.5 实际排查DNS问题时用什么命令
工程中排查询题,最常用的三个工具(三选一即可):
# Windows / Linux / macOS 通用 nslookup example.com # 更推荐,输出更详细,能看到查询耗时和具体记录 dig example.com # 指定DNS服务器查询(比如用公共DNS) dig @8.8.8.8 example.com我之前排查过一个网站间歇性打不开的案例,最后定位到是本地运营商DNS缓存了旧IP,而那个IP对应的服务器已经下线了。dig @8.8.8.8 example.com解析出的是新IP,但本地解析出来的是旧IP,对比两边的结果,问题立刻清晰了。
判断为什么你的DNS解析这么慢,用dig的time字段就能看到每一级的查询耗时长在哪一级。
DNS解析拿到IP后,下一步就是建立连接。这里就进入了 TCP 的主场。
4. TCP三次握手:为什么一定是三次,而不是两次或四次
TCP是面向连接的可靠传输协议。“三次握手”是建立连接时双方交换同步信息的过程,目的是让通信双方确认彼此的收发能力都正常。
4.1 三次握手的完整过程
客户端 服务端 | SYN=1, seq=x | |--------------------------------------->| 第一次握手:客户端发送SYN | | | SYN=1, ACK=1, seq=y, ack=x+1 | |<---------------------------------------| 第二次握手:服务端回复SYN+ACK | | | ACK=1, seq=x+1, ack=y+1 | |--------------------------------------->| 第三次握手:客户端发送ACK逐步拆解:
- 第一次握手:客户端发送一个
SYN=1的TCP报文,进入SYN_SENT状态。客户端会随机生成一个序列号seq=x,这个序列号是后续数据传递的起点。 - 第二次握手:服务端收到后,如果同意建立连接,就回复
SYN=1, ACK=1的报文,同时把自己的初始序列号seq=y带上,并确认客户端的序列号ack=x+1。服务端进入SYN_RCVD状态。 - 第三次握手:客户端收到服务端的SYN+ACK后,回复一个
ACK=1的报文,确认收到服务端的序列号ack=y+1。此时客户端进入ESTABLISHED状态,服务端收到后也进入ESTABLISHED状态。
4.2 为什么必须是三次,两次行不行
这是面试必问题。要理解这个问题,得先想明白TCP要确认什么。
TCP建立连接的最终目的,是确保双方的收发能力都正常,并且同步好彼此的初始序列号。
- 第一次握手之后,服务端知道了“客户端能发”。
- 第二次握手之后,客户端知道了“服务端能收,而且服务端也能发”。
- 第三次握手之后,服务端知道了“客户端能收”。
也就是说,三次握手完成后,双方都确认了自己的发送没问题、对方的接收没问题。如果只握手两次,服务端无法确认客户端是否收到了自己发出的SYN报文。万一客户端没收到呢?服务端会傻傻地认为连接已建立,开始等待客户端的数据,但客户端可能已经因为超时放弃这次连接了,造成资源浪费。
还有一个更经典的论证角度:如果只有两次握手,历史失效连接会引发问题。
举个例子:客户端发送了一个SYN报文,但因为网络拥堵迟迟没到,客户端超时重传了新的SYN。结果旧的SYN先到了服务端,服务端回复SYN+ACK。如果只有两次握手,服务端此时就认为连接建立了。但客户端知道这个旧SYN不是自己当前想发起的连接,会给服务端回一个RST来销毁这个“僵尸连接”。但注意,这是有第三次握手的情况下才能做到的——客户端通过第三次握手携带的序号或标志位来告知服务端“这个连接作废”。两次握手没有这个机会,服务端只能白白挂着这个无效连接。
4.3 握手过程中的序列号与确认号
理解TCP,核心之一就是理解序列号(seq)和确认号(ack)。很多初学者在这两个字段上栽跟头。
- seq(序列号):本报文段第一个字节的编号。TCP是字节流协议,每个字节都有编号,
seq标记了数据流的起始位置。 - ack(确认号):期望收到对方下一个字节的编号。收到
ack=x+1,意味着“你发的第x字节我已经收到了,下一个你应该发x+1”。
三次握手里交换的seq和ack不是拍脑袋定的,而是为后续的可靠传输打基础。比如客户端发送seq=x后,下一次发数据时seq=x+1,服务端通过ack就能知道前面有没有丢包。
4.4 握手阶段常见的异常:SYN超时与SYN洪泛
实际运维中,握手阶段最容易遇到两类问题。
第一类:SYN超时。如果服务端发了SYN+ACK但一直没收到客户端的ACK,这个半连接会停留在SYN_RCVD状态。服务端会不断重传SYN+ACK(默认重传5次),如果始终没有回应,最终放弃并释放资源。这个机制是为了防止客户端掉线导致服务端无限期等待。Linux中可以通过内核参数调整重传行为:
# 查看当前SYN重传次数 sysctl net.ipv4.tcp_synack_retries # 查看SYN重传次数 sysctl net.ipv4.tcp_syn_retries第二类:SYN洪泛攻击。攻击者发送大量SYN报文,但不回复第三次握手的ACK,让服务端堆积大量半连接,耗尽资源。防御手段有SYN Cookie(不分配资源,通过特殊序列号计算来校验)、限制半连接队列长度、缩短SYN超时时间等。
我排查过一个线上故障:某服务在某个时间点开始大量报connect timeout,服务端ss -t state syn-recv查看发现堆积了上万个半连接。当时第一反应就是SYN洪泛,后来确认是某个内部系统出bug疯狂发起连接又不关,最后在应用层加了连接池和熔断才算解决。
5. TCP四次挥手:为什么断开连接要四次,TIME_WAIT又是怎么回事
三次握手建立连接,四次挥手断开连接。如果说握手是为了“确认彼此能力”,挥手就是为了“干净利落地结束关系,不留后患”。
5.1 四次挥手的完整过程
主动关闭方 被动关闭方 | FIN=1, seq=u | |--------------------------------------->| 第一次挥手:主动方发送FIN | | 主动方进入 FIN_WAIT_1 | ACK=1, seq=v, ack=u+1 | |<---------------------------------------| 第二次挥手:被动方确认收到 | | 被动方进入 CLOSE_WAIT | | 主动方进入 FIN_WAIT_2 | | | FIN=1, seq=w, ack=u+1 | |<---------------------------------------| 第三次挥手:被动方发送FIN | | 被动方进入 LAST_ACK | ACK=1, seq=u+1, ack=w+1 | |--------------------------------------->| 第四次挥手:主动方最终确认 | | 主动方进入 TIME_WAIT每一步的细节:
- 第一次挥手:主动关闭方(比如客户端)发出
FIN=1的报文,表示“我的数据发完了,准备关闭连接”。主动方进入FIN_WAIT_1状态。 - 第二次挥手:被动方收到FIN后,回复
ACK,表示“我收到你的关闭请求了”。此时被动方进入CLOSE_WAIT状态。注意,此时被动方并不一定立刻关闭连接——它可能还有数据要发给主动方,等发送完才会进入下一步。主动方收到这个ACK后进入FIN_WAIT_2状态。 - 第三次挥手:被动方把剩余数据发完后,发出
FIN=1报文,表示“我的数据也发完了,可以关闭了”。被动方进入LAST_ACK状态。 - 第四次挥手:主动方收到FIN后回复ACK,然后进入
TIME_WAIT状态,等待2MSL后才彻底关闭。
5.2 为什么挥手需要四次,而握手只要三次
这个问题的核心在于TCP是全双工通信,两个方向的关闭各自独立。
握手时,SYN和ACK可以合并在同一个报文里,因为建立连接时双方都还没有数据要传,可以一步到位。
挥手时,被动方收到FIN后,可能还有数据没发完。所以它无法把ACK和FIN合并成一次发送。必须先回复ACK表示“我收到了你的关闭请求”,等自己的数据发送完毕,再单独发FIN。这就导致挥手至少需要四步。
换个角度理解:断开连接相当于两个方向的“结束”需要分别确认。主动方停止发送,需要被动方确认一次;被动方停止发送,又需要主动方确认一次。每次确认都是一来一回,加起来就是四次。
5.3 TIME_WAIT:主动关闭方为什么要在关闭前等2MSL
这是挥手阶段最值得深挖的知识点,也是面试区分度最高的地方。
主动关闭方在发送完最后一次ACK后,会进入TIME_WAIT状态,等待2MSL(Maximum Segment Lifetime,报文最大生存时间)后才真正关闭连接。MSL通常是30秒或2分钟,所以2MSL通常是60秒到4分钟不等。
为什么非要等这么久?两个核心原因:
第一,确保最后的ACK能到达对方。这个ACK可能会在网络中丢失。如果丢了,被动方会一直处于LAST_ACK状态,不断重发FIN。主动方在TIME_WAIT期间如果再次收到FIN,就知道自己上次的ACK丢了,需要重发。如果没有TIME_WAIT,主动方直接关闭,被动方重发的FIN就没人理了,被动方只能反复超时,最终无法正常关闭。
第二,让旧连接产生的报文在网络中自然消亡。假设没有TIME_WAIT,主动方关闭连接后马上用同一个四元组(源IP、源端口、目标IP、目标端口)新建一个连接。网络中还残留着旧连接的延迟报文,新连接可能会误收到这些“旧数据”,造成数据混乱。等待2MSL,可以确保所有旧报文都在网络中消失,不会污染新连接。
5.4 大量TIME_WAIT和CLOSE_WAIT意味着什么
线上排查时,这两个状态出现的频率极高。
大量 TIME_WAIT:通常出现在短连接场景,比如高并发的HTTP请求。主动关闭方在关闭连接后会等待2MSL,如果并发高、连接多,TIME_WAIT状态的socket就会堆积。对服务端来说,这意味着大量端口和内存被占用。缓解手段是开启连接复用(tcp_tw_reuse),或者优化应用层使用长连接。
# 查看当前系统TIME_WAIT数量 ss -tan state time-wait | wc -l # 查看连接复用参数(推荐了解即可,不建议生产随意改) sysctl net.ipv4.tcp_tw_reuse大量 CLOSE_WAIT:这个更要警惕。CLOSE_WAIT表示被动关闭方收到了FIN、回复了ACK,但应用层迟迟没有调用close()。也就是说,连接的另一端“想走了”,但你这边应用程序没释放连接。这种情况通常说明代码里有连接没关干净,比如HTTP客户端、数据库连接池用完没释放。CLOSE_WAIT堆积往往意味着文件描述符耗尽,最终导致“too many open files”或者“Cannot assign requested address”。
我曾经排查过一个内存泄漏的Java服务,症状就是CLOSE_WAIT不断增长。最后定位到是一个HTTP调用框架在异常分支没有归还连接,连接池被慢慢掏空,表现就是CLOSE_WAIT堆积、可用端口耗尽、线上间歇性不可用。修完那一行漏掉的close,指标立刻恢复正常。
6. TCP与HTTP的协同:连接建立后数据是怎么流动的
三次握手完成,连接建立,HTTP请求就可以开始发送了。握手和挥手是TCP层的“骨架”,HTTP请求才是真正承载业务内容的“血肉”。
6.1 HTTP请求的完整构成
一个标准的HTTP请求由三部分组成:
请求行:GET /products?id=1 HTTP/1.1 请求头: Host: www.example.com User-Agent: Mozilla/5.0 ... Accept: text/html Connection: keep-alive (空行) 请求体:(GET请求通常没有请求体)以GET https://www.example.com/products?id=1为例,浏览器实际发出的HTTP请求类似这样:
GET /products?id=1 HTTP/1.1 Host: www.example.com Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9注意这里有三点:
Host头是HTTP/1.1必带的,因为一台服务器(同一个IP)可能托管多个域名,服务器靠Host区分你访问的是哪个站点。Connection: keep-alive是HTTP/1.1的默认行为,意思是“TCP连接先不关,继续复用”。这跟前面讲的“TCP四次挥手”紧密相关——如果每个HTTP请求都走一次完整挥手,页面性能会差到没法用。Accept-Encoding: gzip, deflate, br告诉服务器客户端支持哪些压缩算法,服务器可以返回压缩后的内容以节省带宽。
网络上看到一个很常见的报错unexpected status 502 bad gateway: unknown error, url: http://127.0.0.1:1572,本质就是这个请求到达服务器后,服务器作为网关/代理向上游转发时没有得到有效响应。这时候排查思路是先确认上游服务是否存活、端口是否监听、上游处理是否超时。
6.2 TCP如何保证数据可靠传输
HTTP请求交给TCP后,TCP要保证这份数据完整、有序地到达对端。它靠的是三件事:
第一,数据分段与编号。TCP把应用层传来的大块数据切成一个个适合网络传输的“段”(Segment),每个段编上序列号。接收方按序列号重组数据,即使乱序也能恢复。
第二,确认与重传。接收方每收到一段数据,会返回ACK确认。发送方如果一段时间内没收到ACK,就认为数据丢了,会重新发送。这个超时重传时间(RTO)是动态调整的,Linux内核会根据网络延迟情况自动计算。
第三,流量控制与拥塞控制。流量控制通过滑动窗口实现,接收方告诉发送方“我的接收缓冲区还能收多少字节”,发送方据此调整发送速度,避免接收方来不及处理导致丢包。拥塞控制则是发送方根据网络状况调节发送速率,Linux默认使用CUBIC算法,核心是慢启动、拥塞避免、快重传、快恢复。
6.3 HTTPS:在TCP之上加一层加密
现在大部分网站已经是https://开头了,也就是HTTP + TLS/SSL。TLS握手发生在TCP三次握手之后、HTTP请求发送之前。
流程大致是:
- 客户端发送ClientHello,包含支持的TLS版本和加密套件。
- 服务端回复ServerHello,确定使用哪个加密套件,并发送证书。
- 客户端验证证书,生成会话密钥,通过公钥加密后发给服务端。
- 双方确认密钥后,开始加密通信。
这里有一个很常见的坑:TLS握手失败和TCP握手失败是两回事。TCP握手失败表现为连接建立不了、connect超时;TLS握手失败则表现为连接建立了但报证书错误或协议错误。抓包时看到TCP三次握手成功但没有HTTP请求,就要往TLS层想。
6.4 HTTP响应与状态码的实际意义
服务器收到请求后返回HTTP响应,状态码是最直观的排查线索:
| 状态码 | 含义 | 常见场景 |
|---|---|---|
| 2xx | 成功 | 200 OK,请求正常处理 |
| 3xx | 重定向 | 301永久跳转、302临时跳转、304缓存命中 |
| 4xx | 客户端错误 | 404资源不存在、403无权限、429请求过多 |
| 5xx | 服务器错误 | 500内部错误、502网关错误、503服务不可用、504网关超时 |
304是个容易被忽略但实际很给力的状态码。浏览器发送请求时会带上If-Modified-Since或If-None-Match头,服务器比对后发现资源没变,就返回304,不带响应体,浏览器用本地缓存。这也是为什么你刷新一个没变化的页面能秒开的原因之一。
502/504是反向代理场景最常见的错误。502 Bad Gateway 意味着网关或代理服务器收到了无效响应,通常上游服务挂了或者响应不合法;504 Gateway Timeout 则是上游在限定时间内没处理完。排查思路是先绕过网关直连上游,确认上游服务状态,再逐层看日志。
7. 从浏览器地址栏到数据上屏:一次完整链路的总装回顾
把前面几节的内容串成一条完整的线,你现在应该能顺着一条请求,把它从浏览器地址栏一路讲到数据上屏了。
7.1 用一张表格复盘全流程
| 阶段 | 关键动作 | 涉及协议/机制 | 核心状态/字段 |
|---|---|---|---|
| 1. URL解析 | 解析协议、域名、端口、路径 | HTTP/HTTPS | Scheme, Host, Port |
| 2. DNS解析 | 域名 -> IP,多层缓存/逐级查询 | DNS | A记录, CNAME, TTL |
| 3. TCP连接 | 三次握手建立可靠的传输通道 | TCP | SYN, SYN+ACK, ACK |
| 4. TLS握手(HTTPS) | 验证身份、协商加密密钥 | TLS/SSL | 证书、加密套件 |
| 5. HTTP请求 | 发送请求行、请求头、请求体 | HTTP | Method, URL, Headers |
| 6. 服务器处理 | 路由匹配、业务逻辑、返回响应 | 应用层框架 | 状态码、响应体 |
| 7. HTTP响应 | 浏览器接收响应,解析渲染 | HTTP | 状态码、Content-Type |
| 8. TCP断开 | 数据传输完毕,四次挥手释放连接 | TCP | FIN, ACK, TIME_WAIT |
7.2 浏览器收到响应后发生了什么
很多人讲这条链路,讲到“浏览器收到响应”就停了。但浏览器拿到HTML之后的工作,才决定你屏幕上最终看到什么。
- 解析HTML构建DOM树。
- 解析CSS构建CSSOM树。
- DOM树和CSSOM树合并成渲染树。
- 解析过程中遇到
<script>标签,会暂停HTML解析,先下载并执行JavaScript(这是页面渲染性能的关键点,异步加载可以优化)。 - 布局(Layout):计算每个元素的几何位置。
- 绘制(Paint):把内容绘制到屏幕上。
但这里有个经常被忽略的细节:浏览器在解析HTML时,如果遇到外部资源(图片、CSS、JS、字体等),会并行发起新的HTTP请求。这些请求同样要走DNS解析(如果有独立域名的资源)、TCP握手、HTTP请求流程。这也是为什么一个页面在Network面板里能看到几十上百个请求——每个请求都是我们前面讲的完整流程。
7.3 一次真实抓包,把链路对照起来
我平时排查问题最喜欢用curl -v和 Wireshark。拿curl -v https://www.example.com为例,输出中能看到这条链路的完整痕迹:
$ curl -v https://www.example.com * Trying 93.184.216.34:443... * Connected to www.example.com (93.184.216.34) port 443 * SSL connection using TLSv1.3 > GET / HTTP/1.1 > Host: www.example.com > User-Agent: curl/8.0.1 > Accept: */* > < HTTP/1.1 200 OK < Content-Type: text/html每一行对应一个阶段:
Trying 93.184.216.34:443说明DNS解析完成,拿到了IP,开始尝试TCP连接。Connected to ...说明TCP三次握手成功。SSL connection using TLSv1.3说明TLS握手完成。> GET / HTTP/1.1后面的内容是发出的HTTP请求。< HTTP/1.1 200 OK是服务器返回的响应。
字段url: http://127.0.0.1:1572这一类报错,其实也可以用同样的思路排查:先确认这个IP和端口对应的服务是否在本地启动了?监听地址是否是127.0.0.1而不是公网地址?应用层端口是否被防火墙拦截?这些都是TCP连接建立失败导致请求无法完成的常见原因。
8. 排错实践:内网环境URL访问失败的一次完整排查
章节讲了这么多原理,最后用一个真实案例把这些知识串起来。前阵子同事反馈,内网某个系统访问http://192.168.1.88:8080/login一直报连接超时,我按照本文这条链路的顺序逐步排查。
第一步:确认URL解析。因为是IP直连,不涉及DNS解析,URL解析也没有问题。如果访问的是域名,我会先用dig确认解析结果。
第二步:确认TCP连通性。用telnet或nc测试端口:
telnet 192.168.1.88 8080结果不通,问题定位在TCP层。然后用ping测试主机是否在线,通;再查服务端端口是否监听:
ss -tlnp | grep 8080发现8080端口压根没有进程监听。到这里已经明确:不是网络不通,而是服务没起来。
第三步:确认服务状态。登录服务器查看进程和日志,发现后端服务因为磁盘满了导致启动失败。
整个排查过程不到十分钟,核心就是用curl -v看卡在哪一步,然后顺着链路往下走。大多数网络问题,都能在这条链路里找到对应的环节。如果你已经知道DNS解析过程、TCP握手原理、HTTP请求的构成,排查问题的思路就非常清晰:先看是哪一层出的问题,再对症下药。
如果换成我之前遇到过的502 bad gateway问题,排查的“主战场”就从TCP层转移到了HTTP层。连接是通的、请求也发到了网关,但网关转发给上游时没有得到合法响应。这时候继续顺着链路由“网关”这一环往下游查——上游服务有没有监听?Tomcat/Nginx的日志里有没有连接被拒绝的记录?上游处理耗时是否超过网关的超时阈值?链路就没断过。
网络问题排查的经验,说白了就是一个字:分层。每一层都把自己那一关守好,问题就能被快速夹逼到出事的那一层。这也是为什么我一直建议,不管你是不是网络工程师,都值得把从URL输入到页面返回这条链路的每一环彻底吃透。它不仅是面试题,更是所有网络排查工作的底层地图。