19

一文摸透从输入URL到页面渲染的过程

 4 years ago
source link: http://www.cnblogs.com/AhuntSun-blog/p/12641050.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

一文摸透从输入 URL 到页面渲染的过程

从输入 URL 到页面渲染需要 Chrome 浏览器的多个进程配合,所以我们先来谈谈现阶段 Chrome 浏览器的多进程架构。

一、 Chrome 架构

目前 Chrome 采用的是多进程的架构模式,可分为主要的五类进程,分别是:浏览器( Browser )主进程、 GPU 进程、网络( NetWork )进程、多个渲染进程和多个插件进程;

yARBruv.png!web

  • 浏览器进程 。主要负责界面显示、用户交互、子进程管理,同时提供存储等功能。
  • 渲染进程 。核心任务是将 HTMLCSSJavaScript 转换为用户可以与之交互的网页,排版引擎 BlinkJavaScript 引擎 V8 都是运行在该进程中,默认情况下, Chrome 会为每个 Tab 标签创建一个渲染进程。出于安全考虑,渲染进程都是运行在沙箱模式下。
  • GPU 进程 。其实, Chrome 刚开始发布的时候是没有 GPU 进程的。而 GPU 的使用初衷是为了实现 3D CSS 的效果,只是随后网页、 ChromeUI 界面都选择采用 GPU 来绘制,这使得 GPU 成为浏览器普遍的需求。最后, Chrome 在其多进程架构上也引入了 GPU 进程。
  • 网络进程 。主要负责页面的网络资源加载,之前是作为一个模块运行在浏览器进程里面的,直至最近才独立出来,成为一个单独的进程。
  • 插件进程 。主要是负责插件的运行,因插件易崩溃,所以需要通过插件进程来隔离,以保证插件进程崩溃不会对浏览器和页面造成影响

了解了 Chrome 的多进程架构,就能够从宏观上理解从输入 URL 到页面渲染的过程了,这个过程主要分为 导航阶段渲染阶段

二、导航阶段

Ⅰ.浏览器主进程

1.用户输入 URL

  • 1、 浏览器进程检查 url ,组装协议,构成完整的 url ,这时候有两种情况:
    • 输入的是搜索内容:地址栏会使用浏览器默认的搜索引擎,来合成新的带搜索关键字的 URL
    • 输入的是请求 URL :地址栏会根据规则,给这段内容加上协议,合成为完整的 URL
  • 2、 浏览器进程通过进程间通信( IPC )把 url 请求发送给网络进程;

Ⅱ.网络进程

2. URL 请求过程

  • 3、 网络进程接收到 url 请求后检查本地缓存是否缓存了该请求资源,如果有则将该资源返回给浏览器进程;

