跳到主要内容

在浏览器里,从输入 URL 到页面展示,这中间发生了什么?

"在浏览器里,从输入 URL 到页面展示,这中间发生了什么?" 这是一道经典的面试题,能够全面考察应聘者对网络、操作系统、Web 等一系列知识的掌握程度。今天,我将结合下图对这个过程进行分析。当然,实际过程比图示更加复杂。

一、用户输入​

用户在地址栏输入查询关键字后,浏览器会判断输入的是搜索内容还是请求的 URL。

如果是搜索内容,地址栏会使用浏览器默认的搜索引擎,合成带关键字的 URL。如果输入内容符合 URL 规则,浏览器会根据规则加上协议,合成完整的 URL。

用户输入关键字并按下回车后,当前页面将被替换为新页面。在此之前,浏览器会触发当前页面的 beforeunload 事件,允许页面执行数据清理操作或询问用户是否离开页面,例如处理未提交的表单等。如果当前页面未监听 beforeunload 事件或用户同意继续,浏览器进入加载状态。

简而言之,浏览器处理完 URL 后,会将 URL 请求发送给网络进程。

二、网络请求​

接下来进入页面资源请求过程。浏览器进程通过进程间通信(IPC)将 URL 请求发送至网络进程,网络进程接收到请求后,开始真正的 URL 请求流程。

首先,网络进程会检查本地缓存是否有资源。如果存在缓存资源:

1. 浏览器缓存​

涉及强缓存和协商缓存两个概念。

服务端缓存控制​

强缓存与服务端缓存控制相关,启用强缓存的情况包括:

存在 Cache-Control 属性,设置 max-age 并且不存在 no-cache 和 no-store; 不存在 Cache-Control,存在 Expires 字段。

服务端缓存控制状态:

  • no-store:不允许缓存,适用于变化频繁的数据,如秒杀页面。
  • no-cache:允许缓存,但使用前必须向服务器验证是否过期或有新版本。
  • must-revalidate:缓存未过期时可使用,过期后需向服务器验证。

与缓存代理相关的属性:

  • private:缓存仅限客户端,不能通过代理服务器缓存。
  • public:缓存完全开放,代理服务器可缓存和使用。

客户端缓存控制​

进入协商缓存前,需要经过 DNS 解析、建立 TCP/IP 连接,以及如果是 HTTPS 协议,还需建立 TLS 连接。

协商缓存由客户端发起(条件请求),如果未命中强缓存,则进入协商缓存阶段。条件请求常用的头字段有 If-Modified-Since 和 If-None-Match。这些字段需要第一次响应提供 Last-Modified 和 ETag,以验证资源是否最新。

如果资源未变化,服务器回应 304 Not Modified,表示缓存有效。

请求过程中可能遇到服务器返回状态码 301 或 302。

重定向​

重定向由服务器发起,浏览器接收到 301 或 302 状态码后,会跳转到新的 URI。重定向需配合 Location 字段使用。

Connection: keep-alive
Content-Length: 151
Content-Type: text/html
Date: Sun, 21 Feb 2021 00:48:52 GMT
Location: /index.html
Referer: /18-1
Server: openresty/1.19.3.1

Location 字段指定新的 URI,浏览器根据此字段发起新的 HTTP 或 HTTPS 请求。URI 可为绝对或相对路径,站内跳转可用相对 URI,站外需用绝对 URI。重定向报文还可使用 Refresh 字段实现延时重定向。

2. DNS 解析​

若本地缓存不存在资源,需判断是否需要 DNS 解析。若 URI 是域名,则进行 DNS 解析获取服务器 IP 地址。

DNS 系统​

DNS 系统为三层树状分布式服务,对应域名结构:

  • 根域名服务器(Root DNS Server):管理顶级域名服务器,返回顶级域名服务器的 IP 地址。
  • 顶级域名服务器(Top-level DNS Server):管理特定域名下的权威域名服务器,返回域名服务器的 IP 地址。
  • 权威域名服务器(Authoritative DNS Server):管理域名下主机的 IP 地址。

通过树形结构查询域名,从右到左顺序查找,最终获得 IP 地址。核心系统外,DNS 解析还依赖两种缓存手段:

非权威域名服务器:大公司或网络运营商建立的 DNS 服务器,代替用户访问核心 DNS 系统。 操作系统缓存:操作系统缓存 DNS 解析结果,并查找主机映射文件(如 Linux 的 /etc/hosts,Windows 的 C:\WINDOWS\system32\drivers\etc\hosts)。 浏览器也有自己的 DNS 缓存,缓存策略依赖浏览器。

DNS 解析流程​

DNS 查询流程为:

浏览器缓存 → 操作系统缓存 → 本地 hosts 文件 → 非权威域名服务器 → 根域名服务器 → 顶级域名服务器 → 权威域名服务器。

