经典网络面试题--从输入 URL 到页面展示完整流程及其底层原理
注:本篇博客的内容来源自极客时间李兵老师的“浏览器工作原理与实践”课程的笔记总结
基本背景知识介绍
最新的 Chrome 进程架构
下图是最新的Chrome进程架构图:
从图中可以看出,最新的 Chrome 浏览器包括:1 个浏览器(Browser)主进程、1 个 GPU 进程、1 个网络(NetWork)进程、多个渲染进程和多个插件进程。
下面具体介绍这几个进程的功能:
- 浏览器进程: 主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
- 渲染进程: 核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页,排版引擎 Blink 和 JavaScript 引擎 V8 都是运行在该进程中,默认情况下,Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。
- GPU 进程: 其实,Chrome 刚开始发布的时候是没有 GPU 进程的。而 GPU 的使用初衷是为了实现 3D CSS 的效果,只是随后网页、Chrome 的 UI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。最后,Chrome 在其多进程架构上也引入了 GPU 进程。
- 网络进程: 主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。
- 插件进程: 主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响。
完整的 TCP 连接过程
下图为完整的 TCP 连接过程图:
从图中不难看出,一个完整的 TCP 连接的生命周期包括了“建立连接”“传输数据”和“断开连接”三个阶段。
完整的HTTP 请求流程
下图为完整的HTTP 请求流程图:
从图中可以看到,浏览器中的 HTTP 请求从发起到结束一共经历了如下八个阶段:构建请求、查找缓存、准备 IP 和端口、等待 TCP 队列、建立 TCP 连接、发起 HTTP 请求、服务器处理请求、服务器返回请求和断开连接。
完整的渲染流水线
下图为完整的渲染流程图:
此完整的渲染流程大致可总结为如下:
- 渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。
- 渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。
- 创建布局树,并计算元素的布局信息。
- 对布局树进行分层,并生成分层树。
- 为每个图层生成绘制列表,并将其提交到合成线程。
- 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
- 合成线程发送绘制图块命令 DrawQuad 给浏览器进程。
- 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。
从输入 URL 到页面展示完整流程
介绍完前面所需要的三个基本知识背景后(HTTP 请求流程包括了TCP连接),现在只需要将其串起来就能回答从输入 URL 到页面展示完整流程。
同样,先给出从输入 URL 到页面展示完整流程示意图:
由图中不难发现,最外层是三种Chrome进程,而网络进程所执行的操作就是HTTP 请求流程并且渲染进程所执行的操作就是渲染流程。
下面,先给出此流程图精简版的描述:
- 首先,浏览器进程接收到用户输入的 URL 请求,浏览器进程便将该 URL 转发给网络进程。
- 然后,在网络进程中发起真正的 URL 请求。
- 接着网络进程接收到了响应头数据,便解析响应头数据,并将数据转发给浏览器进程。
- 浏览器进程接收到网络进程的响应头数据之后,发送“提交导航 (CommitNavigation)”消息到渲染进程。
- 渲染进程接收到“提交导航”的消息之后,便开始准备接收 HTML 数据,接收数据的方式是直接和网络进程建立数据管道。
- 最后渲染进程会向浏览器进程“确认提交”,这是告诉浏览器进程:“已经准备好接受和解析页面数据了”。
- 浏览器进程接收到渲染进程“提交文档”的消息之后,便开始移除之前旧的文档,然后更新浏览器进程中的页面状态。
下面还有一个结合前面基础知识的更详细的流程描述:
用户输入
- 用户在地址栏按下回车,检查输入(关键字 or 符合 URL 规则),组装完整 URL;
- 回车前,当前页面执行 onbeforeunload 事件;
- 浏览器进入加载状态。
URL 请求
- 浏览器进程通过 IPC 把 URL 请求发送至网络进程;
- 查找资源缓存(有效期内);
- DNS 解析(查询 DNS 缓存);
- 进入 TCP 队列(单个域名 TCP 连接数量限制);
- 创建 TCP 连接(三次握手);
- HTTPS 建立 TLS 连接(client hello, server hello, pre-master key 生成『对话**』);
- 发送 HTTP 请求(请求行[方法、URL、协议]、请求头 Cookie 等、请求体 POST);
- 接受请求(响应行[协议、状态码、状态消息]、响应头、响应体等);
- 状态码 301 / 302,根据响应头中的 Location 重定向;
- 状态码 200,根据响应头中的 Content-Type 决定如何响应(下载文件、加载资源、渲染 HTML)。
准备渲染进程
- 根据是否同一站点(相同的协议和根域名),决定是否复用渲染进程。
提交文档
- 浏览器进程接受到网路进程的响应头数据,向渲染进程发送『提交文档』消息;
- 渲染进程收到『提交文档』消息后,与网络进程建立传输数据『管道』;
- 传输完成后,渲染进程返回『确认提交』消息给浏览器进程;
- 浏览器接受『确认提交』消息后,移除旧文档、更新界面、地址栏,导航历史状态等;
- 此时标识浏览器加载状态的小圆圈,从此前 URL 网络请求时的逆时针选择,即将变成顺时针旋转(进入渲染阶段)。
渲染
渲染流水线
构建 DOM 树
- 输入:HTML 文档;
- 处理:HTML 解析器解析;
- 输出:DOM 树。
样式计算
- 输入:CSS 文本;
- 处理:属性值标准化,每个节点具体样式(继承、层叠);
- 输出:styleSheets(CSSOM)。
布局(DOM 树中元素的计划位置)
- DOM & CSSOM 合并成渲染树;
- 布局树(DOM 树中的可见元素);
- 布局计算。
分层
- 特定节点生成专用图层,生成一棵图层树(层叠上下文、Clip,类似 PhotoShop 里的图层);
- 拥有层叠上下文属性(明确定位属性、透明属性、CSS 滤镜、z-index 等)的元素会创建单独图层;
- 没有图层的 DOM 节点属于父节点图层;
- 需要剪裁的地方也会创建图层。
绘制指令
- 输入:图层树;
- 渲染引擎对图层树中每个图层进行绘制;
- 拆分成绘制指令,生成绘制列表,提交到合成线程;
- 输出:绘制列表。
分块
- 合成线程会将较大、较长的图层(整个屏显示不完,大部分不在视口内)划分为图块(tile, 256256, 512512)。
光栅化(栅格化)
- 在光栅化线程池中,将视口附近的图块优先生成位图(栅格化执行该操作);
- 快速栅格化:GPU 加速,生成位图(GPU 进程)。
合成绘制
- 绘制图块命令——DrawQuad,提交给浏览器进程;
- 浏览器进程的 viz 组件,根据DrawQuad命令,绘制在屏幕上。
结语
此篇博客仅只是基于此问题进行详细的展开介绍,但是仍有很多细节知识点只是捎带而过,所以有想了解更多详细内容的伙伴可以自行百度搜索也可以去看看李兵老师的课程,当然也同样欢迎评论区与我一块讨论。