字节一面:如何用 UDP 实现可靠传输?

沙海 2022年5月13日02:08:11Java评论50字数 7593阅读25分18秒阅读模式
摘要

智能摘要

速蛙云 - 极致体验,强烈推荐!!!

智能摘要文章源自JAVA秀-https://www.javaxiu.com/65368.html

Packet Header首次建立连接时和日常传输数据时使用的Header是不同的。这样的话,客户端就无法判断出是原始报文的响应还是重传报文的响应,这样在计算RTT(往返时间)时应该选择从发送原始报文开始计算,还是重传原始报文开始计算呢?滑动窗口,那么当发生数据丢失,滑动窗口是无法往前移动的,此时就会阻塞住所有的HTTP请求,这属于TCP层队头阻塞。TCP流量控制是通过让「接收方」告诉「发送方」,它(接收方)的接收窗口有多大,从而让「发送方」根据「接收方」的实际接收能力控制发送的数据量。文章源自JAVA秀-https://www.javaxiu.com/65368.html

原文约 6041 | 图片 36 | 建议阅读 13 分钟 | 评价反馈文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?

小黑格子屋 文章源自JAVA秀-https://www.javaxiu.com/65368.html

以下文章来源于小林coding,作者小林coding文章源自JAVA秀-https://www.javaxiu.com/65368.html

文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

小林coding.文章源自JAVA秀-https://www.javaxiu.com/65368.html

专注图解计算机基础,让天下没有难懂的八股文!刷题网站:xiaolincoding.com文章源自JAVA秀-https://www.javaxiu.com/65368.html

文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

来源:小林coding文章源自JAVA秀-https://www.javaxiu.com/65368.html

作者:小林coding文章源自JAVA秀-https://www.javaxiu.com/65368.html

 文章源自JAVA秀-https://www.javaxiu.com/65368.html

我记得之前在群里看到,有位读者字节一面的时候被问到:「如何基于 UDP 协议实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

很多同学第一反应就会说把 TCP 可靠传输的特性(序列号、确认应答、超时重传、流量控制、拥塞控制)在应用层实现一遍。文章源自JAVA秀-https://www.javaxiu.com/65368.html

实现的思路确实这样没错,但是有没有想过,既然 TCP 天然支持可靠传输,为什么还需要基于 UDP 实现可靠传输呢?这不是重复造轮子吗?文章源自JAVA秀-https://www.javaxiu.com/65368.html

所以,我们要先弄清楚 TCP 协议有哪些痛点?而这些痛点是否可以在基于 UDP 协议实现的可靠传输协议中得到改进?文章源自JAVA秀-https://www.javaxiu.com/65368.html

我已经说了 TCP 协议四个方面的缺陷:文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • 升级 TCP 的工作很困难;文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • TCP 建立连接的延迟;文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • TCP 存在队头阻塞问题;文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • 网络迁移需要重新建立 TCP 连接;文章源自JAVA秀-https://www.javaxiu.com/65368.html

现在市面上已经有基于 UDP 协议实现的可靠传输协议的成熟方案了,那就是 QUIC 协议,已经应用在了 HTTP/3。文章源自JAVA秀-https://www.javaxiu.com/65368.html

这次,聊聊 QUIC 是如何实现可靠传输的?又是如何解决上面 TCP 协议四个方面的缺陷文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

QUIC 是如何实现可靠传输的?

要基于 UDP 实现的可靠传输协议,那么就要在应用层下功夫,也就是要设计好协议的头部字段。文章源自JAVA秀-https://www.javaxiu.com/65368.html

拿 HTTP/3 举例子,在 UDP 报文头部与 HTTP 消息之间,共有 3 层头部:文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

整体看的视角是这样的:文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

接下来,分别对每一个 Header 做个介绍。文章源自JAVA秀-https://www.javaxiu.com/65368.html

Packet Header

Packet Header 首次建立连接时和日常传输数据时使用的 Header 是不同的。如下图,注意我没有把 Header 所有字段都画出来,只是画出了重要的字段:文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

Packet Header文章源自JAVA秀-https://www.javaxiu.com/65368.html

细分这两种:文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • Long Packet Header 用于首次建立连接。文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • Short Packet Header 用于日常传输数据。文章源自JAVA秀-https://www.javaxiu.com/65368.html

QUIC 也是需要三次握手来建立连接的,主要目的是为了确定连接 ID。文章源自JAVA秀-https://www.javaxiu.com/65368.html

建立连接时,连接 ID 是由服务器根据客户端的 Source Connection ID 字段生成的,这样后续传输时,双方只需要固定住 Destination Connection ID(连接 ID ),从而实现连接迁移功能。所以,你可以看到日常传输数据的 Short Packet Header 不需要在传输 Source Connection ID 字段了。文章源自JAVA秀-https://www.javaxiu.com/65368.html

Short Packet Header 中的 Packet Number 是每个报文独一无二的编号,它是严格递增的,也就是说就算 Packet N 丢失了,重传的 Packet N 的 Packet Number 已经不是 N,而是一个比 N 大的值。文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

为什么要这么设计呢?文章源自JAVA秀-https://www.javaxiu.com/65368.html

我们先来看看  TCP 的问题,TCP 在重传报文时的序列号和原始报文的序列号是一样的,也正是由于这个特性,引入了 TCP 重传的歧义问题。文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

TCP 重传的歧义问题文章源自JAVA秀-https://www.javaxiu.com/65368.html

比如上图,当 TCP 发生超时重传后,客户端发起重传,然后接收到了服务端确认 ACK 。由于客户端原始报文和重传报文序列号都是一样的,那么服务端针对这两个报文回复的都是相同的 ACK。文章源自JAVA秀-https://www.javaxiu.com/65368.html

这样的话,客户端就无法判断出是原始报文的响应还是重传报文的响应,这样在计算 RTT(往返时间) 时应该选择从发送原始报文开始计算,还是重传原始报文开始计算呢?文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • 如果算成原始报文的响应,但实际上是重传报文的响应(上图),会导致采样 RTT 变大;文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • 如果算成重传报文的响应,但实际上是原始报文的响应(上图),又很容易导致采样 RTT 过小;文章源自JAVA秀-https://www.javaxiu.com/65368.html

RTT 计算不精确的话,那么 RTO (超时时间)也就不精确,因为 RTO 是基于 RTT 来计算的,RTO 计算不准确可能导致重传的概率事件增大。文章源自JAVA秀-https://www.javaxiu.com/65368.html

QUIC 报文中的 Pakcet Number 是严格递增的, 即使是重传报文,它的 Pakcet Number 也是递增的,这样就能更加精确计算出报文的 RTT。文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

如果 ACK 的 Packet Number 是 N+M,就根据重传报文计算采样 RTT。如果 ACK 的 Pakcet Number 是 N,就根据原始报文的时间计算采样 RTT,没有歧义性的问题。文章源自JAVA秀-https://www.javaxiu.com/65368.html

另外,还有一个好处,QUIC 使用的 Packet Number 单调递增的设计,可以让数据包不再像TCP 那样必须有序确认,QUIC 支持乱序确认,当数据包Packet N 丢失后,只要有新的已接收数据包确认,当前窗口就会继续向右滑动文章源自JAVA秀-https://www.javaxiu.com/65368.html

待发送端超过一定时间没收到 Packet N 的确认报文后,会将需要重传的数据包放到待发送队列,重新编号比如数据包 Packet N+M 后重新发送给接收端,对重传数据包的处理跟发送新的数据包类似,这样就不会因为丢包重传将当前窗口阻塞在原地,从而解决了队头阻塞问题。文章源自JAVA秀-https://www.javaxiu.com/65368.html

所以,Packet Number 单调递增的两个好处:文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • 可以更加精确计算 RTT,没有 TCP 重传的歧义性问题;文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • 可以支持乱序确认,防止因为丢包重传将当前窗口阻塞在原地,而 TCP 必须是顺序确认的,丢包时会导致窗口不滑动;文章源自JAVA秀-https://www.javaxiu.com/65368.html

QUIC Frame Header

一个 Packet 报文中可以存放多个 QUIC Frame。文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

每一个 Frame 都有明确的类型,针对类型的不同,功能也不同,自然格式也不同。我这里只举例  Stream 类型的 Frame 格式,Stream 可以认为就是一条 HTTP 请求,它长这样:文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • Stream ID 作用:多个并发传输的 HTTP 消息,通过不同的 Stream ID 加以区别;文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • Offset 作用:类似于 TCP 协议中的 Seq 序号,保证数据的顺序性和可靠性文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • Length 作用:指明了 Frame 数据的长度。文章源自JAVA秀-https://www.javaxiu.com/65368.html

