Versione TCP
C'è poco da dire; la gestione dei socket in modalità TCP di UNIX mette già a
disposizione tutto quello che serve per realizzare una applicazione
client/server.
È sufficiente che il server riservi e si metta in ascolto in una data porta, e
poi attraverso la primitiva accept() si ottiene direttamente un socket (che
può essere usato come un file descriptor) per comunicare con il client.
Quindi una coppia accept()/fork() e il server ad accesso multiplo è fatto.
Il client, visto che TCP garantisce già che la comunicazione vada a buon fine,
deve solo inviare un comando al server e aspettarsi una risposta.
Per questo tipo di applicazione (dove la velocità non è molto importante visto
che i dati sono pochi e possono essere trasmessi in un pacchetto solo) la
versione TCP è sicuramente consigliata.
Tra l'altro questa versione risulta essere piú standard, è possibile
interagire con il server con un semplice telnet.
Versione UDP
UDP (User Datagram Protocol) è praticamente un ``wrapper'' sopra IP per
permettere alle applicazioni di usare IP.
Questo pone tutta una serie di problemi che con TCP sono risolti dal sistema
operativo, e che nel mio programa ho risolto solo parzialmente.
- UDP non è orientato alla connessione
- non esiste una accept(), quindi il server deve utilizzare la primitiva
recvfrom() con cui ottiene non solo il comando ma anche l'indirizzo
del client che lo ha spedito; questo indirizzo deve poi essere usato
per inviare i dati al client.
Questo in particolare ha posto due serie di problemi:
- nel server non potevano essere usate delle semplici printf e
il trucchetto di chiudere STDOUT e riassegnarlo al socket come
fatto per pipe e TCP; si è dovuto quindi creare una unnamed
pipe interna su cui usare il trucchetto qui sopra e da cui
leggere a pacchetti per poi usare sendto().
- nel client non si poteva fare una sendto() due volte dallo
stesso PID (processo), perchè (giustamente) UDP non accetta
piú di una connessione dallo stesso host, port, processo.
Quindi il client contiene una fork() il cui figlio gestisce
effettivamente lo scambio dei dati e il padre tutte le
combinazioni d'errore.
- UDP non è affidabile
- non è possibile garantire che un pacchetto arrivi a destinazione, e
quindi bisogna prendere tutta una serie di precauzioni; per TCP lo fa
il sistema operativo (tra l'altro con gestioni sopraffine), ma qui lo
dobbiamo fare noi.
In particolare vengono effettuati i controlli su:
- timeout; il client se non riceve risposta dal server
entro tot secondi ripete la connessione inviando nuovamente il
comando e aspettando la risposta.
Il timeout è fisso e definibile in config.h.
- out-of-sequence; UDP non garantisce che una serie di
pacchetti arrivi a destinazione in ordine, quindi in testa al
pacchetto il server aggiunge un numero d'ordine e il client se
riceve un pacchetto fuori ordine ripete la trasmissione.
Ammetto che è una strategia tonta, ma implementare un buffer
che permetta di salvare e riordinare i pacchetti sarebbe stato
di complessità nettamente superiore.
La gestione di timeout e out-of-sequence può essere ``forzata''
compilando con le opzioni FORCE_TOUT e FORCE_OOS,
guardate il file CONFIG per maggiori informazioni.
- UDP non ha meccanismi di sincronismo seri
- ...ovvero leggere e scrivere sui suoi socket è un vero panico! Sono
stato costretto a implementare i seguenti trucchetti:
- quando avviene il ``travaso'' dei dati dalla unnamed pipe al
socket sono stato costretto ad aggiungere un falso timeout,
visto che se leggo una quantità intera di BUFLMAX byte lui
si pianta li ad aspettare quello che non verrà mai.
Questo è un problema di unnamed pipe e non di UDP socket, ma
sono tonti uguali... ;)
- nel client quando riceve una quantità intera di BUFLMAX byte
anche recvfrom() si pianta; visto che qui il timeout c'era
già, e quando il client si trova in questa soluzione vuol dire
che anche il server ci si è trovato, semplicemente il server
se esce per timeout da una read() dalla unnamed pipe spedisce
un ulteriore pacchetto di dimensione 1 con un '\0'
dentro.
- le unnamed pipe non hanno capacità infinita e quindi
sono stato costretto a inserire dei tempi di delay qui e li
per permettere a chi legge di leggere qualcosa e a chi
scrive di non riempire la pipe
È da notare che il client non sa il numero di pacchetti che gli
arriveranno; una connessione si ipotizza andata a buon fine se arriva
un pacchetto contenente un numero di byte minore di BUFLMAX.
Quindi, mentre dai socket TCP si può leggere tranquillamente, e la
condizione di fine dati è segnalata da una quantità di dati letta
negativa, con UDP bisogna per forza inserire meccanismi (anche se
sporchi come il mio) di sincronizzazione espliciti.
In generale, quindi, UDP è assolutamente uno spreco di tempo per applicazioni
del genere, visto che non sono in ballo ne grosse quantità di dati nè
occorrono velocità strabilianti.
In particolare, visti i controlli banali che ho implementato, la versione UDP
potrebbe essere del tutto inutilizzabile su tratte lunghe e/o molto trafficate
e disturbate.
Ritorna alla pagina del corso di Reti di Calcolatori