inter-http

网络七层协议

应用层、表达层、会话层、传输层、网络层、数据链路层、物理层。

如何让你设计网络
物理层 - 集线器
数据链路层 - 交换机,维护 端口 - MAC 地址表
网络层 - 路由器,对比源 IP 和目标 IP 子网掩码,不同则走默认路由

TCP 协议

TCP 重传:
TCP 重传机制包括:1、超时重传,2、快速重传,3、SACK,4、D-SACK。
超时重传:数据发送时,设定一个定时器,当超过指定时间,没有收到对方的 ack 确认应答报文,就会重发该数据。超时重传时间 RTO 应该略大于往返时延 RTT(数据发送到接受确认的时刻的差值)。
快速重传:收到三个相同的 ack 报文时,会在定时器过期之前,重传丢失的报文段。
SACK:TCP 头部 SACK 字段,缓存地图发送给发送方,知道发送方那些数据收到了,哪些没有收到。
D-SACk:使用 SACK 告诉发送方哪些数据被重复接收了。

TCP 重传、窗口滑动、流量控制和拥塞控制

没想到 TCP 居然还有这种打开方式

传输层 - UDP,增加源端口号和目标端口号

TCP 窗口流量控制:

  • A -> B 发送 TCP 消息会带上 win,win 是窗口大小,表示 A 的接受能力,B 收到后会回复 ack 和 win,表示 B 的接收能力;
  • 如果两个 win 大小相同,A 每收到一个 ack 后窗口整体移动一步;
  • 如果长度不同,B 发给 A 的 win 变大,证明 B 的接受能力变强,A 的窗口上边界往右边移动,扩大发送窗口;
  • B 发给 A 的 win 变小,证明 B 的接受能力变弱,A 的窗口上边界不动,等接受到 ack 后下边界右移即可,窗口大小变为 win 的长度,再整体移动;
  • win 值是通过试探算出来的,比如第一次发送 2 个,第二次发送 3 个…;

TCP 协议三次握手

1、第一次握手:刚开始客户端处于 CLOSED 的状态,服务端处于 LISTEN 状态。客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN。此时客户端处于 SYN_SEND 状态。
2、第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号 ISN,同时会把客户端的 ISN + 1 作为 ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_REVD 的状态。
3、第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 ESTABLELISHED 状态。
4、服务器收到 ACK 报文之后,也处于 ESTABLELISHED 状态,此时,双方以建立起了链接。

为什么是 3 次?

避免历史连接,确认客户端发来的请求是这次通信的人。

四次挥手

1、第一次挥手:在挥手之前服务端与客户端都处于 ESTABLISTEN 状态。客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于 FIN_WAIT1 状态。
2、第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 + 1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态。
3、第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。
4、第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 + 1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态
5、服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。

参考:关于三次握手和四次挥手,面试官想听到怎样的回答?

HTTP/1.0 HTTP1.1 HTTP2.0 版本之间的差异

HTTP 0.9

  • 1991 年,原型版本,功能简陋,只有一个命令 GET,只支持纯文本内容,该版本已过时。

HTTP1.0

  • 任何格式的内容都可以发送,这使得互联网不仅可以传输文字,还能传输图像、视频、二进制等文件。
  • 除了 GET 命令,还引入了 POST 命令和 HEAD 命令。
  • http 请求和回应的格式改变,除了数据部分,每次通信都必须包括头信息(HTTP header),用来描述一些元数据。
  • 只使用 header 中的 If-Modified-Since 和 Expires 作为缓存失效的标准。
  • 不支持断点续传,也就是说,每次都会传送全部的页面和数据。
  • 通常每台计算机只能绑定一个 IP,所以请求消息中的 URL 并没有传递主机名(hostname)

HTTP 1.1

http1.1 是目前最为主流的 http 协议版本,从 1999 年发布至今,仍是主流的 http 协议版本。

  • 引入了持久连接( persistent connection),即 TCP 连接默认不关闭,可以被多个请求复用,不用声明 Connection: keep-alive。长连接的连接时长可以通过请求头中的 keep-alive 来设置。
  • 引入了管道机制( pipelining),即在同一个 TCP 连接里,客户端可以同时发送多个请求,进一步改进了 HTTP 协议的效率。
  • HTTP 1.1 中新增加了 E-tag,If-Unmodified-Since, If-Match, If-None-Match 等缓存控制标头来控制缓存失效。
  • 支持断点续传,通过使用请求头中的 Range 来实现。
  • 使用了虚拟网络,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个 IP 地址。
  • 新增方法:PUT、 PATCH、 OPTIONS、 DELETE。

