谷歌出品!读懂 QUIC 协议:更快、更高效的通信协议

QUIC结构

QUIC协议模型如下图所示,其放弃了TCP∕IP网络中使用五元组(源IP,源端口,目的IP,目的端口,协议标识符)来唯一标识一条连接的方式,而使用一个全局唯一的随机生成的ID(即Connection ID) 来标识一条连接。

由低向上分层讨论QUIC协议:

•UDP层:在UDP层传输的是UDP报文,此处关注的是UDP报文荷载内容是什么,以及如何高效发送UDP报文;

•Connection层:Connection通过Connection ID来确认唯一连接,connection对packet进行可靠传输和安全传输;

•Stream层:在相应的Connection中,Stream通过Stream ID进行唯一流确认,并对stream frame进行传输管理。

Quic协议相关术语
  • 数据包(Packet):QUIC 协议中一个完整可处理的单元,可以封装在UDP 数据报(datagram)中。多个QUIC 数据包(packets)可以封装在一个UDP 数据报(datagram)中。
  • 帧(Frame):QUIC 数据包(packet)的有效载荷(payload)。
  • 端点(Endpoint):在QUIC 连接中生成、接收和处理 QUIC 数据包(packets)的终端。QUIC中只有两端点(endpoints):客户端(client)和服务端(server)。
  • 客户端(Client): 创建QUIC 连接的端点。
  • 服务端(Server): 接收QUIC 连接的端点。
  • 地址(Address):未经限定使用时,表示网络路径一端的IP 版本、IP地址和 UDP 端口号的元组。
  • 连接ID(Connection ID): 用于标识端点 QUIC 连接的一种标识符。每个端点(endpoint)为其对端(peer)选择一个或多个连接 ID,将其包含在发送到该端点的数据包(packets)中。这个值对peer 不透明。
  • 流(Stream):QUIC 连接中有序字节的单向(unidirectional)或双向(bidirectional)通道。一个QUIC 连接可以同时携带多个流。
  • 应用程序(Application):使用QUIC 发送或者接收数据的实体。
UDP层
UDP荷载大小

•荷载大小受限于3个对象:QUIC协议规定;路径MTU;终端接受能力

1、QUIC不能运行在不支持1200字节的单个UDP传输网络路径上 QUIC规定initial包大小不得小于1200,如果数据本身不足1200(比如initial ack),那么需要用padding方式至少填充到1200字节

2、QUIC不希望出现IP层分片现象本要求意味着udp交给IP层的数据不会大于1个MTU,假设mtu为1500,ipv4场景下,udp的荷载上限为1472字节(1500-20-8),ipv6下,udp荷载上限为1452(1500-40-8)。QUIC建议使用PMTUD以及DPLPMTUD进行mtu探测。在实战中,我们建议设置IPv6的MTU为1280,大于这个值,某些网络会存在丢包现象。

3、终端能接受 transport paraments的max_udp_payload_size(0x03)的是终端接受单个udp包大小的能力,发送端应当遵从这一约定。

UDP荷载内容

UDP荷载内容即为QUIC协议中的packet。协议规定,如果不超过荷载大小的限制,那么多个packet可以组成一个UDP报文发出去。在QUIC实现中,如果每个UDP报文只包含一个QUIC packet,会更容易出现乱序问题。

高效发UDP包

和tcp不同,QUIC需要在应用层就完成UDP数据组装,且每个udp报文不大于1个mtu,如果不加以优化,比如每个包直接用sendto/sendmsg发送,势必会造成大量的系统调用,影响吞吐

1、通过sendmmsg接口进行优化,sendmmsg可以将用户态的多个UDP QUIC包通过一次系统调用发到内核态。内核态对于每个UDP QUIC包独立作为UDP包发出去

2、在1.)解决了系统调用次数问题,开启GSO可以进步一分包延迟到发给网卡驱动前一刻,可以进一步提高吞吐,降低CPU消耗。

Connection层

上节说到,1个udp报文里传输的其实是一个或多个QIUC协议打包的packet。所以在Connection这一层面,其实是以packet为单位进行管理的。一个packet到来,终端需要解析出目标Connection ID(DCID)字段,并将该packet交给找到对应的QIUC connection。一个packet是由header加payload两部分组成。

Connection ID

