rfc9000: 3. Stream States

Stream ID Types

为了阅读rfc9000,最好还是好好记住Stream ID Types:

Bits Stream Type
0x00 客户端发起+双向
0x01 服务端发起+双向
0x02 客户端发起+单向
0x03 服务端发起+单向

总而言之,客户端发起的Stream ID是偶数,服务端发起的是奇数。

Stream States

rfc9000所提出的Stream States主要是largely informative的,一个quic的实现只要保证自己的FSM和rfc9000提出的FSM表现一致即可。

不过既然如此,为什么不直接用rfc9000的状态机呢?

Sending Stream States

发送部<sup id="fnref:1" class="footnote-ref"><a href="#fn:1" rel="footnote"><span class="hint--top hint--rounded" aria-label="发送部,sending part。把stream看作pipe,发送部是这个pipe的write端点。相似的,接收部(receiving part)是read端点。">[1]</span></a></sup>的FSM

发送第一个STREAM帧或STREAM_DATA_BLOCKED帧会导致当前stream的发送部进入Send状态。
由对端发起的双向stream的发送部在接收部建立时进入Ready状态。
Send状态,端点会发送、重传数据,也服从流量控制。

在发送带有FIN的帧后,发送端会进入Data Sent状态。在Data Sent状态,端点只会进行重传,并且不服从流量控制

在所有数据都得到确认后,发送端会进入终态Data Recvd状态。

在发送端处于ReadySendData Sent状态时,或者收到STOP_SENDING帧时,总是可以通过发送RESET_STREAM使当前帧进入Reset Sent状态,并在确认后进入Reset Recvd终态。
这意味着RESET_STREAM可以作为一个stream的第一个帧。

Receiving Stream States

接收部的FSM

显然一个stream的接收部不可能以帧的发送作为状态或活动,因此接收部的FSM以帧的接收为基础。

一个stream接收部的初始状态是Recv
当接收到STREAMSTREAM_DATA_BLOCKEDRESET_STREAM帧时,就应该建立一个stream的接收部。
对于双向stream,如果接收到了MAX_STREAM_DATASTOP_SENDING帧,也应该建立接收部
前者意味着对端已经开始进行流量控制,后者表示对端不想在当前stream上接收数据。
我们需要意识到这一点:由于帧丢失或重排,这两个帧有可能在STREAMSTREAM_DATA_BLOCKED帧之前到来。

在一个stream被创建前,同类型(stream type)的所有前序stream必须也已被创建。

Recv状态,端点会接收STREAMSTREAM_DATA_BLOCKED帧,并发送MAX_STREAM_DATA作为流量控制。

只要接收到了带FINSTREAM帧,这个stream的最终数据量(final size)就是已知的。接收部会进入Size Known状态。在这个状态,端点不需要发送MAX_STREAM_DATA帧以进行流量控制,只需要接收数据的重传。

在所有数据都被接收后,接收端会进入Data Recvd状态。之后所有的STREAMSTREAM_DATA_BLOCKED帧都应该被忽略

在应用层接收所有的数据后,接收端会进入终态Data Read

如果接收端在RecvSize Known状态收到了RESET_STREAM帧,接收部会进入Reset Recvd状态。
在应用层收到stream重置的提示后,接收部会进入终态Reset Read

接收端在Data Recvd状态接收RESET_STREAM帧时的表现并没有被rfc9000所规定,可以自行实现quic在此时的表现。

Permitted Frame Types

在发送端[2],只有三种帧会影响发送端和接收端的FSM:STREAMSTREAM_DATA_BLOCKEDRESET_STREAM
发送端不能从终态发送任何这三种帧,不能Reset Sent状态发送RESET_STREAM帧。

接收部只会在Recv状态发送MAX_STREAM_DATA帧。可以在除了Reset RecvdReset Read之外的其他状态发送STOP_SENDING帧。

Solicited State Transitions

如果接收部不再需要stream上的数据,可以中止stream的读操作,并指定一个应用程序级别的错误码。

如果stream处于Recv/Size Known状态,(接收部的)传输层应该通过发送一个STOP_SENDING帧以提示关闭stream的对端。虽然这表明此端不再阅读之后的数据,但不保证这些数据会被忽略。同时STOP_SENDING之后的STREAM帧也受流量控制。

STOP_SENDING的接收端(也就是发送部)应该发送RESET_STREAM帧。如果stream处于ReadySend状态,接收端必须发送RESET_STREAM帧。如果stream处于Data Sent状态,接收端可能也可以将RESET_STREAM帧的发送延迟到所有数据都被ACK或确定丢失之后。接收端不应该在这种情况下尝试重传。

接收端应该STOP_SENDING的错误码作为RESET_STREAM的错误码。发送端可能会忽略由于STOP_SENDING帧而导致的RESET_STREAM帧的错误码。

STOP_SENDING应该只在没有被对端RST的stream上发送。

如果STOP_SENDING帧丢失,应该再次发送一个STOP_SENDING帧。然而如果帧处于RecvSize Known状态,则没有必要再次发送STOP_SENDING

通过在发送部发送RESET_STREAM、接收部发送STOP_SENDING以中止一个双向stream。


  1. 发送部,sending part。把stream看作pipe,发送部是这个pipe的write端点。相似的,接收部(receiving part)是read端点。
  2. 发送端,sender。对于一个帧来说,尝试发送的端点是发送端,相对的是接收端。

rfc9000: 3. Stream States
https://blog.chromo.top/2025/07/12/quic-Stream-States/
作者
ChromoXYX
发布于
2025年7月12日
许可协议