本来只是想看一下前端的渲染和重绘回流,研究一下加载优化,然后就想着顺便过一下页面渲染前的步骤,发现涉及的东西还挺多的,就总结一下吧,下一章聊加载优化
大致步骤
1、浏览器的地址栏输入URL并按下回车。

2、浏览器查找当前URL是否存在缓存,并比较缓存是否过期。

3、DNS解析URL对应的IP。

4、根据IP建立TCP连接(三次握手)。

5、HTTP发起请求。

6、服务器处理请求,浏览器接收HTTP响应。

7、渲染页面,构建DOM树。

8、关闭TCP连接(四次挥手)。
  
先说URL,举个例子,常见的地址如:http://www.baidu.com ,一般由以下几部分组成

  • protocol,协议头,譬如有http,ftp等
  • host ,主机域名或IP地址
  • port ,端口号
  • path ,目录路径
  • query ,即查询参数
  • fragment ,即#键后面的hash值,一般用来定位到某个位置

NDS解析

按下回车,浏览器发起请求,如果URL是域名而不是IP地址,将会进行DNS解析

ip地址是网络上标识站点的数字地址,为了方便记忆,采用域名来代替IP地址标识站点地址
DNS是计算机域名的缩写,它是由解析器和域名服务器组成的

解析步骤

  • 浏览器先检查本地hosts文件是否有这个网址映射关系,如果有就调用,直接完成域名解析

  • 如果没找到则会查找本地DNS解析器缓存,如果查找到则返回

  • 上一步没有,浏览器会调用解析程序,并成为DNS服务器的一个客户,把待解析的域名放在DNS的请求报文中,以UDP用户数据报的方式发给本地DNS服务器,如果本地DNS服务器查找到相应的域名对应的IP地址,就把对应的IP地址放在回答报文中返回

    UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法

  • 如果还没找到,即本地DNS服务器不知道被查询域名的IP地址,由于主机向本地DNS服务器的查询是递归查询,所以此时,本地DNS服务器就会以DNS客户的身份向其他根DNS服务器继续发出查询请求报文。本地DNS服务器向根DNS服务器的查询是迭代查询,当找到相应域名的IP地址后,就会把这个结果返回给最初发起查询请求的浏览器

    递归查询:在该模式下DNS服务器接收到客户机请求,必须返回一个准确的查询结果给客户机。如果该DNS服务器本地没有存储被查询的DNS信息,那么该服务器会(替客户机)询问其他服务器,并将返回的查询结果再返回给客户机。
    从输入URL到页面加载发生了什么

    迭代查询:在该模式下DNS服务器接收到客户机请求,如果该DNS服务器本地没有存储被查询的DNS信息,DNS服务器会向客户机提供其他能够解析查询请求的DNS服务器地址,让客户机再向这台DNS服务器提交请求,依次循环直到返回查询的结果为止。从输入URL到页面加载发生了什么

三次握手(TCP连接建立 )

浏览器得到IP地址并确认端口后,会向目标服务器发起HTTP请求,HTTP请求是通过TCP连接来发送的(如果是HTTPS则需要先建立SSL连接,再是TCP连接,下面的讨论基于HTTP)
第一次握手: 首先客户端向服务器端发送一段TCP报文,其中:

  • 标记位为SYN,表示“请求建立新连接”;
  • 序号为Seq=X(X一般为1);
  • 随后客户端进入SYN-SENT阶段,等待服务器确认 。

第二次握手: 服务器端接收到来自客户端的TCP报文之后,结束LISTEN阶段。并返回一段TCP报文 ;

  • 标志位为SYN和ACK,表示“确认客户端的报文Seq序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接”(即告诉客户端,服务器收到了你的数据);
  • 序号为Seq=y;
  • 确认号为Ack=x+1,表示收到客户端的序号Seq并将其值加1作为自己确认号Ack的值;随后服务器端进入SYN-RCVD阶段。

第三次握手: 客户端接收到来自服务器端的确认收到数据的TCP报文之后,明确了从客户端到服务器的数据传输是正常的,结束SYN-SENT阶段。并返回最后一段TCP报文。其中 :

  • 标志位为ACK,表示“确认收到服务器端同意连接的信号”(即告诉服务器,我知道你收到我发的数据了);
  • 序号为Seq=x+1,表示收到服务器端的确认号Ack,并将其值作为自己的序号值;
  • 确认号为Ack=y+1,表示收到服务器端序号Seq,并将其值加1作为自己的确认号Ack的值;
  • 随后客户端进入ESTABLISHED阶段(TCP连接成功 状态),完成三次握手。