在前面介绍 Packet Header 时,说到 Packet Number 是严格递增,即使重传报文的 Packet Number 也是递增的,既然重传数据包的 Packet N+M 与丢失数据包的 Packet N 编号并不一致,我们怎么确定这两个数据包的内容一样呢?文章源自JAVA秀-https://www.javaxiu.com/65368.html

所以引入 Frame Header 这一层,通过 Stream ID + Offset 字段信息实现数据的有序性,通过比较两个数据包的 Stream ID 与 Stream Offset ,如果都是一致,就说明这两个数据包的内容一致。文章源自JAVA秀-https://www.javaxiu.com/65368.html

举个例子,下图中,数据包 Packet N 丢失了,后面重传该数据包的编号为 Packet N+2,丢失的数据包和重传的数据包 Stream ID 与 Offset 都一致,说明这两个数据包的内容一致。这些数据包传输到接收端后,接收端能根据 Stream ID 与 Offset 字段信息将  Stream x 和 Stream x+y 按照顺序组织起来,然后交给应用程序处理。文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

总的来说,QUIC 通过单向递增的 Packet Number,配合 Stream ID 与 Offset 字段信息,可以支持乱序确认而不影响数据包的正确组装,摆脱了TCP 必须按顺序确认应答 ACK 的限制,解决了 TCP 因某个数据包重传而阻塞后续所有待发送数据包的问题。文章源自JAVA秀-https://www.javaxiu.com/65368.html

QUIC 是如何解决 TCP 队头阻塞问题的?

什么是 TCP 队头阻塞问题?

TCP 队头阻塞的问题要从两个角度看,一个是发送窗口的队头阻塞,另外一个是接收窗口的队头阻塞文章源自JAVA秀-https://www.javaxiu.com/65368.html

先来说说发送窗口的队头阻塞。文章源自JAVA秀-https://www.javaxiu.com/65368.html

TCP 发送出去的数据,都是需要按序确认的,只有在数据都被按顺序确认完后,发送窗口才会往前滑动。文章源自JAVA秀-https://www.javaxiu.com/65368.html

举个例子,比如下图的发送方把发送窗口内的数据全部都发出去了,可用窗口的大小就为 0 了,表明可用窗口耗尽,在没收到 ACK 确认之前是无法继续发送数据了。文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

可用窗口耗尽文章源自JAVA秀-https://www.javaxiu.com/65368.html

接着,当发送方收到对第 32~36 字节的 ACK 确认应答后,则滑动窗口往右边移动 5 个字节,因为有 5 个字节的数据被应答确认,接下来第 52~56 字节又变成了可用窗口,那么后续也就可以发送 52~56 这 5 个字节的数据了。文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

32 ~ 36 字节已确认文章源自JAVA秀-https://www.javaxiu.com/65368.html

但是如果某个数据报文丢失或者其对应的 ACK 报文在网络中丢失,会导致发送方无法移动发送窗口,这时就无法再发送新的数据,只能超时重传这个数据报文,直到收到这个重传报文的 ACK,发送窗口才会移动,继续后面的发送行为。文章源自JAVA秀-https://www.javaxiu.com/65368.html

举个例子,比如下图,客户端是发送方,服务器是接收方。文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

客户端发送了第 5~9 字节的数据,但是第 5 字节的 ACK 确认报文在网络中丢失了,那么即使客户端收到第 6~9 字节的 ACK 确认报文,发送窗口也不会往前移动。文章源自JAVA秀-https://www.javaxiu.com/65368.html

此时的第 5 字节相当于“队头”,因为没有收到“队头”的 ACK 确认报文,导致发送窗口无法往前移动,此时发送方就无法继续发送后面的数据,相当于按下了发送行为的暂停键,这就是发送窗口的队头阻塞问题文章源自JAVA秀-https://www.javaxiu.com/65368.html

再来说说接收窗口的队头阻塞。文章源自JAVA秀-https://www.javaxiu.com/65368.html

接收方收到的数据范围必须在接收窗口范围内,如果收到超过接收窗口范围的数据,就会丢弃该数据,比如下图接收窗口的范围是 32 ~ 51 字节,如果收到第 52 字节以上数据都会被丢弃。文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

接收窗口文章源自JAVA秀-https://www.javaxiu.com/65368.html

接收窗口什么时候才能滑动?当接收窗口收到有序数据时,接收窗口才能往前滑动,然后那些已经接收并且被确认的「有序」数据就可以被应用层读取。文章源自JAVA秀-https://www.javaxiu.com/65368.html

