# 从按下回车到看见页面：一次网页打开中间发生了什么

把 DNS、TCP、TLS、HTTP 和浏览器渲染串成一条能自己复述的主线

> 浏览器工作原理 · HTTP 与网络 · 页面渲染 · 约 6 分钟 · 09 月 28 日

## 本篇要点

1. 打开一个网页实际是几十次文件往返：浏览器先拿到 HTML，读完才知道还需要哪些 CSS、JavaScript 和图片。
2. 整个过程分两大段：网络阶段把文件送到浏览器，渲染阶段把文件内容变成屏幕上的像素。
3. 浏览器先把地址拆成协议、域名、路径；不像网址的输入会交给搜索引擎。
4. DNS 把域名翻译成 IP 地址，顺序是浏览器缓存、系统缓存、网络缓存，最后才问权威服务器，结果按 TTL 缓存。
5. TCP 用三次握手建立可靠有序的连接，代价是一个完整的往返时间，数据在握手完成前发不出去。
6. TLS 握手验证服务器证书、协商会话密钥，此后通信加密，TLS 1.3 通常只需一个往返。
7. HTTP 请求和响应都是固定格式的文本，含起始行、头信息和正文；200、301、302、404 是最常见的状态码。
8. 渲染依次经过：HTML 变 DOM，CSS 变 CSSOM，两者合成只含可见节点的 render tree，layout 算位置尺寸，paint 画像素并上屏。
9. CSS 阻塞渲染、同步 script 阻塞解析，因为两者都会改变最终显示结果；因此脚本常放在 body 末尾或用 defer。
10. 同域名的后续文件可复用已建立的连接，HTTP/2 还能在一条连接上并行发送多个请求。
11. 这篇描述的是首次访问无缓存的站点，缓存命中与服务器内部流程不在本篇范围内。

---

在地址栏里敲下 `example.com`，按下回车，几百毫秒后页面出现了。这段时间里浏览器做的事情，可以分成两大段：

- 前半段：**把需要的文件要过来**，这是网络部分。
- 后半段：**把这些文件变成屏幕上的像素**，这是渲染部分。

先记住一件有点反直觉的事：浏览器要的从来不是“一个页面”，而是**好几个文件**。它先拿到一个 HTML 文件，读完这个文件，才知道页面还需要哪些 CSS、JavaScript 和图片，然后再去要这些文件。所以“打开一个网页”，实际是几十次文件的往返，不是一次。

下面按顺序走一遍，整体路线是：

```mermaid
flowchart LR
  A["在地址栏输入并回车"] --> B["拆 URL：分出域名和路径"]
  B --> C["DNS：域名换成 IP"]
  C --> D["TCP 握手：建立连接"]
  D --> E["TLS 握手：通道加密"]
  E --> F["HTTP 请求 / 响应：先拿到 HTML"]
  F --> G["DOM + CSSOM"]
  G --> H["render tree"]
  H --> I["layout 算位置尺寸"]
  I --> J["paint 画像素并上屏"]
  F --> K["发现还缺 CSS / JS / 图片"]
  K --> G
```

## 第一步：把地址拆成几块

你输入的那串字符，浏览器先要判断它是网址还是搜索词。像 `example.com` 这样看起来像地址的，才按地址处理；否则它会交给默认搜索引擎。

按地址处理时，浏览器把字符串拆成几部分：协议（`https`）、域名（`example.com`）、路径（比如 `/hello`）。这几部分的用途很明确：域名决定“去找哪台机器”，协议决定“用什么规矩说话”，路径决定“要那台机器上的哪个东西”。

## 第二步：DNS 把域名换成 IP

机器之间互相找，靠的是 IP 地址，也就是类似 `103.102.166.224` 的一串数字。域名是给人看的，所以中间需要一个翻译：DNS（Domain Name System，域名系统），它相当于互联网的电话簿，负责把域名对应到 IP 地址。

