从输入 URL 到页面展示到底发生了什么?
在地址栏敲下网址、按下回车,到页面完整出现在屏幕上,中间要经过 DNS 解析、TCP 建连、TLS 握手、HTTP 交互、服务器处理和浏览器渲染等一系列环节。这道题是面试常客,因为它能把从应用层到链路层的核心协议一次性串起来。
它解决什么问题
单独背协议容易断片:知道 TCP 有三次握手,却不知道它在访问网页时发生在哪一步;知道 DNS 是域名解析,却说不清谁来发查询、问谁要答案。”输入 URL 后发生了什么”给出的不是新知识点,而是一张地图——把每个协议放回它在链路中的位置,看清每一步”做什么、为什么需要它、和上下层怎么交接”。理解了这条主线,页面打开慢、HTTPS 配置错误、跨域失败这类实际问题,才有分段定位的框架。
核心原理:全流程鸟瞰
先把主线画出来,再逐段展开:
输入 URL → 查浏览器缓存 → DNS 解析(域名→IP) → TCP 三次握手 |
注意各层协议的衔接点:DNS 工作在应用层,它的产出是一个 IP 地址,这是向传输层交代”连谁”;TCP 在传输层,为 HTTP 提供可靠有序的字节流通道;TLS 插在 TCP 与 HTTP 之间,把明文通道升级为加密通道;HTTP 本身只定义报文格式,真正的传输全部委托给下层。数据在发送端自应用层向下逐层加头封装,到了对端再自链路层向上逐层解封装,每一层只读写自己的头部。
深入机制
DNS 解析:递归与迭代接力
- 做什么:把
www.example.com这样的人类可读域名,翻译成网络层真正用来寻址的 IP 地址。 - 为什么需要:路由器只认 IP,域名纯粹为记忆而存在,必须有一座”翻译机”。
- 怎么查:先查浏览器 DNS 缓存,再查操作系统缓存,再看本地
hosts文件;都没命中,才发起真正的 DNS 查询。客户端向本地 DNS 服务器(运营商或公共 DNS 如 114.114.114.114)发起的是递归查询——“你必须给我最终答案”;本地 DNS 服务器缓存未命中时,自己出面做迭代查询:先问根服务器拿到.com顶级域服务器地址,再问顶级域服务器拿到权威服务器地址,最后问权威服务器拿到目标 IP。结果逐级缓存后返回给浏览器。绝大多数查询走 UDP 53 端口,一次请求一次响应,省去握手开销。
TCP 三次握手
- 做什么:在双方之间建立一条全双工连接,同步各自的初始序列号。
- 为什么需要:HTTP 依赖可靠、有序的字节流,必须先确认”你收得到我、我也收得到你”再传数据。握手过程是:客户端发
SYN(seq=x);服务端回SYN+ACK(seq=y, ack=x+1);客户端再回ACK(ack=y+1),双方进入 ESTABLISHED。两次握手不够,因为服务端发出 SYN+ACK 后,无法确认客户端是否收到——第三次握手补上的正是服务端对”客户端接收能力”的确认。
TLS 握手(HTTPS 才有)
- 做什么:验证服务器身份,并在公开信道上协商出只有双方知道的会话密钥。
- 为什么需要:HTTP 是明文协议,链路上任何中间节点都能窃听、篡改、冒充,TLS 在 TCP 之上、HTTP 之下补上机密性、完整性与身份认证。
- 怎么握:以 TLS 1.2 + ECDHE 为例,客户端发 ClientHello(版本、密码套件、随机数);服务端回 ServerHello、证书链和自己的临时 ECDHE 公钥(用证书私钥签名);客户端用 CA 信任链验证证书后,也生成临时 ECDHE 公钥发过去,双方各自算出同一个预主密钥,再派生会话密钥。此后 HTTP 报文全部用这把对称密钥加密。TLS 1.3 把完整握手压缩到 1-RTT,并砍掉了静态 RSA 密钥交换。
HTTP 请求与服务器处理
- 做什么:浏览器把”我要什么资源”按固定格式写成报文发出去,服务器处理后把结果写回来。
- 一个典型的 HTTP/1.1 请求:
GET /index.html |
- 其中
Host是 HTTP/1.1 的强制头,因为同一 IP 上可能虚拟托管多个站点,仅靠 IP 无法区分。服务器一侧的处理链路是:Nginx 等 Web 服务器接收并解析报文,按路径把请求分发给后端应用,应用执行业务逻辑(查缓存、读数据库、调下游服务),最后把结果封装成响应报文——状态行(如HTTP/1.1 200 OK)、响应头(Content-Type、Cache-Control、Set-Cookie)加响应体(HTML、JSON 或二进制)。
浏览器解析渲染
- 做什么:把拿到的 HTML 文本变成屏幕上的像素。
- 为什么讲它:页面”能看”不代表”加载完”,渲染管线的阻塞行为直接决定首屏快慢。流程是:解析 HTML 标签构建 DOM 树;下载并解析 CSS 构建 CSSOM 树;两者合并成只含可见节点的渲染树(
display: none的不在内);再做布局算出每个节点的位置大小,绘制成像素,最后合成各图层输出画面。CSS 会阻塞渲染(样式没算完不能画),JS 默认阻塞 HTML 解析(脚本可能改 DOM),所以工程上才有 CSS 放头部、JS 加defer的约定。子资源加载时,HTTP/1.1 浏览器对单域名最多开 6 条并发连接,HTTP/2 则在一条连接上多路复用。
工程实践
- 分段定位慢:页面慢先别猜,按 DNS 耗时、TCP 建连、TLS 握手、首字节时间(TTFB)、资源下载、渲染六段分别取证。浏览器 DevTools 的 Network 面板天然按这个分段展示瀑布图。
- 缓存优先:解析 URL 后浏览器先查 HTTP 缓存——
Cache-Control: max-age未过期直接命中强缓存,完全不走网络;否则带If-None-Match发协商请求,服务端返304就用本地副本。 - 连接复用:HTTP/1.1 默认 keep-alive,一条连接连续传多个请求,省去重复握手;HTTP/2 多路复用解决了应用层队头阻塞,但 TCP 层丢包仍会卡住所有流,这是 HTTP/3 转向 QUIC(基于 UDP)的动机。
对比与权衡
| 阶段 | 核心协议/机制 | 这层出问题时的典型现象 | 常用排查手段 |
|---|---|---|---|
| DNS 解析 | DNS(UDP/53) | 首次访问慢、域名打不开 | dig/nslookup |
| TCP 建连 | TCP 三次握手 | 连接超时、SYN 重传 | ping、telnet ip 80、抓包 |
| TLS 握手 | TLS 1.2/1.3 | 证书告警、握手失败 | openssl s_client |
| HTTP 交互 | HTTP/1.1、2、3 | 状态码异常、TTFB 高 | DevTools、服务端日志 |
| 渲染 | DOM/CSSOM/合成 | 白屏久、卡顿 | Performance 面板 |
面试视角
DNS 的递归查询和迭代查询有什么区别?
看”谁跑腿”。客户端问本地 DNS 服务器是递归:只问一次,对方必须返回最终结果。本地 DNS 服务器自己查根、顶级域、权威服务器是迭代:每次只拿到”下一个该问谁”,多跑几趟。实际链路是两者接力。
TCP 握手为什么是三次,两次行不行?
不行。握手的本质是双方都要确认对方的收发能力。两次握手后,服务端无法确认客户端收到了自己的 SYN+ACK,若这个确认丢失,客户端认为连接没建立,服务端却可能空等甚至白白分配资源。
HTTPS 比 HTTP 在流程上多了什么?
TCP 建连之后、HTTP 请求之前多了 TLS 握手:验证证书确认服务器身份,用 ECDHE 协商会话密钥。代价是 1-2 个 RTT 的额外延迟,可用会话复用和 TLS 1.3 缓解。
为什么建议 CSS 放头部、JS 放底部或加 defer?
CSSOM 没建好就无法构建渲染树,CSS 加载慢会直接推迟首屏,所以要尽早加载;JS 默认阻塞 HTML 解析且可能读写 DOM,放底部或加 defer 可以让解析先跑完,避免白屏。
小结
- 主线流程:解析 URL 与查缓存 → DNS 解析 → TCP 握手 → TLS 握手(HTTPS)→ HTTP 请求响应 → 浏览器渲染 → 连接复用或关闭。
- DNS 是递归加迭代的接力:客户端递归问本地 DNS,本地 DNS 迭代问根、顶级域、权威服务器。
- TLS 插在 TCP 与 HTTP 之间,负责身份认证与密钥协商;HTTP 只管报文格式,可靠传输和安全全部委托给下层。
- 浏览器渲染走 DOM → CSSOM → 渲染树 → 布局 → 绘制 → 合成管线,CSS 阻塞渲染、JS 阻塞解析是性能优化的出发点。
- 遇到”页面慢”,按 DNS、TCP、TLS、TTFB、下载、渲染六段定位,比凭感觉猜有效得多。

