Passa al contenuto principale

2. Streams (Flussi)

I flussi in QUIC forniscono un'astrazione di flusso di byte ordinata e leggera (Ordered Byte-Stream Abstraction) a un'applicazione. I flussi possono essere unidirezionali (Unidirectional) o bidirezionali (Bidirectional).

I flussi possono essere creati inviando dati. Altri processi associati alla gestione dei flussi — terminazione, cancellazione e gestione del controllo di flusso — sono tutti progettati per imporre un overhead minimo. Ad esempio, un singolo frame STREAM (Sezione 19.8) può aprire, trasportare dati per e chiudere un flusso. I flussi possono anche avere una lunga durata e possono durare l'intera durata di una connessione.

I flussi possono essere creati da entrambi gli endpoint, possono inviare simultaneamente dati intercalati con altri flussi e possono essere annullati. QUIC non fornisce alcun mezzo per garantire l'ordinamento tra i byte su flussi diversi.

QUIC consente a un numero arbitrario di flussi di operare contemporaneamente e a una quantità arbitraria di dati di essere inviata su qualsiasi flusso, soggetto ai vincoli di controllo di flusso e ai limiti di flusso; vedere la Sezione 4.


2.1. Stream Types and Identifiers (Tipi di flussi e identificatori)

I flussi possono essere unidirezionali o bidirezionali. I flussi unidirezionali trasportano dati in una direzione: dall'iniziatore del flusso al suo peer. I flussi bidirezionali consentono l'invio di dati in entrambe le direzioni.

I flussi sono identificati all'interno di una connessione da un valore numerico, denominato identificatore di flusso (Stream ID). Un identificatore di flusso è un intero a 62 bit (da 0 a 2^62-1) che è unico per tutti i flussi su una connessione. Gli identificatori di flusso sono codificati come interi a lunghezza variabile; vedere la Sezione 16. Un endpoint QUIC non deve (MUST NOT) riutilizzare un identificatore di flusso all'interno di una connessione.

Regole di codifica degli identificatori di flusso

Il bit meno significativo (0x01) dell'identificatore di flusso identifica l'iniziatore del flusso:

  • Flussi iniziati dal client: identificatori di flusso pari (bit impostato a 0)
  • Flussi iniziati dal server: identificatori di flusso dispari (bit impostato a 1)

Il secondo bit meno significativo (0x02) dell'identificatore di flusso distingue:

  • Flussi bidirezionali: bit impostato a 0
  • Flussi unidirezionali: bit impostato a 1

Tabella dei tipi di flusso

Pertanto, i due bit meno significativi di un identificatore di flusso identificano un flusso come uno dei quattro tipi, come riassunto nella Tabella 1:

Valore dei bitTipo di flusso
0x00Flusso bidirezionale iniziato dal client (Client-Initiated, Bidirectional)
0x01Flusso bidirezionale iniziato dal server (Server-Initiated, Bidirectional)
0x02Flusso unidirezionale iniziato dal client (Client-Initiated, Unidirectional)
0x03Flusso unidirezionale iniziato dal server (Server-Initiated, Unidirectional)

Tabella 1: Tipi di identificatori di flusso

Regole di allocazione degli identificatori di flusso

Lo spazio dei flussi per ciascun tipo inizia dal valore minimo (rispettivamente da 0x00 a 0x03); i flussi successivi di ciascun tipo sono creati con identificatori di flusso numericamente crescenti. Un identificatore di flusso utilizzato fuori ordine comporta l'apertura anche di tutti i flussi di quel tipo con identificatori di flusso con numerazione inferiore.


2.2. Sending and Receiving Data (Invio e ricezione di dati)

I frame STREAM (Sezione 19.8) incapsulano i dati inviati da un'applicazione. Un endpoint utilizza i campi Stream ID e Offset nei frame STREAM per posizionare i dati in ordine.

Gli endpoint devono (MUST) essere in grado di consegnare i dati del flusso a un'applicazione come flusso di byte ordinato. La consegna di un flusso di byte ordinato richiede che un endpoint memorizzi nel buffer tutti i dati ricevuti fuori ordine, fino al limite di controllo di flusso annunciato.

Opzioni di consegna dei dati

QUIC non prevede disposizioni specifiche per la consegna di dati di flusso fuori ordine. Tuttavia, le implementazioni possono (MAY) scegliere di offrire la possibilità di consegnare dati fuori ordine a un'applicazione ricevente.

Duplicazione e coerenza dei dati

Un endpoint potrebbe ricevere dati per un flusso allo stesso offset di flusso più volte. I dati che sono già stati ricevuti possono essere scartati. I dati a un dato offset non devono (MUST NOT) cambiare se vengono inviati più volte; un endpoint può (MAY) trattare la ricezione di dati diversi allo stesso offset all'interno di un flusso come un errore di connessione di tipo PROTOCOL_VIOLATION.

