QUIC学习笔记

quic,quick udp Internet connection(快速互联网连接),是由Google提出的,一种基于udp的协议。

优势

  1. 减少tcp三次握手及TLS握手时间
  2. 改进拥塞控制
  3. 避免头阻塞的多路复用
  4. 连接迁移
  5. 前向冗余纠错

传统的HTTPS建立连接为什么如此慢?

首先需要TCP三次握手和TLS的四次握手,大概需要3个RTT。其中TCP和TLS无法合并,因为TCP在内核态,TLS在用户态。因TCP远古存在不可能被替代掉,然后就通过自创传输层协议放在用户态,再结合TLS,通过把两个建立合二为一形成QUIC

**注意:**一次RTT指从发送端发送数据到发送端接收到接收端发来的数据所经过的时间。RTT时延通常由三部分决定:链路的传播时间、末端系统的处理时间、路由器等网络中间节点的缓存和排队时间。

QUIC连接的建立

初次建立连接需要1RTT完成建连。后续复用实现0-RTT特性。

其中,QUIC连接从握手开始,在握手过程中使用加密握手协议(QUIC-RTT)创建一个共享密钥并协商应用协议。

应用层协议可以在连接握手阶段就发送数据(0-RTT握手)

连接ID

每一条连接拥有一组连接标识符,即ID可标识这条连接。连接ID由一端独立选择,每一端选择连接ID供对端使用。也就是说ID是一致的。

先说一下TCP,TCP通过IP地址唯一确定一个连接,但是当IP地址发生变化就导致连接中断不可复用。但真实情况可能是主机由wifi连接转为手机卡连接,这会导致主机设备并没有变化,而连接要重新握手。而quic通过ID标识,可以实现迁移,保证连接复用。

**注意:**是连接复用,而不是一个连多个。

数据包如何匹配连接

接收到包后,接收端会进行分类,看是建连包还是数据包,建连会创建新的连接,数据包会关联到一个已经存在的连接。如果数据包关联到一个已存在的连接ID,QUIC将在相应的连接上处理该数据包。

0-RTT建连

客户端与服务端首次建连,或者长时间没有通信过,只能先进行1-RTT建连。只有先进行一次完整的建连,后续时间内的通讯才能进行0-RTT建连。

QUIC的建连可以分为两部分:quci连接信息部分和TLS1.3握手部分。

**QUCI连接:**协商QUIC版本号,协商quic传输参数、生成连接ID、确定Packet Number等信息,类似于TCP的SYN报文;保证通信的两端确认过彼此。

**TLS1.3:**标准协议,非对称加密,目的为了协商出对称密钥,然后后续传输的数据使用这个密钥进行加密和解密。

TLS 1.3握手过程

正常TLS握手需要四次,而上图握手过程需要两次,第三次握手带了数据。

第一次握手:client hello

key_share是客户端提前生成好的公钥信息。

第二次握手:Server hello

服务端自己根据DHE算法也生成了一个公私钥对,同样的,Key_share扩展信息中也包含了 服务端的公钥信息。

第二次握手:certificate

服务端会把自己的证书发送给客户端进行校验握手结束。

0-RTT握手

是基于会话复用。

在TLS1.2中完整握手需要四次,2个RTT。如果后续再次建连使用会话复用就会减少1一RTT。

同样的,在TLS1.3中,完整的握手流程需要1个RTT,后续再次建连的时候,也有类似于会话复用的功能,那么会再少1个RTT,1-1=0,这就是0-RTT握手的来源。

为了方便区分1.2与1.3握手次数问题,如下所示:

1.2握手简化理解

cpp 复制代码
客户端                         服务器

ClientHello  ───────────────→

              ←────────────── ServerHello
              ←────────────── Certificate
              ←────────────── ServerKeyExchange
              ←────────────── ServerHelloDone

ClientKeyExchange ──────────→
ChangeCipherSpec ───────────→
Finished ───────────────────→

              ←────────────── ChangeCipherSpec
              ←────────────── Finished

因此完整握手通常需要:

  1. 客户端 → 服务器
  2. 服务器 → 客户端
  3. 客户端 → 服务器
  4. 服务器 → 客户端

1.3握手

1.3把许多东西合并了

cpp 复制代码
客户端                         服务器

