面试中经常会遇到这个问题,简述从输入URL到页面呈现的过程,根据应试者的经验,理解程度不同,答案也是五花八门,下面说说我自己的理解。
目录
我们将这个过程分为以下几个过程
- DNS查询
- TCP连接
- SSL/TLS 握手
- 请求报文
- 响应报文
- 页面构建渲染
- JS代码执行
1. DNS查询
DNS查询的细节可以参考 DNS查询原理
在互联网中的不同主机是通过 IP 来标识的,IP 就相当于网络资源的门牌号一样,帮助我们寻找网络资源的所在主机。但是为了记忆的便利,我们在浏览网站时一般是输入我们熟悉的域名(www.baidu.com),那么如何通过域名来寻找对应 IP 呢?于是出现了域名系统(Domain Name System)。
域名系统(Domain Name System DNS)是互联网的一项服务。它作为将域名和 IP 地址相互映射的一个分布式数据库,能够使人更方便地访问互联网。
我们在浏览器输入一个新网址时候,可能会经历以下几个步骤
- 浏览器查询(浏览器DNS缓存)
- 本地HOST文件查询(系统级DNS缓存)
- local DNS Servr(运营商DNS查询服务)
我们常说的 CDN 也是在这一过程实现的,大概就是通过全局流量管理(GTM)为我们分配合适的 CDN Server
在运营商DNS查询服务中,如果运营商本地没有对应的缓存的话,需要经历 根域名服务(.) -> 顶级域名服务(.com) -> 本地域名服务(.baidu)
2. TCP连接
TCP相关的知识可以参考 TCP/IP简记
TCP 处于传输层,其位于网络层(IP)的上一层,在通过 IP 寻找到对应的主机后,就要进行 TCP 连接了。
TCP 连接需要发送三次数据包发送来确定连接,我们称之为三次握手。
- 第一次握手:客户端将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给服务器端,客户端进入SYN_SENT状态,等待服务器端确认。
- 第二次握手:服务器端接收到数据包后由标志位SYN=1知道客户端请求建立连接,服务端将标志位SYN和ACK都设置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给客户端以确认连接请求,服务器端进入SYN_RCVD状态。
- 第三次握手:客户端接收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给服务器端,服务器端检查ack是否为K+1。
3. TLS/SSL四次握手
关于 TLS/SSL 协议是如何建立连接的,可以参考阮一峰大神的文章,写的非常棒 图解SSL/TLS协议
我们都知道,在使用 HTTP 协议时,因为采用的是明文传输的方式,所以会有以下风险
- 窃听风险,第三方可以获知通信内容
- 篡改风险,第三方可以修改通信内容
- 冒充风险,第三方可以冒充他人身份参与通信
所以现在我们引入了 HTTPS 协议来防止以上风险,而 HTTPS 其实就是 HTTP + SSL 的结合,其重点在于如何建立 SSL 连接。
建立 SSL 连接,其原理在于服务端提供数字证书和证书中的公钥(通过数字证书认证来确认公钥来自服务端),使用非对称加密通信
使用非对称加密会有个问题,因为算法原理,非对称加密计算量大,当数据量大时,计算耗时且耗性能。所以引入对称加密,对后面的数据传输采用对称加密,非对称加密主要用于生成对称加密的对称密钥。
所以建立 SSL 协议有以下过程
- 客户端向服务端获取公钥
- 双方协商生成对称密钥
- 使用对称密钥进行加密通信
现在我们来看看 SSL 连接的四次握手
- 客户端给出协议版本号、一个客户端生成的随机数(Client random),以及客户端支持的加密方法。
- 服务端确认双方使用的加密方法,并给出数字证书、以及一个服务器生成的随机数(Server random)。
- 客户端确认数字证书有效,然后生成一个新的随机数(Premaster secret),并使用数字证书中的公钥,加密这个随机数,发给服务端,同时根据三个随机数生成对称密钥。客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时也是前面发送的所有内容的 hash 值,用来供服务器校验。
- 服务端使用自己的私钥,获取客户端发来的随机数(即Premaster secret),同时根据三个随机数生成对称密钥。服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供客户端校验。
为什么需要三个随机数?
- 使用随机数生成密钥可以保证每次生成的密钥不同,增加密钥安全性。
- SSL 协议不信任每个主机能生成完全随机的随机数,所以通过客户端和服务端生成的三次(伪)随机数生成接近完全随机数的随机数。
SSL
4. 发起请求
当和资源主机建立了 TCP SSL 连接之后,便发起 HTTP 请求。请求这块其实没啥好说的,主要是需要了解下浏览器的缓存策略。通常在服务端我们会设置 HTML 文档的响应头 Cache-Control: no-cache
以便页面不会被强缓存。
关于浏览器缓存可以参考我之前的文章 浏览器缓存机制
5. 响应请求
服务端根据请求资源地址返回客户端需要的页面内容,这一步没什么好说的。
6. 页面构建渲染
浏览器获取到页面内容后,便开始解析页面。从这一步开始,就是浏览器解析渲染的舞台了。
①. DOM解析(文档对象模型)
首先是 html 标签经过 HTML Parser 解析成 DOM Tree,我们称之为 DOM 树,它是一个页面的框架骨骼。
②. CSS解析
样式经过 CSS Parser 解析为 Style Rules(也可叫做 CSSOM)。css 的解析和 DOM Tree 的构建是并行的。
③. DOM Tree + Style Rules => Render Tree
DOM Tree 和 Style Rules 准备好之后,并会根据 css 规则将 DOM Tree 和 Style Rules 结合成 Render Tree(渲染树)。
④. Layout(布局)
Render Tree 构建好之后开始计算节点的位置,大小,也就是不同节点的布局。这步确定页面节点的展示位置,也就是页面的展示框架。
当首次布局计算完成后,后因元素大小,位置等变化引起自身或其它节点的布局改变,我们称之为回流(重排)
⑤. Painting(绘制)
布局计算完成之后,便开始为节点“着色”,将节点的相关样式通过像素填充展现在屏幕上。
当节点的非布局样式改变(如背景颜色),则会重新触发绘制,我们称之为重绘(重新绘制),当然,回流也会引起重绘。
⑥. Display
在 Painting 结束后,其实节点就已经在页面显示了,我们就可以看到一个渲染完成的页面了。
如果严谨的说,这中间还有个合成的过程,不同的渲染层合成为一个合成层,对应图形层。合成层也可能含有多个,例如我们常说的3d硬件加速就是这个原理,通过创建新的合成层,避免引起其它元素的回流重绘,额外的合成层可能导致内容占用,所以也不能滥用。
关于渲染合成的细节可以参考其它大佬的文章 浏览器层合成与页面渲染优化
7. JS执行
在页面解析中遇到 JS 标签时会立即执行,此时 DOM Tree 的构建被阻塞,所以当遇到外联的 JS (非异步)时也会等待 JS 的下载执行后再继续解析 DOM。
重点看看两个事件节点
- DOMContentLoaded
当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完全加载。
需要注意的是,外联的 JS 是会阻塞 html 解析的,所以会影响 DOMContentLoaded 的执行
- load
当整个页面及所有依赖资源如样式表和图片都已完成加载时,将触发 load 事件。它与 DOMContentLoaded 不同之处在于,后者只要页面DOM加载完成就触发,无需等待依赖资源的加载。
总结
断断续续写了快一周终于结束了,写着写着就是越来越没有耐心。本来想重点写页面渲染过程的,但是没有写好,可能是自身的理解还不够。不管怎样,尽力而为,问心无愧。还有一些东西没有说清楚,可能后面会在其它文章中单独介绍。
- 硬件加速原理
- 页面的渲染合成原理
- CSS/JS对应页面解析的阻塞