http协议是一个不大不小的知识体系,当新手还没有整体视角的时候,只会记忆某一版本的某一特性,以为搞懂了一个知识点但随后又发现一个更大的盲区。当抛弃这些繁杂知识点,以解决互联网传输问题为导向,才能更好的理解http的变迁,毕竟http和软件开发有诸多类似,都是一个根据发展现状不算迭代补漏的过程,毕竟设计者也没有预支未来的能力,而这些迭代主要在以下方面:

丰富传输内容:最开始并不支持图片视频类的多媒体文件
减少来回传输次数:减少RTT(Round Trip Time)即消息一来一回所花费的时间
一次传输尽可能传输更多:压缩
尽可能多路复用
加密
缓存:减少不必要的传输

先看时间线

1991年:http0.9 那个年代的互联网有多简陋可想而知,仅支持get
1996年:http1.0 不够用了,添加请求方法POST用于提交,文件类型,如图片视频,添加请求头http Header
1997年:http1.1 目前用的都是1.1,居然诞生于二十多年前
2009年:SPDY1 (http2前身)
2012年:SPDY2 (http2前身)
2015年:http2

http1.1
1997年的http1.1引入和很多实用机制:

持久连接: 减少了RTT,不必每次都重新建立tcp链接,重复三次握手,存在问题:队头阻塞
分块传输编码
缓存控制机制:减少没必要的传输
内容协商机制:给请求头中客户端所期望的内容,如json,text等
管道机制:尴尬
持久连接依然有性能问题:http1中每个请求和响应都有建立一个tcp连接,虽然1.1引用了持久连接机制,但每个连接上同时只能有一个请求或响应,这样,一来,一回完成后,再继续下一个一来一回,处于半双工状态利用率不高。因为服务器发送第一个响应后,到手打第二个请求之前,都是空闲状态。
为了解决此问题,引入了管道。

尴尬的管道机制:管道可以让浏览器把所有请求都发过来,服务器则可以响应一个后继续响应下一个,但有个严重的问题:当第一个非常耗时阻塞时,后续所有响应都要等待,这就是队首阻塞问题(Head-of-Line Blocking)。

第二个主要尴尬原因:链路中间的其他设备不一定支持管道,这些导致管道一直处于理论状态,实际应用很少。

题外话:现代浏览器允许统一域名下并发连接6-8个tcp连接,为此很多网页静态资源放在散列的多个域名下。

还有请求头是未压缩的纯文本,且有很多冗余信息,如cookie、UserAgent等,造成不必要的浪费,优化方法是采用短小的独立域名托管静态资源:1.域名段,字段少,2.独立域名不共享cookie减少无用内容。

队头阻塞

“队头阻塞”与短连接和长连接无关,而是由 HTTP 基本的“请求 - 应答”模型所导致的。
当顺序发送的请求序列中的一个请求因为某种原因被阻塞时,在后面排队的所有请求也一并被阻塞,会导致客户端迟迟收不到数据;

常规解决方式:

  • 减少请求队伍数量——合并:例如切图、数据内嵌与合并,域名分片、JavaScript“黑科技”等等;
  • 增加并发数:协议规定每个域名客户端并发两个两届,但实际浏览器并发6-8个
  • 增加域名【域名分片】:静态资源和接口请求不同的域名机器

http2

总结http的主要两个缺点:

  • 性能不高
  • 安全不足

https解决了安全性,但增加了性能开销:握手、加密成本;只能依赖”长链接“这唯一的办法;

http2采用了一下措施:

头部压缩

http1.1只能用Content-Encoding压缩body,但对繁杂的请求头没有措施;【实则头部信息往往大于body的】
用【HPACK算法】:在客户端和服务器两端建立“字典”,用索引号表示重复的字符串,还釆用哈夫曼编码来压缩整数和字符串,整个流量可以达到 50%~90% 的高压缩率

格式化传输数据 帧压缩header、body:

http1.1中请求和响应报文,由起始行、首部和实体正文组成,中间由文本换行符分割,http2把报文分割成更小的帧,并进行二进制编码,帧(Frame)作为http2通信的最小单位。【虚拟的流:】帧都包含帧首部用来进行流标识,这样帧可以乱序发送,最后靠首部的流标识进行顺序组装。
注意:【可乱序发送帧,解决了http层面”队头阻塞问题“】

服务器推送

收到html的请求,会把可能用到的js、css等一并推送,减少静态资源的单独请求-应答

多路复用:一个域名只复用1个TCP链接,问题:脆弱

http2中,同域名下的所有通信都在一个tcp连接上完成【注意:带来了tcp层面的队头阻塞问题】,且连接是双向通信数据流,这正是tcp协议的长项:三次握手后,持续发送大量数据。
重点:之前http之所以性能差,是因为频繁建立多个tcp,虽然基于tcp,但因为一次连接传输内容少,而连接频发,导致tcp的优势利于的少,劣势利用的多

HTTP/2 对一个域名只开一个TCP连接,一旦这个连接出问题,则整体断连,这一点也是升级http1.1的代价

http2队头阻塞问题

http2用有序号帧解决了自身协议层面的队头阻塞,但基于TCP协议,还是会发生tcp协议的“队头阻塞”;
http2的帧在tcp层面以更小的包【segment段】来传输,TCP 为了保证可靠传输,有“丢包重传”机制,丢失的包必须要等待重新传输确认,其他的包即使已经收到了,也只能放在缓冲区里,上层的应用拿不出来,只能继续等待;

http3.0

由此google设计了QUIC协议,使用UPD,也就是http3.0的前身;
UPD简洁,只对IP层做了简单的封装;不需要握手和挥手(建连、断连),通信成本低,灵活、高效,“可塑性”很强。

但没有丢包重发、拥塞窗口等策略的UDP并不可靠,于是QUIC取其精华、去其糟粕:用自己的协议规则实现了一套可靠传输协议;
HTTP/3 没有指定默认端口号,需要用 HTTP/2 的扩展帧“Alt-Svc”来发现

由此可见,http3.0的优化不再是http层面,而是底层tcp层面,http为了让自己更优秀,顺便把自己的底层依赖tcp也给优化掉了;