Zum Hauptinhalt springen

4. Flusskontrolle

4. Flusskontrolle

Empfänger müssen die Menge der Daten begrenzen, die sie puffern müssen, um zu verhindern, dass ein schneller Sender sie überwältigt oder ein böswilliger Sender eine große Menge an Speicher verbraucht. Um einem Empfänger zu ermöglichen, Speicherverpflichtungen für eine Verbindung zu begrenzen, werden Streams sowohl einzeln als auch für eine Verbindung als Ganzes flusskontrolliert. Ein QUIC-Empfänger kontrolliert die maximale Datenmenge, die der Sender auf einem Stream sowie über alle Streams zu jedem Zeitpunkt senden kann, wie in den Abschnitten 4.1 und 4.2 beschrieben.

Ebenso kontrolliert ein QUIC-Endpunkt die maximale kumulative Anzahl von Streams, die sein Peer initiieren kann, um die Parallelität innerhalb einer Verbindung zu begrenzen, wie in Abschnitt 4.6 beschrieben.

In CRYPTO-Frames gesendete Daten werden nicht auf die gleiche Weise wie Stream-Daten flusskontrolliert. QUIC verlässt sich auf die Implementierung des kryptografischen Protokolls, um übermäßiges Puffern von Daten zu vermeiden; siehe [QUIC-TLS]. Um übermäßiges Puffern auf mehreren Ebenen zu vermeiden, SOLLTEN QUIC-Implementierungen eine Schnittstelle für die Implementierung des kryptografischen Protokolls bereitstellen, um ihre Puffergrenzen zu kommunizieren.

4.1 Datenfluss-Kontrolle

QUIC verwendet ein limitbasiertes Flusskontrollschema, bei dem ein Empfänger das Limit der Gesamtbytes anzeigt, die er auf einem bestimmten Stream oder für die gesamte Verbindung zu empfangen bereit ist. Dies führt zu zwei Ebenen der Datenfluss-Kontrolle in QUIC:

  • Stream-Flusskontrolle, die verhindert, dass ein einzelner Stream den gesamten Empfangspuffer für eine Verbindung verbraucht, indem sie die Datenmenge begrenzt, die auf jedem Stream gesendet werden kann.

  • Verbindungs-Flusskontrolle, die Sender daran hindert, die Pufferkapazität eines Empfängers für die Verbindung zu überschreiten, indem sie die Gesamtbytes der in STREAM-Frames auf allen Streams gesendeten Stream-Daten begrenzt.

Sender DÜRFEN KEINE Daten senden, die eines der beiden Limits überschreiten.

Ein Empfänger setzt initiale Limits für alle Streams durch Transportparameter während des Handshakes (Abschnitt 7.4). Anschließend sendet ein Empfänger MAX_STREAM_DATA-Frames (Abschnitt 19.10) oder MAX_DATA-Frames (Abschnitt 19.9) an den Sender, um größere Limits anzukündigen.

Ein Empfänger kann ein größeres Limit für einen Stream ankündigen, indem er einen MAX_STREAM_DATA-Frame mit der entsprechenden Stream-ID sendet. Ein MAX_STREAM_DATA-Frame gibt den maximalen absoluten Byte-Offset eines Streams an. Ein Empfänger könnte den anzukündigenden Flusskontroll-Offset basierend auf dem aktuellen Offset der auf diesem Stream verbrauchten Daten bestimmen.

Ein Empfänger kann ein größeres Limit für eine Verbindung ankündigen, indem er einen MAX_DATA-Frame sendet, der das Maximum der Summe der absoluten Byte-Offsets aller Streams angibt. Ein Empfänger führt eine kumulative Summe der auf allen Streams empfangenen Bytes, die zur Überprüfung von Verstößen gegen die angekündigten Verbindungs- oder Stream-Datenlimits verwendet wird. Ein Empfänger könnte das anzukündigende maximale Datenlimit basierend auf der Summe der auf allen Streams verbrauchten Bytes bestimmen.

