TCP: controllo del flusso

// obiettivi di apprendimento
Spiegare il problema del trasmettitore veloce vs ricevitore lento e la sua conseguenza sull’overflow del buffer
Descrivere il meccanismo della Receive Window (rwnd) e la regola del mittente
Comprendere il comportamento in caso di Zero Window e il meccanismo dei Window Probe
Analizzare la Silly Window Syndrome e le soluzioni: algoritmo di Nagle e Clark’s Solution
📄
Slides
Diagrammi rwnd, Zero Window, Nagle
Scarica →

Il problema: trasmettitore veloce, ricevitore lento

Immagina un server che trasferisce dati a 1 Gbps verso un client che ha un processore lento e un’applicazione che legge i dati dal buffer ogni 100 ms. Il buffer del ricevitore si riempie rapidamente. Se il mittente continua a inviare senza sapere quanto spazio è disponibile, i dati in eccesso vengono scartati e devono essere ritrasmessi — spreco di banda e aumento della latenza.

Il controllo del flusso è il meccanismo che impone al mittente di non superare la capacità di ricezione dell’altro endpoint. Diverso dal controllo della congestione (che riguarda la rete): qui si tratta di proteggere il buffer del ricevitore.

// definizione formale
Il controllo del flusso TCP è un meccanismo end-to-end che protegge il buffer del ricevitore dall’overflow, adattando la velocità di trasmissione del mittente alla capacità di elaborazione dell’applicazione ricevente tramite il campo Window Size dell’header TCP.

Receive Window (rwnd)

Il campo Window Size nell’header TCP (16 bit, estendibile con Window Scale option fino a 1 GB) comunica al mittente quanti byte può ancora inviare senza ricevere un ACK:

rwnd = spazio_buffer_ricevitore_libero

Regola del mittente:
  LastByteSent − LastByteAcked ≤ rwnd

Dove:
  LastByteSent  = ultimo byte inviato
  LastByteAcked = ultimo byte riscontrato con ACK
  La differenza è il numero di byte "in volo" (inviati ma non ancora confermati)

Il ricevitore aggiorna rwnd in ogni ACK che invia. Man mano che l’applicazione legge i dati dal buffer, il buffer si libera e rwnd aumenta — il mittente può inviare di più. Se l’applicazione è lenta a consumare i dati, rwnd si riduce — il mittente rallenta automaticamente.

Buffer ricevitore: [████████████░░░░░░░░]
                   ↑ dati non letti    ↑ spazio libero = rwnd

Dopo che l'applicazione legge:
Buffer ricevitore: [████░░░░░░░░░░░░░░░░]
                                       ↑ rwnd aumenta → ACK con window più grande

Window Scale Option

Il campo Window Size è 16 bit → massimo 65535 byte. Per reti ad alta latenza e banda elevata (es. fibra intercontinentale, satellitare) questo è insufficiente. L’opzione Window Scale (RFC 7323) aggiunge un moltiplicatore (da 0 a 14): rwnd_effettiva = rwnd × 2^shift_count. Viene negoziata durante il three-way handshake.

Zero Window e Window Probe

Quando il buffer del ricevitore è completamente pieno, il ricevitore invia un ACK con rwnd = 0: Zero Window. Il mittente si ferma — non può inviare dati.

// problema — deadlock potenziale

Dopo Zero Window, il mittente attende. Quando il buffer si libera, il ricevitore invia un Window Update con rwnd > 0. Ma se questo update viene perso (IP è best-effort!), il mittente aspetta per sempre e il ricevitore aspetta dati che non arrivano: deadlock.

La soluzione è il Window Probe: il mittente avvia un timer (Persist Timer). Alla scadenza, invia un segmento da 1 byte per “stimolare” il ricevitore a inviare un ACK aggiornato con il valore corrente di rwnd. Il Persist Timer usa backoff esponenziale per evitare di inondare la rete.

Zero Window timeline:

Ricevitore: rwnd=0 → invia ACK(rwnd=0)
Mittente:   si ferma, avvia Persist Timer
[Persist Timer scade]
Mittente:   invia Window Probe (1 byte)
Ricevitore: buffer ancora pieno → ACK(rwnd=0)
[Persist Timer scade, backoff]
Mittente:   invia Window Probe (1 byte)
Ricevitore: buffer liberato → ACK(rwnd=4096)  ← mittente riprende

Silly Window Syndrome

Un altro problema sorge quando mittente o ricevitore lavorano con porzioni di dati molto piccole — ad esempio un’applicazione che scrive 1 byte alla volta nel socket TCP. Inviare segmenti TCP con 1 byte di payload è estremamente inefficiente: header TCP (20B) + IP (20B) = 40 byte di overhead per 1 byte di dato. Efficienza = 1/41 ≈ 2.4%.

Algoritmo di Nagle (RFC 896) — lato mittente

// algoritmo di Nagle
if dati_disponibili ≥ MSS:
    invia subito un segmento MSS-sized
else if tutti_gli_ACK_precedenti_ricevuti:
    invia subito (nessun segmento in volo)
else:
    accumula i dati nel buffer, attendi ACK
    (invia solo quando hai MSS byte o ACK arriva)

In pratica: il primo segmento parte subito, quelli successivi vengono aggregati finché o si raggiunge MSS o arriva l’ACK del segmento precedente. Riduce drasticamente il numero di piccoli segmenti.

// quando disabilitare Nagle

Le applicazioni interattive a bassa latenza (SSH, terminali remoti, giochi online, tastiere su rete) devono inviare ogni keystroke immediatamente. L’algoritmo di Nagle introdurrebbe un ritardo inaccettabile. Si disabilita con il socket option TCP_NODELAY (setsockopt). Nginx, Redis e la maggior parte dei server ad alte performance usano TCP_NODELAY.

Clark’s Solution — lato ricevitore

Il problema può emergere anche dal lato ricevitore: se il buffer si libera di soli pochi byte, il ricevitore potrebbe annunciare subito una piccola window — e il mittente invierebbe un piccolo segmento. Clark’s Solution: il ricevitore non aggiorna la window finché il buffer libero non raggiunge almeno min(MSS, metà del buffer totale). Questo evita di indurre il mittente a inviare piccoli segmenti.

ProblemaLatoSoluzione
Applicazione mittente scrive dati piccoliMittenteAlgoritmo di Nagle (RFC 896)
Applicazione ricevente legge dati piccoli (buffer quasi pieno)RicevitoreClark’s Solution (ritarda window update)
📌 Riepilogo — Punti chiave
  • Il controllo del flusso protegge il buffer del ricevitore (non la rete — quello è controllo della congestione)
  • rwnd: campo Window Size nell’header TCP — indica quanti byte il mittente può ancora inviare. Regola: LastByteSent − LastByteAcked ≤ rwnd
  • Zero Window: il mittente si ferma. Persist Timer + Window Probe evitano il deadlock se il Window Update viene perso
  • Silly Window Syndrome: segmenti piccoli con overhead enorme — inefficiente
  • Nagle: aggrega segmenti piccoli lato mittente. Si disabilita con TCP_NODELAY per app interattive
  • Clark’s Solution: il ricevitore ritarda l’aggiornamento della window finché non c’è spazio sufficiente

Lascia un commento