Confini dei frame

I flussi sono un'astrazione di flusso di byte ordinato senza altra struttura visibile a QUIC. I confini dei frame STREAM non dovrebbero essere preservati quando i dati vengono trasmessi, ritrasmessi dopo la perdita di pacchetti o consegnati all'applicazione presso un ricevitore.

Controllo di flusso

Un endpoint non deve (MUST NOT) inviare dati su alcun flusso senza garantire che siano entro i limiti di controllo di flusso impostati dal suo peer. Il controllo di flusso è descritto in dettaglio nella Sezione 4.


2.3. Stream Prioritization (Prioritizzazione dei flussi)

Il multiplexing dei flussi può avere un effetto significativo sulle prestazioni dell'applicazione se le risorse allocate ai flussi sono correttamente prioritizzate.

QUIC non fornisce un meccanismo per scambiare informazioni di prioritizzazione. Invece, si basa sulla ricezione di informazioni sulla priorità dall'applicazione.

Un'implementazione QUIC dovrebbe (SHOULD) fornire modi in cui un'applicazione può indicare la priorità relativa dei flussi. Un'implementazione utilizza le informazioni fornite dall'applicazione per determinare come allocare risorse ai flussi attivi.

Spiegazione del meccanismo di prioritizzazione

Differenza con HTTP/2:

  • HTTP/2 definisce un albero di priorità (Priority Tree) a livello di protocollo
  • QUIC delega la decisione di priorità al livello applicazione
  • Questo offre maggiore flessibilità, ma richiede che il protocollo applicativo progetti il proprio schema di priorità

2.4. Operations on Streams (Operazioni sui flussi)

Questo documento non definisce un'API per QUIC; definisce invece un insieme di funzioni sui flussi su cui i protocolli applicativi possono fare affidamento. Un protocollo applicativo può presumere che un'implementazione QUIC fornisca un'interfaccia che include le operazioni descritte in questa sezione. Un'implementazione progettata per l'uso con un protocollo applicativo specifico potrebbe fornire solo quelle operazioni utilizzate da quel protocollo.

Operazioni lato invio

Sul lato invio di un flusso, un protocollo applicativo può:

Scrivere dati (Write Data)
Comprendere quando il credito di controllo di flusso del flusso (Sezione 4.1) è stato riservato con successo per inviare i dati scritti

Terminare il flusso (End Stream) - Terminazione pulita
Risultando in un frame STREAM (Sezione 19.8) con il bit FIN impostato

Reimpostare il flusso (Reset Stream) - Terminazione brusca
Risultando in un frame RESET_STREAM (Sezione 19.4) se il flusso non era già in uno stato terminale

Operazioni lato ricezione

Sul lato ricezione di un flusso, un protocollo applicativo può:

Leggere dati (Read Data)
Leggere i dati ricevuti dal flusso

Interrompere la lettura (Abort Reading)
Interrompere la lettura del flusso e richiedere la chiusura, eventualmente risultando in un frame STOP_SENDING (Sezione 19.5)

Notifica dello stato

Un protocollo applicativo può anche richiedere di essere informato dei cambiamenti di stato sui flussi, inclusi:

  • Quando il peer ha aperto o reimpostato un flusso
  • Quando un peer interrompe la lettura su un flusso
  • Quando nuovi dati sono disponibili
  • Quando i dati possono o non possono essere scritti nel flusso a causa del controllo di flusso

💡 Punti chiave del meccanismo dei flussi

I quattro tipi di flusso

Iniziato dal client     Iniziato dal server
↓ ↓
┌──────────────┐ ┌──────────────┐
│ Bidirezionale│ (ID: 0,4,8..)│ Bidirezionale│ (ID: 1,5,9..)
│ 0x00 │ │ 0x01 │
└──────────────┘ └──────────────┘

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

Ciclo di vita del flusso

Creazione → Invio dati → Terminazione/Reset → Chiusura
↑ ↓
└─ Controllo di flusso ←┘

Caratteristiche principali

  1. Leggero: Un singolo frame può aprire, trasferire e chiudere un flusso
  2. Concorrenza: Un numero arbitrario di flussi può essere attivo contemporaneamente
  3. Indipendenza: Nessuna garanzia di ordine tra i flussi, evita il blocco head-of-line
  4. Flessibilità: Può avere una lunga durata o essere di breve durata

Confronto con TCP

CaratteristicaFlussi QUICTCP
Multiplexing✅ Supporto nativo multi-flusso❌ Flusso di byte singolo
Blocco head-of-line✅ Indipendenza a livello di flusso❌ Blocco globale
Overhead di creazione flussoMolto bassoN/A
Controllo prioritàDeciso dal livello applicazioneNessuno

Capitolo successivo: 3. Stream States (Stati dei flussi) - Modello dettagliato della macchina a stati dei flussi