相较于TCP/IP使用五元组标识一条连接,QIUC在Connection层采用客户端随机产生的64位随机数作为Connection ID标识连接,这样IP或者端口发生变化时,只要ID 不变,这条连接依然维持,可以做到连接平滑迁移。

连接建立时使用UDP端口号来识别指定机器上的特定server,而一旦建立,连接通过其connection ID关联。

上图左边是HTTPS的一次完全握手的建连过程,需要3 个 RTT。就算是Session Resumption,也需要至少 2个 RTT。而 QUIC 由于建立在UDP 的基础上,同时又实现了 0RTT的安全握手,所以在大部分情况下,只需要0 个 RTT就能实现数据发送,在实现前向加密的基础上,并且 0RTT 的成功率相比TLS 的 Sesison Ticket要高很多。QUIC握手(handshake)合并了加密和传输参数的协商,只需要1-RTT 即可完成握手,提升了建立连接到交换应用程序数据的速度。第二次连接时,可以通过第一次连接时获取到的预共享密钥(pre-shared secret)立即发送数据(0-RTT)。

安全传输

QUIC的安全传输依赖TLS1.3,而boring ssl是众多quic实现的依赖库。协议对Packet的头部以及荷载均进行了保护(包括packet number)。TLS1.3提供了0-RTT的能力,在提供数据保护的同时,能在第一时间(服务端收到第一个请求报文时)就将Response Header发给客户端。大大降低了HTTP业务中的首包时间。为了支持0-RTT,客户端需要保存PSK信息,以及部分transport parament信息。

安全传输也经常会涉及到性能问题,在目前主流的服务端,AESG由于cpu提供了硬件加速,所以性能表现最好。CHACHA20则需要更多的CPU资源。在短视频业务上,出于对首帧的要求,通常直接使用明文传输。

Transport Paramenter(TP)协商是在安全传输的握手阶段完成,除了协议规定的TP外,用户也可以扩展私有TP内容,这一特性带来了很大的便利,比如:客户端可以利用tp告知服务端进行明文传输。

可靠传输

QUIC协议是需要像TCP能够进行可靠传输,所以QUIC单独有一个rfc描述了丢包检测和拥塞控制的话题,

  • 丢包检测:

TCP 为了保证可靠性,使用了基于字节序号的 Sequence Number 及 Ack 来确认消息的有序到达。

QUIC 同样是一个可靠的协议,它使用 Packet Number 代替了 TCP 的 sequence number,并且每个 Packet Number 都严格递增。而 TCP ,重传 segment 的 sequence number 和原始的 segment 的 Sequence Number 保持不变,也正是由于这个特性,引入了 TCP 重传的歧义问题。

在普通的TCP里面,如果发送方收到三个重复的ACK就会触发快速重传,如果太久没收到ACK就会触发超时重传,而QUIC使用NACK (Negative Acknowledgement) 可以直接告知发送方哪些包丢了,不用等到超时重传。TCP有一个SACK的选项,也具备NACK的功能,QUIC的NACK有一个区别它每次重传的报文序号都是新的。

但是单纯依靠严格递增的 Packet Number 肯定是无法保证数据的顺序性和可靠性。QUIC 又引入了一个 Stream Offset 的概念,即一个 Stream 可以经过多个 Packet 传输,Packet Number 严格递增,没有依赖。但是 Packet 里的 Payload 如果是 Stream 的话,就需要依靠 Stream 的 Offset 来保证应用数据的顺序。

  • 拥塞控制:QUIC针对TCP协议中的一些缺陷,专门做了优化。QUIC 重新实现了TCP 协议的Cubic算法进行拥塞控制,并在此基础上做了不少改进。
  • 热插拔:tcp的拥塞控制需要内核态实现,而QUIC在用户态实现,因此QUIC 修改拥塞控制策略只需要在应用层操作,并且QUIC 会根据不同的网络环境、用户来动态选择拥塞控制算法。
  • 前向纠错 FEC:QUIC 使用前向纠错(FEC,Forward Error Correction)技术增加协议的容错性。一段数据被切分为10 个包后,依次对每个包进行异或运算,运算结果会作为 FEC 包与数据包一起被传输,当出现丢包时可根据剩余的包和FEC包推算出丢的包。
单调递增的Packet Number

