讲述浏览器从URL到页面展示的 “详尽” 过程

1,304 阅读15分钟

前言

浏览器问题是前端基础面试的重要组成部分之一。
大多数人可能和我一样只能零散地回答一些知识点,并不能将这些知识点串联成线,极其容易基础面就被Pass

今天,就和大家好好地抠一抠细节,早日都成为面霸

(本篇内容会持续更新💕,如有遗漏错误,评论区批斗⛏)

线程与进程

浏览器从07年开始摒弃单进程架构,采用多进程架构模式,在稳定流畅安全上都有了很大的提升。那什么进程呢?进程是资源分配的最小单位,用来启动和管理线程。那什么是线程呢?线程是CPU调度的最小单位,用来执行任务,依附于进程
知乎上biaodianfu大佬的比喻很巧妙:进程=火车,线程=车厢。

线程与进程总结有如下个特点:

  1. 进程与线程是一对多的关系,线程需在进程下执行。
  2. 任意线程执行出错,都会反噬进程,导致整个进程崩溃。
  3. 线程之间资源可共享。进程间互不影响,相互隔离,通信可使用IPC机制。
  4. 当一个进程关闭之后,操作系统会回收进程所占用的内存。
  5. 进程使用的内存地址可上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。
  6. 进程使用的内存地址可以限定使用量。

多进程架构

最新Chrome多进程架构包含1个浏览器主进程、1个GPU进程、1个网络进程、多个渲染进程和多个插件进程。图引自《浏览器工作原理与实践》李兵👍

IMG_2595.png.JPG

浏览器主进程: 负责界面显示、用户交互,子进程管理、存储等功能。

渲染进程: 将HTML、CSS、JavaScript 转换用户可操作的页面。布局引擎(Blink)和JavaScript(V8)均在这个进程。每个Tab页面(不同根域名)都有其唯一的渲染进程。

GPU进程: 早期是没有GPU进程。GPU初衷为了渲染3D CSS,后续慢慢演变UI界面均由GPU进程绘制。

网络进程: 负责页面网络资源的加载,早期为浏览器主进程一个模块,而如今独立出来,成为单独的进程。

插件进程: 负责插件运行。因插件极易崩溃,所以通过插件进程来隔离,保证不会对主进程产生影响。

注意图中右下角到有个sandbox,称安全沙箱,用于给操作系统加锁,避免插件或者脚本读取或者篡改硬盘数据,这也是多进程架构模式增加浏览器安全性的原因。

从输入URL到页面展示

浏览器进程知识点科普结束~,进入正题。

用户输入

