从输入 URL 到页面展示:一次完整的网络请求全流程解析

“在浏览器地址栏输入一个网址,按下回车,到最终看到网页,这中间到底发生了什么?”

这是一道经典的面试题,也是检验一个开发者网络基础是否扎实的试金石。这个问题看似简单,背后却牵扯到 DNS 解析、TCP 握手、TLS 加密、HTTP 协议、负载均衡以及浏览器渲染等无数复杂的工程细节。

今天,我们就以一次真实的 HTTPS 请求为例,把这段旅程完整地拆解开来,看看数据包在光纤和网线中究竟经历了什么。


第一阶段:URL 解析与 DNS 寻址

当你输入 https://www.example.com/index.html 并按下回车时,浏览器首先要做的就是知道这个域名对应的 IP 地址在哪里。因为计算机在网络中通信,底层依赖的是 IP 地址,而不是便于人类记忆的域名。

1. 浏览器缓存查找

浏览器会首先检查自己的缓存中是否记录了这个域名的 IP。如果有,就直接使用;如果没有,就继续往下找。

2. 系统与 hosts 文件

如果浏览器没有,它会去查询操作系统的 DNS 缓存。同时,会检查本机的 /etc/hosts(Windows 下是 C:\Windows\System32\drivers\etc\hosts)文件。这也是为什么有时候我们修改了 hosts 文件,就能绕过 DNS 直接访问特定 IP 的原因。

3. 本地 DNS 服务器(递归查询)

如果本机找不到,请求就会发给本地 DNS 服务器(通常是运营商提供的,或者你手动设置的公共 DNS,如 8.8.8.8 或 223.5.5.5)。这一步是递归查询,也就是你告诉本地 DNS:“我只要结果,中间你帮我跑腿”。

4. 根域名服务器(迭代查询)

本地 DNS 如果也没有缓存,它就会去求助根域名服务器。根服务器不会直接给 IP,而是告诉本地 DNS:“你去问 .com 的顶级域名服务器吧。”

本地 DNS 跑去问 .com 服务器,对方说:“你去问 example.com 的权威域名服务器吧。”

最后,本地 DNS 找到权威域名服务器,终于拿到了真正的 IP 地址,并把它缓存下来,返回给浏览器。


第二阶段:建立 TCP 连接(三次握手)

拿到 IP 地址后,浏览器开始与服务器建立连接。由于 HTTPS 底层依然是 TCP 协议,所以首先要完成 TCP 的三次握手。

假设客户端序列号为 Seq=x,服务器序列号为 Seq=y:

  1. 第一次握手(客户端 -> 服务器):客户端发送一个 SYN 报文(SYN=1, Seq=x),意思是:“我想和你建立连接,你能收到吗?”此时客户端进入 SYN_SENT 状态。
  2. 第二次握手(服务器 -> 客户端):服务器收到后,回复一个 SYN+ACK 报文(SYN=1, ACK=1, Seq=y, Ack=x+1),意思是:“我收到了,我也准备好连你了。”此时服务器进入 SYN_RCVD 状态。
  3. 第三次握手(客户端 -> 服务器):客户端收到后,再次发送 ACK 报文(ACK=1, Seq=x+1, Ack=y+1),意思是:“好的,我也收到你的确认了。”

三次握手完成后,双方进入 ESTABLISHED 状态,一条全双工的可靠通道就建好了。为什么需要三次? 核心目的是为了防止已失效的连接请求报文段突然又传送到了服务端,导致服务端错误地建立连接,白白浪费资源。


第三阶段:TLS 握手(HTTPS 的加密协商)

TCP 通道建好后,因为我们是 HTTPS 请求,所以还要进行 TLS 握手。这个过程主要是为了验证服务器身份,并协商出后续通信用的对称加密密钥。

(注:在 TLS 1.3 中,TLS 握手和 TCP 握手已经可以在一定程度上合并,速度更快。这里以经典的 TLS 1.2 为例)

  1. 客户端发送 Client Hello,包含支持的 TLS 版本、加密套件和一个随机数。
  2. 服务器回复 Server Hello,选定加密套件,返回自己的数字证书(包含公钥)和另一个随机数。
  3. 客户端验证证书合法性,生成第三个随机数(Pre-Master Secret),用公钥加密后发给服务器。
  4. 服务器用私钥解密拿到 Pre-Master Secret。此时,双方用这三个随机数计算出相同的会话密钥(Session Key)。
  5. 双方互发 Finished 消息,验证加密通道是否正常。