TCP 为了保证可靠性,使用Sequence Number 和 ACK确认消息是否有序到达,但这样的设计存在缺陷。超时发生后客户端发起重传,随后接收到了ACK确认,但因为原始请求和重传请求所返回的ACK 消息一样,所以客户端无法分辨此 ACK 对应的是原始请求还是重传请求。如果客户端认为是原始请求的ACK,但实际上是左图的情形,则计算的采样 RTT 偏大;如果客户端认为是重传请求的ACK,但实际上是右图的情形,又会导致采样 RTT 偏小。采样 RTT 会影响超时重传时间(Retransmission TimeOut)的 计算。

QUIC解决了上面的歧义问题。与采用Sequence Number 标记不同的是,其使用的Packet Number标记严格单调递增,如果 Packet N 丢失了,那么重传时 Packet 的标识不会是 N,而是比 N 大的数字,比如N+M,这样发送方接收到确认消息时就能方便地知道 ACK 对应的是原始请求还是重传请求。

Connection层
更大的ACK block

一般来说,接收方收到发送方的消息后都应该发送一个 ACK回复,表示收到了数据。但每收到一个数据就返回一个ACK 回复太麻烦,所以一般不会立即回复,而是接收到多个数据后再回复,TCP SACK 最多提供 3个 ACK block。但有些场景下,比如下载,只需要服务器返回数据就好,但按照 TCP 的设计,每收到 3 个数据包就要返回一个ACK。而QUIC 最多可以捎带 256 个ACK block。在丢包率比较严重的网络下,更多的 ACK block 可以减少返回包的量,提升网络效率。

流量控制

TCP 会对每个 TCP 连接进行流量控制,通过滑动窗口进行实现。

QUIC 的流量控制有两个级别:连接级别和Stream级别,用于表达接收端的接受能力。

单条 Stream的流量控制如上图所示。Stream 还没传输数据时,接收窗口(flow control receive window)就是最大接收窗口(flow control receive window),随着接收方接收到数据后,接收窗口不断缩小。在接收到的数据中,有的数据已被处理,而有的数据还没来得及被处理。蓝色块表示已处理数据,黄色块表示未处理数据,这部分数据的到来,使得Stream的接收窗口缩小。

随着数据不断被处理,接收方就有能力处理更多数据。当满足(flow control receive offset - consumed bytes) < (max receive window / 2) 时,接收方会发送WINDOW_UPDATE frame 告诉发送方你可以再多发送些数据过来。这时flow control receive offset就会偏移,接收窗口增大,发送方可以发送更多数据到接收方。

由于QUIC协议允许多路复用,因此Stream 级别的控制对防止接收端接收过多数据作用有限,更需要借助 Connection 级别的流量控制。

针对stream:

可用窗口数 = 最大窗口数 – 接收到的最大偏移数

针对connection:

可用窗口数 = stream1可用窗口数+ … + streamN可用窗口数

QUIC 的流量控制和TCP 有点区别,TCP为了保证可靠性,窗口左边沿向右滑动时的长度取决于已经确认的字节数。如果中间出现丢包,就算接收到了更大序号的Segment,窗口起始changdu也无法超过这个序列号。 QUIC 不同,就算此前有些 packet 没有接收到,它的滑动窗口也只取决于接收到的最大偏移字节数。

Stream层

Stream是一个抽象的概念,用以表示一个有序传输的数据流,而这些数据其实就是由Stream Frame排列构成。QUIC 使用帧(frames)进行端到端的通信。一个或多个帧(frame)被组装成一个 QUIC 包(packet)。在一个quic connection上,可以同时传输多条流,QUIC通过对多路传输的支持,解决了TCP中的队头阻塞问题。

在QUIC协议中,有序的概念仅维护在单个stream中,stream之间和packet都不要求有序,假设某个packet丢失,只会影响包含在这个包里的stream,其他stream仍然可以从后续乱序到达的packet中提取到自己所需帧交给应用层。

Stream头部

可以创建两种类型的流:双向流(bidirectional streams),允许客户端和服务端互相发送数据。单向流(unidirectional streams),允许单个端点(endpoint)发送数据。一个基于信用的方案(credit-based scheme)用于限制流的创建并限制可发送的数据量。stream的不同类型定义在HTTP3中得到了充分的利用。

Stream荷载

Stream的荷载即为一系列Stream Frame,通过Stream Frame头部的Stream ID来确认单个流。在TCP里,如果一个segment传递丢失,那么后续segment乱序到达,也不会被应用层使用,只到丢失的segment重传成功为止,因此TCP实现的HTTP2的多路复用能力受到制约。在QUIC协议中,有序的概念仅维护在单个stream中,stream之间和packet都不要求有序,假设某个packet丢失,只会影响包含在这个包里的stream,其他stream仍然可以从后续乱序到达的packet中提取到自己所需要的数据交给应用层。

Quic相关开源库
gQUICiQUIC
chromium:quic-client/server-demo模块封装了支持HTTPS的QUIC实现MsQuicMsQuic是IETF quic协议的Microsoft实现。它是跨平台的,用C语言编写,设计成一个通用的QUIC库。
chromium:net/quic模块封装QUIC在更底层模仿TCP Socket操作,在chrome75版本后被弃用,本次quic模块基于74-75版本的模块实现。quic-go使用Go语言来重写的QUIC协议实现库,从github上面看其对于iQUIC和gQUIC这两个分支流派都提供了支持。
quiche目前谷歌使用的QUIC开源代码库,将QUIC从chromium独立出来提供QUIC协议的支持,与iQUIC兼容。QuicwgIETF quic实现工作小组

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/348022.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

1990-2019年城市维度区域创新创业指数面板数据/地级市创新创业指数面板数据

1990-2019年城市维度区域创新创业指数面板数据/地级市创新创业指数面板数据 1、时间&#xff1a;1990-2019年 2、范围&#xff1a;地级市&#xff08;290&#xff09; 3、指标&#xff1a;序号、年份、城市码、城市、总维度&#xff1a;总量指数得分、人均得分、单位面积得分…

应用协议漏洞

应用协议漏洞 一、rsync rsync是Linux下一款数据备份工具&#xff0c;支持通过rsync协议、ssh协议进行远程文件传输。其中rsync协议默认监听873端口 1.未授权访问 打开靶场 判断漏洞是否存在 rsync rsync://目标ip:端口读取文件 rsync rsync://47.99.49.128:873/src/tmp/下…

访问者模式-C#实现

该实例基于WPF实现&#xff0c;直接上代码&#xff0c;下面为三层架构的代码。 一 Model using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace 设计模式练习.Model.访问者模式 {public class Com…

JRT的无源码发布

之前介绍过JRT最大的特点就是业务脚本化。老javaer就会说你业务代码都在发布环境放着&#xff0c;那怎么代码保密&#xff0c;在发布环境别人随便改了启不是不安全&#xff0c;或者一些代码我就是不想让人看源码呢。 其实JRT的业务脚本化只是特性&#xff0c;不是代表就必须要…

如何进行H.265视频播放器EasyPlayer.js的中性化设置?

H5无插件流媒体播放器EasyPlayer属于一款高效、精炼、稳定且免费的流媒体播放器&#xff0c;可支持多种流媒体协议播放&#xff0c;可支持H.264与H.265编码格式&#xff0c;性能稳定、播放流畅&#xff0c;能支持WebSocket-FLV、HTTP-FLV&#xff0c;HLS&#xff08;m3u8&#…

C#用DateAndTime.DateDiff方法和TimeSpan分别计算时间间隔

目录 一、计算时间间隔的方法 1.用DateAndTime.DateDiff方法计算时间间隔 2.使用TimeSpan获取日期时间间隔 二、实例 1.示例一&#xff1a;用DateAndTime.DateDiff方法计算时间间隔 2.示例二&#xff1a;使用TimeSpan获取日期时间间隔 一、计算时间间隔的方法 1.用Date…

深入《羊了个羊》:从0到1的消除游戏开发

一、游戏简介 《羊了个羊》是一款备受欢迎的消除类游戏。玩家需要通过交换相邻的方块&#xff0c;使三个或更多相同方块连成一线&#xff0c;从而将它们消除。消除方块可以获得分数&#xff0c;并在全球排行榜上与其他玩家竞争。 设置项目结构 首先&#xff0c;在文本编辑器中…

【博客搭建记录贴】day4_Hexo基本操作,添加草稿并发布

目录 1.将项目导入到开发环境1.1 先把项目导入到IDEA中1.2 确认IDEA中服务器启动正常 2.Hexo基本操作: 添加草稿并发布2.1 生成一个草稿文件2.2 在页面上查看草稿3.3 将草稿正式发布 1.将项目导入到开发环境 我本地已经安装了 IntelliJ IDEA&#xff08;版本&#xff1a;社区版…

【modelsim使用】数据显示设置

