目录
http2和http3的区别
传输层协议
QUIC协议
介绍
连接建立与握手
建立安全连接的过程
RTT
建连为什么需要两个过程
原因
解决
QUIC协议的1-RTT 建连
必要性
连接过程
第一次握手(Client Hello)
版本号
其他
第二次握手
介绍
Server Hello
身份验证
生成对称密钥
QUIC协议的0-RTT 建连
介绍
连接过程
队头阻塞
如何解决队头阻塞问题
安全性
性能
连接迁移
引入
介绍
连接 ID
过程
拥塞控制算法
介绍
TCP计算RTT
QUIC计算RTT
热插拔特性
QUIC 的两级流量控制
http2和http3的区别
传输层协议
HTTP/2:
基于 TCP(传输控制协议)实现
HTTP/3:
在 HTTP/3 中,弃用 TCP 协议,改为使用基于 UDP 协议的 QUIC 协议实现
- QUIC(Quick UDP Internet Connections)协议(传输层协议)
QUIC协议
介绍
QUIC 协议实现在用户态,建立在内核态的 UDP 的基础之上,集成了 TCP 的可靠传输特性+ TLS1.3 协议,保证了用户数据传输的安全
- 具有建连快,连接迁移的优秀特性
- 解决了tcp的队头阻塞问题
- 实现了更好的拥塞/流量控制算法
连接建立与握手
HTTP/2:
需要进行 TCP 三次握手,然后再进行 TLS(传输层安全)握手
- 总共可能需要多次往返才能建立安全连接
建立安全连接的过程
首先,当客户端使用域名访问服务器时,需要借助DNS服务器来获取ip地址
- DNS服务介绍(hosts文件,域名分级,主域名和子域名),域名解析过程(解析过程,dig命令), 面试题(输入url后会发生什么)_hosts文件中的子域名-CSDN博客
传统http的建连需要TCP和TLS两个过程
- 三次握手需要1 RTT
- 交换秘钥需要2 RTT -- 客户端发起握手,服务端确认TLS版本+发送证书,客户端内部进行验证+生成对称密钥+加密发送,服务器确认
对于一个小请求(用户数据量较小)而言,传输数据只需要 1 个 RTT
RTT
RTT -- (Round-Trip Time,往返时延)
- 从发送方发送一个数据包开始,到接收到接收方返回的应答包为止的总时间
建连为什么需要两个过程
原因
因为TCP 和 TLS 没办法合并
TCP 是内核态协议:传统的 TCP 协议实现是在内核中的,属于操作系统内核的一部分,应用层无法修改其行为
TLS 是用户态协议:TLS 是应用层协议,运行在用户空间,比如浏览器或应用程序内
二者在物理上就不同(内核态/用户态),所以无法合并
解决
如果把tcp挪到用户态呢?
- 不可行,没法弃用内核里的 TCP
所有操作系统和网络设备(防火墙、路由器等)都已经高度适配和优化了 TCP 协议,内核级别实现是几十年演进的结果
想在用户态重写 TCP,意味着你得重新实现拥塞控制、重传机制、滑动窗口、头部格式、与 OS 协同等等,成本极高,而且还不一定跑得过内核版 TCP
更关键的是,没法让全球其他服务器配合你一起改内核,这不现实
既然干不掉,那就自定义一个传输层协议放在用户态.如何呢?
- 当然可行,既然 TCP 合并不了 TLS,那就自己造个轮子
- 在用户态实现后,再结合 TLS.就可以把两个建连过程合二为一了
- 这就是 QUIC协议 的实现思路
HTTP/3:
QUIC 协议将握手和加密过程合并,初次建连只需一次往返即可建立安全连接,减少了连接建立的延迟
- 并且,后续再次建连可以使用 0-RTT 特性
QUIC协议的1-RTT 建连
必要性
如果客户端与服务端初次建连(之前从未进行通信过),或者长时间没有通信过(0-RTT 过期了),只能进行 1-RTT 建连
- 只有先进行一次完整的 1-RTT 建连,后续一段时间内的通信才可以进行 0-RTT 建连
连接过程
整个握手过程需要 2 次握手(第三次是带了数据的)
- 所以整个握手过程只需要 1-RTT(RTT 是指数据包在网络上的一个来回)的时间
- 分成两个部分 -- Client Hello(发送QUIC 连接信息) + Server Hello(TLS1.3 握手部分)
第一次握手(Client Hello)
QUIC连接 : 协商 QUIC 版本号、协商 quic 传输参数、生成连接 ID、确定 Packet Number 等信息
- 类似于 TCP 的 SYN 报文
版本号
Client Hello 在扩展字段里标明了支持的 TLS 版本(Supported Version:TLS1.3)
- 虽然这里写的是TLS1.3,但实际上ClientHello 报文中的Version 字段写的是
0x0303
(TLS 1.2)为什么要这样设计?
- 为了兼容老旧的中间设备
- 很多网络设备(比如负载均衡器、NAT、防火墙)会“窥探”TLS握手包内容,会根据报头中的Version字段决定是否通过
- 但这些老旧设备 只认识 TLS 1.0、1.1、1.2 的标号,如果写入1.3到Version字段,可能会导致拒绝连接/拦截握手,甚至丢包或断链,所以只能写入1.2
- 并且设计了一个扩展字段Supported Version来明确告诉服务器:我其实支持 TLS1.3
key_share
ClientHello 中包含了非常重要的 key_share 扩展
- 客户端在发送之前,会自己根据 DHE 算法生成一个公私钥对
- 发送 Client Hello 报文的时候会把这个公钥(存在于 key_share 中)发过去,key_share 还包含了客户端所选择的曲线 X25519
- 总之,key_share 是客户端提前生成好的公钥信息
其他
Client Hello 里还包括了:客户端支持的算法套、客户端所支持的椭圆曲线以及签名算法、psk 的模式等等,一起发给服务端。
第二次握手
介绍
QUIC协议中,服务器会在第二次握手中一次性并发完成连接建立 + TLS 加密协商
- 会发送多个 QUIC 报文 : Server Hello报文+用于服务端的身份认证和握手确认的报文
Server Hello
- 服务端自己根据 DHE 算法也生成了一个公私钥对,同样的,Key_share 扩展信息中也包含了 服务端的公钥信息
- 服务端通过 ServerHello 报文将这些信息发送给客户端
身份验证
生成对称密钥
至此为止,双方(客户端服务端)都拿到了对方的公钥信息,然后结合自己的私钥信息,生成 pre-master key
- 在这里官方的叫法是(client_handshake_traffic_secret 和 server_handshake_traffic_secret)
- pre-master key属于是一个中间结果,用于生成最终对称加密密钥的原材料
- 然后密钥导出函数得到 key 和 iv(初始化向量),使用 key 和 iv 对 Server Hello 之后所有的握手消息进行加密
QUIC协议的0-RTT 建连
介绍
基于会话复用功能实现
- 在握手完成之后,服务端会发送一个 New Session Ticket 报文(0-RTT 实现的基础)给客户端
作用:
- QUIC 协议的 0-RTT 特性是指,在 首次建立连接后,后续连接可以利用以前的握手信息,跳过部分握手过程,直接开始数据传输,减少延迟
什么时候会再次建连呢?
- 连接超时(QUIC 会保存一定时间的连接信息,如 0-RTT 密钥,允许客户端重新发起连接时跳过传统的 1-RTT 握手) 或 关闭(连接因某些原因被关闭或丢失(比如网络切换)后的重新连接
- 连接断开后的重试(网络切换/设备休眠/客户端IP地址改变)
- 客户端重新连接到同一服务器(一些场景需要客户端在之后的请求中继续与同一个服务器建立连接)
连接过程
虽然client 和 server 在建连时,仍然需要两次握手,仍然需要 1 个 rtt
- 但是client 在发送第一个包 client hello 时,就带上了数据(HTTP 请求) -- 从何时开始发送数据这个角度上来看,的确是 0-RTT
- 这个请求的数据并不会跟 Initial 报文(内含 Client Hello)一起发送,而是单独一个数据包(0-RTT 包)进行发送,它和 Initial 包同时发送
队头阻塞
HTTP/2:
在 TCP 层面,如果发生数据包丢失,必须等待丢失的数据包被重新传输,可能导致队头阻塞,影响多路复用的效率
HTTP/3:
QUIC 协议在应用层实现了多路复用,即使某个数据包丢失,只会影响对应的流,其他流可以继续传输,减少了队头阻塞的影响
如何解决队头阻塞问题
这里有两个请求同时发送
- 红色的是请求 1,蓝色的是请求 2,这两个请求在两条不同的流中进行传输
假设在传输过程中,请求 1 的某个数据包丢了
- 如果是 TCP,即使请求 2 的所有数据包都收到了,但是也只能阻塞在内核缓冲区中,无法交给应用层 -- 所有数据(无论属于哪个请求)在传输时,都会进入一条顺序一致的队列中,一旦某个中间的数据包丢失,后面的数据即使已到达,也不能提交给应用层,因为顺序被打乱了
- 但是 QUIC 就不一样了,请求 1 的数据包丢了只会阻塞请求 1,请求 2 不会受到阻塞 -- QUIC 在应用层实现多路复用的独立流,每个流都有自己的数据编号和确认机制
为什么只有 QUIC 才能解决呢?
- 虽然HTTP2中也有流的概念 -- HTTP/2 在用户态实现了多路复用,用“帧”把多个请求的数据包混在一起发
- 但这些帧最后都变成一串字节交给 TCP 发送,TCP 的实现在内核态,而流的实现在用户态,对于TCP来说,它是看不到“流”的
- 所以在 TCP 中,它不知道这个数据包是请求 1 还是请求 2 的,只会根据 seq number 来判断包的先后顺序,所以无法避免队头阻塞
举个例子来说明两个协议的不同:
安全性
HTTP/2:
通常与 TLS 结合使用,但在某些情况下也可以不使用加密
HTTP/3:
设计上强制使用加密,确保数据传输的安全性
性能
HTTP/3在弱网/高延迟/高丢包的场景下性能提升非常明显
- 相较于 HTTP/2 平均能提升 20%~30%
- 并且可以在 WiFi 和蜂窝数据切换时,网络完全不断开、直播不卡顿、视频不缓冲
为什么会有这么高的性能提升呢?
- HTTP3拥有更短的建连过程,还实现了连接迁移功能,并且优化了拥塞/流量控制
但不是说HTTP3就完爆HTTP2了,它在稳定高速网络下,提升不一定明显,甚至开销可能略高
- 在高性能机器、内网等场景下,HTTP/2 的性能也很优秀
连接迁移
引入
我们经常需要在 WiFi 和 4G 之间进行切换,比如:
- 在家里时使用 WiFi,出门在路上,切换到流量
- 出了门,又可以连上其他场所的 WiFi
- 所以我们的日常生活中需要经常性的切换网络
而每一次的切换网络,都变化我们的 IP 地址
介绍
传统的 TCP 协议是以四元组(源 IP 地址、源端口号、目的 ID 地址、目的端口号)来标识一条连接
- 一旦四元组的任何一个元素发生了改变,这条连接就会断掉 -> 这条连接中正在传输的数据就会断掉
- 切换到新的网络后可能需要重新去建立连接,然后重新发送数据 -- 在用户看来,就是他的网络会“卡”一下
但是,QUIC 不再以四元组作为唯一标识,QUIC 使用连接 ID 来标识一条连接
- 无论网络如何切换,只要连接 ID 不变,那么这条连接就不会断,这就叫连接迁移
连接 ID
每条连接拥有一组连接标识符,也就是连接 ID
- 连接 ID 是由一端独立选择的,每个端(客户端和服务端统称为端)选择连接 ID 供对端使用
- 也就是说,客户端生成的连接 ID 供服务端使用(服务端发送数据时使用客户端生成的连接 ID 作为目的连接 ID),反过来同理
连接 ID 的主要功能:
- 解决连接迁移时的识别问题,确保连接不会因 IP地址 / 端口号变化而中断
过程
QUIC 限制连接迁移仅客户端可以发起 (由客户端负责发起所有迁移)
- 如果客户端接收到了一个未知的服务器发来的数据包,客户端必须丢弃这些数据包,而不会去识别 -- 可以避免伪造包/欺骗连接的风险
连接迁移过程总共需要四个步骤:
连接迁移之前,客户端使用 IP1 和服务端进行通信
当客户端 IP 变成 IP2,并且使用 IP2 发送非探测帧给服务端启动路径验证(双方都需要互相验证,所以图中是两个过程)
通过 PATH_CHANLLENGE 帧和 PATH_RESPONSE 帧进行验证
验证通过后,后续就可以使用 IP2 进行通信
拥塞控制算法
介绍
拥塞控制算法中最重要的一个参数是 RTT,RTT 的准确性决定了拥塞控制算法的准确性
- RTT 是数据从发送端到接收端再返回所需的总时间,可以反映网络时延变化,作为动态调整发送速率的依据
然而,TCP 的 RTT 测量往往不准确,QUIC 的 RTT 测量是准确的
TCP计算RTT
TCP协议中的原包和重传包的序号是一样的
- 那么拥塞控制算法进行计算 RTT 的时候,无法区别是初始包还是重传包
- 也就导致 RTT 的计算值要么偏大,要么偏小
QUIC计算RTT
- QUIC 通过 Packet Number 来标识包的序号 (用来代替tcp的序号)
- 它规定 Packet Number 只能单调递增,这也就解决了初始包和重传包的二义性
- 从而保证 RTT 的值是准确的
热插拔特性
不同于实现在内核态的TCP,实现在用户态QUIC 的拥塞控制算法是可插拔的
- 它可以根据不同的业务 / 连接灵活选择使用不同的拥塞控制算法
- Reno、New Reno、Cubic、BBR 等算法都有自己适合的场景
QUIC 的两级流量控制
QUIC 是双级流控 -- 连接级别 + 流级别的流控
每个流都有自己的可用窗口,可用窗口的大小取决于(最大窗口数 - 发送出去的最大偏移数)
- 跟中间已经发送出去的数据包,是否按顺序收到了对端的 ACK 无关
这与TCP中的机制在本质上不同:
- TCP 的窗口滑动是依赖于“按序 ACK”,如果中间一个包没 ACK,后面的都不能滑动 -- 也就是基于ACK滑动流控窗口
而 QUIC 是“基于 offset 的流控”,只关心你已经发送到了哪个 offset,不管你中间的 ACK 来没来 (这一点和http2类似,它在发送时可以将一个包分成多个帧,每个帧带上偏移量,接收方根据偏移量拼接报文,而不要求按序到达) -- ACK只是用来进行丢包检测和重传,不作为窗口滑动的依据
参考 -- 一文读懂QUIC协议:更快、更稳、更高效的网络通信_后端_李龙彦_InfoQ精选文章
(写的超级好啊o( ̄▽ ̄)d)