Sobald ein Empfänger ein Limit für die Verbindung oder einen Stream angekündigt hat, ist es kein Fehler, ein kleineres Limit anzukündigen, aber das kleinere Limit hat keine Wirkung.

Ein Empfänger MUSS die Verbindung mit einem Fehler vom Typ FLOW_CONTROL_ERROR schließen, wenn der Sender gegen die angekündigten Verbindungs- oder Stream-Datenlimits verstößt; siehe Abschnitt 11 für Details zur Fehlerbehandlung.

Ein Sender MUSS alle MAX_STREAM_DATA- oder MAX_DATA-Frames ignorieren, die die Flusskontrolllimits nicht erhöhen.

Wenn ein Sender Daten bis zum Limit gesendet hat, kann er keine neuen Daten senden und gilt als blockiert. Ein Sender SOLLTE einen STREAM_DATA_BLOCKED- oder DATA_BLOCKED-Frame senden, um dem Empfänger anzuzeigen, dass er Daten zu schreiben hat, aber durch Flusskontrolllimits blockiert ist. Wenn ein Sender für einen Zeitraum blockiert ist, der länger als das Idle-Timeout ist (Abschnitt 10.1), könnte der Empfänger die Verbindung schließen, selbst wenn der Sender Daten hat, die zur Übertragung verfügbar sind. Um zu verhindern, dass die Verbindung geschlossen wird, SOLLTE ein durch Flusskontrolle begrenzter Sender periodisch einen STREAM_DATA_BLOCKED- oder DATA_BLOCKED-Frame senden, wenn er keine ACK-auslösenden Pakete im Flug hat.

4.2 Erhöhung der Flusskontrolllimits

Implementierungen entscheiden, wann und wie viel Guthaben in MAX_STREAM_DATA- und MAX_DATA-Frames angekündigt werden soll, aber dieser Abschnitt bietet einige Überlegungen.

Um das Blockieren eines Senders zu vermeiden, KANN ein Empfänger einen MAX_STREAM_DATA- oder MAX_DATA-Frame mehrmals innerhalb eines Round-Trips senden oder ihn früh genug senden, um Zeit für den Verlust des Frames und die nachfolgende Wiederherstellung zu ermöglichen.

Kontroll-Frames tragen zum Verbindungs-Overhead bei. Daher ist das häufige Senden von MAX_STREAM_DATA- und MAX_DATA-Frames mit kleinen Änderungen unerwünscht. Andererseits erfordern weniger häufige Aktualisierungen größere Erhöhungen der Limits, um das Blockieren eines Senders zu vermeiden, was größere Ressourcenverpflichtungen beim Empfänger erfordert. Es besteht ein Kompromiss zwischen Ressourcenverpflichtung und Overhead bei der Bestimmung, wie groß ein Limit angekündigt wird.

Ein Empfänger kann einen Autotuning-Mechanismus verwenden, um die Häufigkeit und Menge des angekündigten zusätzlichen Guthabens basierend auf einer Round-Trip-Time-Schätzung und der Rate, mit der die empfangende Anwendung Daten verbraucht, abzustimmen, ähnlich wie bei gängigen TCP-Implementierungen. Als Optimierung könnte ein Endpunkt Frames, die mit der Flusskontrolle zusammenhängen, nur dann senden, wenn andere Frames zu senden sind, um sicherzustellen, dass die Flusskontrolle keine zusätzlichen Pakete verursacht.

Ein blockierter Sender ist nicht verpflichtet, STREAM_DATA_BLOCKED- oder DATA_BLOCKED-Frames zu senden. Daher DARF ein Empfänger NICHT auf einen STREAM_DATA_BLOCKED- oder DATA_BLOCKED-Frame warten, bevor er einen MAX_STREAM_DATA- oder MAX_DATA-Frame sendet; andernfalls könnte der Sender für den Rest der Verbindung blockiert bleiben. Selbst wenn der Sender diese Frames sendet, führt das Warten auf sie dazu, dass der Sender für mindestens einen ganzen Round-Trip blockiert bleibt.

