写点什么

从输入 URL 到渲染的过程中到底发生了什么?

作者:loveX001
  • 2023-01-02
    浙江
  • 本文字数:7814 字

    阅读完需:约 26 分钟

  • CDN

  • 缓存

  • DNS

  • TCP 三次握手、四次挥手

  • 浏览器渲染过程

  • 输入 URL 到页面渲染过程的一些优化


下面我将“从输入 URL 到渲染的全过程”大概的描述出来,再对其过程加以解释,了解过程中可以做哪些优化。文章内容有点长,需要有足够的耐心看完哟!!下面我要开始啦!


1、URL 解析


2、DNS 解析


3、建立 TCP 链接


4、客户端发送请求


5、服务器处理和响应请求


6、浏览器解析并渲染响应内容


7、TCP 四次挥手断开连接

一、URL 解析

地址解析和编码

我们输入 URL 后,浏览器会解析输入的字符串,判断是 URL 还是搜索关键字,如果是 URL 就开始编码。


一般来说 URL 只能使用英文字母、阿拉伯数字和某些标点符号,不能使用其他文字和符号,所以,如果 URL 中有文字就必须编码后使用。但是 URL 编码很混乱,不同的操作系统、浏览器、网页字符集,会导致不同的编码结果。所以我们需要使用 JavaScript 先对 URL 编码,然后提交给服务器,不给浏览器插手的机会。我们通常会使用 encodeURI()函数或者 encodeURIComponent()函数来编码 URL

HSTS

HSTS(HTTP Strict TransportSecurity)是一种新的 Web 安全协议,HSTS 的作用是强制客户端使用 HTTPS 与服务器创建连接。比如你在地址栏输入http://xxx/,浏览器会自动将http转写成https,然后直接向 https://xxx/ 发送请求。

缓存检查

浏览器在发送请求之前先检查有没有缓存,过程如下:



浏览器会先去查看强缓存(Expires 和 cache-control)判断是否过期,如果强缓存生效,直接从缓存中读取资源;若不生效则进行协商缓存(Last-Modified / If-Modified-Since 和 Etag/If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回 200,并重新返回资源和缓存标识,再次存入浏览器缓存中;生效则返回 304,并从缓存中读取资源。(协商缓存之前要经过 DNS 域名解析,之后建立 TCP 链接)


那么浏览器缓存的位置在哪呢?


  • Service Worker:浏览器独立线程进行缓存

  • Memory Cache:内存缓存

  • Disk Cache:硬盘缓存

  • Push Cache:推送缓存(HTTP/2 中的)


注意:输入网址之后,会查找内存缓存,没有再找硬盘,都没有就发生网络请求。普通刷新(F5):因为 TAB 没有关闭,所以内存缓存可用,如果匹配上会被优先使用,其次是磁盘缓存强制刷新(Ctrl+F5):浏览器不使用缓存,因此发送的请求头均带有 Cache-control:no-cache,服务器直接返回 200 和最新内容。

二、进行 DNS 解析

DNS

(1)、DNS:把域名和 ip 地址相互映射分布式数据库,让用户能更方便的访问互联网,DNS 协议运行在 UDP 协议之上


(2)、DNS 解析:通过域名最终得到对应 ip 地址的过程。


(3)、DNS 缓存:浏览器,操作系统,路由器,本地 DNS,根域名服务器都会对 DNS 结果作出一定的缓存

DNS 解析过程

(1)、首先搜索浏览器自身的 DNS 缓存,有缓存直接返回;


(2)、浏览器自身 DNS 不存在,浏览器就会调用一个类似 gethostbyname 的库函数,此函数会先去检测本地 hosts 文件,查看是否有对应 ip。


(3)、如果本地 hosts 文件不存在映射关系,就会查询路由缓存,路由缓存不存在就去查找本地 DNS 服务器(一般 TCP/IP 参数里会设首选 DNS 服务器,通常是 8.8.8.8)(客户端到本地 DNS 服务器是递归过程)


(4)、如果本地 DNS 服务器还没找到就会向根服务器发出请求。(DNS 服务器之间是迭代过程)


具体过程:


DNS 优化

DNS 也是开销,通常浏览器查找一个给定域名的 IP 地址要花费 20~120 毫秒,在完成域名解析之前,浏览器不能从服务器加载到任何东西。那么如何减少域名解析时间,加快页面加载速度呢?


(1)、减少 DNS 请求次数

(2)、DNS 预获取,DOM 还没开始,浏览器预解析地址,把解析好的地址放在本地缓存里面,DOM 树生成完,要加载图片类的发现 DNS 已经解析好了,再发送请求。<link rel='dns-prefetch'href='//dfns.tanx.com'>

(主要对图片资源)

(3)、DNS 查询的过程经历了很多的步骤,如果每次都如此,会耗费太多的时间、资源。所以我们应该尽早的返回真实的 IP 地址:(减少查询过程,也就是 DNS 缓存。浏览器获取到 IP 地址后,一般都会缓存到浏览器的缓存中,本地的 DNS 缓存服务器,也可以去记录。另外,每天几亿网名的访问需求,一秒钟几千万的请求域名服务器如何满足?就是 DNS 负载均衡。通常我们的网站应用各种云服务,或者各种服务商提供类似的服务,由他们去帮我们处理这些问题。 DNS 系统根据每台机器的负载量,地理位置的限制(长距离的传输效率)等等,去提供高效快速的 DNS 解析服务。

(4)、当客户端 DNS 缓存(浏览器和操作系统)缓存为空时,DNS 查找的数量与要加载的 Web 页面中唯一主机名的数量相同,包括页面 URL、脚本、样式表、图片、Flash 对象等的主机名。减少主机名的数量就可以减少 DNS 查找的数量;

(5)、减少唯一主机名的数量会潜在减少页面中并行下载的数量(HTTP1.1 规范建议从每个主机名并行下载两个组件,但实际上可以多个);但是减少主机名和并行下载的方案会产生矛盾,需要大家自己权衡。建议将组件放到至少两个但不多于 4 个主机名下,减少 DNS 查找的同时也允许高度并行下载。


DNS 解析后会把域名的解析权交给 cname()指向的内容分发(CDN)专用的 DNS 服务器。CDN 专用的 DNS 服务器把 CDN 的全局负载均衡设备的 ip 地址返回给用户


参考 前端进阶面试题详细解答

CDN

举个例子:以前坐火车买票,都要到火车站买,所有人都去火车站买票,火车站售票厅的压力可想而知有多大。后来火车票代售点出现了,分布在各个城市,城镇,我们只需要去距离我们最近的火车票售卖点买票就可以了。卖火车票的代理售票点(缓存服务器),为买票者提供了方便,帮助他们在最近的地方(最近的 CDN 节点),用最短的时间(最短的请求时间)买到票(拿到资源)。减轻了售票大厅的压力(起到分流作用,减轻服务器负载压力)

CDN 缓存:

在浏览器本地缓存失效后,浏览器会像 CDN 边缘节点发起请求,类似浏览器缓存,CDN 边缘节点也存在一套缓存机制,


  • CDN 边缘节点缓存策略因服务商不同而不同,通过 http 响应头中的 cache-control:max-age 字段设置 CDN 边缘节点数据缓存时间。

  • 当浏览器向 CDN 节点请求数据时,CDN 节点会判断缓存数据是否过期,若缓存数据过期,CDN 会向服务器发出回源请求,从服务器拉取最新数据,更新本地缓存,并将最新数据返回给客户端,CDN 服务商一般会提供基于文件后缀,目录多个维度来指定 CDN 缓存时间,为用户提供更精细化的缓存管理。

CDN 工作方式:

(1)、当你点击网站页面的 url 时,经过本 DNS 解析,DNS 解析后会把域名的解析权交给 cname()指向的内容分发专用的 DNS 服务器。内容分发专用的 DNS 服务器把内容分发的全局负载均衡(GSLB)设备的 ip 地址返回给用户。

(2)、当你向 CDN 的全局负载均衡设备的 ip 地址发起 url 访问请求,CDN 的全局负载均衡设备会为你选择一台合适的缓存服务器提供服务。

  • 选择的依据:用户的 ip 地址,判断哪台服务器距离用户最近,根据用户请求的 url 中携带的内容名称判断哪台服务器上有用户要的数据,查询各个服务器当前负载情况,判断哪台服务器有服务能力。

  • 分配:基于这些条件综合分析后,区域负载均衡设备会向全局负载均衡设备请求返回一台缓存服务器的 IP 地址。全局负载均衡设备返回服务器 IP 地址,用户向缓存服务器发起请求,缓存服务器响应用户请求,将用户所需内容传送到用户终端,如果这台缓存服务器没有用户想要的内容,而区域均衡设备依然将它分配给了用户,那么这台服务器就要向它的上一级缓存服务器请求内容,直至追溯到网站的源服务器将内容拉到本地。域名解析服务器根据用户 ip 地址,把域名解析成相应节点的缓存服务器 ip 地址,实现用户就近访问,使用 CDN 服务的网站,只要将其域名解析权交给 CDN 的全局负载均衡设备,将需要分发的内容注入到 CDN 就可以实现内容加速了。

CDN 优势:

(1)、CDN 节点解决了跨运营商和跨地域访问的问题,访问延时大大降低;

(2)、大部分请求在 CDN 边缘节点完成,CDN 起到分流作用,减轻了源服务器的负载。

CDN 劣势

(1)、当网站更新时,如果 CDN 节点上数据没有及时更新,即便用户在浏览器使用 Ctrl +F5 的方式使浏览器端的缓存失效,也会因为 CDN 边缘节点没有同步最新数据而导致用户访问异常。

(2)、CDN 不同的缓存时间会对“回源率”产生直接的影响:

  • 如果缓存时间短,CDN 边缘节点的内容经常失效,导致频繁回源。不仅增加服务器压力,也增加了用户访问时间。

  • 如果缓存时间长,数据更新了,边缘节点的内容都还没更新,开发者对特定的任务做特定的数据缓存时间管理。

CDN 刷新缓存

CDN 边缘节点对开发者是透明的,相比于浏览器 Ctrl+F5 的强制刷新来使浏览器本地缓存失效,开发者可以通过 CDN 服务商提供的“刷新缓存”接口来达到清理 CDN 边缘节点缓存的目的。这样开发者在更新数据后,可以使用“刷新缓存”功能来强制 CDN 节点上的数据缓存过期,保证客户端在访问时,拉取到最新的数据。 |

CDN 优化

(1)、前端需要被加速的文件大致包括:


js、css、图片、视频、和页面等文件。页面文件有动态和静态之分。这些文件和页面(比如 html)最大的区别是:这些文件都是静态的,改动比较小,这类静态文件适合做 CDN 加速。我们把这些静态文件通过 CDN 分发到世界各地的节点,用户可以在距离最近的边缘节点拿到需要的内容,从而提升内容下载速度加快网页打开速度。页面分为动态页面和静态页面,动态页面不适合做 CDN 缓存,因为页面是动态的话,内容的有效期就比较活跃。边缘节点的数据经常失效要回源,造成源服务器压力。


(2)、减少资源请求的等待时间


不同浏览器的并发数量不一样:IE11 、IE10 、chrome、Firefox 的并发连接数是 6 个,IE9 是 10 个。如果页面静态资源(图片等)过多(大于 6 个)会存在资源请求等待的情况。目前现实状况是大多用户带宽越来越大,但是我们的静态资源并非那么大,很多文件都是几 k 或者几十 k,6 个文件加起来都小于带宽。这样就导致了资源的浪费。

  • 解决方案是:用多个不同 IP 的服务器来存储这些文件,并在页面中通过绝对路径的方式引用(要求同一 IP 的文件不超过 6 个)。这样就可以尽可能的减少资源请求等待的情况。


至此,你已经获取到缓存服务器的 IP 地址,并且准备向这个 IP 地址发送请求了。

三、建立 TCP 连接

TCP

(1)、TCP 是一种面向连接的,可靠的,基于字节流的传输层通信协议。

(2)、建立 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 状态,完成三次握手。

为什么 TCP 建立一定要三次呢?两次不行吗?

原因

  • 双方要明确对方接收能力都是正常的,(客户端发之后,服务端可以确定客户端发送能力正常,服务端发送给客户端,客户端可以确定服务端的接收和发送能力正常,最后客户端发送确认,来确定客户端的接收能力。

  • 为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误”。

四、客户端发送请求

TCP 三次握手建立连接成功后,客户端按照指定的格式开始向服务端发送 HTTP 请求。

请求过程优化

减少 HTTP 请求次数和请求资源大小


(1)、资源合并压缩

(2)、字体图标(精灵图基本不是好的优化方式了,不好维护)

(3)、base64

(4)、Gzip(一般文件能压缩 60%)

(5)、图片懒加载

(6)、数据延迟分批加载

(7)、CDN 资源

五、服务器响应请求

服务器端收到请求后由 web 服务器(准确说应该是 http 服务器)处理请求,诸如 Apache、Ngnix、IIS 等。web 服务器解析用户请求,了解了要调度哪些资源文件,再通过响应的资源文件处理用户请求和参数,并调用数据库信息,最后将结果通过 web 服务器返回给浏览器客户端。

六、断开连接

服务器响应完客户端请求之后,解除 TCP 连接,释放过程(四次挥手过程)如下:



(1)、客户端发送标记为 FIN=1(finished 的缩写,表示接收完成,请求释放连接),同时生成一个 Seq=u 的序列号,之后进入 FIN-WAIT-1 半关闭阶段(此时客户端到服务端发送数据的通道已经关闭,但是仍然可以接收服务端发过来的数据);

(2)服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号 seq=v,此时,服务端就进入了 CLOSE-WAIT(关闭等待)状态。TCP 服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个 CLOSE-WAIT 状态持续的时间。

(3)客户端收到服务器的确认请求后,此时,客户端就进入 FIN-WAIT-2(终止等待 2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。

(4)服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为 seq=w,此时,服务器就进入了 LAST-ACK(最后确认)状态,等待客户端的确认。

(5)客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是 seq=u+1,此时,客户端就进入了 TIME-WAIT(时间等待)状态。

(6)服务器只要收到了客户端发出的确认,立即进入 CLOSED 状态,就结束了这次的 TCP 连接。

为什么要四次握手而不是三次、两次

因为建立一旦连接,双方既是发送方,又是接收方,为了保证在最后断开的时候,客户端发送的最后一个 ACK 报文段能够被服务器接收到。如果客户端在收到服务器给它的断开连接的请求之后,回应完服务器就直接断开连接的话,服务器就会因为一直没得到客户端响应而一直等待,所以客户端要等待两个最长报文段寿命的时间,以便于服务器没有收到请求之后重新发送请求。

七、浏览器解析并渲染响应内容

在这之前我们先来补充一点基础知识:

浏览器的渲染引擎组成(列举的是基本组成)

(1)、HTML 解析器:将 HTML 解析成 DOM 树。

(2)、CSS 解析器: 为 DOM 中各个元素对象计算出样式信息,为布局提供基础设施。

(3)、JavaScript 引擎:解析并执行 javascript 代码。

(4)、布局 layout 模块:在 DOM 树创建后,webkit 需要将其中的元素对象同样式信息结合起来,计算他们的大小位置等布局信息,形成一个能表达这所有信息的模型。(5)、绘图模块:使用图形库将布局计算后的各个网页的节点绘制成图像的结果。

渲染过程

(1)、浏览器拿到文件后(拿到的是一些字节码)通过编码方式(一般是 utf-8)转换为对应的字符。

(2)、浏览器至上而下解析文档,遇见 HTML 标记,调用 HTML 解析器解析为对应的 tocken,tocken 就是标签文本的序列号,将 tocken 按词法解析解析成具体的标记结构,这个过程已经构建出一颗有标签,有层级,有结构的 DOM 树(就是一块内存,这块内存实际就是一个个 Tocken 构成的);

(3)、遇见 style/link 标记,调用 CSS 解析器处理 CSS 标记并构建 CSSOM 样式树;

(4)、遇见 script 标记,调用 javascript 解析器处理,绑定事件、修改 DOM 树/CSS 树等;

(5)、将 DOM 树和 CSSOM 树合并成一颗 render 树(渲染树)。

(6)、根据渲染树来渲染,计算每个节点的几何信息(这一过程要依赖图形库);

(7)、将各个节点绘制到屏幕上。如果用户操作页面,会触发第(6)或者第(7)步骤,也就是重排和重绘

阻塞渲染

(1)、style 标签的样式:


  • 由 HTML 解析器解析(异步解析);

  • 不阻塞浏览器渲染(可能会出现闪屏(解析一点,显示一点现象);

  • 不阻塞 DOM 解析。


(2)、link 引入的外部 css 样式(推荐使用)


  • 由 CSS 解析器解析(同步解析);

  • 阻塞浏览器渲染(可以利用这种阻塞避免闪屏);

  • 阻塞其后 js 语句的执行:

  • 原因:如果后面 js 的内容是获取元素的样式,例如宽高等属性,如果不等样式解析完毕,后面的 js 就获得了错误的信息,由于浏览器也不知道后续 js 的具体内容,所以只好等前面所有样式解析完毕,再执行 js。例如:firefox 在样式加载和解析过程,会禁止所有脚本。(webkit 内核的浏览器只会在 js 尝试访问样式属性或者可能受到未加载的样式影响时才会禁止脚本。

  • 不阻塞 DOM 的解析:

  • 原因:DOM 解析和 CSS 解析是两个并行的线程。


(3)、优化核心概念:尽可能快的提高外部 css 加载速度。


  • 使用 CDN 节点进行外部资源打包;

  • 对 css 进行压缩(利用打包工具,比如 webpack,glup 等;

  • 减少对 http 请求数量,将多个 css 文件合并;

  • 优化样式的代码。


(4)、js 阻塞:


  • 阻塞 DOM 解析:

  • 原因:浏览器不知道后续脚本的内容,如果先去解析了下面的 DOM,而随后 js 删除了后面的所有 DOM,做了无用功。浏览器无法预估脚本具体做了什么操作,索性全部暂停,等脚本执行完,浏览器再继续向下解析。

  • 阻塞页面的渲染:

  • 原因:js 中也可以给 DOM 设置样式,浏览器同样等该脚本执行完再继续干活,避免做无用功。

  • 阻塞后续 js 的执行:

  • 原因:维护依赖关系,例如:必须先引入 jQuery 再引入 bootstrap。

  • 如果 script 脚本加了 defer:浏览器会发送请求加载 js,但是不会阻塞 DOM 解析,等 DOM 解析完,再执行 js。

  • 如果 script 加了 async:浏览器会发送请求加载 js,不阻塞 DOM 解析,等 js 加载过来了,就先停止 DOM 解析,去执行 js(谁先回来先执行谁),等 js 执行完,继续 DOM 解析。

渲染过程优化

(1)、标签语义化(使用合适的标签,如果不是 w3c 规定的标签,Tocken 令牌和词法解析语法得识别分析,是不是 wc3 规定的)

(2)、减少标签嵌套(生成结构树嵌套太多,就得递归(在 DOM 树构建时候快可以一点)

(3)、样式尽可能少的层级嵌套(使用与编译器的时候,层级嵌套要慎用。CSS 选择器渲染从右到左,.box a{}会 比 a{} 慢

(4)、尽早把 CSS 下载到客户端(充分利用 HTTP 多请求并发机制)

(5)避免阻塞 js 放在底部

(6)、减少回流

  • 放弃传统操作 DOM 时代,基于 vue/react 开始数据影响试图模式

  • 样式集中改变

  • 缓存布局信息,

  • 动画效果应用到 position 属性为 absolute 或 fixed 的元素上(脱离文档流)

  • CSS3 硬件加速(比起考虑如何减少回流重绘,更期望不要回流重绘:transform、opacity、filters 这些属性会触发硬件加速,不会引发回流重绘(过多使用占用大量内存,性能消耗严重

  • 避免使用 table 布局和使用 css 的 js 表达式

结语

通过阅读本文,相信小伙伴们对从输入 URL 到页面渲染的过程有了一个大概的理解。其实整个过程是很复杂也比较繁琐的,不是一篇文章或者几张图就可以囊括的,在这有很多细节不便展开,有兴趣的小伙伴可以对这个过程中的一些细节深入研究研究哦!文章中肯定会有些说的不是很清楚的地方,恳请各位大佬不吝赐教,一起成长!


用户头像

loveX001

关注

还未添加个人签名 2022-09-01 加入

还未添加个人简介

评论

发布
暂无评论
从输入URL到渲染的过程中到底发生了什么?_JavaScript_loveX001_InfoQ写作社区