这里涉及到浏览器的缓存策略问题,有兴趣的可以上网查阅相关资料。

  • 4、准备 IP 地址和端口:进行 DNS 解析时先查找缓存,没有再使用 DNS 服务器解析,查找顺序为:

    • 浏览器缓存;
    • 本机缓存;
    • hosts 文件;
    • 路由器缓存;
    • ISP DNS 缓存;
    • DNS 递归查询(本地 DNS 服务器 -> 权限 DNS 服务器 -> 顶级 DNS 服务器 -> 13 台根 DNS 服务器)
  • 5、等待 TCP 队列:浏览器会为每个域名最多维护 6TCP 连接,如果发起一个 HTTP 请求时,这 6TCP 连接都处于忙碌状态,那么这个请求就会处于排队状态;解决方案:

    • 采用域名分片技术:将一个站点的资源放在多个( CDN )域名下面。
    • 升级为 HTTP2 ,就没有 6TCP 连接的限制了;
  • 6、通过三次握手建立 TCP 连接:

    baeuEvj.png!web

    • 第一次: 客户端先向服务器端发送一个同步数据包,报文的 TCP 首部中:标志位: 同步 SYN 1 ,表示这是一个请求建立连接的数据包;序号 Seq=xx 为所传送数据的第一个字节的序号,随后进入 SYN-SENT 状态;

    标志位值为 1 表示该标志位有效。

    • 第二次: 服务器根据收到数据包的 SYN 标志位判断为建立连接的请求,随后返回一个确认数据包,其中标志位 SYN=1ACK=1 ,序号 seq=y ,确认号 ack=x + 1 表示收到了客户端传输过来的 x 字节数据,并希望下次从 x+1 个字节开始传,并进入 SYN-RCVD 状态;

    这里要区分标志位 ACK 和确认号 ack

    • 第三次: 客户端收到后,再给服务器发送一个确认数据包,标志位 ACK=1 ,序号 seq=x+1 ,确认号 ack=y+1 ,随后进入 ESTABLISHED 状态;

    服务器端收到后,也进入 ESTABLISHED 状态,由此成功建立了 TCP 连接,可以开始数据传送;

    • 为什么要第三次挥手? 避免服务器等待造成 资源浪费 ,具体原因:

    如果没有最后一个数据包确认(第三次握手), A 先发出一个建立连接的请求数据包,由于网络原因绕远路了。 A 经过设定的超时时间后还未收到 B 的确认数据包。

    于是发出第二个建立连接的请求数据包,这次网路通畅, B 的确认数据包也很快就到达 A 。于是 AB 开始传输数据;

    过了一会 A 第一次发出的建立连接的请求数据包到达了 BB 以为是再次建立连接,所以又发出一个确认数据包。由于A已经收到了一个确认数据包,所以会忽略 B 发来的第二个确认数据包,但是 B 发出确认数据包之后就要一直等待 A 的回复,而 A 永远也不会回复。

    由此造成服务器资源浪费,这种情况多了 B 计算机可能就停止响应了。

  • 7、构建并发送 HTTP 请求信息;

  • 8、服务器端处理请求;

  • 9、客户端处理响应,首先检查服务器响应报文的状态码:

    • 如果是 301/302 表示服务器已更换域名需要重定向,这时网络进程会从响应头的 Location 字段里面读取重定向的地址,然后再发起新的 HTTP 或者 HTTPS 请求,跳回第 4 步。
    • 如果是 200 ,就检查 Content-Type 字段,值为 text/html 说明是 HTML 文档,是 application/octet-stream 说明是文件下载;

fANBnq2.png!web

  • 10、 请求结束,当通用首部字段 Conection 不是 Keep-Alive 时,即不为 TCP 长连接时,通过四次挥手断开 TCP 连接:

vQ7BVre.png!web

  • 第一次: 客户端(主动断开连接)发送数据包给服务器,其中标志位 FIN=1 ,序号位 seq=u ,并停止发送数据;
  • 第二次: 服务器收到数据包后,由于还需传输数据,无法立即关闭连接,先返回一个标志位 ACK=1 ,序号 seq=v ,确认号 ack=u+1 的数据包;
  • 第三次: 服务器准备好断开连接后,返回一个数据包,其中标志位 FIN=1 ,标志位 ACK=1 ,序号 seq=w ,确认号 ack=u+1
  • 第四次: 客户端收到数据包后,返回一个标志位 ACK=1 ,序号 seq=u+1 ,确认号 ack=w+1 的数据包。

由此通过四次挥手断开 TCP 连接。

详细过程参见: 详解TCP连接的“三次握手”与“四次挥手”(上)

  • 为什么要四次挥手? 由于服务器不能马上断开连接,导致 FIN 释放连接报文与 ACK 确认接收报文需要分两次传输,即第二次和第三次"挥手";

3.准备渲染进程

  • 11、 准备渲染进程:浏览器进程检查当前 url 是否与之前打开了渲染进程的页面的根域名相同,如果相同,则复用原来的进程,如果不同,则开启新的渲染进程;

4.提交文档

  • 12、 提交文档:
    • 渲染进程 准备好后, 浏览器渲染进程 发起“ 提交文档 ”的消息, 渲染进程 接收到消息后与 网络进程 建立传输数据的“ 管道
    • 渲染进程 接收完数据后,向浏览器发送“ 确认提交
    • 浏览器进程 接收到确认消息后更新浏览器界面状态: 安全状态 地址栏 url 前进后退的历史状态 更新 web 页面

mYZFzam.png!web

三、渲染阶段

在渲染阶段通过 渲染流水线 在渲染进程的主线程和合成线程配合下,完成页面的渲染;

Ⅲ.渲染进程

qU3M3qu.png!web

渲染进程中的主线程部分

5.构建 DOM

  • 13、先将请求回来的数据解压,随后 HTML 解析器将其中的 HTML 字节流 通过 分词器 拆分为一个个 Token ,然后生成节点 Node ,最后解析成浏览器识别的 DOM 树结构。

    可以通过 Chrome 调试工具的 Console 选项打开控制台输入 document 查看 DOM 树;

