跳到主要内容

从输入URL到看到页面

先放一张流传很广的图片 url 主要有以下几个过程

  • DNS 解析
  • 发起 TCP 连接
  • 发送 HTTP 请求
  • 服务器处理请求并返回 HTTP 报文
  • 浏览器解析渲染的页面
  • 连接结束
  • TCP 的四次挥手

DNS 解析

DNS 解析是一个递归的过程,首先在本地的域名服务器中去查找,如果没有找到就到根域名服务器中去查找,没有找到就再去顶级域名服务器中查找,直到找到对应的 IP 地址。 但是如果每次都像上面一样还是很耗费时间的所以可以使用 DNS 缓存来减少时间。 DNS 存在着多级缓存,从距离浏览器的距离排序的话有以下几种:浏览器缓存操作系统缓存路由器缓存IPS 服务器缓存根域名服务器缓存顶级域名服务器缓存主域名服务器缓存

什么是负载均衡

可以发现每次访问同一个网站的时候,比如说baidu.com,每次响应的都不一定是同一个服务器,DNS 可以返回一个合适的服务器 IP 地址,这个过程就是负载均衡。

发起 TCP 连接

TCP 提供一种可靠的传输,这个过程涉及到三次握手四次挥手

来看一下 TCP 的头部分 首先是 16 位源端口号,16 位目的端口号,32 位序列号,32 位确认号,4 位首部长度,6 位保留位,6 位标志位,16 位窗口大小,16 位校验和,16 位紧急指针,选项,数据 tcp

  • 源端口和 IP 地址是为了让服务器知道是谁发送的请求
  • 目的端口是接收方上计算机的应用程序的端口
  • 序号是为了保证数据包的顺序
  • 确认号就是 ACK,表明下一个期待收到的字节序号,表明该序号之前所有的数据都已经准确无误的收到,只有当 ACK 标志为 1 的时候才有效,一开始建立连接的时候 SYN 报文的 ACK 为 0
  • 首部长度占 4 位
  • 保留位
  • 控制位
    • URG,当 URG=1 时,表明紧急指针字段有效,紧急指针指向的数据应该尽快传送
    • 确认 ACK,当 ACK=1 时,确认号字段有效,表明确认号字段有效
    • PSH,当 PSH=1 时,表明数据包应该尽快传送给接收方的应用程序
    • RST,当 RST=1 时,表明 TCP 连接中出现严重错误,需要重新建立连接
    • SYN,当 SYN=1 时,表明这是一个连接请求或连接接受报文
    • FIN,当 FIN=1 时,表明发送方已经发送完数据,要求释放连接

三次握手

第一次握手: 用户端发送 SYN 报文,将 SYN 标志位设为 1,Seqx,表示客户端的初始序列号。用户端进入 SYN_SEND 状态,等待服务器确认。(SYN =1,Seq=x,初始序列号为 x) 第二次握手 服务器端收到 SYN 报文,需要确认客户的 SYN,同时自己也发送一个 SYN 报文,将 ACK 标志位设为 1,ACKNumx+1SeqNumy,表示服务器的初始序列号。服务器端进入 SYN_RECV 状态。发送一个 SYN+ACK 的报文(SYN=1,ACK=1,Seq=y,ACKNum=x+1) 第三次握手 客户端收到服务器的 SYN 报文,向服务器发送确认报文,将 ACK 标志位设为 1ACKNumy+1SeqNumx+1,服务器收到这个报文之后,双方进入 ESTABLISHED 状态,完成三次握手。(ACK=1,ACKNum=y+1,Seq=x+1)

为什么采用三次握手呢? 采用三次握手是为了防止失效的链接请求报文段突然又传送到主机 B,因而产生错误。如果采用两次握手,那么只要主机 A 发送的链接请求报文段丢失,主机 B 不会收到这个链接请求报文段,主机 B 就不会有 ACK 报文段发送给主机 A,此时主机 A 会认为链接已经建立,而主机 B 不会认为链接已经建立,所以主机 A 会一直发送链接请求报文段,而主机 B 不会发送 ACK 报文段。

为什么不是四次握手呢? 有一个著名的红军和蓝军的故事,表示通信时不可能 100%可靠,所以四次握手是多余的。

四次挥手

数据传输完璧后双方都可以释放链接 最开始的时候都处于 ESTABLISHED 状态 第一次挥手 主动放发送 FIN 报文,表示数据传输完毕,但是还可以接收数据,发送完毕后进入 FIN_WAIT_1 状态,FIN = 1 它的序号 seq = u 此时进入到了 FIN_WAIT_1 状态 第二次挥手 服务器接收到FIN包之后发送一个 ACK,确认号为收到的 seq+1,此时服务器进入CLOSE_WAIT状态,客户端接收到 ACK 之后进入FIN_WAIT_2状态 第三次挥手 服务器再次发送一个FIN用来关闭链接,此时服务器进入LAST_ACK状态 第四次挥手 主动关闭方收到了FIN后发送一个 ACK 给被动关闭方,确认序号为收到的序号+1,此时进入TIME_WAIT状态,等待 2MSL 后进入CLOSED状态,被动关闭方接收到 ACK 之后进入CLOSED状态

发送 HTTP 请求

HTTP 的端口号是 80/8080 HTTPS 的端口号是 443 请求行 请求行的格式时 Method Request-URI HTTP-Version CRLF 例子 GET index.html HTTP/1.1 HTTP 1.1 常见的方法有 GET POST PUT DELETE OPTIONS HEAD

GET 和 POST 的区别

