应该有很多人面试被问到过输入URL到页面渲染这个问题,这篇内容针对这部分了做了一遍系统的梳理,对http和https的差异、请求过程及页面解析中的细节做了补充和完善。
1. 地址解析与URL分析 📚
在输入URL之后,首先浏览器检查URL的格式,确定协议(HTTP/HTTPS/FTP),域名,端口(如果有),以及路径。
2. DNS解析📚
确定域名信息之后,就开始进行DNS的解析,DNS(Domain Name System)的全称叫做域名系统,作用是将域名地址转为服务器对应的IP地址;
浏览器会先检查本地的DNS缓存,查看是否有域名对应的IP地址,如果有就直接返回,如果没有,就向本地的DNS服务器发起查询请求,或者会向更高层的DNS服务器查询,
整个过程可以大概分为下面几步:
- 首先,操作系统会尝试从本地DNS缓存中查找域名对应的IP地址。
- 如果本地DNS缓存中没有相应的记录,系统会向根域名服务器发起查询请求。
- 根域名服务器会返回顶级域名服务器的地址。
- 然后,系统向顶级域名服务器发送查询请求。
- 顶级域名服务器会返回权威域名服务器的地址。
- 系统向权威域名服务器发送查询请求。
- 最终,权威域名服务器返回目标域名对应的IP地址。
- 系统将IP地址返回给应用程序,完成域名解析过程。
总结一下其实就是如果本地没有DNS缓存,就会往向上游服务器查询,上游服务器会返回对应的地址,直到权威域名服务器返回对应的IP地址,中间每一层服务器查询都会缓存结果。
3. 和服务器建立连接 📚
不管是HTTP,还是HTTPS,都先会通过TCP三次握手建立连接,
- 客户端发送一个带有
SYN
(synchronize同步)标志的数据包给服务端。 - 服务端接收成功后,回传一个带有
SYN/ACK
标志的数据包传递确认信息,表示我收到了。 - 客户端再回传一个带有
ACK
标志的数据包,表示我知道了,握手结束。
场景脑补:有一天你看到个喜欢的人事,然后发了份简历过去 --> 人事看到了就回复说已收到 --> 你看到人事回消息了,就很开心的发了个🙈过去
我总结下就是,客户端发连接请求,服务端收到就发送应答请求,客户端收到后,发送响应请求表示确认收到就连上了;
而HTTPS还需要进行TLS握手,
- 客户端发送一个当前客户端信息的请求;包含TLS版本、生成的随机数、支持的加密算法组合及客户端所支持的参数选项。
- 服务器发送响应请求;包含服务器证书和一些加密参数,像加密算法、压缩算法、生成的随机数等。
- 客户端验证服务器证书的有效性,合法的CA、是否过期、请求域名和证书域名是否匹配...
- 客户端在确认证书没问题之后,就会生成一个随机数(称为预主密钥),然后使用服务器公钥加密,并将其发送给服务器。
- 服务器收到密钥,并对预主密钥解密,生成会话密钥;如果是RSA加密,会用随机数(客户端随机数和服务端随机数)以及预主密钥生成;如果是Diffie-Hellman或ECDH加密,则是用客户端和服务端各自生成的随机数做交换计算得到中间值来生成会话密钥。
继续总结下,就是客户端把自己信息发给服务器,服务器再根据这些信息返回匹配的内容及证书,客户端确认证书没问题,就生成个随机数发给服务器,服务器收到之后,就生成一个密钥,后续用这个密钥来当连接的校验凭证。
4. 客户端发送请求 📚
根据设置好的请求路径、类型、请求头参数发起一个数据请求,但是这里还有个细节就是缓存,
一般在没有禁止缓存的情况下,如果是get请求或者是静态资源请求,在请求的时候会先去访问本地是否存在缓存,如果存在就返回200或304,不存在才会向服务端发送请求。
缓存分为两种:
强缓存,只要资源未过期直接读取本地资源并返回200,通过Expires
和Cache-Control:max-age=<seconds>
两个请求头来控制,为了更精确的控制(max-age
参数为请求的相对时间、减少max-age
值来手动刷新),Cache-Control
的优先级比Expires
更高。
协商缓存,强缓存失效时,浏览器携带上一次请求返回的响应头中的缓存标记(如ETag、Last-Modified
等)向服务器发起请求,查询缓存的资源是否有效;资源没更新,就返回304,通知浏览器读取本地缓存,否则返回新的内容;参数的优先级方面,ETag
要高于Last-Modified
,因为ETag
有更高的唯一性,对比资源变化会更加精准。
5. 服务器处理请求并将对应内容给客户端 📚
这里没有什么逻辑,就是服务器根据接口参数返回相关内容及状态码。
6. 断开连接 📚
正常情况下的断开连接发生在请求完毕之后的一段时间,可以分为请求完成之后的默认关闭、服务器主动关闭(设置Connection:close
)及客户端主动关闭,这里就只说默认关闭,
现在大部分是http1.1及http2版本,都默认持久连接,http1.1由Connection:keep-alive
设置,http2本身就默认持久连接,
不管HTTP1.1还是HTTP2都是一般情况下都是客户端通过主动发送关闭标记来触发,通过四次挥手来完全关闭连接,
- 客户端发送关闭请求,客户端发送一个
FIN
,用来关闭客户端到服务器的数据传送,告诉服务器这已经处理完了,并且指定一个序列号。客户端进入FIN_WAIT_1
状态。 - 服务端收到请求并回复,服务器收到
FIN
后,发送一个ACK
给客户端,确认序号为客户端的序列号值+1 ,表明已经收到客户端的报文了,此时服务器处于CLOSE_WAIT
状态。 - 服务端进入准备关闭阶段,服务器发送完数据之后,就会发送一个
FIN
,用来关闭服务器到客户端的数据传送,服务器进入LAST_ACK
状态。 - 两个端关闭连接,客户端收到
FIN
后,客户端进入TIME_WAIT
状态,接着发送一个ACK
给服务器,确认序号为收到序号+1 ,服务器收到确认后进入CLOSED
状态。
总结下,就是上面内容的黑色加粗文字字单独拎出来连成一句话,
场景脑补:
有一天...你提了个离职,
人事看到点了同意,并告诉你流程已经通过了,
又过了一会,人事准备好了离职单,送到了你办公桌,
你拿到之后,签好字就去送给了人事,
然后回到工位坐等下班走人,
划重点,如果是http
协议,其实不用到下面的内容就已经结束了,但是!!!
现在基本上都用上了https
,这里还有一个细节,就是在进行TCP
四次挥手之前,https
还有一个TLS
的关闭握手,
- 任意一端发起关闭,会发送一个
close_notify
握手消息来发起TLS
会话的关闭,发送完毕之后发送方的写通道立即关闭,它不会再发送任何应用数据,但它可以继续接收发送方消息,直到它接收到对方的close_notify
消息。 - 接收方响应关闭:接收方在收到
close_notify
消息后,也会发送一个close_notify
消息给发送方表示同意关闭。 - 断开连接:一旦双方各自发送了
close_notify
消息,TLS
记录协议就会停止使用之前的加密状态进行加密,并且双方都可以释放TLS
连接的资源。
这里的发送方也不一定就是客户端,也有可能是服务端发起关闭
7. 客户端接收文件,解析HTML 📚
客户端拿到html
文件之后,就开始从上到下的逐行解析生成DOM
树,解析CSS
文件生成CSSOM
树,最后由DOM
树和CSS
树合并为渲染树,
需要注意的是,在整个解析过程,除了script
脚本会阻塞页面解析(可以使用async
或者defer
),遇到外部CSS
文件时,在等待下载的过程同样是会阻塞页面的(可以加载个第三方的CSS
文件链接复现,可通过JS
异步加载CSS
文件解决),
8. 页面渲染 📚
回流,首先浏览器遍历渲染树,计算每个可见节点在屏幕上的位置和大小等几何属性;在这个过程中,因为节点几何属性的变化,又需要对受影响节点重新计算布局,同时回流的时候必定会触发重绘,所以频繁回流会占用大量的资源来进行布局计算。
重绘,在布局完成之后,按照渲染树的顺序,逐个绘制节点,包括颜色、边框、渲染背景、文字、图像等内容。
分割线🌟🌟🌟
写在最后
在经过了上面这些流程,浏览器就完成了从输入URL到显示网页内容的全过程,在整个过程中,还会有很多页面加载性能的优化可以去做,感兴趣的可以看看这个写给自己的前端性能优化。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。