渲染引擎还有一个 安全检查模块XSSAuditor ,是用来 检测词法安全 的。在分词器解析出来 Token 之后,它会检测这些模块是否安全,比如 是否引用了外部脚本 是否符合 CSP 规范 是否存在跨站点请求 等。如果出现不符合规范的内容, XSSAuditor 会对该脚本或者下载任务 进行拦截

首次解析 HTML渲染进程 会开启一个 预解析线程 ,遇到 HTML 文档中内嵌的 JavaScriptCSS 外部引用就会同步提前下载这些文件,下载时间以最后下载完的文件为准。

IR7zQrz.png!web

6.构建 CSSOM

  • 14、 CSS 解析器将 CSS 转换为浏览器能识别的 styleSheets 也就是 CSSOM :可以通过控制台输入 document.styleSheets 查看;

    这里要考虑一下阻塞的问题,由于 JavaScript 有修改 CSSHTML 的能力,所以,需要先等到 CSS 文件下载完成并生成 CSSOM ,然后再执行 JavaScript 脚本,最后再继续构建 DOM 。由于这种阻塞,导致了 解析白屏

优化方案:

  • 移除 jscss 的文件下载 :通过内联 JavaScript 、内联 CSS
  • 尽量减少文件大小 :如通过 webpack 等工具 移除 不必要的 注释 ,并 压缩 js 文件
  • 将不进行 DOM 操作或 CSS 样式修改的 JavaScript 标记上 sync 或者 defer 异步引入;
  • 使用媒体查询属性 :将大的 CSS 文件拆分成多个不同用途的 CSS 文件,只有在特定的场景下才会加载特定的 CSS 文件。

可以通过浏览器调试工具的 Network 面板中的 DOMContentLoaded 查看最后生成 DOM 树所需的时间;

Y7Zjqmq.png!web

u6z6RnB.png!web

7.样式计算

  • 15、 转换样式表中的属性值,使其标准化。比如将 em 转换为 pxcolor 转换为 rgb
  • 16、 计算 DOM 树中每个节点的具体样式,这里遵循 CSS 的继承和层叠规则;可以通过 Chrome 调试工具的 Elements 选项的 Computed 查看某一标签的最终样式;

fUj2I3E.png!web

8.布局阶段

  • 17、创建布局树,遍历 DOM 树中的所有节点,去掉所有隐藏的节点(比如 head ,添加了 display:none 的节点),只在布局树中保留可见的节点。

  • 18、计算布局树中节点的坐标位置(较复杂,这里不展开);

9.分层

  • 19、 对布局树进行分层,并生成分层树( Layer Tree ),可以通过 Chrome 调试工具的 Layer 选项查看。分层树中每一个节点都直接或间接的属于一个图层(如果一个节点没有对应的层,那么这个节点就从属于父节点的图层)

INF3Uvy.png!web

10.图层绘制

  • 20、 为每个图层生成绘制列表(即绘制指令),并将其提交到合成线程。以上操作都是在渲染进程中的主线程中进行的,提交到合成线程后就不阻塞主线程了;

7zmqmuM.png!web

渲染进程中的合成线程部分

niyIRrv.png!web

11.切分图块

21、合成线程将图层切分成大小固定的图块( 256x256 或者 512x512 )然后 优先绘制 靠近视口的图块,这样就可以大大加速页面的显示速度;

6riemaV.png!web

Ⅳ. GPU 进程

12.栅格化操作

  • 22、光栅化线程池 中将 图块 转换成 位图 ,通常这个过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫 快速栅格化 ,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。

NVzAfiI.png!web

Ⅴ.浏览器主进程

13.合成与显示

  • 23、 合成:一旦所有图块都被光栅化, 合成线程 就会将它们合成为一张图片,并生成一个绘制图块的命令——“ DrawQuad ”,然后将该命令提交给浏览器进程。

注意了:合成的过程是在渲染进程的 合成线程 中完成的,不会影响到渲染进程的 主线程 执行;

  • 24、 显示:浏览器进程里面有一个叫 viz 的组件,用来接收合成线程发过来的 DrawQuad 命令,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上。

到这里,经过这一系列的阶段,编写好的 HTMLCSSJavaScript 等文件,经过浏览器就会显示出漂亮的页面了。

参考资料: 浏览器工作原理与实践


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK