Zum Hauptinhalt springen

3. Stream-Zustände

3. Stream-Zustände

Dieser Abschnitt beschreibt Streams im Hinblick auf ihre Sende- oder Empfangskomponenten. Es werden zwei Zustandsautomaten beschrieben: einer für die Streams, auf denen ein Endpunkt Daten überträgt (Abschnitt 3.1), und ein anderer für Streams, auf denen ein Endpunkt Daten empfängt (Abschnitt 3.2).

Unidirektionale Streams verwenden entweder den Sende- oder den Empfangs-Zustandsautomaten, abhängig vom Stream-Typ und der Endpunkt-Rolle. Bidirektionale Streams verwenden beide Zustandsautomaten an beiden Endpunkten. Die Verwendung dieser Zustandsautomaten ist größtenteils gleich, unabhängig davon, ob der Stream unidirektional oder bidirektional ist. Die Bedingungen zum Öffnen eines Streams sind für einen bidirektionalen Stream etwas komplexer, da das Öffnen entweder der Sende- oder der Empfangsseite dazu führt, dass der Stream in beiden Richtungen geöffnet wird.

Die in diesem Abschnitt gezeigten Zustandsautomaten sind weitgehend informativ. Dieses Dokument verwendet Stream-Zustände, um Regeln dafür zu beschreiben, wann und wie verschiedene Arten von Frames gesendet werden können und welche Reaktionen erwartet werden, wenn verschiedene Arten von Frames empfangen werden. Obwohl diese Zustandsautomaten für die Implementierung von QUIC nützlich sein sollen, sind diese Zustände nicht dazu gedacht, Implementierungen einzuschränken. Eine Implementierung kann einen anderen Zustandsautomaten definieren, solange ihr Verhalten mit einer Implementierung konsistent ist, die diese Zustände implementiert.

hinweis

In einigen Fällen kann ein einzelnes Ereignis oder eine einzelne Aktion einen Übergang durch mehrere Zustände verursachen. Zum Beispiel kann das Senden von STREAM mit gesetztem FIN-Bit zwei Zustandsübergänge für einen sendenden Stream verursachen: vom Zustand "Ready" zum Zustand "Send" und vom Zustand "Send" zum Zustand "Data Sent".

3.1 Sendende Stream-Zustände

Abbildung 2 zeigt die Zustände für den Teil eines Streams, der Daten an einen Peer sendet.

       o
| Create Stream (Sending)
| Peer Creates Bidirectional Stream
v
+-------+
| Ready | Send RESET_STREAM
| |-----------------------.
+-------+ |
| |
| Send STREAM / |
| STREAM_DATA_BLOCKED |
v |
+-------+ |
| Send | Send RESET_STREAM |
| |---------------------->|
+-------+ |
| |
| Send STREAM + FIN |
v v
+-------+ +-------+
| Data | Send RESET_STREAM | Reset |
| Sent |------------------>| Sent |
+-------+ +-------+
| |
| Recv All ACKs | Recv ACK
v v
+-------+ +-------+
| Data | | Reset |
| Recvd | | Recvd |
+-------+ +-------+

Abbildung 2: Zustände für sendende Teile von Streams

Der sendende Teil eines Streams, den der Endpunkt initiiert (Typen 0 und 2 für Clients, 1 und 3 für Server), wird von der Anwendung geöffnet. Der Zustand "Ready" repräsentiert einen neu erstellten Stream, der Daten von der Anwendung akzeptieren kann. Stream-Daten könnten in diesem Zustand zur Vorbereitung auf das Senden gepuffert werden.

Das Senden des ersten STREAM- oder STREAM_DATA_BLOCKED-Frames veranlasst einen sendenden Teil eines Streams, in den Zustand "Send" einzutreten. Eine Implementierung könnte sich entscheiden, die Zuweisung einer Stream-ID zu einem Stream aufzuschieben, bis sie den ersten STREAM-Frame sendet und in diesen Zustand eintritt, was eine bessere Stream-Priorisierung ermöglichen kann.

Der sendende Teil eines bidirektionalen Streams, der von einem Peer initiiert wird (Typ 0 für einen Server, Typ 1 für einen Client), startet im Zustand "Ready", wenn der empfangende Teil erstellt wird.

Im Zustand "Send" überträgt ein Endpunkt – und überträgt bei Bedarf erneut – Stream-Daten in STREAM-Frames. Der Endpunkt respektiert die von seinem Peer gesetzten Flusskontrolllimits und akzeptiert und verarbeitet weiterhin MAX_STREAM_DATA-Frames. Ein Endpunkt im Zustand "Send" erzeugt STREAM_DATA_BLOCKED-Frames, wenn es durch Stream-Flusskontrolllimits am Senden gehindert wird (Abschnitt 4.1).

Nachdem die Anwendung anzeigt, dass alle Stream-Daten gesendet wurden und ein STREAM-Frame mit dem FIN-Bit gesendet wird, tritt der sendende Teil des Streams in den Zustand "Data Sent" ein. Aus diesem Zustand überträgt der Endpunkt nur bei Bedarf Stream-Daten erneut. Der Endpunkt muss keine Flusskontrolllimits überprüfen oder STREAM_DATA_BLOCKED-Frames für einen Stream in diesem Zustand senden. MAX_STREAM_DATA-Frames könnten empfangen werden, bis der Peer den endgültigen Stream-Offset erhält. Der Endpunkt kann alle MAX_STREAM_DATA-Frames, die er von seinem Peer für einen Stream in diesem Zustand erhält, sicher ignorieren.

Sobald alle Stream-Daten erfolgreich bestätigt wurden, tritt der sendende Teil des Streams in den Zustand "Data Recvd" ein, der ein Endzustand ist.

Von jedem Zustand, der einer von "Ready", "Send" oder "Data Sent" ist, kann eine Anwendung signalisieren, dass sie die Übertragung von Stream-Daten aufgeben möchte. Alternativ könnte ein Endpunkt einen STOP_SENDING-Frame von seinem Peer erhalten. In beiden Fällen sendet der Endpunkt einen RESET_STREAM-Frame, der dazu führt, dass der Stream in den Zustand "Reset Sent" eintritt.

Ein Endpunkt KANN einen RESET_STREAM als ersten Frame senden, der einen Stream erwähnt; dies bewirkt, dass der sendende Teil dieses Streams geöffnet wird und dann sofort in den Zustand "Reset Sent" übergeht.

Sobald ein Paket, das einen RESET_STREAM enthält, bestätigt wurde, tritt der sendende Teil des Streams in den Zustand "Reset Recvd" ein, der ein Endzustand ist.

3.2 Empfangende Stream-Zustände

Abbildung 3 zeigt die Zustände für den Teil eines Streams, der Daten von einem Peer empfängt. Die Zustände für einen empfangenden Teil eines Streams spiegeln nur einige der Zustände des sendenden Teils des Streams beim Peer wider. Der empfangende Teil eines Streams verfolgt keine Zustände auf dem sendenden Teil, die nicht beobachtet werden können, wie den Zustand "Ready". Stattdessen verfolgt der empfangende Teil eines Streams die Bereitstellung von Daten an die Anwendung, von denen einige vom Sender nicht beobachtet werden können.

       o
| Recv STREAM / STREAM_DATA_BLOCKED / RESET_STREAM
| Create Bidirectional Stream (Sending)
| Recv MAX_STREAM_DATA / STOP_SENDING (Bidirectional)
| Create Higher-Numbered Stream
v
+-------+
| Recv | Recv RESET_STREAM
| |-----------------------.
+-------+ |
| |
| Recv STREAM + FIN |
v |
+-------+ |
| Size | Recv RESET_STREAM |
| Known |---------------------->|
+-------+ |
| |
| Recv All Data |
v v
+-------+ Recv RESET_STREAM +-------+
| Data |--- (optional) --->| Reset |
| Recvd | Recv All Data | Recvd |
+-------+<-- (optional) ----+-------+
| |
| App Read All Data | App Read Reset
v v
+-------+ +-------+
| Data | | Reset |
| Read | | Read |
+-------+ +-------+

