Zum Hauptinhalt springen

2. Streams (Datenströme)

Streams in QUIC bieten einer Anwendung eine leichtgewichtige, geordnete Bytestrom-Abstraktion (Ordered Byte-Stream Abstraction). Streams können unidirektional (Unidirectional) oder bidirektional (Bidirectional) sein.

Streams können durch Senden von Daten erstellt werden. Andere mit der Stream-Verwaltung verbundene Prozesse — Beenden, Abbrechen und Verwalten der Flusskontrolle — sind alle so konzipiert, dass sie minimalen Overhead verursachen. Beispielsweise kann ein einzelnes STREAM-Frame (Abschnitt 19.8) einen Stream öffnen, Daten dafür tragen und schließen. Streams können auch langlebig sein und die gesamte Dauer einer Verbindung bestehen.

Streams können von beiden Endpunkten erstellt werden, können gleichzeitig Daten verschachtelt mit anderen Streams senden und können abgebrochen werden. QUIC bietet keine Mittel zur Sicherstellung der Reihenfolge zwischen Bytes auf verschiedenen Streams.

QUIC erlaubt eine beliebige Anzahl von Streams, die gleichzeitig betrieben werden, und eine beliebige Menge an Daten, die auf jedem Stream gesendet werden, vorbehaltlich Flusskontrollbeschränkungen und Stream-Limits; siehe Abschnitt 4.


2.1. Stream Types and Identifiers (Stream-Typen und Identifikatoren)

Streams können unidirektional oder bidirektional sein. Unidirektionale Streams tragen Daten in eine Richtung: vom Initiator des Streams zu seinem Peer. Bidirektionale Streams ermöglichen das Senden von Daten in beide Richtungen.

Streams werden innerhalb einer Verbindung durch einen numerischen Wert identifiziert, der als Stream-ID (Stream ID) bezeichnet wird. Eine Stream-ID ist eine 62-Bit-Ganzzahl (0 bis 2^62-1), die für alle Streams auf einer Verbindung eindeutig ist. Stream-IDs werden als Ganzzahlen variabler Länge kodiert; siehe Abschnitt 16. Ein QUIC-Endpunkt darf (MUST NOT) eine Stream-ID innerhalb einer Verbindung nicht wiederverwenden.

Stream-ID-Kodierungsregeln

Das niedrigstwertige Bit (0x01) der Stream-ID identifiziert den Initiator des Streams:

  • Client-initiierte Streams: Stream-IDs sind gerade (Bit auf 0 gesetzt)
  • Server-initiierte Streams: Stream-IDs sind ungerade (Bit auf 1 gesetzt)

Das zweitniedrigstwertige Bit (0x02) der Stream-ID unterscheidet:

  • Bidirektionale Streams: Bit auf 0 gesetzt
  • Unidirektionale Streams: Bit auf 1 gesetzt

Stream-Typen-Tabelle

Daher identifizieren die zwei niedrigstwertigen Bits einer Stream-ID einen Stream als einen von vier Typen, wie in Tabelle 1 zusammengefasst:

BitwerteStream-Typ
0x00Client-initiiert, Bidirektional (Client-Initiated, Bidirectional)
0x01Server-initiiert, Bidirektional (Server-Initiated, Bidirectional)
0x02Client-initiiert, Unidirektional (Client-Initiated, Unidirectional)
0x03Server-initiiert, Unidirektional (Server-Initiated, Unidirectional)

Tabelle 1: Stream-ID-Typen

Stream-ID-Zuweisungsregeln

Der Stream-Raum für jeden Typ beginnt beim Mindestwert (0x00 bis 0x03, jeweils); aufeinanderfolgende Streams jedes Typs werden mit numerisch steigenden Stream-IDs erstellt. Eine Stream-ID, die außerhalb der Reihenfolge verwendet wird, führt dazu, dass auch alle Streams dieses Typs mit niedrigeren Stream-IDs geöffnet werden.


2.2. Sending and Receiving Data (Senden und Empfangen von Daten)

STREAM-Frames (Abschnitt 19.8) kapseln von einer Anwendung gesendete Daten. Ein Endpunkt verwendet die Stream-ID- und Offset-Felder in STREAM-Frames, um Daten in Ordnung zu bringen.

Endpunkte müssen (MUST) in der Lage sein, Stream-Daten als geordneten Bytestrom an eine Anwendung zu liefern. Die Bereitstellung eines geordneten Bytestroms erfordert, dass ein Endpunkt alle außerhalb der Reihenfolge empfangenen Daten puffert, bis zur angekündigten Flusskontrollgrenze.

Datenbereitstellungsoptionen

QUIC macht keine spezifischen Vorkehrungen für die Bereitstellung von Stream-Daten außerhalb der Reihenfolge. Implementierungen können (MAY) jedoch wählen, die Möglichkeit anzubieten, Daten außerhalb der Reihenfolge an eine empfangende Anwendung zu liefern.

Datenduplikation und -konsistenz

Ein Endpunkt kann Daten für einen Stream am selben Stream-Offset mehrfach empfangen. Bereits empfangene Daten können verworfen werden. Daten an einem gegebenen Offset dürfen (MUST NOT) sich nicht ändern, wenn sie mehrfach gesendet werden; ein Endpunkt kann (MAY) den Empfang unterschiedlicher Daten am selben Offset innerhalb eines Streams als Verbindungsfehler vom Typ PROTOCOL_VIOLATION behandeln.

Frame-Grenzen