在 Windows 10 中,启动时会缓存 hosts 文件条目,并动态更新操作系统缓存。

3. 建立 TCP/IP 连接​

获取服务器 IP 后,建立 TCP/IP 连接。

网络分层模型​

TCP/IP 协议分为四层:

链接层(Link Layer):在以太网、WiFi 等底层数据上发送原始数据包,使用 MAC 地址标记设备。 网际层(Internet Layer):IP 协议所在层。 传输层(Transport Layer):保证数据在两点间可靠传输,TCP 和 UDP 协议位于此层。 应用层(Application Layer):面向具体应用的协议,如 HTTP。

三次握手​

了解以下概念:

  • ACK:确认字符。
  • SYN:同步序列编号。

主要状态:

  • LISTEN:监听 TCP 端口的连接请求。
  • SYN-SENT:发送连接请求后等待确认。
  • SYN-RECEIVED:收到连接请求后等待确认。
  • ESTABLISHED:连接已建立,可发送数据。

三次握手流程:

  1. 客户端发送 SYN 标志位(序号 J),进入 SYN-SENT 状态。
  2. 服务器收到 SYN J,发送 ACK(序号 J+1)和 SYN(序号 K),进入 SYN-RECEIVED 状态。
  3. 客户端发送 ACK(序号 K+1),服务器收到后进入 ESTABLISHED 状态,连接建立。

序号 J 和 K 是初始序列号(ISN)。SYN 报文不携带数据,占用一个序号。客户端随机选择 ISN。

ACK 确认 SYN 段已接收,指定下次发送段的序号。建立连接后,浏览器立即发送请求报文。

4. 建立 TLS 连接​

建立 TCP/IP 连接后,如果协议是 HTTPS,还需建立 TLS 连接。

HTTPS​

HTTP 不安全,HTTPS 增加了机密性、完整性、身份认证和不可否认性。它将 HTTP 的传输协议由 TCP/IP 换为 SSL/TLS,即 "HTTP over SSL/TLS"。

SSL/TLS​

SSL(Secure Sockets Layer)由网景公司于 1994 年发明,发展到 v3 时被 IETF 改名为 TLS(Transport Layer Security),标准化为 TLS1.0。

当前主流是 TLS1.2,包含记录协议、握手协议、警告协议、变更密码规范协议、扩展协议等,综合使用对称加密、非对称加密、身份认证等技术。

对称加密、非对称加密​

对称加密使用相同密钥加密和解密,如 AES 和 ChaCha20。AES 是 DES 的替代,ChaCha20 性能优于 AES。

非对称加密使用公钥和私钥,如 RSA 和 ECC。公钥可公开,私钥需保密。公钥加密只能用私钥解密,解决密钥交换问题。

混合加密​

混合加密结合对称加密和非对称加密。初始通信使用非对称加密解决密钥交换,随后使用对称加密进行高效通信。

数字签名与证书​

摘要算法(如 SHA-2)保证数据完整性。数字签名结合私钥和摘要算法,实现身份认证和不可否认性。

数字证书由 CA 颁发,包含公钥和身份信息,构建信任链。浏览器和操作系统内置根证书,通过验证证书链确认公钥可信。

ECDHE 握手过程​

TLS 握手过程使用混合加密、摘要算法、数字签名和数字证书。ECDHE 握手流程如下:

  1. 客户端发送 "Client Hello" 消息,包含版本号、支持的密码套件和随机数。
  2. 服务器回复 "Server Hello",选择密码套件,发送证书和 "Server Key Exchange" 消息,包含椭圆曲线公钥和签名,最后发送 "Server Hello Done"。
  3. 客户端验证证书,发送 "Client Key Exchange" 消息,包含客户端公钥参数。
  4. 双方使用 ECDHE 算法生成 "Pre-Master Secret",结合随机数通过 PRF 生成 "Master Secret"。
  5. 客户端发送 "Change Cipher Spec" 和 "Finished" 消息,服务器验证后也发送相同消息。
  6. 握手结束,开始加密通信。

三、处理响应数据​

HTTP 请求的数据类型通过 Content-Type 头字段区分。Content-Type 指示响应体的数据类型,浏览器根据其值决定如何处理。

如果 Content-Type 被判断为下载类型,浏览器会交由下载管理器处理,导航流程结束。如果是 HTML,浏览器继续导航流程,准备渲染进程。

四、准备渲染进程​

默认情况下,Chrome 为每个页面分配一个渲染进程。若多个页面属于同一站点,则复用渲染进程。