但是,当接收窗口收到的数据不是有序的,比如收到第 33~40 字节的数据,由于第 32 字节数据没有收到, 接收窗口无法向前滑动,那么即使先收到第 33~40 字节的数据,这些数据也无法被应用层读取的。只有当发送方重传了第 32 字节数据并且被接收方收到后,接收窗口才会往前滑动,然后应用层才能从内核读取第 32~40 字节的数据。文章源自JAVA秀-https://www.javaxiu.com/65368.html

好了,至此发送窗口和接收窗口的队头阻塞问题都说完了,这两个问题的原因都是因为 TCP 必须按序处理数据,也就是 TCP 层为了保证数据的有序性,只有在处理完有序的数据后,滑动窗口才能往前滑动,否则就停留。文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • 停留「发送窗口」会使得发送方无法继续发送数据。文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • 停留「接收窗口」会使得应用层无法读取新的数据。文章源自JAVA秀-https://www.javaxiu.com/65368.html

其实也不能怪 TCP 协议,它本来设计目的就是为了保证数据的有序性。文章源自JAVA秀-https://www.javaxiu.com/65368.html

HTTP/2  的队头阻塞

HTTP/2 通过抽象出 Stream 的概念,实现了 HTTP 并发传输,一个 Stream 就代表 HTTP/1.1 里的请求和响应。文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

HTTP/2文章源自JAVA秀-https://www.javaxiu.com/65368.html

在 HTTP/2 连接上,不同 Stream 的帧是可以乱序发送的(因此可以并发不同的 Stream ),因为每个帧的头部会携带 Stream ID 信息,所以接收端可以通过 Stream ID 有序组装成 HTTP 消息,而同一 Stream 内部的帧必须是严格有序的。文章源自JAVA秀-https://www.javaxiu.com/65368.html

但是 HTTP/2 多个 Stream 请求都是在一条 TCP 连接上传输,这意味着多个 Stream 共用同一个 TCP 滑动窗口,那么当发生数据丢失,滑动窗口是无法往前移动的,此时就会阻塞住所有的 HTTP 请求,这属于 TCP 层队头阻塞文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

没有队头阻塞的 QUIC

QUIC 也借鉴 HTTP/2 里的 Stream 的概念,在一条 QUIC 连接上可以并发发送多个 HTTP 请求 (Stream)。文章源自JAVA秀-https://www.javaxiu.com/65368.html

但是 QUIC 给每一个 Stream 都分配了一个独立的滑动窗口,这样使得一个连接上的多个 Stream 之间没有依赖关系,都是相互独立的,各自控制的滑动窗口文章源自JAVA秀-https://www.javaxiu.com/65368.html

假如 Stream2 丢了一个 UDP 包,也只会影响 Stream2 的处理,不会影响其他 Stream,与 HTTP/2 不同,HTTP/2 只要某个流中的数据包丢失了,其他流也会因此受影响。文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

QUIC 是如何做流量控制的?

TCP 流量控制是通过让「接收方」告诉「发送方」,它(接收方)的接收窗口有多大,从而让「发送方」根据「接收方」的实际接收能力控制发送的数据量。文章源自JAVA秀-https://www.javaxiu.com/65368.html

在前面说到,TCP 的接收窗口在收到有序的数据后,接收窗口才能往前滑动,否则停止滑动;TCP 的发送窗口在收到对已发送数据的顺序确认 ACK后,发送窗口才能往前滑动,否则停止滑动。文章源自JAVA秀-https://www.javaxiu.com/65368.html

QUIC 是基于 UDP 传输的,而 UDP 没有流量控制,因此 QUIC 实现了自己的流量控制机制。不过,QUIC 的滑动窗口滑动的条件跟 TCP 有所差别的文章源自JAVA秀-https://www.javaxiu.com/65368.html

QUIC 实现了两种级别的流量控制,分别为 Stream 和 Connection 两种级别:文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • Stream 级别的流量控制:每个 Stream 都有独立的滑动窗口,所以每个 Stream 都可以做流量控制,防止单个 Stream 消耗连接(Connection)的全部接收缓冲。文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • Connection 流量控制:限制连接中所有 Stream 相加起来的总字节数,防止发送方超过连接的缓冲容量。文章源自JAVA秀-https://www.javaxiu.com/65368.html

Stream 级别的流量控制