Streams sind eine geordnete Bytestrom-Abstraktion ohne andere für QUIC sichtbare Struktur. STREAM-Frame-Grenzen werden voraussichtlich nicht beibehalten, wenn Daten übertragen, nach Paketverlust erneut übertragen oder an die Anwendung beim Empfänger geliefert werden.

Flusskontrolle

Ein Endpunkt darf (MUST NOT) keine Daten auf einem Stream senden, ohne sicherzustellen, dass sie sich innerhalb der von seinem Peer festgelegten Flusskontrollgrenzen befinden. Flusskontrolle wird ausführlich in Abschnitt 4 beschrieben.


2.3. Stream Prioritization (Stream-Priorisierung)

Stream-Multiplexing kann einen signifikanten Einfluss auf die Anwendungsleistung haben, wenn die den Streams zugewiesenen Ressourcen korrekt priorisiert werden.

QUIC bietet keinen Mechanismus zum Austausch von Priorisierungsinformationen. Stattdessen verlässt es sich darauf, Prioritätsinformationen von der Anwendung zu erhalten.

Eine QUIC-Implementierung sollte (SHOULD) Möglichkeiten bieten, mit denen eine Anwendung die relative Priorität von Streams angeben kann. Eine Implementierung verwendet von der Anwendung bereitgestellte Informationen, um zu bestimmen, wie Ressourcen aktiven Streams zugewiesen werden.

Erklärung des Priorisierungsmechanismus

Unterschied zu HTTP/2:

  • HTTP/2 definiert einen Prioritätsbaum (Priority Tree) auf Protokollebene
  • QUIC delegiert die Prioritätsentscheidung an die Anwendungsschicht
  • Dies bietet mehr Flexibilität, erfordert aber, dass das Anwendungsprotokoll sein eigenes Prioritätsschema entwirft

2.4. Operations on Streams (Operationen auf Streams)

Dieses Dokument definiert keine API für QUIC; stattdessen definiert es eine Reihe von Funktionen auf Streams, auf die sich Anwendungsprotokolle verlassen können. Ein Anwendungsprotokoll kann davon ausgehen, dass eine QUIC-Implementierung eine Schnittstelle bereitstellt, die die in diesem Abschnitt beschriebenen Operationen umfasst. Eine für die Verwendung mit einem bestimmten Anwendungsprotokoll entworfene Implementierung kann nur die von diesem Protokoll verwendeten Operationen bereitstellen.

Sendeseitige Operationen

Auf der Sendeseite eines Streams kann ein Anwendungsprotokoll:

Daten schreiben (Write Data)
Verstehen, wann Stream-Flusskontrollkredit (Abschnitt 4.1) erfolgreich reserviert wurde, um die geschriebenen Daten zu senden

Stream beenden (End Stream) - Saubere Beendigung
Führt zu einem STREAM-Frame (Abschnitt 19.8) mit gesetztem FIN-Bit

Stream zurücksetzen (Reset Stream) - Abrupte Beendigung
Führt zu einem RESET_STREAM-Frame (Abschnitt 19.4), wenn der Stream noch nicht in einem Endzustand war

Empfangsseitige Operationen

Auf der Empfangsseite eines Streams kann ein Anwendungsprotokoll:

Daten lesen (Read Data)
Empfangene Daten aus dem Stream lesen

Lesen abbrechen (Abort Reading)
Lesen des Streams abbrechen und Schließung anfordern, was möglicherweise zu einem STOP_SENDING-Frame (Abschnitt 19.5) führt

Zustandsbenachrichtigung

Ein Anwendungsprotokoll kann auch anfordern, über Zustandsänderungen bei Streams informiert zu werden, einschließlich:

  • Wann der Peer einen Stream geöffnet oder zurückgesetzt hat
  • Wann ein Peer das Lesen auf einem Stream abbricht
  • Wann neue Daten verfügbar sind
  • Wann Daten aufgrund von Flusskontrolle in den Stream geschrieben werden können oder nicht

💡 Kernpunkte des Stream-Mechanismus

Die vier Stream-Typen

Client-initiiert           Server-initiiert
↓ ↓
┌──────────────┐ ┌──────────────┐
│ Bidirektional│ (ID: 0,4,8..)│ Bidirektional│ (ID: 1,5,9..)
│ 0x00 │ │ 0x01 │
└──────────────┘ └──────────────┘

┌──────────────┐ ┌──────────────┐
│Unidirektional│ (ID: 2,6,10..)│Unidirektional│ (ID: 3,7,11..)
│ 0x02 │ │ 0x03 │
└──────────────┘ └──────────────┘

Stream-Lebenszyklus

Erstellung → Daten senden → Beendigung/Reset → Schließung
↑ ↓
└─ Flusskontrolle ←┘

Hauptmerkmale

  1. Leichtgewichtig: Ein einzelnes Frame kann einen Stream öffnen, übertragen und schließen
  2. Parallelität: Beliebig viele Streams können gleichzeitig aktiv sein
  3. Unabhängigkeit: Keine Reihenfolgengarantie zwischen Streams, vermeidet Head-of-Line Blocking
  4. Flexibilität: Kann langlebig sein oder kurzfristig genutzt werden

Vergleich mit TCP

MerkmalQUIC-StreamsTCP
Multiplexing✅ Native Multi-Stream-Unterstützung❌ Einzelner Bytestrom
Head-of-Line Blocking✅ Stream-Ebene unabhängig❌ Globales Blocking
Stream-ErstellungsoverheadSehr geringN/A
PrioritätskontrolleVon Anwendungsschicht entschiedenKeine

Nächstes Kapitel: 3. Stream States (Stream-Zustände) - Detailliertes Stream-Zustandsmaschinenmodell