经典案例:从URL输入到页面展示到底发生了什么?
目录:
一、用户输入
- 是否为URL
- 是否有beforeunload方法
二、URL请求
- 浏览器进程通过 IPC 把 URL 请求发送至网络进程
- 查找资源缓存(有效期内)
- DNS解析
- 建立TCP连接
- HTTPS建立TLS连接
- 发送HTTP请求
- 处理服务端返回
- 断开TCP连接
三、浏览器渲染
- 构建DOM树
- 样式计算
- 布局
- 分层
- 绘制
- 合成
这是navigation timing监测指标图,从图中我们可以看出,浏览器在得到用户请求之后,经历了下面这些阶段:重定向→拉取缓存→DNS查询→建立TCP链接→发起请求→接收响应→处理HTML元素→元素加载完成
一、用户输入
-
根据用户输入是否是URL,判断是否为URL,若是则加上协议合成完整的URL;若不是,则根据浏览器默认搜索引擎合成新的带搜索内容的URL
-
检查是否有beforeunload方法,若有则留在当前页面,等待用户进一步操作;若无,则发起URL请求
二、URL请求
- 浏览器进程通过 IPC 把 URL 请求发送至网络进程;
- 查找资源缓存(有效期内)
- DNS解析
DNS解析就是域名解析,也就是获取当前域名的ip地址。
流程是:
- 首先查找本地 hosts 文件中是否存在对应规则,若有则返回;若无则请求本地DNS服务器(ISP网络提供商如中国电信)
- 本地DNS服务器缓存中若有,则返回;若无,则需请求DNS根服务器查询
- 根DNS服务器没有记录具体的域名和IP地址的对应关系,而是告诉本地DNS服务器,你可以到域服务器上去继续查询,并给出域服务器的地址
- 本地DNS服务器继续向域服务器发出请求,在这个例子中,请求的对象是.com域服务器。.com域服务器收到请求之后,也不会直接返回域名和IP地址的对应关系,而是告诉本地DNS服务器,你的域名的解析服务器的地址
- 最后,本地DNS服务器向域名的解析服务器发出请求,这时就能收到一个域名和IP地址对应关系,本地DNS服务器不仅要把IP地址返回给用户电脑,还要把这个对应关系保存在缓存中,以备下次别的用户查询时,可以直接返回结果,加快网络访问
4、建立TCP连接 拿到域名对应的IP地址后,浏览器会以一个随机端口(1024<端口<65535)向服务器的Web程序(常用的有httpd,nginx等)80端口发起TCP的连接请求。
经过网络传输,这个连接请求到达服务器后,最终达到Web程序,建立了TCP/IP的连接。
建立TCP连接很简单,通过三次握手便可建立连接。 建立好连接后,开始传输数据。TCP数据传输牵涉到的概念很多:超时重传、快速重传、流量控制、拥塞控制等等。 断开连接的过程也很简单,通过四次握手完成断开连接的过程
三次握手建立连接:
- 第一次握手:客户端发送syn包(seq=x)到服务器,并进入SYN_SEND状态,等待服务器确认;
- 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(seq=y),即SYN+ACK包,此时服务器进入SYN_RECV状态;
- 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
三次握手分别称为:SYN 报文、SYNACK 报文、ACK 报文
问:为什么要三次握手而不是两次?
答:三次握手的目的是为了让双方验证各自的接收能力和发送能力。因为如果只有两次握手,则可能会因为网络延迟,客户端A发出的 SYN 报文并没有丢失,而是延迟了某个时间到达服务端B,本来这是一个已经失效的报文段,但B收到后却误以为是A又发出一次新的请求连接,于是就向A发出确认报文段,同意建立连接。由于现在A并没有发出建立连接的请求,因此不会理睬B的确认,也不会向B发送数据,但B却以为新的运输连接已经建立了,并一直等待A发来的数据。B的许多资源就这样白白浪费了。
问:客户端正在和服务端建立 TCP 连接,然而当服务器变 SYN-RCVD 后,此时一个旧的 SYN 报文 又到达了,服务器会如何处理?
答:服务端在SYN_RECEIVED状态下,接收到旧的SYN 报文时是不能作出判断的,而是照常返回,当客户端接收到该报文后发现异常,才会发送RST 报文,重置连接。
问:知道SYN攻击吗?如何防范?
答:所谓SYN 洪泛攻击,就是利用SYNACK 报文的时候,服务器会为客户端请求分配缓存,那么黑客(攻击者),就可以使用一批虚假的ip向服务器大量地发建立TCP 连接的请求,服务器为这些虚假ip分配了缓存后,处在SYN_RCVD状态,存放在半连接队列中;另外,服务器发送的请求又不可能得到回复(ip都是假的,能回复就有鬼了),只能不断地重发请求,直到达到设定的时间/次数后,才会关闭。 服务器不断为这些半开连接分配资源(但从未使用),导致服务器的连接资源被消耗殆尽,不过所幸,我们可以使用SYN Cookie进行有效地防御。
所谓的SYN Cookie防御系统,与前面接收到SYN 报文就分配缓存不同,此时暂不分配资源;同时利用SYN 报文的源和目的地IP和端口,以及服务器存储的一个秘密数,使用它们进行散列,得到server_isn,然后附着在SYNACK 报文中发送给客户端,接下来就是对ACK 报文进行判断,如果其返回的ack字段正好等于server_isn + 1,说明这是一个合法的ACK,那么服务器才会为其生成一个具有套接字的全开的连接。
握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。
传输数据过程
- 超时重传:超时重传机制用来保证TCP传输的可靠性。每次发送数据包时,发送的数据报都有seq号,接收端收到数据后,会回复ack进行确认,表示某一seq 号数据已经收到。发送方在发送了某个seq包后,等待一段时间,如果没有收到对应的ack回复,就会认为报文丢失,会重传这个数据包。
- 快速重传:接受数据一方发现有数据包丢掉了。就会发送ack报文告诉发送端重传丢失的报文。如果发送端连续收到标号相同的ack包,则会触发客户端的快速重 传。比较超时重传和快速重传,可以发现超时重传是发送端在傻等超时,然后触发重传;而快速重传则是接收端主动告诉发送端数据没收到,然后触发发送端重传。
- 流量控制:这里主要说TCP滑动窗流量控制。TCP头里有一个字段叫Window,又叫Advertised-Window,这个字段是接收端告诉发送端自己 还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。 滑动窗可以是提高TCP传输效率的一种机制。
- 拥塞控制滑动窗用来做流量控制。流量控制只关注发送端和接受端自身的状况,而没有考虑整个网络的通信情况。拥塞控制,则是基于整个网络来考虑的。考虑一下这 样的场景:某一时刻网络上的延时突然增加,那么,TCP对这个事做出的应对只有重传数据,但是,重传会导致网络的负担更重,于是会导致更大的延迟以及更多 的丢包,于是,这个情况就会进入恶性循环被不断地放大。试想一下,如果一个网络内有成千上万的TCP连接都这么行事,那么马上就会形成“网络风 暴”,TCP这个协议就会拖垮整个网络。为此,TCP引入了拥塞控制策略。拥塞策略算法主要包括:慢启动,拥塞避免,拥塞发生,快速恢复。
四次握手断开连接:
- 第一次挥手:主动关闭方发送一个FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不会再给你发数据了(当然,在fin包之前发送出去的数据,如果没有收到对应的ack确认报文,主动关闭方依然会重发这些数据),但此时主动关闭方还可以接受数据。
- 第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号)。
- 第三次挥手:被动关闭方发送一个FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。
- 第四次挥手:主动关闭方收到FIN后,发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,完成四次挥手。
问:为什么建立连接是三次握手,而关闭连接却是四次挥手呢?
答:这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。
5、HTTPS建立TLS连接
1.服务器申请证书
6、发送HTTP请求 客户端向服务器发起http请求的时候,会有一些请求信息,请求信息包含三个部分:
- 请求方法URI协议/版本
- 请求头(Request Header)
- 请求正文
GET/sample.jspHTTP/1.1
Accept:image/gif.image/jpeg,*/*
Accept-Language:zh-cn
Connection:Keep-Alive
Host:localhost
User-Agent:Mozila/4.0(compatible;MSIE5.01;Window NT5.0)
Accept-Encoding:gzip,deflate
username=jinqiao&password=1234
注意:最后一个请求头之后是一个空行,发送回车符和换行符,通知服务器以下不再有请求头。
7、处理服务端返回
HTTP响应与HTTP请求相似,HTTP响应也由3个部分构成,分别是:
- 状态行
- 响应头(Response Header)
- 响应正文
HTTP/1.1 200 OK
Date: Sat, 31 Dec 2005 23:59:59 GMT
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 122
<html>
<head>
<title>http</title>
</head>
<body>
<!-- body goes here -->
</body>
</html>
- 状态码 301 / 302,根据响应头中的 Location 重定向;
- 状态码 200,根据响应头中的 Content-Type 决定如何响应(下载文件、加载资源、渲染 HTML)
8、断开TCP连接
三、浏览器渲染
渲染流水线
渲染进程的主要任务是将HTML,CSS,以及JavaScript转变为我们可以进程交互的网页内容。
渲染进程里面有:一个主线程、几个worker线程、一个合成线程以及一个光栅线程
- 构建DOM:生成文档结构
- 子资源加载:CSS、图片、JS等,浏览器会进行预加载扫描,提前请求资源。JS会阻塞解析过程,因为JS代码可能改变文档流,可以给
<script>添加一个async或者defer来异步加载 - 样式计算:得到每个DOM节点的计算样式(computed style)
- 布局(复杂):计算每个节点的几何信息,如x,y坐标及盒子大小,同时隐藏不可见元素,伪元素节点会展示
- 绘画:生成绘制顺序,主线程会根据一定规则将页面分层,并将层次树和绘制顺序提交给合成线程
- 合成:
- 合成线程会光栅化页面的每一层(一般会分块提交给
光栅线程进行上色,并存储在GPU内存中) - 合成线程会收集光栅后的分块信息:绘制方块,来创建合成帧
- 合成线程会通过IPC向浏览器进程提交一个合成帧
- 注意:合成操作不涉及主线程,因此只通过合成来构建动画可以提升用户体验。如果页面需要被重新布局或者绘制的话,主线程一定会参与进来的。
- 合成线程会光栅化页面的每一层(一般会分块提交给
一些概念:
重排
- 更新了元素的几何属性(如宽、高、边距);
- 触发重新布局,解析之后的一系列子阶段;
- 更新完成的渲染流水线,开销最大。
重绘
- 更新元素的绘制属性(元素的颜色、背景色、边框等);
- 布局阶段不会执行(无几何位置变换),直接进入绘制阶段。
合成
- 直接进入合成阶段(例如CSS 的 transform 动画);
- 直接执行合成阶段,开销最小。
GPU加速在哪个阶段参与?
我们把容易触发重排重绘的元素单独触发渲染层,让它与那些“静态”元素隔离,让GPU分担更多的渲染工作,我们通常把这样的措施成为硬件加速,或者是GPU加速。
默认在3D元素渲染时会打开,也可使用其他2D CSS属性如 transform:translateX(0) 来触发GPU加速,但不要滥用,以免影响整体性能。
GPU实现动画的优缺点?
- 优点:
- 利用了GPU合成图层实现动画,可以做到动画平滑、流畅动画
- 合成工作在GPU线程,不会被CPU的js运行阻塞
- 缺点:
- 绘图层必须传输到GPU,当图层较多时传输过程可能会导致渲染缓慢
- 每个复合层都需要消耗额外的内存,过多的内存可能导致浏览器的崩溃
- 复合层合成需要更多的时间
为什么transform可以提升性能?