http1.x 版本问题

  • 在传输数据过程中,所有内容都是明文,客户端和服务器端都无法验证对方的身份,无法保证数据的安全性。
  • HTTP/1.1 版本默认允许复用 TCP 连接,但是在同一个 TCP 连接里,所有数据通信是按次序进行的,服务器通常在处理完一个回应后,才会继续去处理下一个,这样子就会造成队头阻塞。
  • http/1.x 版本支持 Keep-alive,用此方案来弥补创建多次连接产生的延迟,但是同样会给服务器带来压力,并且的话,对于单文件被不断请求的服务,Keep-alive 会极大影响性能,因为它在文件被请求之后还保持了不必要的连接很长时间。

HTTP 2.0

  • 二进制分帧。这是一次彻底的二进制协议,头信息和数据体都是二进制,并且统称为”帧”:头信息帧和数据帧。
  • 头部压缩。HTTP 1.1 版本会出现 「User-Agent、Cookie、Accept、Server、Range」 等字段可能会占用几百甚至几千字节,而 Body 却经常只有几十字节,所以导致头部偏重。HTTP 2.0 使用 HPACK 算法进行压缩。
  • 多路复用。复用 TCP 连接,在一个连接里,客户端和浏览器都可以同时发送多个请求或回应,且不用按顺序一一对应,这样子解决了队头阻塞的问题。
  • 服务器推送。允许服务器未经请求,主动向客户端发送资源,即服务器推送。
  • 请求优先级。可以设置数据帧的优先级,让服务端先处理重要资源,优化用户体验。

谈一谈你对 HTTP/2 理解

头部压缩

HTTP1.1 版本出现 「User-Agent、Cookie、Accept、Server、Range」 等字段可能会占用几百甚至几千字节,而 Body 却经常只有几十字节,所以导致头部偏重。
HTTP2.0 使用 HPACK 算法进行压缩。「传索引」的方式,可以说让请求头字段得到极大程度的精简和复用。其次是对于整数和字符串进行「哈夫曼编码」,哈夫曼编码的原理就是先将所有出现的字符建立一张索引表,然后让出现次数多的字符对应的索引尽可能短,传输的时候也是传输这样的「索引序列」,可以达到非常高的压缩率。

多路复用

HTTP1.x 中,如果想并发多个请求,必须使用多个 TCP 链接,且浏览器为了控制资源,还会对单个域名又 6-8 个的 TCP 链接请求限制。

HTTP2 中:

  • 同域名下所有通信都在单个连接上完成。
  • 单个连接可以承载任意数量的双向数据流。
  • 数据流以消息的形式发送,而消息又由一个或多个帧组成,多个帧之间可以乱序发送,因为根据帧首部的流标识可以重新组装,也就是 Stream ID,流标识符,有了它,接收方就能从乱序的二进制帧中选择 ID 相同的帧,按照顺序组装成请求/响应报文。

服务器推送

浏览器发送一个请求,服务器主动向浏览器推送与这个请求相关的资源,这样浏览器就不用发起后续请求。

相比较 HTTP/1.1 的优势

  • 推送资源可以由不同页面共享。
  • 服务器可以按照优先级推送资源。
  • 客户端可以缓存推送的资源。
  • 客户端可以拒收推送过来的资源。

二进制分帧

之前是明文传输,不方便计算机解析,对于回车换行符来说到底是内容还是分隔符,都需要内部状态机去识别,这样子效率低,HTTP/2 采用二进制格式,全部传输 01 串,便于机器解码。
这样子一个报文格式就被拆分成一个个二进制帧,用「Headers 帧」存放头部字段,「Data 帧」存放请求体数据。这样子的话,就是一堆乱序的二进制帧,它们不存在先后关系,因此不需要排队等待,解决了 HTTP 队头阻塞问题。

在客户端与服务器之间,双方都可以互相发送二进制帧,这样子「双向传输的序列」,称为流,所以 HTTP/2 中以流来表示一个 TCP 连接上进行多个数据帧的通信,这就是多路复用概念。

