QUIC基本知识
QUIC 协议实现在用户态,建立在内核态的 UDP
的基础之上,集成了 TCP 的可靠传输特性,集成了 TLS1.3
协议,保证了用户数据传输的安全。
- 利用缓存,显著减少连接建立时间。连接平滑迁移,网络状态的变更不会影响连接断线。
0-1RTT快速握手
。
QUIC是如何保证可靠传输的?
Packet Header
分类
- Long Packet Header 用于首次建立连接。
- Short Packet Header 用于日常传输数据。
- 建立连接时,连接 ID 是由服务器根据客户端的 Source Connection ID 字段生成的,这样后续传输时,双方只需要固定住 Destination Connection ID(连接 ID ),从而实现连接迁移功能。
- Short Packet Header 中的 Packet Number 是每个报文独一无二的编号,它是严格递增的,也就是说就算 Packet N 丢失了,重传的 Packet N 的 Packet Number 已经不是 N,而是一个比 N 大的值。
- packet Number 单调递增的优势:
- 可以更加精确计算 RTT,没有 TCP 重传的歧义性问题;
- 可以支持乱序确认,防止因为丢包重传将当前窗口阻塞在原地,而 TCP 必须是顺序确认的,丢包时会导致窗口滑动;
QUIC Frame Header
一个 Packet 报文中可以存放多个 QUIC Frame。
frame格式如下:
- Stream ID 作用:多个并发传输的 HTTP 消息,通过不同的 Stream ID 加以区别;
- Offset 作用:类似于 TCP 协议中的 Seq 序号,保证数据的顺序性和可靠性;
- Length 作用:指明了 Frame 数据的长度。
既然重传数据包的 Packet N+M 与丢失数据包的 Packet N 编号并不一致,我们怎么确定这两个数据包的内容一样呢?
引入 Frame Header 这一层,通过 Stream ID + Offset 字段信息实现数据的有序性,通过比较两个数据包的 Stream ID 与 Stream Offset ,如果都是一致,就说明这两个数据包的内容一致。
总结
QUIC 通过单向递增的 Packet Number,配合 Stream ID 与 Offset 字段信息,可以支持乱序确认而不影响数据包的正确组装,摆脱了TCP 必须按顺序确认应答 ACK 的限制,解决了 TCP 因某个数据包重传而阻塞后续所有待发送数据包的问题。
QUIC 如何解决队头阻塞的(TCP队头阻塞)?
QUIC 给每一个 Stream 都分配了一个独立的滑动窗口,这样使得一个连接上的多个 Stream 之间没有依赖关系,都是相互独立的,各自控制的滑动窗口。
QUIC 如何解决流量控制?
QUIC 实现了两种级别的流量控制,分别为 Stream 和 Connection 两种级别:
- Stream 级别的流量控制:每个 Stream 都有独立的滑动窗口,所以每个 Stream 都可以做流量控制,防止单个 Stream 消耗连接(Connection)的全部接收缓冲。
-
- QUIC 协议中同一个 Stream 内,滑动窗口的移动仅取决于接收到的最大字节偏移(尽管期间可能有部分数据未被接收)
- Connection 流量控制:限制连接中所有 Stream 相加起来的总字节数,防止发送方超过连接的缓冲容量。
-
- 而对于 Connection 级别的流量窗口,其接收窗口大小就是各个 Stream 接收窗口大小之和。
QUIC 如何快速握手
对于 HTTP/1 和 HTTP/2 协议,TCP 和 TLS 是分层的,分别属于内核实现的传输层、openssl 库实现的表示层,因此它们难以合并在一起,需要分批次来握手,先 TCP 握手(1RTT),再 TLS 握手(2RTT),所以需要 3RTT 的延迟才能传输数据。
但是 HTTP/3 的 QUIC 协议并不是与 TLS 分层,而是QUIC 内部包含了 TLS,它在自己的帧会携带 TLS 里的“记录”,再加上 QUIC 使用的是 TLS1.3,因此仅需 1 个 RTT 就可以「同时」完成建立连接与密钥协商,甚至在第二次连接的时候,应用数据包可以和 QUIC 握手信息(连接信息 + TLS 信息)一起发送,达到 0-RTT 的效果。
1-RTT握手过程
- Client Hello 客户端在发送之前,会自己根据 DHE 算法生成一个公私钥对。发送 Client Hello 报文的时候会把这个公钥发过去
- Server Hello 服务端自己根据 DHE 算法也生成了一个公私钥对,同样的,Key_share 扩展信息中也包含了 服务端的公钥信息。
- 双方都拿到了对方的公钥信息,然后结合自己的私钥信息,生成 pre-master key,在这里官方的叫法是,然后根据算法进行算出 key 和 iv,使用 key 和 iv 对 Server Hello 之后所有的握手消息进行加密。
注意:在握手完成之后,服务端会发送一个 New Session Ticket 报文给客户端,这个包非常重要,这是 0-RTT 实现的基础。
0-RTT握手过程
client 和 server 在建连时,仍然需要两次握手,仍然需要 1 个 rtt,但是为什么我们说这是 0-rtt 呢,是因为 client 在发送第一个包 client hello 时,就带上了数据(HTTP 请求),从什么时候开始发送数据这个角度上来看,的确是 0-RTT。
QUIC 如何解决连接迁移的?
QUIC 协议没有用四元组的方式来“绑定”连接,而是通过连接 ID来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己,因此即使移动设备的网络变化后,导致 IP 地址变化了,只要仍保有上下文信息(比如连接 ID、TLS 密钥等),就可以“无缝”地复用原连接,消除重连的成本,没有丝毫卡顿感,达到了连接迁移的功能。