Wenn ein Sender nach einer Blockierung Guthaben erhält, könnte er möglicherweise eine große Menge an Daten als Reaktion senden, was zu kurzfristiger Stauung führt; siehe Abschnitt 7.7 von [QUIC-RECOVERY] für eine Diskussion darüber, wie ein Sender diese Stauung vermeiden kann.

4.3 Flusskontroll-Performance

Wenn ein Endpunkt nicht sicherstellen kann, dass sein Peer immer verfügbares Flusskontrollguthaben hat, das größer ist als das Bandbreite-Verzögerungs-Produkt des Peers auf dieser Verbindung, wird sein Empfangsdurchsatz durch die Flusskontrolle begrenzt.

Paketverluste können Lücken im Empfangspuffer verursachen, die verhindern, dass die Anwendung Daten verbraucht und Empfangspufferplatz freigegeben wird.

Das Senden von zeitnahen Aktualisierungen der Flusskontrolllimits kann die Leistung verbessern. Das Senden von Paketen nur zur Bereitstellung von Flusskontroll-Aktualisierungen kann die Netzwerklast erhöhen und die Leistung negativ beeinflussen. Das Senden von Flusskontroll-Aktualisierungen zusammen mit anderen Frames, wie ACK-Frames, reduziert die Kosten dieser Aktualisierungen.

4.4 Behandlung der Stream-Aufhebung

Endpunkte müssen sich schließlich über die Menge des Flusskontrollguthabens einigen, das auf jedem Stream verbraucht wurde, um alle Bytes für die Flusskontrolle auf Verbindungsebene berücksichtigen zu können.

Beim Empfang eines RESET_STREAM-Frames wird ein Endpunkt den Zustand für den passenden Stream abbauen und weitere auf diesem Stream ankommende Daten ignorieren.

RESET_STREAM beendet eine Richtung eines Streams abrupt. Für einen bidirektionalen Stream hat RESET_STREAM keine Auswirkungen auf den Datenfluss in der entgegengesetzten Richtung. Beide Endpunkte MÜSSEN den Flusskontrollzustand für den Stream in der nicht beendeten Richtung beibehalten, bis diese Richtung in einen Endzustand eintritt.

4.5 Stream-Endgröße

Die Endgröße ist die Menge an Flusskontrollguthaben, die von einem Stream verbraucht wird. Unter der Annahme, dass jedes zusammenhängende Byte auf dem Stream einmal gesendet wurde, ist die Endgröße die Anzahl der gesendeten Bytes. Allgemeiner ist dies eins höher als der Offset des Bytes mit dem größten auf dem Stream gesendeten Offset, oder Null, wenn keine Bytes gesendet wurden.

Ein Sender kommuniziert die Endgröße eines Streams immer zuverlässig an den Empfänger, unabhängig davon, wie der Stream beendet wird. Die Endgröße ist die Summe der Offset- und Length-Felder eines STREAM-Frames mit einem FIN-Flag, wobei zu beachten ist, dass diese Felder implizit sein können. Alternativ trägt das Final Size-Feld eines RESET_STREAM-Frames diesen Wert. Dies garantiert, dass beide Endpunkte über die vom Sender auf diesem Stream verbrauchte Flusskontrollguthabenmenge übereinstimmen.

Ein Endpunkt kennt die Endgröße für einen Stream, wenn der empfangende Teil des Streams in den Zustand "Size Known" oder "Reset Recvd" eintritt (Abschnitt 3). Der Empfänger MUSS die Endgröße des Streams verwenden, um alle auf dem Stream gesendeten Bytes in seinem Verbindungsebenen-Flusskontroller zu berücksichtigen.

Ein Endpunkt DARF KEINE Daten auf einem Stream an oder jenseits der Endgröße senden.