URL语法规则: scheme:[//authority]path[?query][#fragment]

用户在地址栏输入内容,按下回车键,浏览器主进程判断输入内容是否符合URL规则

若符合URL规则,整合URL + 对应协议头(http/https)形成完整的URL,进行URL请求;否则,拼接默认搜索引擎 + 文本进行搜索。

URL请求

浏览器主进程通过IPC发送URL请求至网络进程。

经历 DNS解析 -> TCP连接 -> 构建请求 -> 解析响应数据 -> 关闭TCP连接。

DNS解析

如下过程,只要有一步找到域名拿到IP,便可结束查询。

  1. 查询浏览器缓存是否有域名解析后的IP地址
  2. 查询本地hosts文件是否有 域名和IP 的映射关系。
  3. 查询本地DNS解析器缓存是否有 域名和IP 的映射关系。
  4. 查询本地DNS服务器缓存是否有 域名和IP 的映射关系。
  5. 本地DNS服务器根据设置的转发器(未用转发模式/使用转发模式)进行查询。 未用转发模式:本地DNS服务器把请求发至根DNS服务器,根DNS服务器根据域名查询是哪个顶级DNS服务器授权管理,返回其 IP 给本地 DNS 服务器。本地DNS服务器联系这台顶级DNS服务器 ,若顶级DNS服务器无法解析,则返回它下一级DNS服务器的IP给本地DNS,至找到域名。这个过程称DNS迭代查询

使用转发模式:本地DNS服务器会把请求转发至上一级DNS服务器,由上一级服务器解析,若上级服务器无法解析,则转至上上级,如此反复,至找到域名,把结果返回给本地DNS服务器,再由本地DNS服务器返回给客户机。

TCP连接

网络进程拿到 IP 后,查看URL请求是否携带端口号,若有,则从URL中解析出来,创建新的套接字发起 TCP 连接请求。Tips: http 默认端口80, https 默认端口443

建立TCP连接会先经历三次握手,目的在于确认双方收发能力是否正常。
初始状态:客户端处于closed状态,服务端处于listen状态。

  1. 客户端发送带有 SYN=1 标记、初始化序列号 ISN(c) 的报文给服务器,发送完毕,客户端处于 SYN_SENT 状态,等待服务器确认。
  2. 服务端收到客户端带有 SYN=1 的报文后,向客户端发送带有 SYN=1 、 ACK=1、ack=ISN(c) + 1 ,以及初始化序列号 ISN(s) 的报文,发送完毕,服务器处于 SYN_RCVD 状态。
  3. 客户端收到后,检查应答字段 ack 是否为 ISN(c)+1 , ACK 是否为 1 ,如果正确,向服务器发送带有 ACK=1 、 ack=ISN(s)+1 的报文。服务端收到客户端的包,检查 ack 是否为 ISN(s)+1 , ACK 是否为 1 ,如果正确,连接建立成功。客户端和服务端进入ESTABLISHED状态,开始传输数据。

问:为何不能是两次握手?
答:可能连接请求因网络滞留了一段时间,以至于到达服务端已经失效了,但是服务端会误认为是个新的连接请求,于是向客户端发出确认报文,同意建立连接。假设采用两次握手,那么服务端发出确认报文,表示连接已建立,但客户端并没有发出确认建立的连接,也不会向服务端发送任何数据,服务端因连接占用导致资源浪费。

构建请求

成功建立TCP连接后,网络进程会构建请求头信息(携带Cookie等身份认证信息),向服务端发送请求头信息。在发送请求之前,会现在浏览器缓存中查询是否有要请求的文件,若有,则拦截请求,返回该资源请求副本,并结束请求。

浏览器缓存有强缓存和协商缓存,请求资源先判断是否命中强缓存,再判断是否命中协商缓存。

强缓存: 根据本地缓存资源header中的ExpiresCache-Control字段判断是否命中强缓存,如果命中,则直接使用缓存中的资源。Cache-Control 字段优先级于 Expires 字段。当下述几种情况,进入协商缓存,优先级由高到低。

  1. Cache-Control: no-cache
  2. Cache-Control:max-age=xxx。比较当前时间与上一次状态码为 200 时的时间差,若超过设置的值,进入协商缓存。没超过,命中强缓存。
  3. 没有Cache-Control,依据Expires的值与时间差进行比较,规则同上一条。

from disk cache 和 from memory cache 的区别?
两者都属于强缓存。from disk cache 是磁盘缓存,表示在之前某个时间点已经加载过该资源,直接从硬盘中读取缓存,关闭浏览器后,缓存依然存在。from memory cahce 是内存缓存,速度比 from disk cache 要快,但浏览器关闭后内存缓存则会销毁。

协商缓存: 当强缓存没有命中,浏览器会发送一个请求到服务器,服务器根据 header 中的Last-Modify/If-Modify-SinceETag/If-None-Match.来判断是否命中缓存。如果命中,则返回 304 ,告诉浏览器资源未更新,可使用本地的缓存。

Last-Modify: 最后修改时间,浏览器第一次给服务器发请求后,会在响应头加上这个字段。浏览器接收后,下次请求头会带上If-Modify-Since字段,值即为服务器传来的最后修改时间。
服务器拿请求头中的If-Modify-Since字段,与服务器资源最后修改时间做对比:

  • 若小于最后修改时间,需更新,返回新的资源。
  • 若大于等于最后修改时间,返回304,使用缓存。

ETag: 根据当前文件内容,给文件生成唯一标识,只要有改动,这个值就会变。服务器会通过响应头把值返回到浏览器。浏览器则再下次请求把值赋给If-None-Match字段塞到请求头中。
服务器收到If-None-Match字段,与该资源的ETag比较。

  • 若两者不一样,需更新,返回新的资源。
  • 否则,返回304,使用缓存。

Last-Modify性能优于EtagEtag精确度高于Last-Modify。若同时存在,服务端优先支持ETag

解析响应数据

网络进程接收到响应头和响应信息,并解析响应内容。
若响应头中状态码是 301 或 302 ,则从响应头的Location字段获取重定向 URL,再次发生新一轮HTTP或HTTPS请求;( 301 是永久重定向,302 是 暂时 重定向)
若 200 则继续处理响应数据,浏览器进程也会将响应数据进行缓存,降低服务器负担,加速下次用户访问速度。根据Content-Type字段判断返回是text/html文件还是application/octet-stream下载流。若是下载流,则提交给浏览器主进程的下载管理器,否则,进行下一步渲染页面。

关闭TCP连接

为了避免客户端与服务器双方的资源占用和损耗,当双方没有请求或响应传递时,任意一方都可发起关闭请求。而关闭TCP连接需要经过四次挥手,由于TCP的半关闭特性造成的。(提供连接的一端在结束它的发送后还能接收来自另一端的数据)

初始状态:双方处于ESTABLISHED状态。

  1. 客户端向服务端发送携带 FIN=1 、初始化序列号 seq=u 的报文,并停止再发送数据,主动关闭 TCP 连接,进入FIN_WAIT_1状态,等待服务端确认。
  2. 服务端收到 FIN 之后,会向客户端发送 ACK=1、ack=u+1、seq=v 的报文。,服务端进入CLOSE_WAIT状态,此时TCP处于半关闭状态,客户端到服务端的连接释放,进入FIN_WAIT_2状态,等待服务端发出连接释放报文段。
  3. 如果服务端也想断开连接,向客户端发送 FIN=1、ACK=1、ack=u+1、初始化序列号 seq=w 的报文。服务端处于LAST_ACK状态,等待客户端确认。
  4. 客户端收到 FIN 报文,向服务端发送ACK=1、ack=w+1 的报文,客户端处于TIME_WAIT状态,等待2MSL后,客户端才进入CLOSED状态。服务端收到 ACK 报文后,就处于CLOSED状态。

问:为什么是 2MSL ?
答:MSL(Maximum Segment Lifetime)最长报文寿命时间。客户端发送ACK数据包最长存活 1MSL,如果丢失,服务端可再发送FIN包,FIN包最长存活也是1MSL。2MSL的时间可确保客户端能再收到服务端的FIN包,从而再发送最后的ACK数据包,保证双方最后可正常进入CLOSED状态。

渲染页面

新页面若和标签页的页面具备相同的根域名,则复用进程;否则,单独创建新的渲染进程。

浏览器主进程收到网络进程的响应数据后,会向渲染进程发起提交文档的消息。渲染进程收到提交文档消息,会和网络进程建立传输数据管道。等文档传输完成之后,渲染进程会返回确认提交的消息给浏览器进程;浏览器进程在收到确认提交消息后,会更新浏览器界面状态,包括了安全状态、地址栏URL、前进后退的历史状态,并更新 Web。

构建 DOM 树

浏览器无法直接理解和使用 HTML,所以需要由HTML解析器将HTML转化为浏览器能够理解的 DOM 树。

DOM 树描述了文档的内容。<html>元素是第一个标签也是文档树的根节点。树反映了不同标记之间的关系和层次结构。嵌套在其他标记中的标记是子节点。DOM 节点的数量越多,构建 DOM 树所需的时间就越长。

DOM.gif

当解析器发现非阻塞资源,例如 CSS 文件或者是 图片,会请求这些资源并且继续解析。但是对于script标签(无 async 、defer、type="module")会阻塞渲染并停止 HTML 解析。尽管浏览器的预加载扫描器加速了这个过程,但过多的脚本仍然是一个重要的瓶颈。

type="module" 与 defer 作用相同,async 与 defer 区别如下。

284aec5bb7f16b3ef4e7482110c5ddbb_fix732.jpg

两者在加载是相同的,均是异步。区别在于 defer 执行需在元素解析完成后,DOMContentLoaded事件触发之前,而async 则在加载完后立即执行并中断元素解析过程。

构建 CSSOM 树

CSS来源有link标签style标签内联样式。渲染进程接收到CSS文件时,会执行转换操作,将CSS文本转换为浏览器可以理解的结构 styleSheets,styleSheets 可由 document.styleSheets获取,Chrome面板可以打印看一下 ~

获取到 styleSheets 结构后,需要将所有值转换为渲染引擎容易理解、标准化的计算值。

未命名文件 (1).png

属性标准化之后,需计算 DOM 树中每个节点的样式属性,这涉及到CSS的继承层叠。每个节点样式计算完成后生成最终的 CSSOM 树。

构建渲染树

在 DOM 树和 CSSOM 树构建完成之后,将创建的 DOM 树和 CSSOM 树合并成一个渲染树。从 DOM 树的根节点开始构建,遍历每个可见节点,并把这些节点添加到布局树。不可见的节点包含<head>和它的子节点以及任何具有display: none样式属性的节点。

布局

构建渲染树后,开始布局,计算所有节点的宽度、高度和位置,以及确定页面每个对象大小和位置。第一次确定节点大小和位置称为布局。

第一次确定节点的大小和位置称为布局。随后对节点大小和位置的重新计算称为回流。

绘制

将布局阶段计算的每个框转换为屏幕上的实际像素,包括文本、颜色、边框、阴影以及替换的元素。

绘制阶段可以将元素分解为多个层。将内容提升到 GPU 上的层,提高绘制和重新绘制的性能,如特定的元素和属性均可以实例化一个层。元素如<video>和<canvas>,属性如 opacity、3D变换、will-change。这些特定的节点或包含特定属性的节点将与子节点一起绘制到它们的层上,除非其子节点是特定元素或包含特定属性。

层可以提高性能,但是以内存管理为代价。

合成

当各个部分以不同的层绘制,相互重叠时,必须进行合成,以确保它们以正确的顺序绘制到屏幕上,并正确显示内容。

推荐一篇好文浏览器层合成与页面渲染优化

总结

文章涉及大量的计算机网络知识切忌死记硬背,更多地是理解💜。可以点赞或收藏,多看几遍。(持续更新)。

  • 已更新第一遍(2021/09/02 0:53)😴,细化了URL请求过程,明天准备细化渲染页面流程。
  • 已更新第二遍(2021/09/03),细化了渲染流程。
  • 已更新第三遍(2021/09/04),修改渲染流程。
  • 已更新第四遍(2021/11/02),修改渲染总流程,感觉更接地气。

参考

浏览器工作原理与实践
线程和进程的区别是什么
从输入URL到看到页面的过程分析
初识网络原理: 从浏览器地址栏输入 URL 到页面渲染之间都经历了什么
DNS的原理和解析过程
浏览器渲染流程&Composite(渲染层合并)简单总结
TCP为什么是三次握手,而不是两次或四次
渲染页面:浏览器的工作原理
defer和async的区别