我们前一篇学习了HTTP/2,相比于HTTP/1,HTTP/2在性能上有了大幅的改进,但是HTTP/2因为底层还是基于TCP协议的,虽然HTTP/2在应用层引入了流的概念,利用多路复用解决了队头阻塞的问题,但是在TCP中队头阻塞的问题仍旧存在。
又由于TCP协议的僵化、TCP的慢启动,为了确保连接建立而产生的延迟问题等顽固问题。HTTP/2虽然在性能上达到了极致,但是还是改变不了底层TCP的性能影响。毕竟应用层。得,死路一条。那怎么办呢?
既然不能用TCP,那就用UDP好啦。我们来看张图,把我们学过的协议的栈层都罗列一下,方便对比:
HTTPS的SSL和TLS我们在下一篇就开始聊,先不管。我们看HTTP/1、HTTPS、HTTP/2,和HTTP3在传输层有啥区别?HTTP/3用的是UDP。因为UDP是无序的,包与包之间没有依赖关系,就像HTTP/2的多路复用一样,从根本上解决了TCP的队头阻塞。
但是,你肯定知道,UDP是一个简单的,不可靠的协议,只是对IP协议的一层很薄的包装,和TCP相比,它的实际应用很少。不过正是因为它简单,不需要建连和断连,通信成本低,也就非常灵活、高效,“可塑性”很强。
所以,QUIC 就选定了 UDP,在它之上把 TCP 的那一套连接管理、拥塞窗口、流量控制等“搬”了过来,“去其糟粕,取其精华”,打造出了一个全新的可靠传输协议,可以认为是“新时代的 TCP”。
QUIC 最早是由 Google 发明的,被称为 gQUIC。而当前正在由 IETF 标准化的 QUIC 被称为 iQUIC。两者的差异非常大。gQUIC 混合了 UDP、TLS、HTTP,是一个应用层的协议。而 IETF 则对 gQUIC 做了“清理”,把应用部分分离出来,形成了 HTTP/3,原来的 UDP 部分“下放”到了传输层,所以 iQUIC 有时候也叫“QUIC-transport”。接下来要说的 QUIC 都是指 iQUIC,要记住,它与早期的 gQUIC 不同,是一个传输层的协议,和 TCP 是平级的。
聊到这里,我又想起了之前说过的话,如果一层不行,那就再加一层~
了解了QUIC的基本概念,我们再来看看QUIC的特点,这样的特点给我们带来了哪些好处。
前面说过,QUIC是基于UDP的,而UDP是无连接的,根本就不需要握手和挥手,所以天生比TCP快很多。
就像 TCP 在 IP 的基础上实现了可靠传输一样,QUIC 也基于 UDP 实现了可靠传输,保证数据一定能够抵达目的地。它还引入了类似 HTTP/2 的“流”和“多路复用”,单个“流”是有序的,可能会因为丢包而阻塞,但其他“流”不会受到影响。
并且,QUIC全面使用加密通信,这样可以很好的抵御篡改和协议僵化。
另外,QUIC并不是建立在TLS之上的,而是内部包含了TLS。它使用自己的帧“接管”了 TLS 里的“记录”,握手消息、警报消息都不使用 TLS 记录,直接封装成 QUIC 的帧发送,省掉了一次开销。
QUIC的内容很多,我们只简单的聊一聊两个内部的关键知识点。
QUIC基本数据传输单位是包(packet)和帧(frame),一个包由多个帧组成,包面向的是”连接“,帧面向的是”流“。
QUIC 使用不透明的“连接 ID”来标记通信的两个端点,客户端和服务器可以自行选择一组 ID 来标记自己,这样就解除了 TCP 里连接对“IP 地址 + 端口”(即常说的四元组)的强绑定,支持“连接迁移”(Connection Migration)。换句话说,当IP变化的时候,QUIC连接里的两端ID不会变,逻辑上连接没有中断,所以无需像TCP一样再重新连接,节省了一定的性能,消除了连接成本,实现连接的无缝迁移。
了解了QUIC后,再来学习HTTP/3就要容易很多了。
因为 QUIC 本身就已经支持了加密、流和多路复用,所以 HTTP/3 的工作减轻了很多,把流控制都交给 QUIC 去做。调用的不再是 TLS 的安全接口,也不是 Socket API,而是专门的 QUIC 函数。不过这个“QUIC 函数”还没有形成标准,必须要绑定到某一个具体的实现库。
HTTP/3 里仍然使用流来发送“请求 - 响应”,但它自身不需要像 HTTP/2 那样再去定义流,而是直接使用 QUIC 的流,相当于做了一个“概念映射”。
HTTP/3中的头部压缩算法也升级成了“QPACK”,使用方式上也做了改变。虽然也分成静态表和动态表,但在流上发送 HEADERS 帧时不能更新字段,只能引用,索引表的更新需要在专门的单向流上发送指令来管理,解决了 HPACK 的“队头阻塞”问题。另外,QPACK 的字典也做了优化,静态表由之前的 61 个增加到了 98 个,而且序号从 0 开始,也就是说“:authority”的编号是 0。
还有一个有趣的点是,HTTP/3 没有指定默认的端口号,也就是说不一定非要在 UDP 的 80 或者 443 上提供 HTTP/3 服务。那么,该怎么“发现”HTTP/3 呢?
这就要用到 HTTP/2 里的“扩展帧”了。浏览器需要先用 HTTP/2 协议连接服务器,然后服务器可以在启动 HTTP/2 连接后发送一个“Alt-Svc”帧,包含一个“h3=host:port”的字符串,告诉浏览器在另一个端点上提供等价的 HTTP/3 服务。浏览器收到“Alt-Svc”帧,会使用 QUIC 异步连接指定的端口,如果连接成功,就会断开 HTTP/2 连接,改用新的 HTTP/3 收发数据。
如果你想尝试HTTP/3的例子,我们可以在这个网址玩一下,https://quic.nginx.org/。如果不行的话,就需要多刷新几下,如果还是不行,那就需要打开Chrome浏览器在地址栏输入“chrome://flags”,打开设置页面,然后搜索“QUIC”,找到启用 QUIC 的选项,把它改为“Enabled”。就像这样:
然后,再去刷新几下,运气好的话,就可以看到这样的Network了:
如果你发现不管你怎么刷新都不行的话,试试把你的VPN关了应该会好的~
这是整个H3的报文:
是不是有那么一点点陌生又熟悉?
本篇,我们简单的聊了聊HTTP/3,聊了聊QUIC是什么,它是怎么解决之前HTTP为之苦恼的队头阻塞的问题。以及HTTP/3的协议栈相比于HTTP/2、HTTP/1有什么区别,多了哪些内容等等。HTTP/3可以说是集大成之作,把之前所有版本的HTTP协议的精华摘取,去其糟粕,形成了现在几乎完美的HTTP/3协议。
那么关于“性能”部分,我们就到此为止了,后面我们会花几篇文章的时间,来学一学HTTPS,也就是HTTP是如何在“安全”这条路上走向完善的。