了解输入url到页面加载,为了更好地性能优化

一、 DNS解析过程

DNS 全称 DomainName System 域名系统,俗称电话本。DNS解析就是通过域名最终得到对应ip地址,知道哪台服务器上有你需要的资源的过程。当然如何进行DNS 解析又是另外的话题。

1、首先搜索浏览器自身的DNS缓存,有缓存直接返回;
2、浏览器自身DNS不存在,浏览器就会调用一个类似 gethostbyname 的库函数,此函数会先去检测本地hosts文件,查看是否有对应ip。
3、如果本地hosts文件不存在映射关系,就会查询路由缓存,路由缓存不存在就去查找本地DNS服务器(一般TCP/IP参数里会设置首选DNS服务器,通常是8.8.8.8);
4、如果本地DNS服务器还没找到就会向根服务器发出请求。具体过程:
  • 本地DNS服务器代我们的浏览器发起迭代DNS解析请求,首先它会找根域的DNS的IP地址(全球13台哟,可惜中国没有!)。找到根域的DNS地址,就会向其发起请求;
  • 根域发现这是一个顶级域top域的一个域名,于是告诉本地DNS服务器我不知道这个域名的IP地址,但是我知道top域的IP地址,你去找它去吧;
  • 于是本地DNS服务器就得到了top域的IP地址,又向top域的IP地址发起了请求(请问www.luoruihuan.top这个域名的IP地址是多少呀?),于是top域服务器告诉本地DNS服务器我不知道www.luoruihuan.top这个域名的IP地址,但是我知道luoruihuan.top这个域的DNS地址,你去找它去;
  • 于是本地DNS服务器又向luoruihuan.top这个域名的DNS地址(这个一般就是由域名注册商提供的,像万网,新网等)发起请求(请问www.luoruihuan.top这个域名的IP地址是多少?),这个时候luoruihuan.top域的DNS服务器一查,呀!果真在我这耶,于是就把找到的结果发送给本地DNS服务器;
  • 这个时候本地DNS服务器就拿到了www.luoruihuan.top这个域名对应的IP地址。

从上述过程中,可以看出网址的解析是一个从右向左的过程: top ->luoruihuan.top -> www.luoruihuan.top。但是你是否发现少了点什么,根域名服务器的解析过程呢?事实上,真正的网址是www.luoruihuan.top.,并不是我多打了一个.,这个.对应的就是根域名服务器,默认情况下所有的网址的最后一位都是.,既然是默认情况下,为了方便用户,通常都会省略,浏览器在请求DNS的时候会自动加上,所有网址真正的解析过程为: . -> .top -> luoruihuan.top. -> www.luoruihuan.top.

DNS 缓存,DNS存在着多级缓存,从离浏览器的距离排序的话,有以下几种: 浏览器缓存,系统缓存,路由器缓存,IPS服务器缓存,根域名服务器缓存,顶级域名服务器缓存,主域名服务器缓存。


二、TCP 链接(三次握手)

所谓三次握手(Three-way Handshake),是指建立一个 TCP 连接时,需要客户端和服务器总共发送3个包。

三次握手的目的是连接服务器指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号,交换 TCP 窗口大小信息。

1、客户端发送带有SYN标识(SYN=1,seq=x)的请求报文段,然后进入SYN_SEND状态,等待服务端确认;
2、服务端接收到客户端SYN报文段后,需要发送ACK信息对这个SYN进行确认,同时还要发送自己的SYN信息(SYN=1,ACK=1,seq=y,ack=x+1)服务端把这些信息放在一个报文段中((SYN+ACK报文段),一并发给客户端,此时客户端进入SYN_RECV状态;
3、客户端接收到服务端的SYN+ACK报文段后会向服务端发送ACK(ACK=1,seq=x+,ack=y+1)确认报文段,这个报文段发送后, 客户端和服务端都进入ESTABLISHED状态,完成三次握手。

vim

为什么会三次握手?这块咋一看会感觉比较复杂,其实这个大家稍微思考一下就能明白,如果两个人需要互相确定可以通讯,步骤其实就是三次,类似于:

浏览器:在吗?
服务器:在的
浏览器: 好的,我找你是为了诚挚的邀请你,晚上请我吃饭。

这个过程多了或者少了都不能保证确保互相通信,实际信号就是:

浏览器:syn
服务器:syn + ack
浏览器: ack


三、客户端HTTP请求

发送HTTP请求的过程就是: 构建HTTP请求报文并通过TCP协议发送到服务器指定端口(HTTP协议80/8080, HTTPS协议443)。 HTTP请求报文是由三部分组成: 请求行, 请求报头和请求正文。

1
2
3
4
5
6
7
POST /login HTTP/1.1

Host: www.example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 30

username=hello&password=123456
四、服务端响应HTTP请求

HTTP响应也是由 HeaderBody 两部分组成,响应的第一行总是 HTTP版本 响应代码 响应说明,例如,HTTP/1.1 200 OK 表示版本是 HTTP/1.1 ,响应代码是 200,响应说明是 OK。客户端只依赖响应代码判断HTTP响应是否成功。

1
2
3
4
5
6
7
8
HTTP/1.1 200 OK

Content-Type: text/html
Content-Length: 133251

<!DOCTYPE html>
<html><body>
<h1>Hello</h1>

响应状态码是由3位数组成,第一个数字定义了响应的类别,且有五种可能取值:

  • 1xx:指示信息–表示请求已接收,继续处理。
  • 2xx:成功–表示请求已被成功接收、理解、接受。
  • 3xx:重定向–要完成请求必须进行更进一步的操作。
  • 4xx:客户端错误–请求有语法错误或请求无法实现。例如400表示因为Content-Type等各种原因导致的无效请求,404表示指定的路径不存在;
  • 5xx:服务器端错误–服务器未能实现合法的请求。例如500表示服务器内部故障,503表示服务器暂时无法响应。

当浏览器收到第一个http响应后,解析html后又会发起一系列http请求,例如请求图片,文件等。请求图片时。服务器响应图片请求后,会直接把二进制内容的图片发送给浏览器,因此,服务器总是被动的接收客户端的一个HTTP请求,然后响应他,客户端则根据需要发送若干个HTTP请求。对于最早期的HTTP/1.0协议,每次发送一个HTTP请求,客户端都需要先创建一个新的TCP连接,然后,收到服务器响应后,关闭这个TCP连接。由于建立TCP连接就比较耗时,因此,为了提高效率,HTTP/1.1协议允许在一个TCP连接中反复发送-响应,这样就能大大提高效率,因为HTTP协议是一个请求-响应协议,客户端在发送了一个HTTP请求后,必须等待服务器响应后,才能发送下一个请求,这样一来,如果某个响应太慢,它就会堵住后面的请求。可见,HTTP/2.0进一步提高了效率。


五、TCP链接断开

TCP 的连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake),。客户端或服务器均可主动发起挥手动作。