ClientHello
+ KeyShare      ─────────────→

              ←────────────── ServerHello
              ←────────────── EncryptedExtensions
              ←────────────── Certificate
              ←────────────── CertificateVerify
              ←────────────── Finished

Finished       ──────────────→

以此为:

  1. 客户端 → 服务器
  2. 服务器 → 客户端

TLS 1.2 → "能安全通信";TLS 1.3 → "在更安全的基础上,把握手简化、加速,并砍掉旧的危险机制"。

流的概念是HTTP/2提出来的,目的是实现 多个请求在同一个连接上并发,从而提升网页加载的效率。

流的多路复用

QUIC通过流,实现了多路复用,也避免的TCP的队头阻塞问题。

假设有两个流Resquest 1和 Resquest 2,若是TCP前后顺序是先1后2,即使Resquest 2到达,但因Request 1丢失,也需要阻塞等待1重传到达,因为TCP是序列化的。但是QUIC就不一样了,请求1的数据包丢了只会阻塞请求1,请求2不会受到阻塞。

HTTP2的传输层用的TCP,TCP在内核层,而流在用户态,TCP看不到所谓流,因此只能根据seq number来判断包的先后数据先后顺序。

流ID

在同一条连接中,流ID唯一标识一条流,不同的流ID之间的数据互不干扰。同一个流ID的数据必须保持有序性,有序性靠offset来保证。数据接收端按照offset以及length大小来对该流的数据进行排序,排序之后才能交给应用程序。

流可以是单向的,也可以是双向的。流能够被双端的任何一端创建。

流类型与标识符

|------|-----------|
| 位 | 流类型 |
| 0x00 | 客户端创建的双向流 |
| 0x01 | 服务端创建的双向流 |
| 0x02 | 客户端创建的单向流 |
| 0x03 | 服务端创建的单向流 |

流可以是单向或双向的。单向流往一个方向传输数据:从流发起端向对端发送;双向流允许双端向对端发送数据。

流操作

在流的发送部分,应用层协议可以:

  • 写数据,只有当流量控制允许的空间内才能成功写数据;
  • 结束流(清理并关闭),发送一个设置FIN位为1的流帧;
  • 重置流(中止并关闭),当流未处在终止状态时发送一个RESET_STREAM帧

在流的接收部分,应用层协议可以:

  • 读数据
  • 中止读取流数据并请求关闭流,该操作可能需要发送STOP_SENDING帧。

流量控制

**流量控制要解决的问题:**接收方控制发送方的数据发送的速度。

**拥塞控制要解决的问题:**数据在网络的传输过程中,是否网络有拥塞,是否有丢包,是否有乱序等问题。

流量控制解决的是接收方的接收能力问题,一般采用滑动窗口算法;拥塞控制要解决的是中间传输的时候网络是否拥堵的问题,一般采用慢启动、拥塞避免、拥塞恢复、快速重传等算法。

滑动窗口

发送窗口与接收窗口,核心思想就是这两个窗口如何滑动。TCP是字节流,因此窗口滑动以字节为单位,QUIC基于UDP以数据包为单位。

发送窗口

发送窗口有四个部分:

  • 已发送并且已ACK的数据:就是这块数据已经发送出去了并且确认对方已经收到了的。
  • 已发送并但未收到ACK的数据:这块数据我已经发送出去了,但是我不知道对方有没有收到,因为他没给我ACK。
  • 未发送但是可以发送的数据:就是这块数据在我的发送能力范围之内,我还没来得及发送。
  • 无法发送的数据:就是这块数据我没有能力发送,不在我的发送窗口范围之内。

核心思想就是:当我收到对端的ACK后,我的窗口就可以往右滑动,这样后面原本无法发送的数据就可以发送了。

QUIC与TCP的区别点在于:收到对端发过来的ACK乱序了怎么处理。对于TCP来说,如上图,如果5号包的ACK丢了,那么即使3、4、5都已经收到ACK了,那也没办法往右滑动,必须得等3的ACK回来了,才能往右滑动,这就是经典的队头阻塞问题。QUIC的窗口是否需要往右滑动取决于是否达到预设的MAX_DATA值。

