QUIC协议 接收端状态机

本篇内容主要介绍QUIC的接收端状态机。 与发送端状态机类似,QUIC的接收端状态机有六种状态:一种初始状态、三种中间状态和两种结束状态。 如果你已经熟悉了发送端状态机的相关概念,理解接收端状态机将会很容易。

一、QUIC流接收端状态机详解

先上图,对QUIC的接收端状态机先有一个初步印象,下文将对各个状态、状态转换、各状态下的行为逐个介绍。
在这里插入图片描述

一个初始状态

Recv

我们回顾一下发送端状态机的初始状态(Ready),发送端状态机只在两种情况下进入初始状态:

  1. 本端创建了一个发送流。
  2. 对端创建了一个双向流。

接收端状态机的初始状态是Recv,相比于发送端状态机,接收端状态机进入初始状态的条件就比较多了:

  1. 本端创建了一个双向流。
  2. 收到了对端的STREAM帧、STREAM_DATA_BLOCKED帧或RESET_STREAM帧。
  3. 收到了对端的MAX_STREAM_DATA帧或STOP_SENDING帧(双向流情况下)。
  4. 本端创建了一个流ID数字更大的流。

前两条应该很好理解,本端创建了一个双向流当然包含接收端,此时接收端状态机进入初始状态。

对于第二条来说,收到了对端的这三种帧之一说明对端已经存在一个发送流了,并且很可能后续就要发送数据了,本端流的接收端状态机也要进入Recv的状态。

后面两个条件就不那么直观,要稍微琢磨一下: 先看第三条,MAX_STREAM_DATA帧或STOP_SENDING帧是由对端流的接收部分通知给本端流的发送部分,这两种帧的内容是用来控制本端流发送部分的数据发送的。对于对端初始化的双向流来说,我们可以把这两种帧认为是一种通知(可以这样理解:对端流的接收端已经开始工作了,本端也不能闲着,接收端也要创建起来),在此刻将本端流的接收部分一并创建是个合适的时机。

再看第四条,当本端创建了一个流ID数字更大的流时会创建流的接收部分,为什么要这样做呢?在QUIC协议中,创建流时指定的流ID是递增的,在新的流被创建时比它ID数字小的流必须被创建,这样带来的好处是在通讯的两端创建流的顺序将保持一致。

Recv状态下,流的接收端会进行哪些动作呢。顾名思义,Recv状态下流会接收发送端传送的数据帧。流的接收端内部会有一个缓存,以应对网络的丢包与乱序,它将重组后的有序的数据交给上层应用。 另外处于Recv状态下的接收端,会向发送端发出MAX_STREAM_DATA帧,来指导对端的传送速率,MAX_STREAM_DATA控制的传输速率与接收端内部缓存的余量也是有关联的。

可以从Recv状态进入两个中间状态:

  1. 收到了设置FIN位的STREAM帧,此时进入Size Known状态。
  2. 收到了流重置的通知(RESET帧),此时进入Reset Recvd状态。

用一张图总结Recv状态:
在这里插入图片描述

三个中间状态

Size Known

Size Knwon状态是由Recv状态转换而来,由对端流发送部分发出的带FIN位的STREAM帧触发,表示对端数据已经发送完毕。

本端流的接收部分在此时会知晓在流上一共发送了多少的数据,并等待接收缺失的数据(缺失数据由网络丢包、延迟、乱序等造成)。 在此状态下的流接收端,不需要再生成MAX_STREAM_DATA帧。

用一张图总结Size Known状态:
在这里插入图片描述

Data Recvd

Data Recvd状态是由Size Known状态转变而来的,表明流的接收端已经收到了在流上传输的所有数据。 该状态仍是一个中间状态,因为此时数据只是接收到了,存储在协议层的缓存里,没有完全交付给上层应用,只有在数据完整的交付给上层应用后才能进入最终状态。

用一张图总结Data Recvd状态:
在这里插入图片描述

Reset Recvd

能够进入Reset Recvd的状态有很多,除了两种结束状态,其余三种状态(Recv, Size Known, Data Recvd)都可以在收到Reset后进入Reset Recv状态。 Reset Recvd表示本端流的协议层已经知晓对端流发出了异常中断,但该状态仍不是一个结束状态,原因是还没有将Reset的信息通知给上层应用。 用一张图总结Reset Recvd状态:

两个结束状态

结束状态的行为比较简单,都不再收、发消息,也不能再转换为其它的状态,此时可以释放相关资源。

Data Read

该状态由Data Recvd状态转换而来,当流上的数据都完整的交付给了上层后才会进入此状态,这是一个正常结束的状态。

Reset Read

该状态由Recv Recvd状态转换而来,协议层接收端将流被Reset的事件交付给上层后,就会进入此状态,这也是一个结束状态。

在最终状态下,除了等待释放资源就没有什么其它事情可做了,就不画图解释了。

二、两种可选的状态转换

Data RecvdReset Recvd两种状态之间也是可以存在状态转换的,不过该类状态转换不是强制的,在实现QUIC协议时可以自由的处理这两类情况。

第一种情况 Data Recvd后收到RESET帧

进入Data Recvd后,流上传输的数据已经全部到达接收端,在此时如果收到了RESET帧可以进入Reset Recvd状态,这就要中断数据对上层应用的交付。

这种情况下,虽然收到了完整的数据,但上层不能够完整的读取,未免太可惜了。

那么如果发生这样的情况,不将Reset事件报给上层,就当它没发生过,让上层应用完整的处理数据行不行?

答案是可以的,可以选择“抑制”reset事件,在实现QUIC协议时你将拥有这个自由。

第二种情况 收到RESET帧后收到了完整的数据

和前一种情况类似,在Size Known状态下,收到RESET帧但还没有通知给上层reset事件的间隙收到了流上所有的数据,此时也可以选择抑制reset事件,将完整的数据交付给上层应用。

三、总结与思考

QUIC流的接收端状态机设计还是非常简洁且完备的,虽然看起来要比发送端状态机复杂一些,尤其是有4个条件可以进入初始状态。

QUIC接收端状态机与发送端的状态数量是类似的,都是一个初始状态、三个中间状态和两个结束状态,这倒是有助于我们记忆。

Recv状态下可以收数据、反馈发送端收数据的ACK、控制发送端的发送速率。

Size Known状态下等待数据传送完整。

Data RecvdData Read 分别表明协议层和应用层收到了完整的数据。

Reset RecvdReset Read 分别表明协议层和应用层收到了reset事件。

我们观察接收端状态机的两个最终状态Data ReadReset Read,都是保障了上层应用收到了完整数据或者事件,而不是仅仅关注协议层,对协议的使用者来说,还是非常体贴的。

此外,在实现中,在收到完整数据的情况下还允许抑制reset事件,这对某些应用场景还是很友好的(避免了重新请求)。

C++服务器开发 学习资料、教程 有需要的可以自行添加学习交流群739729163 领取

C/C++Linux服务器开发教程: https://ke.qq.com/course/417774?flowToken=1031343

作者:亦无风雨亦无晴 链接:https://juejin.cn/post/7197388716545179703

猜你喜欢

转载自blog.csdn.net/weixin_52622200/article/details/129145862