同一站点定义为相同根域名(如 geekbang.org)、协议(https:// 或 http://)、子域名和端口。例如:

https://time.geekbang.org
https://www.geekbang.org
https://www.geekbang.org:8080

Chrome 的策略:

  • 通常为每个新页面创建独立渲染进程。
  • 若新页面与父页面同属一站点,复用父页面的渲染进程。

渲染进程准备好后,浏览器进程将网络进程接收到的 HTML 数据提交给渲染进程,进入提交文档阶段。

五、提交文档​

提交文档指浏览器进程将网络进程的 HTML 数据提交给渲染进程,流程如下:

  1. 浏览器进程接收到响应头后,向渲染进程发送“提交文档”消息。
  2. 渲染进程与网络进程建立数据传输管道。
  3. 文档数据传输完成后,渲染进程返回“确认提交”消息。
  4. 浏览器进程收到确认消息,更新界面状态,包括安全状态、地址栏 URL、历史记录等。

完成导航流程后,进入渲染阶段。

六、渲染阶段​

渲染模块执行多个子阶段,将 HTML 转换为像素,形成渲染流水线。主要子阶段包括构建 DOM 树、样式计算、布局、分层、绘制、分块、光栅化和合成。

1. 构建 DOM 树​

浏览器将 HTML 转换为 DOM 树,表示页面的结构。

2. 样式计算​

计算 DOM 树中每个元素的具体样式,分为以下步骤:

转换 CSS 为浏览器理解的结构​

CSS 样式来源:

  • 通过 link 引用的外部 CSS 文件。
  • 元素的 style 属性内嵌的 CSS。

浏览器将 CSS 文本转换为 styleSheets 结构,可在 Chrome 控制台中查看 document.styleSheets。

标准化样式属性值​

将 CSS 属性值转换为渲染引擎理解的标准化值,如将 2em、blue、bold 转换为具体数值。

计算具体样式​

根据 CSS 继承和层叠规则,计算 DOM 树中每个节点的最终样式。例如:

body {
font-size: 20px;
}
p {
color: blue;
}
span {
display: none;
}
div {
font-weight: bold;
color: red;
}
div p {
color: green;
}

对应的 DOM 树样式:

子节点继承父节点样式,如 body 的 font-size 为 20px,其下所有节点的 font-size 亦为 20px。

3. 布局阶段​

计算 DOM 树中可见元素的几何位置,包含创建布局树和布局计算。

创建布局树​

从 DOM 树中筛选可见节点,构建布局树。

不可见节点(如 head 标签、display: none 元素)不包含在布局树中。

布局计算​

计算布局树节点的坐标位置,渲染引擎将布局运算结果写回布局树。Chrome 正在重构布局系统,以更清晰地分离输入和输出。

4. 分层​

为特定节点生成图层,形成图层树(LayerTree)。

图层叠加形成最终页面图像:

布局树节点可能不对应独立图层,最终每个节点从属于某一图层。

创建图层的条件:

拥有层叠上下文属性的元素,如定位属性、透明属性、CSS 滤镜等。

需要剪裁的元素也会创建新图层。

七、绘制和光栅化​

1. 绘制​

绘制阶段会把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表。

例如,一个图层包含文字和阴影,那么绘制过程会被拆分为:

  1. 绘制阴影
  2. 绘制文字
  3. 绘制边框

这些指令都会被记录在绘制列表中。

2. 光栅化​

光栅化是将绘制指令转换为屏幕上的像素点的过程。

由于一个页面通常很大,但用户只能看到其中的一部分,所以合成线程会将图层划分为图块(tile),然后优先光栅化视口附近的图块。

光栅化过程主要包括:

  1. 图块划分:将图层分成多个小的图块
  2. 优先处理:优先处理视口附近的图块
  3. GPU 加速:使用 GPU 进行光栅化,提高处理速度

最佳实践:

  • 使用 will-change 属性提示浏览器哪些元素可能发生变化,让浏览器提前做好优化准备
  • 合理使用图层,过多的图层会增加内存占用和合成时间
  • 避免大面积重绘,可以通过分层来减少重绘范围

八、合成​

合成是将光栅化后的图层合并成最终页面的过程。主要包含以下步骤:

1. 图层合成​

合成线程会按照图层的前后顺序,将所有图层叠加在一起。这个过程考虑:

  • 图层的层叠顺序(z-index)
  • 透明度(opacity)
  • 变形(transform)等属性

2. 显示合成结果​

合成线程将最终的合成结果提交给 GPU 进程,由 GPU 进行最后的渲染,显示在屏幕上。

这个过程的特点是:

  • 在合成线程中进行,不会阻塞主线程
  • 可以利用 GPU 硬件加速
  • 不需要重新计算布局和绘制

最佳实践:

  • 使用 CSS transform 进行动画,可以避免重排和重绘
  • 合理使用 will-change 属性,提示浏览器提前创建独立图层
  • 控制图层数量,避免过多图层导致内存占用过大
  • 使用 requestAnimationFrame 来执行动画,保证动画流畅