第四阶段:发送 HTTP 请求与服务器处理

现在,数据可以在加密通道中传输了。浏览器开始拼接 HTTP 请求报文:

GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 ...
Accept: text/html,application/xhtml+xml...
Connection: keep-alive

请求到达服务器后,通常不会直接打到业务代码上。在服务器前端,一般会有 Nginx 等反向代理或负载均衡器。

  • 负载均衡:Nginx 根据策略(如轮询、IP Hash、最少连接)将请求分发给后端的多台应用服务器。
  • 应用处理:后端的 Java/Python/Go 程序收到请求,查询 Redis 缓存,或者去 MySQL 查库,组装出最终的 HTML 页面数据,生成 HTTP 响应报文。
  • 返回数据:服务器将响应报文沿着建立的 TLS 和 TCP 通道返回给浏览器。

第五阶段:浏览器渲染(关键渲染路径)

浏览器拿到 HTML 字符串后,真正的重头戏才开始:渲染。

1. 构建 DOM 树

浏览器解析 HTML 字节流,词法分析后生成节点,最终构建成一棵 DOM(Document Object Model)树。

2. 构建 CSSOM 树

遇到 标签或外链的 CSS 文件,浏览器会解析生成 CSSOM(CSS Object Model)树。需要注意的是,CSS 的加载是阻塞渲染的。如果 CSS 没加载完,浏览器不会渲染任何内容,以免出现样式闪烁(FOUC)。

3. 执行 JavaScript

如果在解析过程中遇到 标签,浏览器会暂停 DOM 的解析,去下载并执行 JS 代码。JS 是阻塞 HTML 解析的。这也是为什么我们通常建议把 JS 放到 HTML 底部,或者加上 defer / async 属性。

4. 生成 Render 树

DOM 树和 CSSOM 树结合,剔除掉不需要显示的节点(比如 display: none 的元素),生成 Render 树。

5. 布局(Layout / Reflow)

浏览器计算 Render 树中每个节点在屏幕上的确切位置和大小。这一步被称为重排(Reflow)。

6. 绘制(Paint)

浏览器把计算好的节点转换成屏幕上的实际像素,这被称为重绘(Repaint)。

7. 合成(Composite)

现代浏览器会将页面分成多个图层,利用 GPU 分别渲染后,再在屏幕上合成起来。这就是为什么用 transform 做动画比改 top 属性流畅得多的原因,因为前者只触发合成,后者会触发重排。


第六阶段:断开连接(四次挥手)

当页面加载完毕,如果开启了 Connection: keep-alive,TCP 连接会保持一段时间,以便复用加载图片和 JS。如果超时或者主动关闭,就会经历 TCP 的四次挥手:

  1. 第一次挥手:客户端发送 FIN,表示“我没有数据要发了”。
  2. 第二次挥手:服务器收到 FIN,回复 ACK。此时处于半关闭状态,服务器可能还有数据要发。
  3. 第三次挥手:服务器数据发完了,发送 FIN,表示“我也要关了”。
  4. 第四次挥手:客户端收到 FIN,回复 ACK。服务器收到后进入 CLOSED 状态。客户端在经过 2MSL(最大报文生存时间)的等待后,也进入 CLOSED 状态。

为什么挥手需要四次? 因为 TCP 是全双工的。当客户端说“我不发了”时,只能证明客户端没有数据发了;但服务器可能还有数据没传完,所以服务器的 ACK 和 FIN 通常是分开发送的。


总结:看不见的庞大工程

从输入 URL 按下回车,到看见页面,看似只过了零点几秒,但底层却经历了:

DNS 寻址 -> TCP 三次握手 -> TLS 加密协商 -> HTTP 请求与响应 -> DOM/CSSOM 解析 -> 渲染与绘制 -> TCP 四次挥手。

这其中的每一个环节,都有无数精妙的协议和算法在支撑着现代互联网的运行。作为开发者,理解这套底层逻辑,不仅能在排查网络性能瓶颈(比如为什么首屏加载这么慢)时游刃有余,更能让你对整个软件生命周期有更宏观的认知。

下次再遇到页面卡顿,不要只盯着前端代码了。打开浏览器的 F12 网络面板,或者用 curl 和 traceroute 敲一敲,也许你能在这条链路的某一个环节,找到那个隐藏的“堵点”。


2026年10月19日

添加评论
点赞收藏
点踩分享查看原文
评论
?
参与讨论