那乱序的二进制帧,是如何组装成对的报文呢?

  • 所谓乱序,值是不同 ID 的 Stream 是乱序的,对于同一个 Stream ID 的帧是按顺序传输的。
  • 接收方收到二进制后,将相同的 Stream ID 组装成完整的请求报文和响应报文。
  • 二进制帧中有一些字段,控制着优先级和流量控制等功能,这样子的话,就可以设置数据帧的优先级,让服务器处理重要资源,优化用户体验。

介绍一下 HTTP 常见状态码

RFC 规定 HTTP 的状态码为「三位数」,第一个数字定义了响应的类别,被分为五类:

  • 「1xx」: 代表请求已被接受,需要继续处理。
  • 「2xx」: 表示成功状态。
  • 「3xx」: 重定向状态。
  • 「4xx」: 客户端错误。
  • 「5xx」: 服务器端错误。

1xx 信息类

接受的请求正在处理,信息类状态码。

2xx 成功

  • 200 OK 表示从客户端发来的请求在服务器端被正确请求。
  • 204 No content,表示请求成功,但没有资源可返回。
  • 206 Partial Content,该状态码表示客户端进行了范围请求,而服务器成功执行了这部分的 GET 请求
    响应报文中包含由 「Content-Range」 指定范围的实体内容。

3xx 重定向

  • 301 moved permanently,永久性重定向,表示资源已被分配了新的 URL,这时应该按 Location 首部字段提示的 URI 重新保存。
  • 302 found,临时性重定向,表示资源临时被分配了新的 URL。
  • 303 see other,表示资源存在着另一个 URL,应使用 GET 方法获取资源。
  • 304 not modified,当协商缓存命中时会返回这个状态码。
  • 307 temporary redirect,临时重定向,和 302 含义相同,不会改变 method

4XX 客户端错误

  • 400 bad request,请求报文存在语法错误。
  • 401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息。
  • 403 forbidden,表示对请求资源的访问被服务器拒绝。
  • 404 not found,表示在服务器上没有找到请求的资源。
  • 405 Method Not Allowed,服务器禁止使用该方法,客户端可以通过 options 方法来查看服务器允许的访问方法,如下 👇

    Access-Control-Allow-Methods →GET,HEAD,PUT,PATCH,POST,DELETE

5XX 服务器错误

  • 500 internal sever error,表示服务器端在执行请求时发生了错误。
  • 502 Bad Gateway,服务器自身是正常的,访问的时候出了问题,具体啥错误我们不知道。
  • 503 service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求。

参考:「查缺补漏」巩固你的 HTTP 知识体系

DNS 如何工作的

DNS 协议提供的是一种主机名到 IP 地址的转换服务,就是我们常说的域名系统。是应用层协议,通常该协议运行在 UDP 协议之上,使用的是 53 端口号。

本地 DNS 服务器查询:递归查询

1
客户端-- > 浏览器缓存-- > 本地的hosts文件-- > 本地DNS解析器缓存-- > 本地DNS服务器

本地 DNS 服务器向其他域名服务器请求的过程:迭代查询

1
2
3
本地DNS服务器-- > 根域名服务器
本地DNS服务器-- > 顶级域名服务器
本地DNS服务器-- > 权威域名服务器

递归查询和迭代查询

  • 递归查询指的是查询请求发出后,域名服务器代为向下一级域名服务器发出请求,最后向用户返回查询的最终结果。使用递归查询,用户只需要发出一次查询请求。
  • 迭代查询指的是查询请求后,域名服务器返回单次查询的结果。下一级的查询由用户自己请求。使用迭代查询,用户需要发出多次的查询请求。

一般而言,本地服务器查询是递归查询,本地 DNS 服务器向其他域名服务器请求的过程是迭代查询。

DNS 为什么用 UDP 协议作为传输

DNS 使用 UDP 协议作为传输层协议的主要原因是为了避免使用 TCP 协议造成的连接时延。

Connection: keep-alive

HTTP 协议采用“请求-应答”模式,当使用普通模式,即非 keep-alive 模式时,每个请求、应答客户和服务器都要新建一个连接,完成之后立即断开连接。
当使用 keep-alive 模式时,keep-alive 功能使客户端到服务端的连接持续有效,当出现对服务器的后继请求时,keep-alive 功能避免建立或者重新建立连接。

为什么要使用 keep-alive