Sobald eine Endgröße für einen Stream bekannt ist, kann sie sich nicht ändern. Wenn ein RESET_STREAM- oder STREAM-Frame empfangen wird, der eine Änderung der Endgröße für den Stream anzeigt, SOLLTE ein Endpunkt mit einem Fehler vom Typ FINAL_SIZE_ERROR antworten; siehe Abschnitt 11 für Details zur Fehlerbehandlung. Ein Empfänger SOLLTE den Empfang von Daten an oder jenseits der Endgröße als Fehler vom Typ FINAL_SIZE_ERROR behandeln, selbst nachdem ein Stream geschlossen ist. Das Erzeugen dieser Fehler ist nicht obligatorisch, da die Anforderung, dass ein Endpunkt diese Fehler erzeugt, auch bedeutet, dass das Endpunkt den Endgrößenzustand für geschlossene Streams aufrechterhalten muss, was eine erhebliche Zustandsverpflichtung bedeuten könnte.

4.6 Kontrolle der Parallelität

Ein Endpunkt begrenzt die kumulative Anzahl eingehender Streams, die ein Peer öffnen kann. Nur Streams mit einer Stream-ID kleiner als (max_streams * 4 + first_stream_id_of_type) können geöffnet werden; siehe Tabelle 1. Initiale Limits werden in den Transportparametern gesetzt; siehe Abschnitt 18.2. Nachfolgende Limits werden mit MAX_STREAMS-Frames angekündigt; siehe Abschnitt 19.11. Separate Limits gelten für unidirektionale und bidirektionale Streams.

Wenn ein max_streams-Transportparameter oder ein MAX_STREAMS-Frame mit einem Wert größer als 2^60 empfangen wird, würde dies eine maximale Stream-ID ermöglichen, die nicht als Integer variabler Länge ausgedrückt werden kann; siehe Abschnitt 16. Wenn eines von beiden empfangen wird, MUSS die Verbindung sofort mit einem Verbindungsfehler vom Typ TRANSPORT_PARAMETER_ERROR geschlossen werden, wenn der beleidigende Wert in einem Transportparameter empfangen wurde, oder vom Typ FRAME_ENCODING_ERROR, wenn er in einem Frame empfangen wurde; siehe Abschnitt 10.2.

Endpunkte DÜRFEN das von ihrem Peer gesetzte Limit NICHT überschreiten. Ein Endpunkt, das einen Frame mit einer Stream-ID erhält, die das von ihm gesendete Limit überschreitet, MUSS dies als Verbindungsfehler vom Typ STREAM_LIMIT_ERROR behandeln; siehe Abschnitt 11 für Details zur Fehlerbehandlung.

Sobald ein Empfänger ein Stream-Limit mit dem MAX_STREAMS-Frame angekündigt hat, hat das Ankündigen eines kleineren Limits keine Wirkung. MAX_STREAMS-Frames, die das Stream-Limit nicht erhöhen, MÜSSEN ignoriert werden.

Wie bei Stream- und Verbindungs-Flusskontrolle überlässt dieses Dokument es den Implementierungen zu entscheiden, wann und wie viele Streams einem Peer über MAX_STREAMS angekündigt werden sollen. Implementierungen könnten sich entscheiden, Limits zu erhöhen, wenn Streams geschlossen werden, um die Anzahl der für Peers verfügbaren Streams ungefähr konstant zu halten.

Ein Endpunkt, das aufgrund der Limits des Peers keinen neuen Stream öffnen kann, SOLLTE einen STREAMS_BLOCKED-Frame senden (Abschnitt 19.14). Dieses Signal wird für das Debugging als nützlich angesehen. Ein Endpunkt DARF NICHT darauf warten, dieses Signal zu empfangen, bevor es zusätzliches Guthaben ankündigt, da dies bedeuten würde, dass der Peer für mindestens einen ganzen Round-Trip und möglicherweise unbegrenzt blockiert würde, wenn der Peer sich entscheidet, keine STREAMS_BLOCKED-Frames zu senden.