一. 输入URL到呈现页面

按照实现的功能我认为可以划分为三个阶段

  • 本地校验阶段
  • 网络请求阶段
  • 本地渲染阶段

1. 本地校验阶段

本地校验阶段其实很简单,判断输入的信息是搜索还是域名(服务器地址IP)

  • 如果是搜索,浏览器将会把默认搜索引擎URL + 搜索内容合并成一个新的URL
  • 如果是网址,浏览器则会根据URL协议,将网址补全为合法的URL

常见的例子

可以看出:

  • 输入搜索内容时,浏览器会将默认搜索引擎URL + 搜索内容合并成一个新的URL,并且可能会添加其他的参数。
  • 输入网址时,浏览器会自动将原有的网址补全为合法的 URL,如加上 http/https,特别注意当输入 baidu.com时,可能会跳转 www.baidu.com,这并不是浏览器的操作而是由服务器重定向到下级的www,目的是 SEO 和保证 cookie 等存储的安全。

2. 网络请求阶段

网络请求阶段是一个复杂的过程,为了优化用户体验,浏览器会尽可能早的将页面呈现在用户面前,所以网络请求阶段与第三个阶段本地渲染是同步进行的,请求完成可以解析的内容就开始解析,并同时加载剩下地资源。TCP是一个全双工协议。

网络请求阶段可以细分为四个阶段

  • DNS解析(可选)
  • 建立TCP连接
  • 发送HTTP请求
  • 关闭TCP连接

2.1 DNS解析

DNS解析是为解析域名的IP而存在的,反向解析则是通过IP查询对应的域名。因为仅靠数字点分型的IP是很难记住的,所以在大部分时候都是通过域名访问网站,因而也需要一个地址簿即 DNS 服务器。

DNS 服务器是高可用、高并发和分布式的,它是树状结构,如图:

image

所以域名的层级关系类似一个树状结构:

  • 根 DNS 服务器:返回顶级域 DNS 服务器的 IP 地址,存储如(.com, .cn)服务器的地址
  • 顶级域 DNS 服务器(com):返回权威 DNS 服务器的 IP 地址
  • 权威 DNS 服务器(server.com):返回相应主机的 IP 地址,真正存储当前查询域名的地址簿,如在阿里注册的域名会给分配DNS服务器存储解析dns17.hichina.com,可以在阿里买域名然后转腾讯,此时的阿里服务的权威DNS就会变成腾讯服务的权威DNS
  • 本地域名服务器(查询代理和DNS查询结果缓存): 只是一个查询代理,可以是路由器,也可以是运营商的服务器

目前流行的是递归+迭代查询方式。

如果手机或者电脑上设置解析的DNS为114.114.114.114,那么本地服务器就是114,从设备到114之间为递归查询,其余的为迭代查询。

如果设备设置的解析地址为DHCP即路由器或其他自动分配,那么可以认为当前设备到路由器是递归查询,其余为迭代查询。

image.png

image

注:未找到本地DNS解析器缓存,这里我认为是路由器缓存

在客户端输入 URL 后,会有一个递归+迭代的过程,浏览器缓存 -> 系统DNS缓存 -> 本地hosts文件 -> 本地DNS解析器缓存 ? -> 本地区域DNS服务器迭代查询,这个过程中任何一步找到了都会结束查找流程。如果本地DNS服务器无法查询到,则根据本地DNS服务器设置的转发器进行查询。若未用转发模式(是否委托其他服务器来干这件事),则迭代查找过程如下图:

image

在查找过程中,有以下优化点:

  • DNS存在着多级缓存,从离浏览器的距离排序的话,有以下几种: 浏览器缓存,系统缓存,路由器缓存,IPS服务器缓存,根域名服务器缓存,顶级域名服务器缓存,主域名服务器缓存。
  • 在域名和 IP 的映射过程中,给了应用基于域名做负载均衡的机会,可以是简单的负载均衡,也可以根据地址和运营商做全局的负载均衡。

2.2 建立Tcp连接

首先,判断是不是 https ,如果是,则请求过程其实是 HTTP + SSL / TLS 两部分组成,也就是在 HTTP 上又加了一层处理加密信息的模块。服务端和客户端的信息传输都会通过 TLS 进行加密,即传输都是加密后的数据。

三次握手,建立TCP连接

  1. 第一次握手:建立连接,客户端发送连接请求报文段。将SYN位置为1,此时表示客户端向服务端请求建立连接,Sequence Number为x,随机数用于校验;客户端进入SYN_SEND状态,等待服务器的确认
  2. 第二次握手:服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x+1(Sequence Number+1);发送SYN请求信息,将SYN位置为1,此时表示服务端向客户端发送建立连接的请求;Sequence Number为y,随机数用于校验;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态;
  3. 第三次握手:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入 ESTABLISHED 状态,完成TCP三次握手。

简单总结:三次握手的原因是保证双方通信,TCP是一个全双工协议,所以链路需要分别建立,客户端向服务端请求建立连接,服务端向客户端应答同时请求建立连接,客户端向服务端应答。

SSL握手过程

  1. 第一步 :爱丽丝给出支持SSL协议版本号,一个客户端 随机数 (Client random,第一个随机数),客户端支持的加密方法等信息
  2. 第二步: 鲍勃收到信息后,确认双方使用的加密方法,并返回数字证书,一个服务器生成的 随机数 (Server random,第二个随机数)等信息
  3. 第三步: 爱丽丝确认数字证书的有效性(计算机系统已默认存储相关机构的证书),然后生成一个新的 随机数 (Premaster secret),然后使用数字证书中的公钥,加密这个随机数,发给鲍勃。
  4. 第四步: 鲍勃使用自己的私钥,获取爱丽丝发来的 随机数 (即Premaster secret);(第三、四步就是非对称加密的过程了)
  5. 第五步: 爱丽丝和鲍勃通过约定的加密方法(通常是AES算法),使用前面三个随机数,生成 对话密钥 ,用来加密接下来的通信内容

image

完成之后,客户端和服务器端就可以开始传送数据。更多 HTTPS 的资料可以看这里:

简单总结:Tcp建立完成后,如果没有加密 SSL/TLS 过程,此时可以开始 HTTP 请求,如果有加密过程,则按照五个阶段进行准备,SSL使用非对称来加密会话密钥,后期使用会话密钥对称加密会话。

ACK:此标志表示应答域有效,就是说前面所说的TCP应答号将会包含在TCP数据包中;有两个取值:0和1,为1的时候表示应答域有效,反之为0。TCP协议规定,只有ACK=1时有效,也规定连接建立后所有发送的报文的ACK必须为1。

SYN(SYNchronization):在连接建立时用来同步序号。当SYN=1而ACK=0时,表明这是一个连接请求报文。对方若同意建立连接,则应在响应报文中使SYN=1和ACK=1. 因此, SYN置1就表示这是一个连接请求或连接接受报文。

FIN(finis)即完,终结的意思, 用来释放一个连接。当 FIN = 1 时,表明此报文段的发送方的数据已经发送完毕,并要求释放连接。

2.3 http/https请求

TCP连接建立后,浏览器就可以利用HTTP/HTTPS协议向服务器发送请求了。服务器接受到请求,就解析请求头,如果头部有缓存相关信息如if-none-match与if-modified-since,则验证缓存是否有效,若有效则返回状态码为304,若无效则重新返回资源,状态码为200.

这里有发生一个HTTP缓存过程,是一个常考的考点,大致过程如图:

image

细节放到了《二. Web性能与安全》处,也可以参考 浏览器相关原理(面试题)详细总结一 - 掘金 (juejin.cn) 中浏览器缓存这一部分。

2.5 断开Tcp连接

  • 第一次分手:主机1(可以使客户端,也可以是服务器端),设置Sequence Number和Acknowledgment Number,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;
  • 第二次分手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment Number为Sequence Number加1;主机1进入FIN_WAIT_2状态;主机2告诉主机1,我”同意”你的关闭请求;
  • 第三次分手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入LAST_ACK状态;
  • 第四次分手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。

简单总结:TCP是一个全双工协议,所以链路需要分别拆除,因为链路是双向的,所以无论是建立还是拆除,都需要分别建立,分别拆除,建立和拆除都是一问一答的形式。

在建立链路时,客户端发送请求建立,服务端应答同时也请求建立,客户端应答。

在拆除链路时,客户端发送拆除链路,服务端第一次应答,第二次发送拆除链路请求,客户端发送应答。可以是客户端先发送拆除,也可以是服务端先发送拆除,没有顺序要求但是要符合一问一答,与建立连接不同的是拆除链路的应答和发送分开发送,所以是四次挥手。

分开发送的目的是一条链路不再需要后可以及时断开,如果与建立连接类似同时发送 Ack + Fin,那么只有等到被动关闭端处理所有的信息后才会发送。

举个例子,A向B紧急借钱,B可以选择立刻给钱或者开一个借条后再给钱,立刻给钱可以及时响应,而开借条后再借钱则是耗费了A的等待时间,必须等B处理完所有借条工作后才能够拿到。

为什么需要四次挥手?

