Il livello Trasporto.

Bibliografia:
Computer Networking. Cap. 3. Autori: James F. Kurose (University of Massachusetts, Amherst) Keith W. Ross (Polytechnic Institute of NYU).

Multiplexing e Demultiplexing delle Applicazioni

In questa sezione si descriverà il multiplexing/demultiplexing del livello trasporto, cioè si vedrà come il servizio di consegna tra due sistemi terminali fornito dal livello Rete viene esteso al servizio di consegna tra due processi del livello applicazione, in esecuzione sui sistemi terminali.

Il multiplexing/demultiplexing riguarda il transito dei messaggi tra il livello Trasporto e il livello Applicazione.

Il livello IP scambia i pacchetti tra due sistemi terminali in base all'indirizzo IP. La consegna dei messaggi ai processi in esecuzione sui sistemi terminali è affidata al servizio di multiplexing e demultiplexing del protocollo di Trasporto.

Ad esempio: un programmatore, dal suo computer, sta consultando le pagine web che ha sviluppato e ha pubblicato su un server mentre, contemporaneamente, mantiene una sessione ftp per pubblicare le pagine e due sessioni telnet per altre necesità. Quindi ci sono quattro applicazioni di rete in esecuzione (due telnet, una ftp, e una http). Quando il livello trasporto del computer del programmatore riceve dati dal livello Rete, sul quale si appoggia, ha bisogno di smistare i dati ricevuti ad uno dei quattro processi.