keep-alive 技术的创建目的,能在多次 HTTP 之前重用同一个 TCP 连接,从而减少创建、关闭多个 TCP 连接开销(包括响应时间、CPU 资源,减少拥堵)。

客户端如何开启

在 HTTP、1.0 协议中,默认是关闭的,需要 http 头加入 Connection: keep-alive,才能启动。http1.1 中默认启动。如果加入 Connection: close 关闭。

HTTP 缓存策略

强缓存

强缓存两个相关字段,ExpiresCache-Control。HTTP1.0 版本使用Expires,HTTP1.1 使用 Cache-Control

Expires

Expires 即过期时间,时间是相对于服务器的时间而言的,存在于服务端返回的响应头中,在这个过期时间之前可以直接从缓存里面获取数据,无需再次请求。

1
Expires:Mon, 29 Jun 2020 11:10:23 GMT

表示该资源在 2020 年 7 月 29 日 11:10:23 过期,过期时就会重新向服务器发起请求。
这种方式有一个问题:服务器的时间和浏览器的时间可能并不一致。

Cache-Control

HTTP1.1 版本,使用该字段,这个字段采用的是时间过期时长,对应是 max-age

1
Cache-Control:max-age=6000

上面表示该资源返回后 6000 秒,可以直接使用缓存。

注意:

  • 当 Expires 和 Cache-Control 同时存在时,优先考虑 Cache-Control;
  • 当没有命中强缓存,接下来进入协商缓存。

协商缓存

强缓存失效后,浏览器在请求头中携带响应的缓存 tag 来向服务器发送请求,服务器根据对应的 tag,来决定是否使用缓存。

缓存分为两种,Last-ModifiedETag。两者各有优势,并不存在谁对谁绝对优势。

Last-Modified

这个字段表示的是最后修改时间。在浏览器第一次给服务器发送请求后,服务器会在响应头中加上这个字段。

浏览器接收后,如果再次请求,会在请求头携带 If-Modified-Since 字段,这个字段的值就是服务器传来的最后修改时间。

服务器拿到请求头中的 If-Modified-Since的字段后,其实会和这个服务器中该资源的最后修改时间对比:

  • 如果请求头中的这个值小于最后修改时间,说明是时候更新了。返回新的资源,跟常规请求的流程一样;
  • 否则返回 304,高速浏览器直接使用缓存。
ETag

ETag 是服务器根据当前文件的内容,对文件生成唯一的标识,只要里面内容有改动,这个值就会修改,服务器通过把响应头将该字段返回给浏览器登录。

浏览器接收到 ETag 值,会在下次请求的时候,将这个值作为 If-None-Match 这个字段的内容,发给服务器。

服务器接收到 If-None-Match 后,会跟服务器上该资源的 ETag 进行对比。

  • 如果两者一样的话,直接返回 304,高速浏览器直接使用缓存;
  • 如果不一样的话,说明内容更新了,返回新的资源,跟常规的 HTTP 请求响应的流程一样。
两者对比
  • 性能上,Last-Modified 优于 ETagLast-Modified记录的是时间点,而ETag需要根据文件算法生成对应的 hash 值。
  • 精度上,ETag 优于 Last-ModifiedETag 按照内容给资源带上标识,能准确感知资源变化,Last-Modified在某些场景并不能感知变化。
    • 编辑了资源文件,但是文件内容并没有更改,这样会造成缓存失效;
    • Last-Modified 能够感知的单位是秒,如果文件在 1s 内改变了多次,那么这个时候的 Last-Modified 并没有体现出修改了。

缓存位置

浏览器缓存的位置,分为四种,优先级从高到低:

  • Service Worker
  • Memory Cache
  • Disk Cache
  • Push Cache

HTTP 的请求方法

  • HTTP1.0 定义了三种请求方法:GET、POST 和 HEAD 方法;
  • HTTP1.1 新增了五种请求方法:OPITONS、PUT、DELETE、TRACE 和 CONNECT。

HTTP1.1 规定一下请求方法

  • GET:请求获取 Request-URI 所标识的资源;
  • POST:在 Request-URI 所标识的资源后附加新的数据;
  • HEAD:请求获取由 Request-URI 所标识的资源的响应消息报头;
  • PUT:请求服务器存储一个资源,并用 Request-URI 作为其标识;
  • DELETE:请求服务器删除对应所标识的资源;
  • TRACE:请求服务器回送收到的请求信息,主要用于测试或者诊断;
  • CONNECT:建立连接隧道,用于代理服务器;
  • OPTIONS:列出可对资源实行的请求方法,用来跨域请求。