HTTP请求的请求报文是直接附在第三次握手的消息中
从输入URL到页面加载发生了什么

穿插补充小知识,为什么是三次握手,而不是两次四次?

《计算机网络》一书中也有讲过这个问题,给出的解释是 :三次握手是为了防止失效的连接请求报文段被服务端接收,从而产生错误

具体例子如下所述: client发出的一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。 假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。但是由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。而server却以为新的连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。

浏览器向服务器发送HTTP请求

完整的HTTP请求包含请求起始行、请求头部、请求主体三部分。
从输入URL到页面加载发生了什么

浏览器接收响应

服务器在收到浏览器发送的HTTP请求之后,会将收到的HTTP报文封装成HTTP的Request对象,并通过不同的Web服务器进行处理,处理完的结果以HTTP的Response对象返回,主要包括状态码,响应头,响应报文三个部分。

状态码主要包括以下部分

1xx:指示信息–表示请求已接收,继续处理。

2xx:成功–表示请求已被成功接收、理解、接受。

3xx:重定向–要完成请求必须进行更进一步的操作。

4xx:客户端错误–请求有语法错误或请求无法实现。

5xx:服务器端错误–服务器未能实现合法的请求。

响应头主要由Cache-Control、 Connection、Date、Pragma等组成。

响应体为服务器返回给浏览器的信息,主要由HTML,css,js,图片文件组成。

页面渲染

  • 浏览器开始自上而下,自左而右的加载HTML文档,最开始会遇到<!DOCTYPE>声明,然后根据<!DOCTYPE>声明浏览器就知道该用哪种规范来解析这个文档
    1. HTML解析出DOM Tree
    2. CSS解析出Style Rules
    3. 将二者关联生成Render Tree
    4. Layout 根据Render Tree计算每个节点的信息
    5. Painting 根据计算好的信息绘制整个页面
  • 浏览器继续加载渲染,如果遇到<script>标签,浏览器会立即执行(暂不考虑defer及async属性),此时会出现页面阻塞,不仅要等待文档中JS文件下载加载完毕,还要等待JS解析执行完毕,才可以恢复HTML文档的加载解析。

这是浏览器为了防止出现JS修改DOM树,需要重新构建DOM树的情况,DOM树改变浏览器需要回过头来重新渲染这部分代码,所以浏览器希望通过阻塞其他内容的下载和呈现,来避免出现更多的不必要的Reflow(称为回流或者重绘)

如果脚本是外部的,会等待脚本下载完毕,再继续解析文档。现在可以在script标签上增加属性 defer或者async

defer属性相当于告诉浏览器立即下载,延迟执行。它使得加载后续文档元素的过程将和JS文件的加载并行进行(异步),但是JS文件的执行要在整个页面解析完成之后