这个翻译并不会每次都从头查。顺序是逐层找缓存：先看浏览器自己的缓存，再看操作系统的缓存，再看路由器或网络服务商的缓存。都没有，才由 DNS 解析器去问别人：先问根服务器，再问 `.com` 这类顶级域的服务器，最后问管着这个域名的权威服务器，拿到答案 [1]。

答案会带一个 TTL（存活时间），告诉你能缓存多少秒。所以第一次访问一个新站点，这一步要多花几十毫秒；下次再访问，基本是瞬间完成。

## 第三步：TCP 握手，先确认双方能通话

有了 IP 地址，浏览器要跟这台机器的 443 端口建立一条 TCP 连接（HTTPS 默认用 443）。TCP 的职责是提供一条**可靠、有序**的字节流：丢了的包会重发，顺序乱了会重新排好。

建立连接需要三次握手。客户端先发 SYN，意思是“我想连，我的字节从编号 X 开始”；服务器回 SYN-ACK，意思是“收到你的 X 了，我的字节从编号 Y 开始”；客户端再回一个 ACK，确认收到 Y。三次看起来麻烦，但双方各自都要确认“我能发出去、也能收到”，并且约定从哪个字节开始编号，所以这一步不能省 [4]。

代价是时间：这三次消息要走一个完整的往返（去一趟、再回来一趟）。数据在这之前一个字节也发不出去。

## 第四步：TLS 握手，把通道加密

裸的 TCP 通道上，路径中间的节点既能看也能改。HTTPS 里的那个 S，来自 TLS（Transport Layer Security），它负责给这条通道加密。握手主要做三件事：服务器出示证书，浏览器检查这张证书是不是由自己内置信任的机构签发的、证书上的名字是否包含你访问的域名，以此确认对面确实是这个网站；双方各自算出一份谁也没在网络上传输过的会话密钥；此后往来的每个字节都加密。

用 TLS 1.3 时，这一步通常只需要一个往返。之后 HTTP 请求就在这条加密通道里跑。

## 第五步：发请求，收响应

通道建好，浏览器才正式开口。HTTP 请求本身就是一段有固定格式的文本：

```
GET /hello HTTP/1.1
Host: example.com
Accept: text/html
```

第一行说明“做什么、要哪个资源、用哪个版本的 HTTP”，后面的头信息补充细节，例如目标域名、能接受什么格式、有没有该网站的 Cookie。

响应的结构是对称的：状态行、响应头、响应体。状态码只需先记住几个：`200` 表示成功，`301` 和 `302` 表示资源搬了家，浏览器会按响应里给出的新地址再请求一次，`404` 表示找不到 [1]。

响应体里就是那个 HTML 文本。也就是说，这一轮往返拿到的只是页面的骨架，页面还远没有显示出来。

## 第六步：把 HTML 变成屏幕上的像素

浏览器读 HTML 的方式，是边读边建一棵树。HTML 里的每个标签、属性和文本，都会变成这棵树上的一个节点，这棵树叫 DOM（Document Object Model）。它代表页面的结构，存在内存里。

读的过程中，只要发现 `<link>`、`<script>`、`<img>` 这类引用，浏览器就会顺手再发起请求去要那些文件。这也是为什么一次打开网页会对应几十次请求。

CSS 到了以后，浏览器同样把它解析成一棵树，叫 CSSOM，记录“哪条样式规则作用在哪些节点上”。接着，DOM 和 CSSOM 合并成一棵 render tree。注意它只保留要显示的内容：`display: none` 的元素根本不会进这棵树。

render tree 有了，但每个东西放在哪、多大，还不知道。**layout**（布局）这一步负责算出每个可见节点在屏幕上的精确位置和尺寸，把相对单位换算成真实像素。算完之后进入 **paint**，把每个节点的颜色、文字、边框、阴影真正画成像素；如果页面被分成多层，还要经过合成再送进屏幕 [3]。