GET 和 POST 请求的区别

  • 从缓存角度看,GET 请求后,浏览器会主动缓存,POST 默认情况下不能;
  • 从参数角度看,GET 请求一般放在 URL 中,因此不安全,POST 请求放在请求体中,相对而言较为安全,但是在抓包的情况下都是一样的;
  • 从编码角度看,GET 请求只能 URL 编码,只能接受 ASCII 码,而 POST 支持更多的编码类型且不对数据类型限制;
  • GET 请求幂等,POST 请求不幂等,幂等指发送 M 和 N 次请求(两者不相同且都大于 1),服务器上资源的状态一致;
  • GET 请求会一次性发送请求报文,POST 请求通常范围两个 TCP 数据包,首先发 header 部分,如果服务器响应 100(continue),然后发 body 部分;

options 方法作用

  • OPTIONS 请求与 HEAD 类似,一般也是用于客户端查看服务器的性能;
  • 这个方法会请求服务器返回该资源所支持的所有 HTTP 请求方法,该方法会用“*”来代替资源名称,向服务器发送 OPTIONS 请求,可以测试服务器功能是否正常;
  • JS 的 XMLHttpRequest 对象进行 CORS 跨域资源共享时,对于复杂请求,就会使用 OPTIONS 方法发送嗅探请求,以判断是否对指定资源的访问权限。

对头阻塞问题

什么是队头阻塞

对于每个 HTTP 请求而言,这些任务是会被放入一个任务队列中串行执行的,一旦队首任务请求太慢时,就会阻塞后面的请求处理,这就是 HTTP 队头阻塞问题。

并发连接

我们知道对于一个域名而言,是允许分配多个长连接的,那么可以理解成增加了任务队列,也就是说不会导致一个任务阻塞了改任务队列的其他任务,在 RFC 规范中规定客户端最多并发 2 个连接,不过实际情况要更多,比如,Chrome 是 6 个。

域名分片

顾名思义,我们可以在一个域名下分出两个二级域名,而它们最终指向的还是同一个服务器,这样子的话就可以处理的任务队列更多,解决队头阻塞问题。

跨域

浏览器遵循同源策略,协议(scheme)、主机(host)和端口号(port)都相同称为同源。非同源站点有一些限制:

  • 不能读取和修改对方的 DOM;
  • 不能访问对方的 Cookie、indexDB 和 localStorage。

当浏览器向目标 URI 发 Ajax 请求时,当前 URL 和目标 URL 不同源,则产生跨域,被称为跨域请求。底层原理是由于浏览器将每个渲染进程装进了沙箱,为了防止 CPU 芯片一直存在的 Spectre 和 Meltdown 漏洞,采取了站点隔离手段,给每个不同站点(一级域名不同)分配了沙箱,互补干扰。

CORS

CORS 是 W3C 的一个标准,全称是跨域资源共享。它需要浏览器和服务器的共同支持,在弄清楚 CORS 原理之前,需要知道两个概念:简单请求和非简单请求。

属于如下条件的是简单请求:

  • 请求方法为 GET、POST 和 HEAD;
  • 请求头的取值范围:Accept、Accept-Language、Content-Language、Content-Type(只限于三个值 application/x-www-form-urlencoded、multipart/form-data 和 text/plain)。

非简单请求主要体现在两个方面:预检请求和响应字段。

预检请求的方法是 OPTIONS,还会加上两个关键字段:

  • Access-Control-Request-Method,列出 CORS 请求用到哪个 HTTP 方法;
  • Access-Control-Request-Headers,清除 CORS 请求将要加上什么请求头

其中有这样几个关键的响应头字段:

Access-Control-Allow-Origin: 表示可以允许请求的源,可以填具体的源名,也可以填*表示允许任意源请求。
Access-Control-Allow-Methods: 表示允许的请求方法列表。
Access-Control-Allow-Credentials: 简单请求中已经介绍。
Access-Control-Allow-Headers: 表示允许发送的请求头字段
Access-Control-Max-Age: 预检请求的有效期,在此期间,不用发出另外一条预检请求。

参考 (建议精读)HTTP 灵魂之问,巩固你的 HTTP 知识体系