回想一下 TCP,当发送方发送 seq1、seq2、seq3 报文,由于 seq2 报文丢失了,接收方收到 seq1 后会 ack1,然后接收方收到 seq3 后还是回 ack1(因为没有收到 seq2),这时发送窗口无法往前滑动。文章源自JAVA秀-https://www.javaxiu.com/65368.html

但是,QUIC 就不一样了,即使中途有报文丢失,发送窗口依然可以往前滑动,具体怎么做到的呢?我们来看看。文章源自JAVA秀-https://www.javaxiu.com/65368.html

最开始,接收方的接收窗口初始状态如下:文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

接着,接收方收到了发送方发送过来的数据,有的数据被上层读取了,有的数据丢包了,此时的接收窗口状况如下:文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

可以看到,接收窗口的左边界取决于接收到的最大偏移字节数,此时的接收窗口 = 最大窗口数 - 接收到的最大偏移数,这里就跟 TCP 不一样了。文章源自JAVA秀-https://www.javaxiu.com/65368.html

那接收窗口触发的滑动条件是什么呢?看下图:文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

接收窗口触发的滑动文章源自JAVA秀-https://www.javaxiu.com/65368.html

当图中的绿色部分数据超过最大接收窗口的一半后,最大接收窗口向右移动,同时给对端发送「窗口更新帧」。当发送方收到接收方的窗口更新帧后,发送窗口也会往前滑动,即使中途有丢包,依然也会滑动,这样就防止像 TCP 那样在出现丢包的时候,导致发送窗口无法移动,从而避免了无法继续发送数据。文章源自JAVA秀-https://www.javaxiu.com/65368.html

在前面我们说过,每个 Stream 都有各自的滑动窗口,不同 Stream 互相独立,队头的 Stream A 被阻塞后,不妨碍 StreamB、C的读取。而对于 TCP 而言,其不知道将不同的 Stream 交给上层哪一个请求,因此同一个Connection内,Stream A 被阻塞后,StreamB、C 必须等待。文章源自JAVA秀-https://www.javaxiu.com/65368.html

经过了解完 QUIC 的流量控制机制后,对于队头阻塞问题解决得更加彻底。文章源自JAVA秀-https://www.javaxiu.com/65368.html

QUIC 协议中同一个 Stream 内,滑动窗口的移动仅取决于接收到的最大字节偏移(尽管期间可能有部分数据未被接收),而对于 TCP 而言,窗口滑动必须保证此前的 packet 都有序的接收到了,其中一个 packet 丢失就会导致窗口等待。文章源自JAVA秀-https://www.javaxiu.com/65368.html

Connection 流量控制

而对于 Connection 级别的流量窗口,其接收窗口大小就是各个 Stream 接收窗口大小之和。文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

Connection 流量控制文章源自JAVA秀-https://www.javaxiu.com/65368.html

上图所示的例子,所有 Streams 的最大窗口数为 120,其中:文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • Stream 1 的最大接收偏移为 100,可用窗口 = 120 - 100 = 20文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • Stream 2 的最大接收偏移为 90,可用窗口 = 120 - 90 = 30文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • Stream 3 的最大接收偏移为 110,可用窗口 = 120 - 110 = 10文章源自JAVA秀-https://www.javaxiu.com/65368.html

那么整个 Connection 的可用窗口 = 20 + 30 + 10 = 60文章源自JAVA秀-https://www.javaxiu.com/65368.html

可用窗口 = Stream 1 可用窗口 + Stream 2 可用窗口 + Stream 3 可用窗口

QUIC 对拥塞控制改进

QUIC 协议当前默认使用了 TCP 的 Cubic 拥塞控制算法(我们熟知的慢开始、拥塞避免、快重传、快恢复策略),同时也支持 CubicBytes、Reno、RenoBytes、BBR、PCC 等拥塞控制算法,相当于将 TCP 的拥塞控制算法照搬过来了,QUIC 是如何改进 TCP 的拥塞控制算法的呢?文章源自JAVA秀-https://www.javaxiu.com/65368.html

QUIC 是处于应用层的,应用程序层面就能实现不同的拥塞控制算法,不需要操作系统,不需要内核支持。这是一个飞跃,因为传统的 TCP 拥塞控制,必须要端到端的网络协议栈支持,才能实现控制效果。而内核和操作系统的部署成本非常高,升级周期很长,所以 TCP 拥塞控制算法迭代速度是很慢的。而 QUIC 可以随浏览器更新,QUIC 的拥塞控制算法就可以有较快的迭代速度文章源自JAVA秀-https://www.javaxiu.com/65368.html