这里有两个容易绊人的现象，原因都在渲染顺序里。CSS 会**阻塞渲染**：因为后面的规则可能覆盖前面的，浏览器必须等 CSS 全部到齐，才能确定最终样式，否则显示出来的样子会是错的。另外，写在 HTML 里的同步 `<script>` 会**阻塞解析**：因为 JavaScript 可能修改 DOM，浏览器必须先把它执行完，才知道后面的 HTML 该建成什么样 [2]。所以常见做法是把脚本放到 `<body>` 末尾，或者用 `defer` 让它延后执行。

还要知道，同一个域名上的后续文件可以复用已经建好的连接，不必每个文件重新握手一次；HTTP/2 还能在一条连接上同时跑多个请求，进一步省掉排队时间 [4]。

## 这篇的边界

以上描述的是**第一次访问一个没有缓存的站点**。如果你之前来过，浏览器可能直接从本地缓存里取文件，连请求都不发；DNS 也可能直接命中缓存。另外，服务器收到请求之后内部还做了什么，属于服务器那一侧的事，这一篇没有展开。

一句话总结这条主线：网络阶段负责把需要的文件送到浏览器，渲染阶段负责把文件里的描述变成屏幕上的像素。以后遇到白屏、样式闪烁、图片加载后内容被挤下去这类问题，你就可以在脑子里按这两大段、六个步骤去定位它卡在哪一环。

## 术语表

- IP 地址：网络里每台机器的数字门牌号，数据靠它找到目的地。
- DNS：把域名翻译成 IP 地址的系统，答案会被逐层缓存并按 TTL 过期。
- TCP：提供可靠、有序字节流的传输协议，用三次握手建立连接。
- 三次握手：双方各确认一次“我能发也能收”，并约定字节起始编号，耗时一个往返。
- TLS 握手：验证服务器身份、协商只有双方知道的会话密钥，让之后的通信加密。
- HTTP 请求与响应：浏览器和服务器之间的文本格式消息，由起始行、头信息、正文三部分组成。
- DOM：浏览器解析 HTML 后在内存里建出的节点树，代表页面结构。
- CSSOM：解析 CSS 得到的样式规则树，记录哪条规则作用在哪些节点上。
- render tree：DOM 与 CSSOM 合并后只保留可见节点的树，是布局和绘制的输入。
- layout / paint：前者算出每个可见节点的位置和尺寸，后者把节点画成像素。
- 渲染阻塞与解析阻塞：CSS 阻塞渲染、同步 script 阻塞 HTML 解析，因为二者都会改变页面的最终显示结果。

## 来源

1. [MDN：How the web works — DNS、TCP/IP、HTTP 与常见状态码的基础说明](https://developer.mozilla.org/en-US/docs/Learn_web_development/Getting_started/Web_standards/How_the_web_works)
2. [MDN：How browsers load websites — 浏览器如何把收到的文件组装成页面](https://developer.mozilla.org/en-US/docs/Learn_web_development/Getting_started/Web_standards/How_browsers_load_websites)
3. [MDN：Critical rendering path — DOM、CSSOM、render tree、layout、paint 各阶段](https://developer.mozilla.org/en-US/docs/Web/Performance/Guides/Critical_rendering_path)
4. [The Life of a Request — DNS、TCP 三次握手、TLS 1.3 往返与连接复用](https://teachyourselfcoding.com/system-design-life-of-a-request/)
5. [web.dev：Render-tree Construction, Layout, and Paint — 渲染各阶段的输出](https://web.dev/articles/critical-rendering-path/render-tree-construction)

---

原文：https://pangzhengboyin.com/articles/what-happens-when-you-open-a-webpage-e1f9c82b

> **庞征博引** · 想学的，慢慢都会
>
> 庞征博引是把想学的东西写成连载的 AI 学习工具。说出想学什么，它会先了解你的基础，再把主题写成一篇篇 5–10 分钟能读完的文章；边读边问，接下来学什么跟着你走。这篇就是这样写出来的。
>
> 开始你自己的连载 → https://pangzhengboyin.com