本文介绍modelsim使用中数据的显示设置&#xff0c;定点小数的显示、模拟波形的显示、数据截位查看、信号颜色和行高设置的操作。 文章目录 定点小数显示模拟波形的显示选取信号的某几位组合查看信号颜色与行高设置 定点小数显示 使用modelsim进行仿真时&#xff0c;涉及到定点…

【GitHub项目推荐--一款美观的开源社区系统】【转载】

推荐一款开源社区系统&#xff0c;该系统基于主流的 Java Web 技术栈&#xff0c;如果你是一名 Java 新手掌握了基本 JavaEE 框架知识&#xff0c;可以拿本项目作为练手项目。 开源社区系统功能还算完善包含发布帖子、发布评论、私信、系统通知、点赞、关注、搜索、用户设置、…

【MySQL】内外连接

内外连接 一、内连接二、外连接1、左外连接2、右外连接 表的连接分为内连和外连。 一、内连接 内连接实际上就是利用where子句对两种表形成的笛卡儿积进行筛选。只不过为了让sql的可读性更好&#xff0c;我们使用其他的关键字进行内连接。 语法&#xff1a; SELECT ... FRO…

BGP路由协议通告原则

1仅将自己最优的路由发给BGP邻居 一般情况下,如果BGP Speaker学到去往同一网段的路由多于一条时,只会选择一条最优的路由给自己使用,即用来发布给邻居,同时上送给IP路由表。但是,由于路由器也会选择最优的路由给自己使用,所以BGP Speaker本身选择的最优的路由也不一定被…

简单模拟实现一个线程池

废话不多说之间上代码 import java.util.ArrayList; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue;public class MyThreadPoolExecutor {private List<Thread> listnew ArrayList<>();pri…

Linux 离线安装Docker(脚本自动安装)

文章目录 1. 准备docker离线包2. 准备docker.service 系统配置文件3. 准备安装脚本和卸载脚本4. 安装5、docker存储 前言 关于服务器不能联网的情况&#xff0c;这样就没法用yum安装软件&#xff0c;docker也是如此&#xff1b;或者由于CentOS系统采用Yum 安装过程中很慢&#…

SpringBootAdmin邮件通知

在上一篇中我们学习到了 Admin Service Clustering 分布式缓存配置 &#xff0c;这一篇我们来学习&#xff0c;客户端离线&#xff0c;出现故障的时候&#xff0c;我们这么能及时知道呢&#xff0c;发现邮件通知也许是最简单的方式了&#xff01; 邮件通知 邮件通知将作为使用…

​在 Linux ​中管理用户

在 Linux 系统中&#xff0c;用户是系统资源的主要使用者&#xff0c;每个用户都有一个唯一的标识符&#xff08;用户ID&#xff09;。为了更好地组织和管理用户&#xff0c;Linux 还引入了用户组的概念。用户组是用户的集合&#xff0c;有助于更有效地分配权限和资源。 用户是…

LabVIEW工业机器人系统

介绍了ABB工业机器人与LabVIEW之间进行数据交互的解决方案。通过使用TCP/IP协议的socket通信&#xff0c;实现了机器人坐标数据的读取&#xff0c;为人机交互提供了一个更便捷、更高效的新思路。 系统主要由ABB工业机器人、基于TCP/IP协议的通信接口和LabVIEW软件组成。工业机…

Excel:将截面数据转换成面板数据

原始截面数据如下&#xff1a; 步骤&#xff1a;数据——自表格/区域 点击确定&#xff0c;出现下图&#xff1a; 然后&#xff0c;在这个界面选择&#xff1a;“转换”——“逆透视列”下选择逆透视其他列。会出现面板数据形式。 然后&#xff0c;点击“主页”——关闭并上载即…

day16打卡

day16打卡 104. 二叉树的最大深度 递归法时间复杂度&#xff1a;O(N)&#xff0c;空间复杂度&#xff1a;O(N) class Solution { public:int maxDepth(TreeNode* root) {if(root nullptr) return 0;return 1 max(maxDepth(root->left), maxDepth(root->right));} };…

Android Settings 显示电池点亮百分比

如题&#xff0c;Android 原生 Settings 里有个 电池电量百分比 的选项&#xff0c;打开后电池电量百分比会显示在状态栏。 基于 Android 13 &#xff0c; 代码在 ./packages/apps/Settings/src/com/android/settings/display/BatteryPercentagePreferenceController.java &am…