TCP 更改拥塞控制算法是对系统中所有应用都生效,无法根据不同应用设定不同的拥塞控制策略。但是因为 QUIC 处于应用层,所以就可以针对不同的应用设置不同的拥塞控制算法,这样灵活性就很高了。文章源自JAVA秀-https://www.javaxiu.com/65368.html

QUIC 更快的连接建立

对于 HTTP/1 和 HTTP/2 协议,TCP 和 TLS 是分层的,分别属于内核实现的传输层、openssl 库实现的表示层,因此它们难以合并在一起,需要分批次来握手,先 TCP 握手(1RTT),再 TLS 握手(2RTT),所以需要 3RTT 的延迟才能传输数据,就算 Session 会话服用,也需要至少 2 个 RTT。文章源自JAVA秀-https://www.javaxiu.com/65368.html

HTTP/3 在传输数据前虽然需要 QUIC 协议握手,这个握手过程只需要 1 RTT,握手的目的是为确认双方的「连接 ID」,连接迁移就是基于连接 ID 实现的。文章源自JAVA秀-https://www.javaxiu.com/65368.html

但是 HTTP/3 的 QUIC 协议并不是与 TLS 分层,而是QUIC 内部包含了 TLS,它在自己的帧会携带 TLS 里的“记录”,再加上 QUIC 使用的是 TLS1.3,因此仅需 1 个 RTT 就可以「同时」完成建立连接与密钥协商,甚至在第二次连接的时候,应用数据包可以和 QUIC 握手信息(连接信息 + TLS 信息)一起发送,达到 0-RTT 的效果文章源自JAVA秀-https://www.javaxiu.com/65368.html

如下图右边部分,HTTP/3 当会话恢复时,有效负载数据与第一个数据包一起发送,可以做到 0-RTT:文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

QUIC 是如何迁移连接的?

基于 TCP 传输协议的 HTTP 协议,由于是通过四元组(源 IP、源端口、目的 IP、目的端口)确定一条 TCP 连接。文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

图片文章源自JAVA秀-https://www.javaxiu.com/65368.html

那么当移动设备的网络从 4G 切换到 WIFI 时,意味着 IP 地址变化了,那么就必须要断开连接,然后重新建立 TCP 连接文章源自JAVA秀-https://www.javaxiu.com/65368.html

而建立连接的过程包含 TCP 三次握手和 TLS 四次握手的时延,以及 TCP 慢启动的减速过程,给用户的感觉就是网络突然卡顿了一下,因此连接的迁移成本是很高的。文章源自JAVA秀-https://www.javaxiu.com/65368.html

QUIC 协议没有用四元组的方式来“绑定”连接,而是通过连接 ID来标记通信的两个端点,客户端和服务器可以各自选择一组 ID 来标记自己,因此即使移动设备的网络变化后,导致 IP 地址变化了,只要仍保有上下文信息(比如连接 ID、TLS 密钥等),就可以“无缝”地复用原连接,消除重连的成本,没有丝毫卡顿感,达到了连接迁移的功能。文章源自JAVA秀-https://www.javaxiu.com/65368.html

参考资料:文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • https://www.taohui.tech/2021/02/04/%E7%BD%91%E7%BB%9C%E5%8D%8F%E8%AE%AE/%E6%B7%B1%E5%85%A5%E5%89%96%E6%9E%90HTTP3%E5%8D%8F%E8%AE%AE/文章源自JAVA秀-https://www.javaxiu.com/65368.html

  • https://zhuanlan.zhihu.com/p/32553477文章源自JAVA秀-https://www.javaxiu.com/65368.html

最后

偷懒了,总结交给你们了字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

-End-

文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

文章源自JAVA秀-https://www.javaxiu.com/65368.html

文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

文章源自JAVA秀-https://www.javaxiu.com/65368.html

文章源自JAVA秀-https://www.javaxiu.com/65368.html

文章源自JAVA秀-https://www.javaxiu.com/65368.html

文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输? 可乐记得加冰,爱我就要置顶 字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

字节一面:如何用 UDP 实现可靠传输?素质三连biubiubiu~字节一面:如何用 UDP 实现可靠传输?文章源自JAVA秀-https://www.javaxiu.com/65368.html

继续阅读
文章末尾固定信息...
weinxin
资源分享QQ群
本站是JAVA秀团队的技术分享社区, 会经常分享资源和教程; 分享的时代, 请别再沉默!
沙海
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定