Abbildung 3: Zustände für empfangende Teile von Streams

Der empfangende Teil eines von einem Peer initiierten Streams (Typen 1 und 3 für einen Client oder 0 und 2 für einen Server) wird erstellt, wenn der erste STREAM-, STREAM_DATA_BLOCKED- oder RESET_STREAM-Frame für diesen Stream empfangen wird. Für von einem Peer initiierte bidirektionale Streams erstellt der Empfang eines MAX_STREAM_DATA- oder STOP_SENDING-Frames für den sendenden Teil des Streams auch den empfangenden Teil. Der Anfangszustand für den empfangenden Teil eines Streams ist "Recv".

Für einen bidirektionalen Stream tritt der empfangende Teil in den Zustand "Recv" ein, wenn der vom Endpunkt initiierte sendende Teil (Typ 0 für einen Client, Typ 1 für einen Server) in den Zustand "Ready" eintritt.

Ein Endpunkt öffnet einen bidirektionalen Stream, wenn ein MAX_STREAM_DATA- oder STOP_SENDING-Frame vom Peer für diesen Stream empfangen wird. Der Empfang eines MAX_STREAM_DATA-Frames für einen nicht geöffneten Stream zeigt an, dass der Remote-Peer den Stream geöffnet hat und Flusskontrollguthaben bereitstellt. Der Empfang eines STOP_SENDING-Frames für einen nicht geöffneten Stream zeigt an, dass der Remote-Peer keine Daten mehr auf diesem Stream empfangen möchte. Beide Frames könnten vor einem STREAM- oder STREAM_DATA_BLOCKED-Frame ankommen, wenn Pakete verloren gehen oder neu angeordnet werden.

Bevor ein Stream erstellt wird, MÜSSEN alle Streams desselben Typs mit niedrigeren Stream-IDs erstellt werden. Dies stellt sicher, dass die Erstellungsreihenfolge für Streams an beiden Endpunkten konsistent ist.

Im Zustand "Recv" empfängt der Endpunkt STREAM- und STREAM_DATA_BLOCKED-Frames. Eingehende Daten werden gepuffert und können in der richtigen Reihenfolge für die Bereitstellung an die Anwendung zusammengesetzt werden. Während Daten von der Anwendung verbraucht werden und Pufferspeicher verfügbar wird, sendet der Endpunkt MAX_STREAM_DATA-Frames, um dem Peer zu ermöglichen, mehr Daten zu senden.

Wenn ein STREAM-Frame mit einem FIN-Bit empfangen wird, ist die Endgröße des Streams bekannt; siehe Abschnitt 4.5. Der empfangende Teil des Streams tritt dann in den Zustand "Size Known" ein. In diesem Zustand muss der Endpunkt keine MAX_STREAM_DATA-Frames mehr senden; er empfängt nur noch Neuübertragungen von Stream-Daten.

Sobald alle Daten für den Stream empfangen wurden, tritt der empfangende Teil in den Zustand "Data Recvd" ein. Dies könnte als Ergebnis des Empfangs desselben STREAM-Frames geschehen, das den Übergang zu "Size Known" verursacht. Nachdem alle Daten empfangen wurden, können alle STREAM- oder STREAM_DATA_BLOCKED-Frames für den Stream verworfen werden.

Der Zustand "Data Recvd" bleibt bestehen, bis Stream-Daten an die Anwendung geliefert wurden. Sobald Stream-Daten geliefert wurden, tritt der Stream in den Zustand "Data Read" ein, der ein Endzustand ist.

Der Empfang eines RESET_STREAM-Frames im Zustand "Recv" oder "Size Known" bewirkt, dass der Stream in den Zustand "Reset Recvd" eintritt. Dies könnte dazu führen, dass die Bereitstellung von Stream-Daten an die Anwendung unterbrochen wird.

Es ist möglich, dass alle Stream-Daten bereits empfangen wurden, wenn ein RESET_STREAM empfangen wird (d.h. im Zustand "Data Recvd"). Ebenso ist es möglich, dass verbleibende Stream-Daten nach dem Empfang eines RESET_STREAM-Frames ankommen (der Zustand "Reset Recvd"). Eine Implementierung kann diese Situation nach eigenem Ermessen verwalten.

Das Senden eines RESET_STREAM bedeutet, dass ein Endpunkt die Bereitstellung von Stream-Daten nicht garantieren kann; es gibt jedoch keine Anforderung, dass Stream-Daten nicht bereitgestellt werden, wenn ein RESET_STREAM empfangen wird. Eine Implementierung KANN die Bereitstellung von Stream-Daten unterbrechen, alle nicht verbrauchten Daten verwerfen und den Empfang des RESET_STREAM signalisieren. Ein RESET_STREAM-Signal könnte unterdrückt oder zurückgehalten werden, wenn Stream-Daten vollständig empfangen wurden und gepuffert sind, um von der Anwendung gelesen zu werden. Wenn das RESET_STREAM unterdrückt wird, bleibt der empfangende Teil des Streams in "Data Recvd".

Sobald die Anwendung das Signal empfängt, das anzeigt, dass der Stream zurückgesetzt wurde, geht der empfangende Teil des Streams in den Zustand "Reset Read" über, der ein Endzustand ist.

3.3 Erlaubte Frame-Typen

Der Sender eines Streams sendet nur drei Frame-Typen, die den Zustand eines Streams beim Sender oder Empfänger beeinflussen: STREAM (Abschnitt 19.8), STREAM_DATA_BLOCKED (Abschnitt 19.13) und RESET_STREAM (Abschnitt 19.4).

Ein Sender DARF KEINEN dieser Frames aus einem Endzustand ("Data Recvd" oder "Reset Recvd") senden. Ein Sender DARF KEINEN STREAM- oder STREAM_DATA_BLOCKED-Frame für einen Stream im Zustand "Reset Sent" oder einem Endzustand senden – d.h. nach dem Senden eines RESET_STREAM-Frames. Ein Empfänger könnte jeden dieser drei Frames in jedem Zustand aufgrund der Möglichkeit verzögerter Zustellung von Paketen empfangen, die sie tragen. Der Empfang von Frames in späteren Zuständen DARF NICHT als Fehler behandelt werden und ändert nicht den Zustand des sendenden Teils.

Ein Empfänger sendet MAX_STREAM_DATA- (Abschnitt 19.10) und STOP_SENDING-Frames (Abschnitt 19.5).

Der Empfänger sendet MAX_STREAM_DATA-Frames nur im Zustand "Recv". Ein Empfänger kann einen STOP_SENDING-Frame in jedem Zustand senden, in dem er keinen RESET_STREAM-Frame empfangen hat; d.h. in jedem Zustand außer "Reset Recvd" oder "Reset Read". Es hat jedoch wenig Wert, einen STOP_SENDING-Frame im Zustand "Data Recvd" zu senden, da alle Stream-Daten empfangen wurden. Ein Sender kann diese Frames in jedem Zustand als Ergebnis verzögerter Zustellung von Paketen empfangen.

3.4 Bidirektionale Stream-Zustände

Ein bidirektionaler Stream besteht aus einem sendenden Teil und einem empfangenden Teil. Implementierungen können Zustände des bidirektionalen Streams als Zusammensetzungen der Zustände der sendenden und empfangenden Teile darstellen. Das einfachste Modell stellt den Stream als "offen" dar, wenn entweder die sendenden oder empfangenden Teile in einem nicht-terminalen Zustand sind, und "geschlossen", wenn beide Teile in Endzuständen sind.

Tabelle 2 zeigt eine komplexere Zuordnung bidirektionaler Stream-Zustände, die lose den Stream-Zuständen in HTTP/2 [HTTP2] entspricht. Dies zeigt, dass mehrere Zustände auf sendenden oder empfangenden Teilen von Streams demselben zusammengesetzten Zustand zugeordnet werden. Beachten Sie, dass dies nur eine Möglichkeit für eine solche Zuordnung ist; diese Zuordnung erfordert, dass Daten bestätigt werden, bevor der Übergang zu einem "geschlossenen" oder "halb-geschlossenen" Zustand erfolgt.