1、第一次挥手(FIN=1,seq=x)

假设客户端想要关闭连接,客户端发送一个 FIN 标志位置为1的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。发送完毕后,客户端进入 FIN_WAIT_1 状态。

2、第二次挥手(ACK=1,ACKnum=x+1)

服务器端确认客户端的 FIN 包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。
发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。

3、第三次挥手(FIN=1,seq=y)

服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN 置为1。
发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个ACK。

4、第四次挥手(ACK=1,ACKnum=y+1)

客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT状态,等待可能出现的要求重传的 ACK 包。

服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。

客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。

当然4次挥手主要就是为了保证数据互相完成发送,例如:

1
2
3
4
浏览器:吃饱了,我要走了。
服务器:干饭人等一下,我看一下晚上吃饭钱你转给我没有。
服务器:钱收到了,那我也走了。
浏览器:好的。

当然大家可能想问,其实三次也是可以关闭,为什么非要分四次,这是因为服务端在LISTEN状态下,收到关闭连接的报文时,仅仅表示对方不在发送数据,但是还是可以接受数据的,服务端是否现在关闭数据通道可以理解为上层应用是否还有其他事情要做,因此服务端的ACK和FIN一般都是分开发送。

vim


六、浏览器渲染资源回流

导致 reflow 发生的一些原因:

  • 改变窗口大小
  • 改变文字大小
  • 添加删除样式变
  • 内容的改变
  • 激活伪类
  • 操作class属性
  • 脚本操作DOM
  • 计算offsetWidth和offsetHeight
  • 设置style属性

减少 reflow 的一些方法

  • 避免设置大量的style属性,因为设置style每一次都会触发refolw 最好使用class
  • 尽量不使用table布局,因为因为table中某个元素旦触发了reflow,那么整个table的元素都会触发reflow。那么在不得已使用table的场合,可以设置table-layout:auto;或者是table-layout:fixed这样可以让table一行一行的渲染,这种做法也是为了限制reflow的影响范围。
  • 如果css里面有计算表达式,每次都会重新计算一遍,触发一次reflow
七、总结

    上面主要介绍了一次完整请求对应的过程,了解这个过程的主要目的其实也就是为了web优化,前端的本质一句话可以很好地概括,将用户所需要的信息,快速并友好的展示给用户,并能够与用户进行交互,快速肯定就是尽可能短的时间内完成页面的加载,当然,如何快速的完成页面加载,可以参考雅虎34条军规,这个34条军规实际上就是围绕请求过程进行的一些优化方式。如何尽快的加载资源,大概的一个总体思路,能不从网络中加载的资源就不从网络中加载,这就要我们合理利用缓存,将资源放在浏览器端,这也是最快的方式。如果资源必须从网络中加载,则考虑缩短连接时间,也就是上面提到的DNS优化,类似于DNS 缓存,DNS 负载均衡等,Http方面可以优化响应内容大小,即对内容进行压缩。另一方面,当资源到达浏览器以后,浏览器开始进行解析渲染,浏览器中最耗时的部分就是reflow 回流,围绕这一部分考虑的就是如何减少reflow的次数,reflow 是导致DOM脚本执行效率低的关键因素之一,页面上任何一个节点触发了 reflow,都会导致他的子节点及祖先节点重新渲染,