A questo punto della trattazione, svilupperemo in modo complementare i lati mittente e ricevente di un protocollo di trasporto affidabile rendendolo via via più complesso.
Il lato mittente del protocollo di trasporto sarà invocato con rdt_send()
mentre il lato ricevente con rdt_rcv()
. rdt sta per reliable data transfer.
Quando un pacchetto raggiunge il lato ricevente del canale (di comunicazione affidabile) viene chiamata rdt_rcv()
. Nel momento in cui rdt voglia consegnare i dati al livello superiore (applicazione) verrà utilizzata la chiamata deliver_data()
. Spiegheremo solo il trasporto unidirezionale e non bidirezionale (full-duplex), che non è concettualmente più complesso. È comunque importante notare che i lati mittente e ricevente del protocollo rdt avranno la necessità di scambiarsi informazioni in entrambe le direzioni. Vedremo anche che oltre a scambiarsi pacchetti contenenti dati da trasferire, i due lati di rdt si scambieranno pacchetti di controllo utilizzando la chiamata udt_send()
(UDT: unreliable data trasfer).
Vedremo le varie versioni di questo protocollo sotto forma di automi a stati finiti (FSM - finite state machine)
Lo stato del mittente è rappresentato dalla figura (a), mentre lo stato del ricevente è rappresentato dalla figura (b).
Lato mittente si attendono i pacchetti dal livello superiore tramite l'evento rdt_send()
che:
packet = make_pkt(data)
udt_send(packet)
Lato ricevente si attendono i pacchetti dal livello sottostante tramite l'evento rdt_rcv(packet)
che:
extract(packet, data)
deliver_data(data)
In questa versione del protocollo viene affrontato il problema del rilevamento dell'errore: se un pacchetto si corrompe durante il tragitto verso la destinazione, il ricevente se ne rende conto grazie al campo checksum che viene aggiunto al protocollo rdt2.0. Se il ricevente nota che il pacchetto è corrotto, lo comunica al mittente che lo re-invia. Il ricevente comunicherà al mittente di aver ricevuto il pacchetto con un feedback positivo (acknowledgement - ACK) o con un feedback negativo (NAK).
Il mittente (a), attende pacchetti dal livello superiore, quando essi arrivano viene prodotto un pacchetto (come in rdt1.0) e inviato via udt_send
.
Il mittente, fatto ciò, passa all'altro stato e attende di ricevere un ACK o un NAK per il pacchetto appena inviato:
rdt_rcv(rcvpkt)
, riceve il pacchetto inviatogli dal ricevente, tale pacchetto viene passato alla funzione isNAK(rcvpkt)
che controlla se esso è un NAK (notifica negativa), se è vero che è un NAK: il pacchetto creato nel primo stato della macchina viene inviato nuovamente (rimanendo in attesa di ACK per il nuovo pacchetto inviato);Il ricevente (b), ha ancora un solo stato, inizialmente è in attesa di ricevere pacchetti dal livello sottostante (come per rdt1.0). Quando li riceve controlla se il pacchetto ricevuto è corrotto o no:
rdt_rcv(rcvpkt)
riceve il pacchetto, esso viene passato a corrupt(rcvpkt)
. Se il risultato del controllo booleano è vero, come conseguenza si ha la creazione di un pacchetto di tipo NAK che viene inviato;notcorrupt(rcvpkt)
dà come risultato booleano vero, come conseguenza viene creato un pacchetto di ACK e viene inviato.Il protocollo rdt2.0 sembrerebbe funzionare, ma presenta ancora un grave problema: non è stato considerato il caso in cui i pacchetti ACK o NAK siano corrotti. Come minimo vanno aggiunti dei bit di checksum ai pacchetti ACK/NAK per dare la possibilità al mittente di riconoscere eventuali errori nei pacchetti. Il problema fondamentale riguarda come il protocollo debba comportarsi nel caso in cui riceve pacchetti di ACK/NAK corrotti. Vediamo due possibili soluzioni:
Macchina a stati finiti del mittente:
qui vediamo che dopo aver inviato il pacchetto, il mittente si mette in attesa per ricevere ACK o NAK.
Macchina a stati finiti per il destinatario:
il destinatario dal primo stato, attende il pacchetto con numero di sequenza 0 per passare allo stato successivo (&& has_seq0(rcvpkt
), inoltre verifica che il pacchetto non sia corrotto:
sndpkt = make_pkt(ACK, checksum)
e lo invia;sndpkt = make_pkt(NAK, checksum)
).Come abbiamo notato, il protocollo appena descritto, che chiamiamo rdt 2.1 usa acknowledgement positivi quando non riceve pacchetti alterati e acknowledgement negativi quando riceve pacchetti alterati.
Possiamo eliminare i NAK: il ricevente usa solo ACK quando riceve pacchetti non alterati, quando riceve un pacchetto alterato invia un ACK che riguarda il pacchetto precedente, il mittente si ritroverà 2 ACK per lo stesso pacchetto inviato, quindi sa che il ricevente non ha ricevuto il pacchetto successivo al pacchetto cui si riferiscono gli ACK duplicati ricevuti.
Mittente invia pacchetto 0.
Ricevente invia ACK per 0.
Mittente invia pacchetto 1.
Il destinatario ha ricevuto un pacchetto corrotto, quindi informa il mittente che l'ultimo pacchetto non corrotto ricevuto è il pacchetto 0. Quindi invia ACK per 0.
Il ricevente riceve ACK per 0 e dice "ma io ho inviato il pacchetto 1. Invio nuovamente il pacchetto 1".
Arricchiamo il rdt 2.1 con questa funzione, facendolo diventare rdt 2.2.
Macchina a stati finiti del mittente:
notiamo che qui il mittente, dopo aver inviato il pacchetto con numero di sequenza 0, attende solo per ACK.
Macchina a stati finiti del destinatario:
Se si riceve pacchetto corrotto si invia ACK per il pacchetto precedente (l'ultimo ricevuto).
Sarà un protocollo per trasferimento dati affidabile con perdita di pacchetti ed errori sui bit.
Adesso assumiamo che sul canale i pacchetti possano andare persi.
Esistono molti approcci per la gestione della perdita di pacchetti, in questa sede il libro di testo di Kurose-Ross, assume che sia il mittente a gestire la perdita dei pacchetti.
Se i pacchetti spediti dal mittente vanno persi o che vanno persi gli ACK relativi ai pacchetti inviati, il mittente non sa se essi siano effettivamente andati smarriti o se siano vittima di un lungo ritardo. La soluzione è la ritrasmissione. Quanto tempo dovrebbe attendere il mittente prima di ritrasmettere una pacchetto? Almeno per il minimo tempo di andata e ritorno tra mittente e destinatario.
L’idea è di far in modo che il mittente ritrasmetta il pacchetto quando, in termini di tempo, sarebbe già dovuta arrivare la risposta da parte del ricevente. Tuttavia spedire la ritrasmissione tenendo conto di questo lasso tempo rallenterebbe di molto il canale di comunicazione. Per cui si utilizza un lasso di tempo probabile dopo il quale il pacchetto debba essere ritrasmesso. Ciò vuol dire che il mittente potrebbe rispedire un pacchetto che non sia mai stato smarrito (e che sia ancora in viaggio) o di cui non è stato smarrito l’ACK (e che sia ancora in viaggio). Il problema dei pacchetti duplicati è già risolto da rdt 2.2. Per il calcolo del tempo stimato viene aggiunto un nuovo componente al protocollo: un timer.
Il mittente dovrà essere in grado:
Macchina a stati finiti del mittente:
nell'attesa di ACK per il pacchetto inviato il mittente ci resta se:
La macchina a stati finiti del destinatario resta invariata rispetto a rdt 2.2.
Il protocollo rdt3.0 ha delle prestazioni che oggi sarebbero poco accettate. Il problema di prestazioni non buone risiede nel fatto rdt3.0 è di tipo stop-and-wait.
Vediamo un esempio per misurarne le prestazioni.
Supponiamo che ci siano due host: uno sulla costa orientale degli USA è uno sulla costa occidentale.
Il ritardo di propagazione di andate e ritorno (RTT) e di circa 30 ms.
Supponiamo che i due sistemi siano connessi da un canale con tasso trasmissivo
I pacchetti hanno dimensione
Il primo bit del pacchetto viene trasmesso al tempo
L’ultimo bit entra nel canale dopo
Il pacchetto viaggia per
L’ultimo bit giunge al ricevente dopo
Per semplicità immaginiamo che i pacchetti di ACK sono estremamente piccoli e che il loro tempo di trasmissione sia trascurabile (istantaneo): gli ACK giungeranno a destinazione dopo
L’utilizzo (utilizzazione) del mittente è
E si calcola con la formula:
La soluzione a questo problema è permettere al mittente di inviare più pacchetti invece di aspettare di ricevere gli ACK.
Se si consente di inviare tre pacchetti senza l’attesa di ACK, l’utilizzazione del mittente triplica. Questa tecnica è nota come pipelining. Cosa deve cambiare:
Navigazione: