浏览器系列之3-如何从URL到页面显示的

155 阅读13分钟

经典案例:从URL输入到页面展示到底发生了什么?

目录:

一、用户输入

  1. 是否为URL
  2. 是否有beforeunload方法

二、URL请求

  1. 浏览器进程通过 IPC 把 URL 请求发送至网络进程
  2. 查找资源缓存(有效期内)
  3. DNS解析
  4. 建立TCP连接
  5. HTTPS建立TLS连接
  6. 发送HTTP请求
  7. 处理服务端返回
  8. 断开TCP连接

三、浏览器渲染

  1. 构建DOM树
  2. 样式计算
  3. 布局
  4. 分层
  5. 绘制
  6. 合成

image.png

这是navigation timing监测指标图,从图中我们可以看出,浏览器在得到用户请求之后,经历了下面这些阶段:重定向→拉取缓存→DNS查询→建立TCP链接→发起请求→接收响应→处理HTML元素→元素加载完成

一、用户输入

  1. 根据用户输入是否是URL,判断是否为URL,若是则加上协议合成完整的URL;若不是,则根据浏览器默认搜索引擎合成新的带搜索内容的URL

  2. 检查是否有beforeunload方法,若有则留在当前页面,等待用户进一步操作;若无,则发起URL请求

二、URL请求

  1. 浏览器进程通过 IPC 把 URL 请求发送至网络进程;
  2. 查找资源缓存(有效期内)
  3. DNS解析

DNS解析就是域名解析,也就是获取当前域名的ip地址。

流程是:

  • 首先查找本地 hosts 文件中是否存在对应规则,若有则返回;若无则请求本地DNS服务器(ISP网络提供商如中国电信)
  • 本地DNS服务器缓存中若有,则返回;若无,则需请求DNS根服务器查询
  • 根DNS服务器没有记录具体的域名和IP地址的对应关系,而是告诉本地DNS服务器,你可以到域服务器上去继续查询,并给出域服务器的地址
  • 本地DNS服务器继续向域服务器发出请求,在这个例子中,请求的对象是.com域服务器。.com域服务器收到请求之后,也不会直接返回域名和IP地址的对应关系,而是告诉本地DNS服务器,你的域名的解析服务器的地址
  • 最后,本地DNS服务器向域名的解析服务器发出请求,这时就能收到一个域名和IP地址对应关系,本地DNS服务器不仅要把IP地址返回给用户电脑,还要把这个对应关系保存在缓存中,以备下次别的用户查询时,可以直接返回结果,加快网络访问

image.png

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一般都会分开发送。

image.png

TCP/IP四层模型

5、HTTPS建立TLS连接

1.服务器申请证书

看图学HTTPS 几幅图拿下 HTTPS

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线程、一个合成线程以及一个光栅线程

image.png

  • 构建DOM:生成文档结构
  • 子资源加载:CSS、图片、JS等,浏览器会进行预加载扫描,提前请求资源。JS会阻塞解析过程,因为JS代码可能改变文档流,可以给<script>添加一个async或者defer来异步加载
  • 样式计算:得到每个DOM节点的计算样式(computed style)
  • 布局(复杂):计算每个节点的几何信息,如x,y坐标及盒子大小,同时隐藏不可见元素,伪元素节点会展示
  • 绘画:生成绘制顺序,主线程会根据一定规则将页面分层,并将层次树和绘制顺序提交给合成线程
  • 合成:
    • 合成线程会光栅化页面的每一层(一般会分块提交给光栅线程进行上色,并存储在GPU内存中)
    • 合成线程会收集光栅后的分块信息:绘制方块,来创建合成帧
    • 合成线程会通过IPC向浏览器进程提交一个合成帧
    • 注意:合成操作不涉及主线程,因此只通过合成来构建动画可以提升用户体验。如果页面需要被重新布局或者绘制的话,主线程一定会参与进来的。

一些概念:

重排

image.png

  1. 更新了元素的几何属性(如宽、高、边距);
  2. 触发重新布局,解析之后的一系列子阶段;
  3. 更新完成的渲染流水线,开销最大。

重绘

image.png

  1. 更新元素的绘制属性(元素的颜色、背景色、边框等);
  2. 布局阶段不会执行(无几何位置变换),直接进入绘制阶段。

合成

image.png

  1. 直接进入合成阶段(例如CSS 的 transform 动画);
  2. 直接执行合成阶段,开销最小。

GPU加速在哪个阶段参与?

我们把容易触发重排重绘的元素单独触发渲染层,让它与那些“静态”元素隔离,让GPU分担更多的渲染工作,我们通常把这样的措施成为硬件加速,或者是GPU加速。

默认在3D元素渲染时会打开,也可使用其他2D CSS属性如 transform:translateX(0) 来触发GPU加速,但不要滥用,以免影响整体性能。

GPU实现动画的优缺点?

  • 优点:
    • 利用了GPU合成图层实现动画,可以做到动画平滑、流畅动画
    • 合成工作在GPU线程,不会被CPU的js运行阻塞
  • 缺点:
    • 绘图层必须传输到GPU,当图层较多时传输过程可能会导致渲染缓慢
    • 每个复合层都需要消耗额外的内存,过多的内存可能导致浏览器的崩溃
    • 复合层合成需要更多的时间

为什么transform可以提升性能?

juejin.cn/post/684490…

参考