Sendender TeilEmpfangender TeilZusammengesetzter Zustand
No Stream/ReadyNo Stream/Recv *1idle
Ready/Send/Data SentRecv/Size Knownopen
Ready/Send/Data SentData Recvd/Data Readhalf-closed (remote)
Ready/Send/Data SentReset Recvd/Reset Readhalf-closed (remote)
Data RecvdRecv/Size Knownhalf-closed (local)
Reset Sent/Reset RecvdRecv/Size Knownhalf-closed (local)
Reset Sent/Reset RecvdData Recvd/Data Readclosed
Reset Sent/Reset RecvdReset Recvd/Reset Readclosed
Data RecvdData Recvd/Data Readclosed
Data RecvdReset Recvd/Reset Readclosed

Tabelle 2: Mögliche Zuordnung von Stream-Zuständen zu HTTP/2

*1

Beachten Sie, dass der Zustand "No Stream" hypothetisch ist; weder ein Endpunkt noch sein Peer haben den Stream erstellt.

3.5 Angeforderte Zustandsübergänge

Wenn eine Anwendung nicht mehr an den Daten interessiert ist, die sie auf einem Stream empfängt, kann sie das Lesen des Streams abbrechen und einen Anwendungsfehlercode angeben.

Wenn sich der Stream im Zustand "Recv" oder "Size Known" befindet, SOLLTE der Transport dies signalisieren, indem er einen STOP_SENDING-Frame sendet, um den Abschluss des Streams in der entgegengesetzten Richtung zu veranlassen. Dies zeigt typischerweise an, dass die empfangende Anwendung die Daten, die sie vom Stream erhält, nicht mehr liest, aber es ist keine Garantie dafür, dass eingehende Daten ignoriert werden.

STREAM-Frames, die nach dem Senden eines STOP_SENDING-Frames empfangen werden, werden immer noch auf die Verbindungs- und Stream-Flusskontrolle angerechnet, obwohl diese Frames beim Empfang verworfen werden können.

Ein STOP_SENDING-Frame fordert, dass der empfangende Endpunkt einen RESET_STREAM-Frame sendet. Ein Endpunkt, das einen STOP_SENDING-Frame empfängt, MUSS einen RESET_STREAM-Frame senden, wenn sich der Stream im Zustand "Ready" oder "Send" befindet. Wenn sich der Stream im Zustand "Data Sent" befindet, KANN der Endpunkt das Senden des RESET_STREAM-Frames aufschieben, bis die Pakete mit ausstehenden Daten bestätigt oder als verloren deklariert wurden. Wenn ausstehende Daten als verloren deklariert werden, SOLLTE der Endpunkt einen RESET_STREAM-Frame senden, anstatt die Daten erneut zu übertragen.

Ein Endpunkt SOLLTE den Fehlercode aus dem STOP_SENDING-Frame in den RESET_STREAM-Frame kopieren, den er sendet, kann aber jeden Anwendungsfehlercode verwenden. Der Endpunkt, der einen STOP_SENDING-Frame sendet, KANN den Fehlercode in allen RESET_STREAM-Frames ignorieren, die anschließend für diesen Stream empfangen werden.

STOP_SENDING SOLLTE nur für einen Stream gesendet werden, der vom Peer nicht zurückgesetzt wurde. STOP_SENDING ist am nützlichsten für Streams im Zustand "Recv" oder "Size Known".

Ein Endpunkt wird erwartet, einen weiteren STOP_SENDING-Frame zu senden, wenn ein Paket, das einen vorherigen STOP_SENDING enthält, verloren geht. Sobald jedoch entweder alle Stream-Daten oder ein RESET_STREAM-Frame für den Stream empfangen wurden – d.h. der Stream ist in jedem Zustand außer "Recv" oder "Size Known" – ist das Senden eines STOP_SENDING-Frames unnötig.

Ein Endpunkt, das beide Richtungen eines bidirektionalen Streams beenden möchte, kann eine Richtung beenden, indem es einen RESET_STREAM-Frame sendet, und es kann eine schnelle Beendigung in der entgegengesetzten Richtung fördern, indem es einen STOP_SENDING-Frame sendet.