9.10. TCP – un flusso affidabile di byte¶
TCP, il Transmission Control Protocol, è l’altro servizio del livello di trasporto che si appoggia su IP. Mentre UDP si può descrivere al meglio come «invia un pacchetto e spera», TCP è «apri una connessione tra due endpoint e trattala come un canale bidirezionale di byte che l’altra parte riceve con certezza, in ordine ed esattamente una volta». La maggior parte del traffico internet lo utilizza, e gran parte di ciò che la camera fa in rete lo usa a sua volta.
9.10.1. Cosa aggiunge TCP a IP¶
TCP fa molto di più di UDP. Aggiunge:
Una connessione. Prima che scorra qualsiasi dato, i due endpoint si scambiano un breve handshake per concordare che stanno comunicando. La connessione ha uno stato – «aperta», «semi-chiusa», «chiusa» – che entrambe le parti tracciano.
Consegna affidabile. Ogni byte che il mittente immette viene confermato dal destinatario. Tutto ciò che non viene confermato entro un timeout viene inviato di nuovo. L’applicazione non vede mai un byte perso; vede un ritardo mentre il protocollo ritrasmette.
Consegna ordinata. I byte arrivano nello stesso ordine in cui sono stati inviati. Anche se i pacchetti arrivano al destinatario fuori ordine, TCP li riordina prima che l’applicazione li legga.
Controllo di flusso. Se il destinatario è lento, al mittente viene detto di rallentare; la connessione si adatta alla velocità dell’estremità più debole.
Controllo della congestione. Se la rete intermedia inizia a scartare pacchetti, il mittente si ritrae finché le cose non si ristabiliscono. Questo evita che una singola connessione metta in ginocchio un collegamento saturo.
Tutto questo è automatico. L’API Python che l’applicazione utilizza è semplicemente send(bytes) e recv(n); TCP gestisce tutto il resto al di sotto.
9.10.2. L’handshake¶
Una connessione TCP si apre con uno scambio a tre vie prima che venga consentito il passaggio di qualsiasi dato:
L’handshake a tre vie. Una volta che entrambe le parti hanno confermato, la connessione è aperta e i dati possono scorrere.¶
Il client invia un pacchetto SYN (synchronise) chiedendo di aprire. Il server risponde con SYN-ACK (synchronise + acknowledge), accettando. Il client invia un ACK finale per confermare. Dopo questo viaggio di andata e ritorno, entrambe le parti concordano che la connessione è aperta e hanno sincronizzato i loro contatori per tracciare quali byte sono stati inviati e ricevuti.
L’handshake costa un tempo di andata e ritorno di latenza prima che passi il primo byte utile. Per le reti locali si tratta di un millisecondo; per le connessioni intercontinentali si tratta di circa 100 ms. Questo è il costo principale di TCP e la ragione per cui i messaggi brevi e una tantum a volte conviene inviarli tramite UDP.
9.10.2.1. L’handshake di chiusura¶
Anche le connessioni TCP si chiudono con uno scambio (un FIN da ciascuna parte). Ciascuna estremità può chiudere la propria metà della connessione in modo indipendente; uno stato semi-chiuso in cui una parte ha finito di inviare ma l’altra sta ancora comunicando è lecito, anche se non comune. L’applicazione normalmente si limita a chiamare close() e lascia che il protocollo gestisca la sequenza di chiusura.
9.10.3. Cosa TCP non garantisce¶
Alcune cose che a volte si presume TCP faccia ma che non fa:
Confini dei messaggi. L’applicazione invia un flusso di byte, non un flusso di messaggi. Due chiamate
send(b"hello")potrebbero arrivare come un’unicarecv()dib"hellohello", oppure come duerecv()di dimensioni variabili. Se l’applicazione vuole un framing dei messaggi, deve aggiungere il framing da sola (un a capo, un prefisso di lunghezza, qualunque cosa). Inviare documenti JSON su TCP, per esempio, richiede che ogni documento sia separato da un a capo o da qualche altro marcatore.Cifratura. TCP trasporta i byte che l’applicazione gli ha dato, in chiaro, per tutto il percorso. Se i contenuti devono essere riservati, l’applicazione deve avvolgere la connessione in TLS (vedi Socket cifrati e TLS).
Autenticazione. TCP si assicura che i byte arrivino intatti. Non dice nulla su chi li ha inviati. Anche l’autenticazione è una questione di livello superiore.
Vitalità su una connessione silenziosa. Se nessuna delle due parti invia dati per molto tempo, la connessione è tecnicamente ancora aperta ma non può rilevare che l’altra estremità è andata in crash o è scomparsa. Sul socket si possono abilitare delle sonde keepalive per risolvere questo problema quando è importante.
9.10.4. Quando usare TCP¶
TCP è la risposta giusta per quasi ogni conversazione che rientra nella forma «il client apre una connessione a un server, si scambiano alcuni byte, la connessione si chiude al termine». Le richieste HTTP e HTTPS, le sessioni SSH, le query ai database, i trasferimenti di file, i caricamenti di immagini – tutto su TCP.
Ricorri a UDP solo quando la conversazione non rientra in quella forma: messaggi indipendenti e autonomi in cui la perdita è accettabile, traffico multicast, sincronizzazione temporale, risoluzione dei nomi o casi estremamente sensibili alla latenza in cui il costo dell’handshake è proibitivo.
Con le porte, UDP e TCP tutti sul tavolo, la storia del livello di trasporto è completa. L’API Python che espone entrambi è nella pagina successiva: Oggetti socket.