GET 在浏览器回退时是无害的,而 POST 会再次提交请求 GET 产生的 URL 地址可以被收藏,而 POST 不可以 GET 请求会被浏览器主动缓存,而 POST 不会,除非手动设置 GET 请求只能进行 URL 编码,而 POST 支持多种编码方式 GET 请求参数会被完整保留在浏览器历史记录里,而 POST 中的参数不会被保留 GET 请求在 URL 中传送的参数是有长度限制的,而 POST 没有限制 对参数的数据类型,GET 只接受 ASCII 字符,而 POST 没有限制 GETPOST 更不安全,因为参数直接暴露在 URL 上,所以不能用来传递敏感信息 GET 参数通过 URL 传递,POST 放在 Request body 中

GET 会产生一个 TCP 数据包,POST 会产生两个 TCP 数据包 因为对于 GET 的请求,浏览器会把 http header 和 data 一并发送出去,服务器响应 200(返回数据); 而对于 POST 的请求,浏览器先发送 header,服务器响应 100 continue,浏览器再发送 data,服务器响应 200 OK(返回数据)

传递的数据存储在正文中请求的数据格式一般是 json 所以设置为 Content-Type: application/json

HTTP 缓存

HTTP 缓存的规则

分为强制缓存,协商缓存

强制缓存

当缓存数据库中有客户端需要的数据可以直接从中拿出来用

协商缓存

当客户端从缓存数据库中拿到一个缓存的标识,然后想服务端验证标识是否时失效,如果没有失效,服务端会返回 304,这样客户端可以直接去缓存数据库中拿出数据

强制缓存的优先级高于协商缓存,若两种缓存皆存在,且强制缓存命中目标,则协商缓存不再验证标识。

缓存的方案

上面我们大概了解了缓存如何运行的,但是服务端如何判断缓存是否失效呢,在浏览器和服务器进行交互的时候会发送一些请求数据和响应数据,我们称之为 HTTP 报文,保温中包含首部的 header 和主体部分的 body,与缓存相关的信息就保存在 header 中

强制缓存

对于强制缓存服务器响应的 header 中会用两个字段来表明ExpiresCache-ControlExpires 是一个绝对时间,Cache-Control 是一个相对时间,Cache-Control 优先级高于 ExpiresCache-Control 有这几个值 private(客户端可以缓存) public(客户端和服务端都可以缓存) no-cache(需要使用协商缓存来验证) no-store(所有内容都不会被缓存) max-age(缓存会在多少秒后失效)

协商缓存

协商缓存会对比判断是否可以使用缓存,浏览器第一次请求数据的时候,服务器会将缓存标识和数据一起响应给客户端,客户端将他们备份到缓存中 Last-ModifiedIf-Modified-SinceLast-Modified 是服务器响应的 header 中的一个字段,If-Modified-Since 是客户端请求的 header 中的一个字段,当客户端再次请求数据的时候,会将上次的 Last-Modified 传给服务器,服务器会对比两个时间,如果时间一致,服务器会返回 304,客户端可以直接从缓存中拿数据 Etag 服务器响应请求时,通过此字段告诉浏览器当前资源在服务器生成唯一的标识 If-None-Match:再次请求服务器时,浏览器的请求报文头部会包含此字段,后面的值为在缓存中获取的标识。服务器接收到次报文后发现 If-None-Match 则与被请求资源的唯一标识进行对比。

缓存的优点

  • 减少了冗余的数据传输
  • 减少了服务器的负担
  • 加快了客户端加载网页的速度,这是主要原因

状态码

200 成功 204 无内容 301 永久重定向 302 临时重定向 304 未修改 400 请求错误 401 未授权 403 禁止访问 404 未找到 422 请求格式正确,但是由于含有语义错误,无法响应 500 服务器错误

服务器解析渲染页面

  • 解析 HTML 形成 DOM 树
  • 解析 CSS 形成 CSSOM 树
  • 合并 DOM 树和 CSSOM 树形成渲染树
  • 浏览器开始渲染和绘制页面

DOM 节点都是以盒子模型呈现的,所以浏览器需要去计算位置和宽度,这个过程叫做回流,回流是很消耗性能的,所以我们要尽量减少回流的次数 等到所有的节点都渲染完毕之后,浏览器会将页面绘制出来,这个过程叫做重绘

性能优化之回流和重绘

回流

页面首次渲染,浏览器窗口大小发生改变,元素位置发生改变,元素尺寸发生改变,元素内容发生改变,元素字体大小发生改变,添加或者删除可见的 DOM 元素,激活 CSS 伪类,查询某些属性或调用某些方法 激活 CSS 伪类比如:hover查询某些属性或调用某些方法比如offsetWidthoffsetHeightclientWidthclientHeightscrollTopscrollLeftgetComputedStyle()getBoundingClientRect()scrollIntoView()scrollIntoViewIfNeeded()

重绘

当页面中的元素样式发生改变,但是它的位置和尺寸没有发生改变,浏览器会将新的样式绘制到页面上,这个过程叫做重绘

优化

CSS

  • 避免使用 table 布局
  • 尽量减少 DOM 深度
  • 避免设置多层内联样式
  • 避免使用 CSS 表达式

JS

  • 避免频繁操作样式
  • 避免频繁操作 DOM,创建一个 documentFragment,在它上面应用所有 DOM 操作,最后再把它添加到文档中。
  • 可以先将元素设为 display:none,操作完再显示
  • 对具有复杂动画的元素使用绝对定位,使其脱离文档流,减少回流次数