因为TCP是一个全双工协议,必须单独拆除每一条信道。4次挥手的目的是终止数据传输,并回收资源,此时两个端点两个方向的序列号已经没有了任何关系,必须等待两方向都没有数据传输时才能拆除虚链路,不像初始化时那么简单,发现SYN标志就初始化一个序列号并确认SYN的序列号。因此必须单独分别在一个方向上终止该方向的数据传输。

如果是三次挥手,会怎么样?三次的话,被动关闭端在收到FIN消息之后,需要同时回复ACK和Server端的FIN消息。如果Server端在该连接上面并没有Pending的消息要处理,那么是可以的,如果Server端还需要等待一段时间才可以关闭另外一个方向的连接,那么这样的三次挥手就不能满足条件。

【TCP/IP】四次挥手的过程及原因 - 简书 (jianshu.com)

3. 本地渲染阶段

按照渲染的时间顺序,流水线可分为如下几个子阶段:构建 DOM 树,CSSOM树、样式计算、布局阶段、分层、栅格化和显示。如图:

image

  • 渲染进程将 HTML 内容转换为能够读懂DOM 树结构,渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式,此时的过程是同步的,即HTML Parser 和 CSS Parser,Render Tree构建是同步的。
  • 创建layout布局树,并计算元素的布局信息。
  • 对布局树进行分层,并生成分层树。
  • 为每个图层生成绘制列表,并将其提交到合成线程。合成线程将图层分图块,并栅格化将图块转换成位图。
  • 合成线程发送绘制图块命令给浏览器进程。浏览器进程根据指令生成页面,并显示到显示器上。

3.1 构建 DOM 树

浏览器从网络或硬盘中获得HTML字节数据后会经过一个流程即将字节解析为DOM树,先将HTML的原始字节数据转换为文件指定编码的字符,然后浏览器会根据HTML规范来将字符串转换成各种令牌标签,如html、body等。最终解析成一个树状的对象模型,就是dom树。

image

  • 转码(Bytes -> Characters)—— 读取接收到的 HTML 二进制数据,按指定编码格式将字节转换为 HTML 字符串
  • Tokens 化(Characters -> Tokens)—— 解析 HTML,将 HTML 字符串转换为结构清晰的 Tokens,每个 Token 都有特殊的含义同时有自己的一套规则
  • 构建 Nodes(Tokens -> Nodes)—— 每个 Node 都添加特定的属性(或属性访问器),通过指针能够确定 Node 的父、子、兄弟关系和所属 treeScope(例如:iframe 的 treeScope 与外层页面的 treeScope 不同)
  • 构建 DOM 树(Nodes -> DOM Tree)—— 最重要的工作是建立起每个结点的父子兄弟关系

具体的步骤可以参考《三. 浏览器内部工作 - 解析方向》

3.2 样式计算

渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。

CSS 样式来源主要有 3 种,分别是通过 link 引用的外部 CSS 文件、style标签内的 CSS、元素的 style 属性内嵌的 CSS。,其样式计算过程主要为:

image

可以看到上面的 CSS 文本中有很多属性值,如 2em、blue、bold,这些类型数值不容易被渲染引擎理解,所以需要将所有值转换为渲染引擎容易理解的、标准化的计算值,这个过程就是属性值标准化。处理完成后再处理样式的继承和层叠,有些文章将这个过程称为CSSOM的构建过程。

具体的可以参考:《三. 浏览器内部工作 - 解析方向》

3.3 页面布局

布局过程,即排除 script、meta 等功能化、非视觉节点,排除 display: none 的节点,计算元素的位置信息,确定元素的位置,构建一棵只包含可见元素布局树。如图:

image

其中,这个过程需要注意的是回流和重绘,具体的可以参考《三. 浏览器内部工作 - 解析方向》

3.4 生成分层树

页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree),如图:

image

如果你熟悉 PS,相信你会很容易理解图层的概念,正是这些图层叠加在一起构成了最终的页面图像。在浏览器中,你可以打开 Chrome 的”开发者工具”,选择”Layers”标签。渲染引擎给页面分了很多图层,这些图层按照一定顺序叠加在一起,就形成了最终的页面。

并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。那么需要满足什么条件,渲染引擎才会为特定的节点创建新的层呢?

具体参考:浏览器相关原理(面试题)详细总结二 - 掘金 (juejin.cn)

3.5 栅格化

合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。如图:

image

通常一个页面可能很大,但是用户只能看到其中的一部分,我们把用户可以看到的这个部分叫做视口(viewport)。在有些情况下,有的图层可以很大,比如有的页面你使用滚动条要滚动好久才能滚动到底部,但是通过视口,用户只能看到页面的很小一部分,所以在这种情况下,要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要。

3.6 显示

最后,合成线程发送绘制图块命令给浏览器进程。浏览器进程根据指令生成页面,并显示到显示器上,渲染过程完成。

4. 参考文章