QUIC是双级流控,不仅连接一个级别的流控,还有一个流控。每个流都有自己的可用窗口,可用窗口的大小取决于最大窗口数减去发送出去的最大偏移数,跟中间已经发送出去的数据包,是否按顺序收到了对端的ACK 无关。

假设一个QUIC有三个stream:

cpp 复制代码
Connection
│
├── Stream 1:HTTP 请求
├── Stream 2:HTTP 请求
└── Stream 3:HTTP 请求

QUIC会同时会限制

cpp 复制代码
Stream 1 ── 最大允许接收 X 字节
Stream 2 ── 最大允许接收 X 字节
Stream 3 ── 最大允许接收 X 字节

整个 Connection ── 最大允许接收 Y 字节

connection级别限制整个QUIC连接所有的QUIC连接所有stream加起来能发多少数据。

stream级别限制某一条最多能发多少数据。

如何实现流量控制

QUIC的流量控制体现在什么时候以多快的增长速度来提高控制限制,但这里经常会发生问题。

一方面为了阻塞发送方,接收方可以在一个往返时间内多次发送一个最大流帧或最大数据帧。

另一方面,控制帧也会引入连接开销。也就是频繁发送最大流数据帧以及最大数据帧是不可取的。如果更新不够频繁,每次更新时就要对接收方上限做最大幅度的提升以防发送方被阻塞,使得接收方耗费需要更多的资源。

正常情况下可以判断缓冲区是否有一般空闲,如果是就可以发送这个帧让发送方快发数据。比如每次增长可以是2倍。

连接迁移

传统的TCP协议是以四元组(源IP地址、源端口号、目的ID地址、目的端口号)来标识一条连接,那么一旦四元组的任何一个元素发生了改变,这条连接就会断掉,那么这条连接中正在传输的数据就会断掉,切换到新的网络后可能需要重新去建立连接,然后重新发送数据。这将会导致用户的网络会"卡"一下。但是,QUIC不再以四元组作为唯一标识,QUIC使用连接ID来标识一条连接,无论你的网络如何切换,只要连接ID不变,那么这条连接就不会断,这就叫连接迁移。

连接迁移过程

在建连过程中,是不允许连接迁移的,在握手过程中要保证稳定的地址。

探测新路径

在连接迁移成功前,或者说在使用新的地址发送数据前,服务端可以使用地址验证来探测客户端新地址的可达性。地址验证的失败仅仅意味着 这条连接无法使用这个新路径,地址验证的失败不会引起连接关闭,除非不存在其他可替代的有效路径。

服务器如何发起连接迁移

客户端从新地址发送包含非探测帧的数据包的方式来进行发起连接迁移。在迁移时,新路径可能不支持老速率。因此客户端须重置他的拥塞控制器和RTT值。

服务器如何响应连接迁移

在同一个连接上(由连接ID进行判断,看连接ID是否属于这条连接的,值得注意的是,一条连接会有多个备用的连接ID),收到了从新的客户端地址发来的数据包,表明客户端已经迁移到那个地址。

此时,接收方后续数据包的发送,必须改向新的对端地址发送。必须先发起路径验证来验证新地址的合法性。

服务器也可向未经验证的新地址发送数据,但这样存在风险。

相关推荐
火眼金睛炼单词1 小时前
单词发音学习深度解读:方法步骤与优化策略
人工智能·学习
点心的游戏开发世界1 小时前
GDScript 入门笔记:目录
开发语言·笔记·游戏引擎·godot
旺仔小馒头wang1 小时前
AI 与教育行业如何协同,助力孩子高效学习
人工智能·学习
山岚的运维笔记1 小时前
mysql 专业笔记 -- 第 17 章:连接:连接三个具有相同名称 ID 的表
运维·数据库·笔记·后端·学习·mysql·dba
后台模板学习2 小时前
学习的心态高频面试题
java·数据库·学习
GISMagic2 小时前
Agent学习,写在开始之前
大数据·学习·agent
又見山2 小时前
用 Obsidian 写了两年的笔记,怎么备份?怎么同步到其他设备上
笔记·obsidian·obsidian插件·obsidian同步·obsidian教程
有Li2 小时前
AI帮你做CT/MR分割,想分哪里分哪里
人工智能·学习·医学生
zihan5182 小时前
理论:构建“基于客观事实、可量化、闭环自给自足”的个人操作系统
笔记·职场和发展·学习方法