async属性相当于告诉浏览器立即下载执行,并且页面的加载渲染不需要等待该脚本加载和执行,它们两者会异步进行。标记为async的脚本不会按照它们出现的先后顺序执行,而是谁先下载完了谁就先执行

  • 浏览器继续加载渲染,如果遇到图片资源,浏览器也会另外发出一个请求,来获取图片资源,这是异步请求,所以不会等到图片下载完,而是继续渲染后面的HTML文档。
  • 等到服务器返回图片文件,如果先前并没有为这个图片设定宽高,那么由于图片占用了一定面积,影响了后面段落的排布,浏览器会进行回流Reflow
    • Repaint:如果只是改变了某个元素的背景颜色,文字颜色等,不影响元素周围或内部布局的属性,将只会引起浏览器的Repaint`,重绘某一部分
    • Reflow:如果某个部分发生了的变化影响了布局,那浏览器就需要倒回去重新渲染,每次Reflow必然会导致Repaint

关闭TCP连接(四次挥手)

从输入URL到页面加载发生了什么
(1)首先客户端想要释放连接,向服务器端发送一段TCP报文,其中:

  • 标记位为FIN,表示“请求释放连接“;
  • 序号为Seq=U;
  • 随后客户端进入FIN-WAIT-1阶段,即半关闭阶段。并且停止在客户端到服务器端方向上发送数据,但是客户端仍然能接收从服务器端传输过来的数据。
  • 注意:这里不发送的是正常连接时传输的数据(非确认报文),而不是一切数据,所以客户端仍然能发送ACK确认报文。

(2)服务器端接收到从客户端发出的TCP报文之后,确认了客户端想要释放连接,随后服务器端结束ESTABLISHED阶段,进入CLOSE-WAIT阶段(半关闭状态)并返回一段TCP报文,其中:

  • 标记位为ACK,表示“接收到客户端发送的释放连接的请求”;
  • 序号为Seq=V;
  • 确认号为Ack=U+1,表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值;
  • 随后服务器端开始准备释放服务器端到客户端方向上的连接。客户端收到从服务器端发出的TCP报文之后,确认了服务器收到了客户端发出的释放连接请求,随后客户端结束FIN-WAIT-1阶段,进入FIN-WAIT-2阶段

前"两次挥手"既让服务器端知道了客户端想要释放连接,也让客户端知道了服务器端了解了自己想要释放连接的请求。于是,可以确认关闭客户端到服务器端方向上的连接了
3)服务器端自从发出ACK确认报文之后,经过CLOSED-WAIT阶段,做好了释放服务器端到客户端方向上的连接准备,再次向客户端发出一段TCP报文,其中:

(3)服务器端自从发出ACK确认报文之后,经过CLOSED-WAIT阶段,做好了释放服务器端到客户端方向上的连接准备,再次向客户端发出一段TCP报文,其中:

  • 标记位为FIN,ACK,表示“已经准备好释放连接了”。注意:这里的ACK并不是确认收到服务器端报文的确认报文。
  • 序号为Seq=W;
  • 确认号为Ack=U+1;表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值。
  • 随后服务器端结束CLOSE-WAIT阶段,进入LAST-ACK阶段。并且停止在服务器端到客户端的方向上发送数据,但是服务器端仍然能够接收从客户端传输过来的数据

(4)客户端收到从服务器端发出的TCP报文,确认了服务器端已做好释放连接的准备,结束FIN-WAIT-2阶段,进入TIME-WAIT阶段,并向服务器端发送一段报文,其中:

  • 标记位为ACK,表示“接收到服务器准备好释放连接的信号”。
  • 序号为Seq=U+1;表示是在收到了服务器端报文的基础上,将其确认号Ack值作为本段报文序号的值。
  • 确认号为Ack=W+1;表示是在收到了服务器端报文的基础上,将其序号Seq值作为本段报文确认号的值。随后客户端开始在TIME-WAIT阶段等待2MSL

后“两次挥手”既让客户端知道了服务器端准备好释放连接了,也让服务器端知道了客户端了解了自己准备好释放连接了。于是,可以确认关闭服务器端到客户端方向上的连接了,由此完成“四次挥手

为什么客户端在TIME-WAIT阶段要等2MSL?

当客户端发出最后的ACK确认报文时,并不能确定服务器端能够收到该段报文。所以客户端在发送完ACK确认报文之后,会设置一个时长为2MSL的计时器。MSL指的是Maximum Segment Lifetime:一段TCP报文在传输过程中的最大生命周期。2MSL即是服务器端发出为FIN报文和客户端发出的ACK确认报文所能保持有效的最大时长。

服务器端在1MSL内没有收到客户端发出的ACK确认报文,就会再次向客户端发出FIN报文;

如果客户端在2MSL内,再次收到了来自服务器端的FIN报文,说明服务器端由于各种原因没有接收到客户端发出的ACK确认报文。客户端再次向服务器端发出ACK确认报文,计时器重置,重新开始2MSL的计时;
否则客户端在2MSL内没有再次收到来自服务器端的FIN报文,说明服务器端正常接收了ACK确认报文,客户端可以进入CLOSED阶段,完成“四次挥手”。

为什么“握手”是三次,“挥手”却要四次?

建立连接时,被动方服务器端结束CLOSED阶段进入“握手”阶段并不需要任何准备,可以直接返回SYN和ACK报文,开始建立连接。

释放连接时,被动方服务器,突然收到主动方客户端释放连接的请求时并不能立即释放连接,因为还有必要的数据需要处理,所以服务器先返回ACK确认收到报文,经过CLOSE-WAIT阶段准备好释放连接之后,才能返回FIN释放连接报文

相关文章: