全面解析URL请求到页面显示完整过程

网页加载流程

URL解析

先进行 URL 解析,看看输入的内容是否符合 URL 规则(解析 URL 提取出协议、域名、端口号,对于一些特殊字符,在传递的时候须要进行编码解码)。css

  • encodeURIdecodeURI能够对中文、空格等编码解码,适用于 URL 自己
  • encodeURIComponentdecodeURIComponent范围更广,会编码解码一些特殊字符如 :/?=+@#$,适用于给参数编码解码

缓存检查

URL 符合规则,浏览器进程会经过进程通讯将 URL 请求发送给网络进程,网络进程会依次查找 Memory CacheDisk Cache中是否有缓存内容,有且没过时则使用,不然则发送网络请求。html

DNS 解析

网络请求第一步就是先进行 DNS 解析,获取请求域名服务器的 IP 地址。前端

什么是 DNS 解析,每台计算机都有一个惟一 IP 地址,可是 IP 地址不方便记忆,因此采用更方便记忆的网址去查找其余计算机,将网址转换成 IP 地址的过程就是 DNS 解析。webpack

域名解析是一个递归查询 + 迭代查询的过程。git

  1. 浏览器缓存,向浏览器的缓存中读取上一次的访问记录
  2. 操做系统的缓存,查找存储在系统运行内存中的缓存
  3. host 文件中查找
  4. 路由器缓存:有些路由器会把访问过的域名存在路由器上
  5. ISP互联网服务提供商缓存,好比 114.114.114.114
  6. 缓存中找不到,则本地 DNS 服务器进行迭代查询:.DNS 服务器 -> .com 顶级服务器 -> 主域名服务器 -> ...,直到服务器返回对应的 IP

DNS 负载均衡:github

网站对应的 IP 不止一个,DNS 能够根据每台机器的负载量、距离用户的距离等返回一个合适的服务器 IP 给用户,这个过程就是 DNS 负载均衡,又叫作 DNS 重定向。 CDN 就是利用 DNS 的重定向技术, DNS 会返回一个用户最接近的点的 IP 给用户。web

TCP 链接三次握手

拿到 IP 后,(检查当前域名是否达到 TCP 链接上限),经过三次握手进行 TCP 链接面试

tcp3.png

三次握手:算法

  1. 第一次:客户端发送 SYN 包和初始序号 seq = x 给服务端,此时客户端状态为 SYN-SENT
  2. 第二次:服务端收到 SYN 包后,将标识位 SYNACK 置为1,确认序号 ack = x + 1, 初始序号 seq = y 发送给客户端,此时服务端状态为 SYN-RECEIVED
  3. 第三次:客户端收到后,将标识位 ACK 置为1, 确认序号 ack = y + 1, 本身的序号 seq = x + 1, 发送给服务端,服务端收到后也将状态切换为 ESTABLISHED

三次握手抽象版:浏览器

  1. 客户端:你是服务端吗
  2. 服务端:是的,我是服务端,你是客户端吗
  3. 客户端:是的,我是客户端
  • seq序号,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记
  • ack确认序号,只有ACK标志位为1时,确认序号字段才有效,ack=seq+1

标识位:

  • ACK:确认标识,用于表示对数据包的成功接收。
  • SYN:同步标识,表示 TCP 链接已初始化,发起一个新链接。
  • FIN:完成标识,释放一个链接,用于拆除上一个 SYN 标识。一个完整的TCP链接过程必定会有 SYNFIN 包。

为何不能两次握手?

TCP 的特色的可靠传输,服务端和客户端都须要可靠传输,就须要确认双方的发送和接收能力,第一次握手确认了客户端的发送能力,第二次确认了服务端的发送和接收能力,第三次确认了客户端的接收能力

两次握手,服务器不能肯定客户端已经收到了确认请求,不能确认是否创建好了链接。服务器认为创建好了链接,发送数据包,结果发的包客户端没收到,那么攻击服务器就很容易了,只发包不收包。

TCPUDP 的区别:

  • TCP 是一个面向链接的、可靠的、基于字节流的传输层协议,TCP 会精准记录哪些数据发送了,哪些数据被对方接收了,哪些没有被接收到,并且保证数据包按序到达,不容许半点差错。这是有状态, 当意识到丢包了或者网络环境不佳,TCP 会根据具体状况调整本身的行为,控制本身的发送速度或者重发。这是可控制
  • UDP 是一个面向无链接的传输层协议,无状态不可控

HTTP请求

TCP 链接创建以后,浏览器端会构建请求行、 请求头等信息,并把和该域名相关的 Cookie 等数据附加到请求头中,而后向服务器发送构建的请求信息。若是是 HTTPS,还须要进行 TSL 协商。

服务器检查 HTTP 请求头是否包含缓存验证信息进行协商缓存

协商缓存

Last-ModifiedIf-Modified-Since

Last_Modified 表示本地文件的最后修改时间,If-Modified-Since 会将 Last-Modified 的值发送给服务器询问该资源是否有更新,若是有更新就会将新的资源发送回来,不然返回 304 状态码,表明资源无更新,继续使用缓存文件。

Last-Modified 弊端:

  1. 若是文件只是被打开,没有修改,也会形成 Last-Modified 修改,服务器不能命中缓存。
  2. 只能以秒计时,若是在毫秒级的时间内修改了文件,服务器 Last-Modified 的值并不会修改,会返回304,浏览器就会是本身的缓存 。

ETagIf-No-Match

ETag 是文件指纹,If-No-Match 会将 ETag 发送给服务器,查询该资源 ETag 是否变更,有变更的话就将新的资源发送回来。ETag 优先级高于 Last-Modified

启发式缓存

若是什么缓存都没设置,浏览器一般会响应头中的 Date 减去 Last-Modified 值的 10% 做为缓存时间。

状态码

状态码用于表示服务器对请求的处理结果

  • 1xx:指示信息——表示请求已经接受,继续处理
    • 100 Continue 通常在发送 post 请求时,已发送了 http header 以后服务端返回此信息,表示确认,以后发送具体参数信息。
  • 2xx:成功
    • 200 OK 正常返回信息
    • 201 Created 请求成功而且服务器建立了新的资源
    • 202 Accepted 服务器已接受请求,但还没有处理
  • 3xx:重定向
    • 301 Moved Permanently 永久重定向
    • 302 Found 临时重定向
    • 303 See Other 临时重定向,且老是使用 GET 请求新的 URI
    • 304 Not Modified 请求内容未改动,走缓存
  • 4xx:客户端错误
    • 400 Bad Request 服务器没法理解请求格式
    • 401 Unauthorized 请求未受权
    • 403 Forbidden 禁止访问
    • 404 Not Found 找不到与 URI 相匹配的资源
  • 5xx:服务器错误。
    • 500 Internal Server Error 服务器内部错误
    • 503 Service Unavailable 服务器暂时没法处理请求

四次挥手

tcp4.png

数据传输完后,若是请求头或响应头里没有 connection: keep-alive,则须要四次挥手断开 TCP 链接,不然会保持链接通道,这样下一次在发送请求,就无需再次TCP三次握手了,节省了网络通讯时间。

http1.0 中默认 Connection 并非 keep-alive,须要手动处理,可是 HTTP1.1 以后,Connection:keep-alive 已经被列入了规范,如今基本都是默认就是长链接,前提是同一个源,向不一样源发送请求要从新创建通道。

四次挥手:

  • 第一次:客户端主动关闭放发送一个 FIN,用来关闭客户端到服务端的数据传输,告诉服务端我不会给你发送数据了
  • 第二次:服务端收到 FIN 包后,发送一个 ACK 给客户端,确认序号为收到序号 + 1
  • 第三次:服务端发送完数据后,服务端发送一个 FIN,用来关闭服务端到客户端的数据传输,告诉客户端我不会给你发数据了
  • 第四次:客户端收到 FIN 后,发送一个 ACK 给服务端,确认序号为收到序号 + 1,完成四次挥手

四次挥手抽象版:

  1. 客户端:服务端,我要和你断开链接
  2. 服务端:好的,断吧
  3. 服务端:我也要和你断开链接
  4. 客户端:好的,断吧

四次握手后,客户端还会等待 2MSL(MSL:最长报文段寿命,通常2min) 的时间,为了保证客户端发送的 ACK 报文可以到达服务器,由于这个报文可能会丢失,服务器收不到确认会超时重传 FIN + ACK 报文段,客户端能在 2MSL 时间内收到这个重传的报文段,而后客户端从新确认。

为何链接的时候是三次握手,关闭的时候倒是四次挥手?

  • 服务端接收到客户端的 SYN 链接请求报文后,能够直接发送 SYN + ACK 报文
  • 可是关闭链接时,当服务端接收到 FIN 报文时,极可能并不会当即关闭链接,因此只能先回复一个 ACk 报文,告诉客户端你发的 FIN 报文我收到了,只有等服务端全部的报文发送完了,我才能发送 FIN 报文,所以不能一块儿发送,因此须要四次。

客户端解析资源

  • 浏览器拿到资源会根据资源类型进行处理,好比是 gzip 压缩后的文件则进行解压缩,若是响应头 Content-typetext/html,则开始解析 HTML

HTML ParserHTML 文件进行处理,根据 HTML 标记关系构建 DOM 树。 - 解析过程当中遇到图片、linkscript会启动下载。 - script标签会阻塞 DOM 树的构建,因此通常将 script 放在底部,或者添加 asyncdefer 标识。 - css 下载时异步,不会阻塞浏览器构建 DOM 树,可是会阻塞渲染,在构建布局树时,会等待 css 下载解析完毕后才进行。

  • 渲染引擎将 CSS 样式表转化为浏览器能够理解的 styleSheets,转换样式表中的属性使其标准化em => px; bold => 700
  • 根据 DOM 树和 styleSheets 构建布局树,计算出元素的布局信息,display: none不可见节点以及 head 这种不可见标签不会插入到布局树里
    • 构建 DOM 树、构建 CSSOM 树、构建树并非严格的前后顺序,为了让用户能尽快看到网页内容,都是并行推动的
  • 对布局树进行分层,生成图层树。
    • position: fixed/absolutez-index:2filter: blue(5px)opacity: .5等拥有层叠上下文属性的元素会进行分层、或者内容须要裁减
  • 绘制图层须要一个个绘制指令,渲染线程将包含绘制指令的绘制列表提交给合成线程,绘制操做是由合成线程来完成的
  • 合成线程将图层划分为一个个图块,优先处理靠近视口的图块,对其进行栅格化处理生成位图
    • 一般,栅格化过程会采用 GPU 加速生成,渲染进程把生成图块的指令发送给 GPU 进程,GPU 生成最终的位图并保存在内存之中
  • 一旦全部图块都被光栅化,合成线程向浏览器进程提交一个绘制图块的命令,将其内容绘制到内存之中,最后显示在屏幕上

优化

  • 尽早的把 CSS 下载到客户端,充分利用 HTTP 多请求并发机制,且 CSS 下载并不会阻塞渲染,style、link、@import 放到页面顶部
  • 避免 JS 加载阻塞渲染,添加 async、defer 标识,标签放到页面底部
  • 减小 DOM 的回流和重绘

asyncdefer 都是异步的,使用 async 标志的脚本文件一旦加载完成,会当即执行;而使用了 defer 标记的脚本文件,须要在 DOMContentLoaded 事件以前执行。

重绘:元素样式的改变,可是宽高、大小、位置等不变,好比:colorbackgroundvisibility

回流:元素的大小或者位置发送了变化,触发了页面的从新布局,甚至调用方法或属性getComputedStyleclientWidth,为了保证获得的结果是即便性准确性,致使布局树从新计算布局和渲染。

优化策略:

  • 减小回流范围:避免使用 table 布局,由于一个小改动可能会形成整个 table 的从新布局
  • 避免逐条改变样式,使用类名去合并样式
  • 使用 documentFragment 操做 dom,操做完成后再添加到文档中
  • 避免频繁读取会引起回流/重绘的属性,若是确实须要屡次使用,就用一个变量缓存起来。
  • 动画效果应用到 position 属性为 absolutefixed 元素上,脱离文档流,单独渲染区域
  • CSS3 硬件加速, transformopacity 等属性会触发 GPU 加速,不会引起回流和重绘,可是过多使用可能会占用大量内存,性能消耗严重
  • 现代浏览器会本身缓存一个 flush 队列,而后一次性清空。

性能优化

DNS 优化

  • DNS 预解析
  • 采用 CDNDNS 负载均衡
<meta http-equiv="x-dns-prefetch-control" content="on">
<link rel="dns-prefetch" href="//g.alicdn.com" />  
复制代码

网络链接优化

  • 分服务器部署,区分 web 服务器、资源服务器、数据服务器,增长 HTTP 并发性
  • 减小 TCP 的三次握手和四次挥手:HTTP1.1默认开启的 Connection: keep-alive

数据缓存

  • 对于静态资源文件实现强缓存和协商缓存
  • 对不常常更新的接口数据采用本地存储作数据缓存,好比地区数据

数据传输

  • 减小数据传输的大小
  1. 利用工具如 webpack 对传输内容进行压缩
  2. 服务端开发 GZIP 压缩,通常能压缩 60% 左右
  3. 大批量数据分批次请求,下拉刷新,分页
  • 减小 HTTP 请求的次数
  1. 资源文件合并处理
  2. 小图片转成 base64,可是可能会形成图片大小增长 1/3

采用 HTTP2.0

HTTP1.1 虽然在串行请求能够经过 Connection: keep-alive 复用同一个 TCP 链接,若是是并行发送多个请求,会创建多个链接,可是浏览器通常限制会限制同一域名下最多同时能够创建6个链接。

  • 请求阻塞:在并发请求达最大限制时,请求必须等到上一个请求完成后,才能够复用这个 TCP 发出下一个请求,因此会受到前面请求的阻塞。
  • 线头阻塞:请求响应的顺序必须和请求发送的顺序一致,若是后发送的请求响应完成了,也要等前面的阻塞的请求返回。

多路复用:容许同时经过单一的 HTTP2.0 链接发起的多重请求 - 响应消息,链接通道是共享的

HTTP2.0 的传输是基于二进制帧的,每一个 TCP 链接中,都有多个双向流通的流,每一个流都有独一无二的标识和优先级,而流就是由二进制帧组成的。二进制帧会标识本身是属于哪一个流的,因此这些流能够交错传输,在接收端根据帧头组装成完整的信息,解决线头堵塞的问题。

头部压缩:HTTP1.xheader 中带有大量的信息,每次都要重复发送,HTTP2.0 使用 HPACK 算法对 header 数据进行压缩,减小须要传输的 header 大小,通信双方各自缓存一个头部字典表,能够差别化更新头部,减小须要传输数据的大小

参考文章