从输入 URL 到开始解析 HTML 之间:浏览器背后发生了什么

第一步:DNS 解析 ------ 把名字变成地址

当你在浏览器地址栏输入 https://example.com 并按下回车,计算机需要先将其转换成它能理解的 IP 地址(如 93.184.216.34)。因此,第一步是 DNS(Domain Name System)解析

浏览器不会立刻联网,而是先查缓存:

  1. 浏览器 DNS 缓存

    Chrome、Firefox 等现代浏览器会缓存近期解析过的域名(可通过 chrome://net-internals/#dns 查看)。

  2. 系统 hosts 文件

    操作系统会检查本地 hosts 文件(如 Windows 的 C:\Windows\System32\drivers\etc\hosts),这是手动映射域名到 IP 的方式。

  3. 操作系统 DNS 缓存

    若前两步未命中,系统会查询自己的 DNS 缓存(Windows 用 ipconfig /displaydns,macOS 用 sudo dscacheutil -cachedump)。

  4. 向 DNS 服务器发起查询

    若仍无结果,操作系统会向配置的 DNS 服务器(如 8.8.8.8 或 ISP 提供的 DNS)发起请求。

上面提到的 8.8.8.8 是公共 DNS 服务器。如果你连接的是公司的网络,公司也有配置的内部 DNS 服务器地址,这样你就可以访问到公司内部的网站。
📌 ISP 是什么? ISP = Internet Service Provider

中文叫:互联网服务提供商 简单说,ISP 就是给你家、你公司、你手机提供"上网服务"的公司
💡 注意:DNS 缓存只保存"域名 → IP"的映射,不缓存网页内容


DNS 查询如何工作?

  • 你(浏览器)发起的是"递归查询" :只需问一次本地 DNS 服务器,就期待拿到最终答案。
  • 本地 DNS 服务器内部用"迭代查询" :它依次问根服务器 → 顶级域(.com)服务器 → 权威 DNS 服务器,每一步只告诉你"下一个该问谁"。

在 DNS 查询流程中提到的 "本地 DNS 服务器"(Local DNS Server) ,是一个网络术语

🧩 类比理解

想象你要查一个陌生公司的电话:

  • = 浏览器/你的电脑
  • 114 查号台 = "本地 DNS 服务器"(比如 8.8.8.8)
  • 工商注册系统、行业目录等 = 根服务器、.com 服务器、权威 DNS

你只打一次 114(递归查询),说:"请帮我查 example.com 的电话"。

114 自己去翻各种资料(迭代查询),最后告诉你结果。

你不需要知道 114 背后怎么查的。

整个过程通常在几十毫秒内完成,但在弱网或跨洋场景下可能显著延迟。

第二步:安全检查 ------ 防止你误入陷阱

在导航流程早期(甚至在 DNS 解析开始前 ),现代浏览器(如 Chrome)就会启动 Safe Browsing 安全检查

  • 浏览器将用户输入的域名或完整 URL 的加密哈希值,与本地缓存的恶意站点黑名单进行比对。
  • 若本地无法确认,会通过隐私保护机制(如私有集合交集)向 Google 等服务发起查询。
  • 该检查完全基于域名或 URL 路径,不依赖 IP 地址。这意味着即使恶意网站托管在合法 IP 上,只要其域名可疑,仍会被拦截。

为提升性能,此步骤通常与 DNS 解析并行执行,而非串行等待。


Google 提供了一个专门用于触发 Safe Browsing 警告的测试网址

🔗 testsafebrowsing.appspot.com/

这个页面由 Google 维护,包含多个模拟的恶意链接,例如:

  • 模拟恶意软件站点
  • 模拟钓鱼网站
  • 模拟不安全的下载

操作步骤:

  1. 打开 Chrome(或其他支持 Safe Browsing 的浏览器)

  2. 访问:testsafebrowsing.appspot.com/

  3. 点击任意一个"Malware "或"Phishing"链接

  4. 正常情况下,浏览器会拦截页面并显示红色警告,例如:

✅ 如果看到警告,说明 Safe Browsing 已启用并正常工作

⚠️ 注意:请勿绕过警告!这些是测试页面,但行为模拟真实攻击。

第三步:建立 TCP 连接 ------ "三次握手"

浏览器会通过操作系统,向目标服务器的 IP 地址发起 TCP 连接请求。因为 HTTP/HTTPS 依赖 TCP 作为底层传输协议,必须先建立可靠的连接通道。

🔄 TCP 三次握手过程:

  1. 客户端 → 服务器 :发送 SYN(同步序列号),表示"我想连接你"。
  2. 服务器 → 客户端 :回复 SYN-ACK(同步+确认),表示"我收到了,我也准备好了"。
  3. 客户端 → 服务器 :发送 ACK(确认),表示"好的,我们开始通信吧"。

至此,TCP 连接建立成功,双方可以可靠地收发数据。

💡 注意:如果是访问 https://example.com,这一步连接的是 目标服务器的 443 端口(HTTPS 默认端口)。

第四步:TLS 握手(仅 HTTPS)------ 加密你的通信

因为访问的是 https://,还需进行 TLS(Transport Layer Security)握手 ,确保通信不被窃听、篡改或冒充

以现代常用的 TLS 1.3 为例,简化流程如下:

  1. Client Hello:浏览器发送支持的加密协议、密钥交换算法等。
  2. Server Hello + 证书:服务器选定协议,返回数字证书(含公钥)。
  3. 证书验证:浏览器检查证书是否由可信机构(CA)签发、域名是否匹配、是否过期。
  4. 密钥协商 :双方通过 ECDHE 等算法,各自计算出相同的会话密钥(密钥从未在网络上传输!)。
  5. 加密通信开始:后续所有 HTTP 数据均用该密钥加密。

TLS 1.3 将握手压缩到 1 个往返(1-RTT) ,甚至支持 0-RTT(对已访问过的站点),极大提升速度。

🔐 TLS 握手只发生在 HTTPS 连接中:

  • HTTP(如 http://example.com
    → 使用 明文传输 ,直接在 TCP 连接建立后发送 HTTP 请求,没有 TLS 握手
    → 数据(包括 Cookie、密码、URL 参数等)在网络中可被窃听或篡改
  • HTTPS(如 https://example.com
    → 在 TCP 连接建立后,必须先完成 TLS 握手,协商加密密钥、验证服务器身份,之后才发送加密的 HTTP 请求。

第五步:发送 HTTP 请求 ------ 要 HTML!

浏览器通过已建立的 TLS 连接,发送一个 HTTP/1.1 或 HTTP/2 请求(具体取决于服务器支持),例如:

html 复制代码
GET / HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0 ...
Accept: text/html
Connection: keep-alive

🔐 这个请求在发送前会被 TLS 层加密,网络上的中间人只能看到加密数据,无法得知你请求的是 / 还是 /passwords.txt

服务器返回加密的 HTTP 响应

服务器收到请求后,生成 HTML 内容,并通过 TLS 通道返回:

http 复制代码
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 1256
...

<!DOCTYPE html>
<html>
<head><title>Example Domain</title></head>
<body>
<h1>Example Domain</h1>
<p>This domain is for use in illustrative examples...</p>
</body>
</html>

🔐 整个响应体(包括 HTML 内容)同样被加密传输。

HTTP/1.1和 HTTP/2 的区别

✅ 1. HTTP/1.1:复用同一个 TCP + TLS 连接(通过 Keep-Alive)

  • 在 HTTP/1.1 中,默认启用 Connection: keep-alive
  • 浏览器在获取 HTML 后,会复用已有的 TCP + TLS 连接去请求 CSS、JS、图片等资源。
  • 只要服务器支持,同一个连接可以连续发送多个请求(串行或有限并发)。

⚠️ 但 HTTP/1.1 有"队头阻塞"问题:多个资源需排队传输,效率不高。

✅ 2. HTTP/2:强烈推荐复用连接(且必须基于 TLS)

  • HTTP/2 强制要求 TLS(浏览器只在 HTTPS 下启用 HTTP/2)。
  • 它支持 多路复用(Multiplexing)
    → 在同一个 TLS 连接上,并行传输 HTML、CSS、JS、图片等所有资源。
  • 完全避免了重复握手开销,极大提升加载速度。

举个例子:

  1. 在 Chrome DevTools → Network

  2. 启用 Throttling (模拟 3G):

    → 点击 "No throttling" → 选 "Slow 3G"

  3. 分别访问:

    • 一个 HTTP/2 站点
    • 一个 HTTP/1.1 站点
  4. 观察 Waterfall(瀑布图)

    • HTTP/1.1:资源呈"阶梯状",大量排队(Stalled/Queueing)
    • HTTP/2:资源密集并行,Stalled 时间短

例如我访问的这个使用 HTTP/1 的大型网站,可以看到有很多排队和 Stalled 的时间:

第六步:解析与渲染 ------ 把代码变成画面

这一步内容比较多,我单独写在一个文章里了,传送门如何解析 HTML:

补充 1:四次挥手

什么是"四次挥手"?

TCP 是面向连接的协议,断开连接时需双方确认,过程称为 四次挥手(Four-Way Handshake)

  1. 客户端 → 服务器FIN(我想关闭连接)
  2. 服务器 → 客户端ACK(收到你的关闭请求)
  3. 服务器 → 客户端FIN(我也准备好了,可以关闭)
  4. 客户端 → 服务器ACK(确认,连接关闭)

📌 注意:如果一方还有数据要发,可以延迟发送自己的 FIN,所以有时看起来像"三次"(合并 ACK+FIN),但逻辑上仍是四步。

关掉浏览器页面会触发四次挥手吗?

不一定立刻触发,原因如下:

情况一:页面关闭 ≠ 立即关闭 TCP 连接

  • 浏览器可能复用连接 给其他标签页(比如你同时开着 github.comexample.com,它们共用连接池)。
  • 即使关闭标签页,浏览器也可能保持连接一段时间 (称为 连接保活 / keep-alive timeout),以便后续请求复用,减少握手开销。

情况二:浏览器主动关闭连接

  • 如果该连接没有被其他页面使用 ,且浏览器决定不再复用,它会主动发起 FIN,启动四次挥手。

  • 这通常发生在:

    • 页面完全关闭
    • 一段时间无活动(如 30--120 秒)
    • 浏览器内存压力大,主动清理连接

情况三:服务器先关闭

  • 服务器也有超时机制(如 Nginx 默认 keepalive_timeout 75s)。
  • 如果客户端长时间不发请求,服务器会先发 FIN,浏览器回应,完成挥手。

补充 2:强缓存与协商缓存

浏览器缓存主要分为两类:强缓存(也称强制缓存)协商缓存(也称对比缓存) 。它们通常协同工作,共同决定资源是否需要重新从服务器获取。

强缓存(Strong Cache)

强缓存由服务端通过响应头中的 Cache-Control 字段控制。一旦命中强缓存,浏览器会直接从本地缓存读取资源,不会向服务器发起任何请求

💡 历史补充

早期 HTTP/1.0 使用 Expires 字段(如 Expires: Wed, 21 Oct 2025 07:28:00 GMT)控制缓存过期时间。

HTTP/1.1 引入了更灵活的 Cache-Control优先级高于 Expires 。若两者同时存在,浏览器以 Cache-Control 为准。

常见的 Cache-Control 指令

max-age=<seconds> 资源在客户端可被缓存的最大时间(单位:秒)。例如max-age=31536000表示缓存一年。
no-cache 并非禁止缓存!而是要求每次使用缓存前必须向服务器验证资源是否更新(即走协商缓存流程)。
no-store 禁止任何形式的缓存,客户端和中间代理(如 CDN)都不能存储该响应。适用于敏感数据。

如何判断是否命中强缓存?

在浏览器开发者工具的 Network 面板中:

  • 若请求的 Size 列显示 (memory cache)(disk cache) ,且 没有发起网络请求(Status 为 200 但无 Timing 数据),说明命中了强缓存。
  • 此时请求不会出现在 Network 列表中(除非勾选"Disable cache")。

💡 小知识

浏览器会根据资源大小、访问频率等因素,决定将缓存存入内存 (速度快,关闭即失)还是磁盘(持久,速度稍慢)。

协商缓存(Revalidation Cache)

当强缓存失效(或被跳过)时,浏览器会发起请求,但会携带缓存验证头,询问服务器:"我本地有这个资源,它还有效吗?" 这就是协商缓存。

工作流程:

  1. 首次请求

    服务器返回资源,并在响应头中附带缓存标识,如:

    • ETag: "abc123"(基于内容生成的唯一标识)
    • Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT(资源最后修改时间)
  2. 后续请求

    浏览器在请求头中带上验证字段:

    • If-None-Match: "abc123"(对应 ETag
    • If-Modified-Since: Wed, 21 Oct 2024 07:28:00 GMT(对应 Last-Modified
  3. 服务器判断

    • 若资源未变 → 返回 304 Not Modified,浏览器使用本地缓存。
    • 若资源已变 → 返回 200 OK + 新资源,并更新缓存标识。

ETag vs Last-Modified

  • 优先使用 ETag,原因如下:

    • Last-Modified 精确到秒级,无法识别一秒内的多次修改。
    • 文件内容未变但修改时间变了(如重新部署),ETag(通常基于内容哈希)仍能识别为"未变",而 Last-Modified 会误判为"已更新"。

强缓存与协商缓存的配合策略

在实际应用中,两者通常组合使用

  • 静态资源(JS/CSS/图片) :设置长期强缓存(如 max-age=31536000),并通过文件名哈希(如 app.a1b2c3.js)解决更新问题。
  • HTML 文件 :通常设置 no-cache,确保每次加载最新入口,再由 HTML 引用带哈希的静态资源。

浏览器不同操作对缓存的影响:

用户操作 强缓存 协商缓存
页面跳转 / 前进后退 ✅ 生效 不触发(直接读缓存)
普通刷新(F5) ❌ 跳过 ✅ 触发(发送验证请求)
强制刷新(Ctrl+F5 或 Cmd+Shift+R) ❌ 跳过 ❌ 跳过(全新请求)

🛠️ 开发调试技巧

在 Chrome DevTools 的 Network 面板中勾选 "Disable cache" ,可临时禁用所有缓存,方便调试最新资源。

为什么需要缓存?

  • 网络请求的延迟和带宽成本远高于本地读取。
  • 静态资源(如 JS、CSS、图片)通常不频繁变动,非常适合缓存。
  • 合理的缓存策略可显著提升首屏加载速度、降低服务器负载。

前端与缓存的关系

在现代前端工程化中,我们常通过构建工具(如 Webpack)为静态资源添加内容哈希

html 复制代码
<!-- 构建前 -->
<script src="./app.js"></script>

<!-- 构建后 -->
<script src="./app.a1b2c3.js"></script>

原理:文件内容变化 → 哈希值变化 → 文件名变化 → 浏览器视为新资源 → 绕过旧缓存。

但在开发模式下,构建通常不设置强缓存,甚至协商缓存也是禁用的,为了方便我们改代码后立刻看到对应的效果。

如果在生产环境中,会自动有个时间较短的强缓存时间,并且协商缓存能默认存在。

🔧 为什么协商缓存能"默认存在"?

因为:

  • ETag 可以基于文件 inode + 修改时间 + 大小 自动生成;
  • Last-Modified 就是文件系统的修改时间;
  • 这些信息服务器天然拥有,所以默认就返回了。

对于强缓存这个 max-age 是平台决定的,作为前端无法修改(除非平台支持自定义头)。

不过在一些部署平台上,我们可以配置响应头(比如:Vercel、Netlify、Nginx、Express、Cloudflare Pages):

http 复制代码
# 对带 hash 的静态资源(.js, .css, .png...)
Cache-Control: public, max-age=31536000, immutable

# 对 index.html
Cache-Control: no-cache

这时:

  • 带 hash 的资源 → 强缓存生效 ✅
  • HTML → 每次验证(协商缓存或 no-cache)✅
相关推荐
10年前端老司机3 小时前
面试官爱问的 Object.defineProperty,90%的人倒在这些细节上!
前端·javascript
少年阿闯~~3 小时前
解决HTML塌陷的方法
前端·html
徐小夕3 小时前
花了4个月时间,我写了一款支持AI的协同Word文档编辑器
前端·vue.js·后端
岁月向前4 小时前
小组件获取主App数据的几种方案
前端
用户47949283569154 小时前
TypeScript 和 JavaScript 的 'use strict' 有啥不同
前端·javascript·typescript
恒创科技HK5 小时前
香港服务器速度快慢受何影响?
运维·服务器·前端
bubiyoushang8885 小时前
MATLAB实现直流电法和大地电磁法的一维正演计算
前端·javascript·matlab
Mintopia5 小时前
🧠 AIGC模型的增量训练技术:Web应用如何低成本迭代能力?
前端·javascript·aigc
Mintopia5 小时前
🧩 Next.js在国内环境的登录机制设计:科学、务实、又带点“国风味”的安全艺术
前端·javascript·全栈