Bibliografia:
Computer Networking. Cap. 3. Autori: James F. Kurose (University of Massachusetts, Amherst) Keith W. Ross (Polytechnic Institute of NYU).
Il problema dell'affidabilità riguarda il livello Trasporto, il livello Collegamento e il livello Applicazione.
![]() | ![]() | |
L'astrazione fornita alle entità dei livelli superiori è quella di un canale affidabile attraverso il quale possono essere trasferiti i dati. Su un canale affidabile i bit dei dati non vengono alterati o perduti, e vengono consegnati nell'ordine in cui sono partiti. Questo è esattamente il modello di servizio offerto dal TCP alle applicazioni Internet. |
È compito di un protocollo di trasferimento affidabile realizzare il servizio astratto descritto. Il compito è particolarmente difficile perchè il livello rete sottostante non è affidabile. Ad esempio, il TCP è un protocollo di trasferimento dati affidabile che è implementato sul livello rete (IP) che non è affidabile. Il livello rete, su cui si appoggia il protocollo di trasporto, potrebbe essere costituito da un unico canale o da una rete. |
In questa sezione si svilupperà il lato mittente e il lato ricevitore di un protocollo di trasferimento affidabile, perfezionando sempre di più il modello del canale sottostante. Dapprima, si ipotizza che il canale sottostante può modificare i bit o perdere un intero pacchetto. Si assume che i pacchetti vengano consegnati nell'ordine in cui partono, nonostante qualcuno possa essere perso. In altri termini, il canale non riordina i pacchetti.
La figura illustra le interfacce del protocollo di trasferimento dei dati.
Il trasmettitore crea un oggetto send:
PrintWriter send = new PrintWriter(socket.getOutputStream(), true);
Il ricevitore crea un oggetto rcv:
BufferedReader rcv = new BufferedReader(new InputStreamReader(socket.getInputStream()));
Il livello applicazione del trasmettitore usa l'oggetto send per inviare i dati:
send.println("Hello.");
Il livello applicazione del ricevitore usa l'oggetto rcv per prelevare i dati:
String dati = rcv.readLine();
In un modello di comunicazione, dal lato del mittente, il servizio di trasferimento verrà richiamato con la funzione send.println(), passando come parametro i dati che dovranno essere consegnati al livello applicazione del ricevitore. Al lato del ricevitore, quando giunge un pacchetto, verrà richiamata la funzione rcv(). Quando il protocollo deve consegnare i dati al livello superiore richiamerà la funzione deliver_data().
Oltre a scambiare pacchetti di dati, il ricevitore e il trasmettitore si scambiano pacchetti di controllo.
Versione 1
Si consideri dapprima il caso del canale perfettamente affidabile. Il comportamento del trasmettitore e del ricevitore può essere descritto da una macchina a stati finiti.
![]() |
Trasmettitore: Ricevitore |
Dal lato del mittente, quando si verifica l'evento send(data), il protocollo accetta dati dal livello superiore, mette i dati in un pacchetto tramite l'azione make_pkt(packet, data) e invia il pacchetto sul canale.
Dal lato del ricevitore, il protocollo acquisisce un pacchetto dal canale sottostante, tramite il gestore dell'evento rcv(packet), estrae i dati dal pacchetto tramite l'azione extract(packet, data) e consegna i dati al livello superiore.
Su un canale perfettamente affidabile non è richiesto che il ricevitore confermi la ricezione corretta di un pacchetto, perchè nessun pacchetto viene perso o modificato. Inoltre il ricevitore è in grado di acquisire pacchetti alla stessa velocità con cui vengono generati dal trasmettitore. In questo modello, il ricevitore non ha bisogno di chiedere al trasmettitore di ridurre la velocità.
Versione 2
Un modello più realistico del canale utilizzato dalle applicazioni ammette che alcuni bit nel pacchetto possano subire delle modifiche. Questi errori possono avvenire nei componenti della rete mentre il pacchetto viene trasmesso, propagato o memorizzato. Per il momento si supponga che i pacchetti trasmessi vengano ricevuti nello stesso ordine in cui sono stati inviati (nonostante, alcuni bit possano subire errori). Allo scopo di progettare un protocollo per una comunicazione affidabile su un tale canale, si consideri il modo in cui si svolge il dialogo tra due persone in un ambiente rumoroso. Questo è caratterizzato da affermazioni del tipo "Ok" dopo che una frase è stata compresa correttamente o dalla richiesta del tipo: "Puoi ripetere?" quando non si comprende la frase. Questo protocollo usa le conferme positive ("OK"') e le conferme negative ("Puoi ripetere?"). Questi messaggi di controllo consentono al ricevitore di far sapere al mittente se il messaggio è stato ricevuto correttamente o è necessaria la ritrasmissione. In una rete di calcolatori l'affidabilità basata sulla ritrasmissione usa tecniche dette ARQ (Automatic Retransmission reQuest).
Un protocollo che si serve di una tecnica ARQ deve possedere tre requisiti per gestire la presenza di errori:
Riconoscimento di Errori. Ovviamente serve un metodo per consentire al ricevitore di accorgersi della presenza di errori. A questo scopo si usa il calcolo del checksum. Esistono anche tecniche di individuazione e correzione degli errori ma, essendo costose e basate sull'ipotesi che la probabilità di errore sia molto bassa, non vengono usate e si preferisce sempre la ritrasmissione dei pacchetti affetti da errore.
Risposta del ricevitore. I processi mittente e destinatario sono in esecuzione su sistemi terminali diversi, di conseguenza il solo modo che ha il mittente di sapere se il pacchetto è giunto correttamente è quello di farselo confermare dal ricevitore tramite le conferme positive (ACK) e quelle negative (NACK).
Ritrasmissione. Quando al ricevitore giunge un pacchetto contenente errori, il trasmettitore lo ripete.
![]() |
Il successivo modello del trasmettitore prevede due stati: X e Y. Nello stato X, il protocollo dal lato del trasmettitore è in attesa di trasmettere dati provenienti dal livello superiore. Quando riceve i dati da inviare, calcola il checksum, lo inserisce nel pacchetto insieme ai dati e consegna il pacchetto al livello rete. Nello stato Y, il protocollo del trasmettitore è in attesa di una conferma (ACK o NAK) proveniente dal ricevitore. Quando il mittente riceve un pacchetto ACK, sa che l'ultimo pacchetto è stato ricevuto correttamente e così il protocollo ritorna nello stato di attesa di dati dal livello superiore. Se viene ricevuto un NAK, il protocollo ritrasmette l'ultimo pacchetto e aspetta che il ricevitore invii il pacchetto di risposta contenente la conferma. È importante notare che quando il ricevitore si trova nello stato attesa di ACK o NAK, non può ricevere altri dati dal livello superiore. Solo quando il trasmettitore riceve un pacchetto di conferma potrà uscire da questo stato Y. Il mittente non può inviare un nuovo pacchetto se non è sicuro che il ricevitore abbia ricevuto correttamente il pacchetto appena inviato. Per questo comportamento questa tecnica ARQ è detta stop-and-wait. |
![]() |
Il ricevitore può trovarsi in un solo stato: X. Alla ricezione di un pacchetto, il destinatario risponde con ACK o NAK, a seconda se il pacchetto è corretto o alterato da rumore. |
In queste considerazioni si è trascurata l'eventualità che l'errore si possa presentare sul pacchetto di conferma. Come minimo occorre aggiungere il campo checksum ai pacchetti ACK/NAK per riconoscere la presenza di errori. Il problema è come si deve comportare il trasmettitore che riceve un errore in un pacchetto ACK o NAK? La difficoltà è che se un pacchetto ACK o un pacchetto NAK è errato, il mittente non ha modo di sapere se il pacchetto è stato ricevuto oppure no.
Ci sono tre ipotesi di gestione dei pacchetti di conferma ACK o NAK errati:
Se il mittente non capisce la conferma dovrebbe chiedere "cosa hai detto?" (un nuovo tipo di pacchetto del protocollo, dal mittente al destinatario). Ma se si pensa che anche questa risposta potrebbe subire una distorsione ci si rende conto che non si risolverà mai il problema.
Si potrebbe ricorrere a metodi che riconoscono e correggono l'errore, ma se si risolve il problema dei pacchetti errati, non si risolve il problema della perdita dei pacchetti.
Il mittente ripete il pacchetto quando riceve un errore nella conferma. Di conseguenza il ricevitore acquisisce anche pacchetti duplicati. In presenza di pacchetti ripetuti, il ricevitore non sa se la conferma all'ultimo pacchetto spedito era giunta al trasmettitore e non può stabilire se nel pacchetto ci sono dati nuovi o ripetuti.
Versione 3
Per quest'ultima ipotesi, il problema del riconoscimento dei pacchetti ripetuti viene risolto con una tecnica (adottata nel TCP) che consiste nell'aggiungere un campo al pacchetto, nel quale il mittente inserisce un numero di sequenza. Il ricevitore deve controllare questo numero di sequenza per distinguere i pacchetti duplicati dai pacchetti nuovi. Per questo semplice caso di tecnica stop-and-wait, basta un numero di sequenza di 1 bit, perchè il trasmettitore cambia il valore del bit quando invia un pacchetto nuovo, lo lascia invariato quando ripete il pacchetto. Si tratta di un contatore modulo 2. Ipotizzando che il canale non perda pacchetti, i pacchetti di conferma ACK e NAK non hanno bisogno di portare il numero di sequenza, perchè il mittente assume che sia confermato sempre l'ultimo pacchetto inviato.
Volendo rappresentare il trasmettitore e il ricevitore con una macchina a stati finiti, bisogna raddoppiare il numero di stati in cui si possono trovare i due interlocutori, perchè lo stato del protocollo deve tenere conto se il pacchetto spedito dal mittente o aspettato dal ricevitore deve avere il numero di sequenza 0 o 1. In entrambi gli stati le azioni sono simmetriche. la sola differenza è la gestione dei numeri di sequenza.
![]() | ![]() |
Macchina a stati finiti del trasmettitore | Macchina a stati finiti del ricevitore |
Nel protocollo che si sta costruendo, il ricevitore usa sia le conferme positive che quelle negative. La conferma negativa viene inviata se viene ricevuto un pacchetto errato o fuori ordine. Invece di mandare una conferma negativa si può, in modo equivalente, inviare la conferma positiva per l'ultimo pacchetto ricevuto correttamente. Il mittente che riceve due conferme per lo stesso pacchetto capisce che il ricevitore non ha acquisito correttamente il pacchetto che segue quello confermato due volte. Un leggero miglioramento della versione del protocollo che si sta costruendo consiste nel modificare il comportamento del ricevitore. Il ricevitore deve includere nel pacchetto ACK il numero di sequenza del pacchetto che deve confermare (cioè ACK 0 o ACK 1), mentre il mittente deve controllare il numero di sequenza del pacchetto che aspetta di essere confermato.
Versione 4
Si introduce adesso l'ipotesi che il canale, oltre a danneggiare i pacchetti, possa perdere i pacchetti. Il protocollo deve avere la possibilità di accorgersi che un pacchetto sia stato smarrito e deve prevedere la possibilità di recuperarlo. L'uso del campo checksum, dei numeri di sequenza, dei pacchetti ACK, e le tecniche ARQ consentono di recuperare un pacchetto perso. Il problema di riconoscere la perdita di un pacchetto, invece, richiede l'introduzione di un nuovo meccanismo nel protocollo.
Si consideri che il mittente invii un pacchetto e che il pacchetto, o la conferma inviata dal ricevitore, si perdano. In ogni caso, il mittente non avrà nessuna risposta dal destinatario. Il mittente stabilisce un massimo tempo di attesa e, se non riceve la risposta entro questo tempo, ritrasmetterà il pacchetto.
La durata dell'attesa deve comprendere il tempo di andata e ritorno impiegato dal pacchetto, incluso i tempi di transito e memorizzazione nei dispositivi intermedi, più un certo tempo impiegato dal ricevitore per elaborare il pacchetto. È molto difficile stimare una durata di attesa minima. Idealmente il pacchetto dovrebbe essere recuperato il prima possibile. Di conseguenza il pacchetto potrebbe essere ritrasmesso perchè la conferma sta arrivando con un ritardo maggiore del previsto provocando il fenomeno della duplicazione dei pacchetti. Ma la gestione di questa eventualità è effettuata con l'impiego dei numeri di sequenza.
Il mittente non sa se il pacchetto o se la conferma è stata smarrita, o se entrambi stanno subendo dei ritardi. In ogni caso il pacchetto viene ritrasmesso. Allo scopo di trattare questo meccanismo è richiesto l'utilizzo di un timer a decremento, inizializzato con un valore tale da giungere a 0 in un tempo prestabilito, e quando giunge a 0, il timer deve generare un'interruzione, per avvertire il mittente che il tempo di attesa è scaduto. Il trasmettitore quindi dovrà ripetere la trasmissione del pacchetto, reinizializzando il timer. Se invece giunge la conferma al pacchetto inviato il timer dovrà essere fermato.
L'esistenza di pacchetti duplicati e di pacchetti perduti introduce una complicazione. Se il mittente riceve un ACK, ha il dubbio se il ricevitore lo ha mandato in risposta agli ultimi pacchetti trasmessi o se è giunto in ritardo ed è relativo ad un vecchio pacchetto. Per confermare correttamente i pacchetti, il ricevitore deve gestire una variabile locale, ad esempio N(R), in cui conserva il numero di sequenza del pacchetto che si aspetta di ricevere e quando deve inviare il pacchetto ACK copia questa variabile nel pacchetto ed incrementa la variabile N(R). Anche il mittente deve gestire una variabile locale N(S) per confrontare il numero del pacchetto confermato con il numero del pacchetto che si aspetta di confermare.
Riepilogo
Trasmissione priva di errori e di perdita di pacchetti Il trasmettitore inizializza la sua variabile locale N(S)=0, I contatori N(S) ed N(R) sono lunghi 1 bit, |
![]() |
![]() |
Perdita di pacchetti Il trasmettitore, per ogni pacchetto inviato avvia il timer. |
Perdita del pacchetto di conferma
Il pacchetto viene ricevuto correttamente e il ricevitore invia il pacchetto di conferma. |
![]() |
![]() |
timeout troppo breve.
Il pacchetto viene ricevuto correttamente e viene confermato. |