Un processo (un'applicazione di rete) può avere uno o più socket, delle porte (o connettori ideali) attraverso cui passano, in entrambe le direzioni, i dati tra il livello Trasporto e il processo del livello Applicazione.

Come mostra la figura, il livello Trasporto nel sistema ricevente (quello centrale) non consegna i dati direttamente al processo, ma li consegna ad un intermediario, il socket. In ogni istante potrebbero essere presenti più socket nel sistema ricevente, ognuno dei quali ha un identificatore univoco. Il formato dell'identificatore dipende dal protocollo utilizzato: UDP o TCP.

Adesso si vuole vedere come il livello trasporto del sistema ricevente smista i segmenti entranti al corretto socket. Ogni segmento del livello Trasporto ha dei campi riservati per compiere questa operazione. Il livello trasporto dal lato del ricevitore esamina questi campi per individuare il socket destinatario e trasmettere i dati a quel socket. Questa operazione di consegna dei dati contenuti nel segmento del livello Trasporto al corretto socket è detta demultiplexing. Al terminale sorgente, l'operazione di incapsulare ogni blocco di dati, provenienti da diversi socket, in una busta corredata dell'intestazione (che al destinatario servirà per il demultiplexing), e chiedere al livello Rete di consegnare i segmenti al destinatario è detta multiplexing. Nella figura, il livello Trasporto nel sistema centrale deve demultiplexare i segmenti in arrivo dal livello Rete sottostante al processo P1 o al processo P2 del livello applicazione superiore. Questa consegna avviene comunicando i dati al socket del corrispondente processo. Il livello Trasporto del sistema centrale deve anche raccogliere i dati uscenti da questi socket, formare i segmenti e chiedere al livello rete il servizio di consegna. Le operazioni di multiplexing e demultiplexing svolte dai processi del livello Trasporto sono un servizio tra livelli adiacenti che possono coinvolgere protocolli diversi in esecuzione sui livelli superiori.

Il multiplexing svolto dal livello Trasporto richiede che:

  1. i socket (o porte) abbiano ognuno un identificatore univoco. Un socket, in altri termini, identifica un'applicazione.

  2. ogni segmento abbia dei campi riservati a contenere l'identificatore del socket a cui consegnare i dati contenuti nel segmento

Questi campi sono il Numero Porta Sorgente e il Numero Porta Destinazione, come mostrato nella figura seguente:

16 bit16 bit
Numero Porta SorgenteNumero Porta Destinazione
… altri dati dell'header …
… dati …
segmento del livello Trasporto

Il campo Numero Porta è rappresentato con un numero di 16 bit. I numeri di porta compresi tra 0 e 1023 sono detti well-known port numbers e sono riservati, nel senso che sono stati assegnati, convenzionalmente, a determinati protocolli del livello Applicazione (su un server, il protocollo HTTP usa il numero di porta 80, il protocollo FTP usa il numero di porta 21). L'elenco completo dei numeri di porta riservati è dato nella RFC 1700. Quando si sviluppa una nuova applicazione si deve assegnare un numero di porta all'applicazione.

In sintesi, il modello di funzionamento del socket è un connettore ideale che presenta, alle applicazioni, due canali uno per la trasmissione e uno per la ricezione. Quando un segmento giunge nel sistema, il livello Trasporto individua il numero della porta destinazione (l'identificativo del socket) e consegna i dati al processo del livello applicativo collegato al socket, realizzando il demultiplexing.

Si ritorni all'esempio dei cugini. Ogni bambino ha un nome. Quando Calogero riceve una busta dal postino compie un'operazione di demultiplexing osservando il destinatario scritto sulla busta e recando la busta al fratello che vi è indicato. Alice compie l'operazione di multiplexing quando raccoglie le buste dai suoi fratelli e le consegna al postino.

Poiché ogni applicazione su un sistema terminale ha un numero di porta univoco, ci si chiede perché il livello trasporto usa due numeri di porta, uno per la sorgente e uno per la destinazione. La risposta è che su un sistema terminale possono esserci due processi dello stesso tipo in esecuzione contemporaneamente, e quindi il numero di porta di un'applicazione non è sufficiente per identificare un particolare processo. Ad esempio un server web deve rispondere a molte richieste http contemporaneamente, riceve molte richieste sulla stessa porta 80. Quindi ha bisogno del secondo numero di porta per identificare univocamente il processo connesso.

Si supponga che un processo nell'host A, dopo aver creato un socket UDP numero 19157, deve inviare un blocco di dati del livello applicazione ad un processo con il numero di porta UDP 46428 in esecuzione sull'host B. Il livello trasporto nell'Host A crea un segmento del livello trasporto che racchiude i dati del livello applicazione, il numero di porta sorgente (19157), il numero di porta destinazione (46428), ed altri due valori (che per adesso non interessano, ma verranno descritti in seguito). Il livello trasporto passa il segmento risultante al livello rete. Il livello rete incapsula il segmento in un pacchetto IP e tenta di consegnare il segmento all'host destinatario. Se il segmento arriva all'Host B, il livello trasporto sull'host ricevitore esamina il numero della porta destinazione nel segmento (46428) e consegna il segmento al socket identificato con il numero di porta 46428. Notare che sull'Host B ci potrebbero essere più processi in esecuzione, ognuno con un proprio socket UDP e il relativo numero di porta. Quando vengono ricevuti segmenti UDP dalla rete, l'Host B demultiplexa ciascun segmento all'appropriato socket esaminando il numero della porta destinazione. È importante notare che un socket UDP è identificato dalla coppia di valori "indirizzo IP destinazione" e "numero di porta destinazione". Di conseguenza, se due segmenti UDP hanno differenti "indirizzi IP sorgente" e/o "numeri di porta sorgente", ma hanno lo stesso indirizzo IP e numero di porta destinazione, allora i due segmenti saranno consegnati allo stesso processo destinatario attraverso lo stesso socket destinazione.

Si ricordi che le applicazioni di rete usano il modello client-server. L'host che inizia la comunicazione è sempre il client che spedisce i suoi pacchetti al server. Si supponga che l'applicazione server sia in ascolto sulla porta numero 23 (Telnet). Si consideri un segmento del livello Trasporto in partenza dal client e destinato al server. Il numero di porta destinazione contenuto nel segmento è 23, il numero di porta sorgente usato dal client non deve essere già assegnato ad un altro processo. Questo valore viene assegnato automaticamente dal sistema operativo sul client, lo sviluppatore dell'applicazione non se ne accorge. Il client quindi decide un numero di porta su cui resta in ascolto della risposta proveniente dal server, ad esempio x. Ogni segmento inviato dal client avrà il numero di porta sorgente impostato a x e il numero di porta destinazione impostato a 23. Quando il segmento viene consegnato al server, i numeri di porta sorgente e destinazione nel segmento consentono al server di passare i dati del segmento al corretto processo applicativo.

Segmento dall'host A all'host B Segmento dall'host B all'host A
Numero Porta Sorgente x    →    Numero Porta Sorgente 23
Numero Porta Destinazione 23    ←    Numero Porta Destinazione x
 
dati del livello
Applicazione
 dati del livello
Applicazione

Uso del numero di porta sorgente e del numero di porta destinazione in un'applicazione client-server

La situazione si inverte per i segmenti che vanno dal server al client. Il numero di porta sorgente diventa 23 e il numero di porta destinazione diventa x. Quando il segmento di risposta arriva al client, i numeri di porta sorgente e destinazione contenuti nel segmento consentono al client di passare i dati al corretto processo applicativo.

Potrebbe succedere che due diversi client stabiliscano una sessione Telnet con un server usando lo stesso numero di porta sorgente. Non si crea confusione perché nel pacchetto è presente l'indirizzo IP del client. Nella Figura l'host A inizia due sessioni Telnet verso l'host C, e l'host B inizia una sessione Telnet verso l'host C. Gli Host A, B e C hanno ciascuno un indirizzo IP univoco. L'Host A assegna due diversi numeri di porta sorgente (x e y) alle due connessioni Telnet. Ma siccome l'host B sta scegliendo indipendentemente da A il numero di porta sorgente, può assegnare il numero di porta x alla sua connessione Telnet. L'host C sarà in grado di demultiplexare le due connessioni grazie ai due indirizzi IP sorgente. In definitiva l'host destinazione riceve i dati dal livello Rete e con i tre campi [indirizzo IP sorgente, numero di porta sorgente, numero di porta destinazione] instrada i dati verso il processo corretto.

due client usano lo stesso numero di porta per comunicare con la stessa applicazione server

Multiplexing / demultiplexing orientato alla connessione.

Allo scopo di comprendere il demultiplexing del TCP, si tenga presente che il TCP è un protocollo orientato alla connessione. Esiste un differenza tra un socket UDP ed un socket TCP. Un socket TCP è identificato da quattro valori (indirizzo IP sorgente, numero di porta sorgente, indirizzo IP destinazione, numero di porta destinazione). Quando un segmento TCP arriva dalla rete ad un host, l'host usa i quattro valori per smistare (demultiplex) il segmento all'appropriato socket. In particolare, a differenza dell'UDP, due segmenti TCP entranti con differente indirizzo IP sorgente o diverso numero di porta sorgente (ad eccezione di un segmento TCP contenente la richiesta di apertura connessione) saranno consegnati a due diversi socket.

Per entrare meglio nei dettagli della questione, si consideri un server TCP che crea un socket sulla porta 12000:

    ServerSocket server = new ServerSocket(12000);

e poi resta in attesa di ricevere richieste di apertura connessione dai client. Quando le riceve, le accetta (tra le varie operazioni, invia un pacchetto di accettazione) e crea un socket per scambiare dati con il client:

    Socket client = server.accept();

Quindi il server TCP ha un socket su cui resta in ascolto di ricevere le richieste di apertura connessione dai client e, quando ne riceve una, crea un nuovo socket attraverso il quale risponde con un pacchetto di accettazione. Il numero di porta a questo secondo socket viene assegnato dal sistema operativo.

Il client TCP crea un socket ed invia una richiesta di apertura connessione all'indirizzo IP del server (o all'hostname, se esiste una riga DNS), specificando anche il numero della porta su cui il processo, su quel server, è in ascolto:

    Socket socket = new Socket(hostName, 12000);

Una richiesta di apertura connessione è un pacchetto TCP con numero di porta destinazione 12000 ed un bit speciale nell'header del segmento, che indica che un client vuole stabilire una connessione. Il segmento include anche il numero di porta sorgente scelto dal sistema operativo del client. Quando il sistema operativo del computer su cui è in esecuzione il processo server, riceve il segmento di richiesta di apertura connessione con il numero di porta destinazione 12000, individua il processo server che è in attesa di accettare una connessione sulla porta numero 12000. Il client riceverà dal server il pacchetto di accettazione, contenente il numero di porta del processo con cui potrà scambiare i dati.

Inoltre, il livello trasporto al server nota i seguenti 4 valori nel segmento di richiesta apertura connessione:

  1. il numero della porta sorgente nel segmento,

  2. l'indirizzo IP dell'host sorgente,

  3. il numero di porta destinazione nel segmento,

  4. il suo proprio indirizzo IP.

Il socket della connessione appena creata è identificato da questi quattro valori; tutti i successivi segmenti ricevuti il cui indirizzo IP sorgente, porta sorgente, porta destinazione, e indirizzo IP destinazione corrispondono a questi quattro valori verranno demultiplexati a questo socket. Con la connessione TCP stabilita, il client ed il server possono scambiarsi dati. L'host server può mantenere molti socket di connessioni TCP, dove ogni socket è legato ad un processo, ed è identificato dai quattro valori.

La situazione è illustrata in figura: l'Host C apre due sessioni HTTP verso il server B, e l'Host A apre una sessione HTTP con B. Gli Host A e C ed il server B hanno ciascuno un indirizzo IP univoco. L'Host C assegna due diversi numeri di porta sorgente (26145 e 7532) alle due connessioni HTTP. Poiché l'Host A sta scegliendo il numero di porta indipendentemente da C, potrebbe anche assegnare il numero di porta sorgente 26145 alla sua connessione HTTP. Ma non è un problema. Il server B sarà in grado di demultiplexare correttamente le due connessioni che hanno lo stesso numero di porta sorgente, perché le due connessioni hanno differenti indirizzi IP sorgenti.

Server Web e TCP

Prima di concludere la descrizione del multiplexing, è utile vedere il modo usato dai server web per gestire i numeri porta. Si consideri un host su cui è in esecuzione un server Web, ad esempio un server Web Apache, sulla porta 80. Quando i client (cioè i browser) inviano segmenti al server, tutti i segmenti specificano la porta destinazione 80. In particolare, sia i segmenti di richiesta apertura connessione sia i segmenti che richiedono pagine web specificheranno la porta 80. Come visto, il server distingue i segmenti dei differenti client usando l'indirizzo IP sorgente e il numero di porta sorgente. La figura mostra un server Web che crea un nuovo processo per ogni connessione. Ognuno di questi processi ha il proprio socket attraverso cui transitano le richieste HTTP entranti e le risposte HTTP uscenti. Si tenga presente, però che non sempre c'è una corrispondenza biunivoca tra i socket ed i processi. Infatti, i nuovi server Web tendono ad usare un solo processo, e creano un nuovo thread con un nuovo socket per ogni nuova connessione con un client. (Un thread può essere pensato come un sotto-processo più veloce e meno ingombrante). Per un tale server, in ogni istante, ci possono essere molti socket (con differenti identificatori) legati allo stesso processo. Se il client ed il server usano connessioni HTTP persistenti, allora, per tutta la durata della connessione persistente il client ed il server scambiano messaggi HTTP attraverso lo stesso socket del server. Se, però, il client ed il server usano connessioni HTTP non-persistenti, allora viene creata una nuova connessione per ogni pacchetto request e verrà chiusa dopo ogni pacchetto response, cioè il socket esisterà solo per la durata necessaria a fornire un pacchetto di risposta. Se la pagina richiesta contiene molti elementi (immagini, fogli di stile, script, …) questa frequente creazione ed eliminazione di socket incide negativamente sulle prestazioni di un server Web molto visitato.