Questo progetto mi ha dato modo di sperimentare la maggior parte del supporto di comunicazione fornito da UNIX (IPC), nonchè gli strumenti per gestire la concorrenza. Per quanto riguarda IPC nel progetto oltre che alle sockets sono stati utilizzati la shared memory (SM) ed i segnali.
Ritengo la SM uno strumento di comunicazione molto utile il cui unico limite è quello di essere limitata superiormente ad un valore di 4kB. Tale limite però non deve essere considerato un difetto in quanto va considerato il contesto al quale la SM intende proporsi. In un' applicazione con interfaccia a caratteri come questa tale limite non ha avuto alcuna influenza sulle scelte implementative effettuate, semmai si è presentato il problema opposto e cioè di limitarne l' impiego per non rendere la natura di un progetto orientato alle reti eccessivamente locale. L' unica difficoltà nell' utilizzo della SM è la sua gestione mutex (indispensabile), che si realizza utilizzando i semafori, che in UNIX sono tutt' altro che immediati: l' intenzione di fornire ai programmatori un' unico strumento valido sia per i semafori binari che per quelli generalizzati comporta fondamentalmente difficoltà nel suo impiego che si concretizza con la CERTEZZA di generare deadlock durante i primi approcci.
In merito alle sockets va detto che il progetto utilizza sockets che fanno riferimento sia al protocollo TCP, per realizzare connessioni STREAM, che al protocollo UDP, per realizzare connessioni DATAGRAM e BROADCAST. Diciamo subito che l' inpiego del Bcast limita l' utilizzo dell' applicazione ad una rete locale, mentre per quanto riguarda il protocollo di trasporto utilizzato è bene fare qualche osservazione. Valutando i vantaggi che comporta TCP ed il maggiore impegno delle risorse di rete rispetto a UDP, nel progetto ho cercato di utilizzare sockets STREAM solamente quando le esigenze di comunicazione richiedono preferibilmente il loro uso. È piuttosto comune la motivazione di messaggio di vitale importanza come discriminante tra l' impiego di TCP o UDP, motivazione che a mio avviso è piuttosto infelice. Dal momento che con le sockets UNIX il concetto di vitale importanza non può essere riferito alla sicurezza, generalmente lo si riconduce al fatto che il messaggio che si invia è importante che giunga a destinazione, quasi a significare il fatto che vi siano messaggi per i quali non ha inportanza che arrivino al destinatario. Se è così perchè inviarli: si risparmia codice ed il dubbio su quale tipo di socket utilizzare.
Le motivazioni che portano alla scelta di un protocollo piuttosto che all' altro dovono basarsi sulla specifica natura della comunicazione. Vi sono comunicazioni di tipo one-shot del tipo " a domanda risposta " (una sorta di quiz) nelle quali ogni messaggio è assolutamente scorrelato dal precedente, ma vi sono anche delle comunicazioni nelle quali c'e una forte correlazione tra i messaggi per cui ha importanza l' ordine o il numero di volte che sono inviati.
Oltre a queste considerazioni di concetto occorre anche valutare quelle che sono le caratteristiche ed i limiti dei protocolli dovuti alla loro implementazione.
Un chiaro esempio di messaggi che devono essere inviati con UDP sono i segnali di probe utilizzati nell' applicazione dai STs ma anche dai NSs per realizzare la funzionalità di keep-alive. Tali messaggi non hanno in sè alcun contenuto informativo è dunque impossibile trovare una loro correlazione, ma è comunque fondamentale per l' applicazione che tali messaggi giungano a destinazione. Non c' è logica nell' inviare tali messaggi con TCP, bensì un inutile overhead di comunicazione e di impegno di risorse di rete.
L' aggiornamento dei dB è invece il tipico esempio di comunicazione che esige TCP come protocollo di trasporto in quanto non c' è conoscenza pregressa tra T e ST sulla lunghezza dell' informazione che si scambiano: il T non può conoscere la lunghezza del messaggio di aggiornamento che invierà al ST finchè l' utente non ha specificato il numero di utenti da aggiornare. Fra ST e T vi è un accordo durante la trasmissione del messaggio nella quale il T invia prima un header, che specifica la lunghezza del messaggio che seguirà, dualmente il ST ricevuto l' header si predispone per la lettura successiva che contiene effettivamente l' informazione. In questo caso è evidente l' idea di stream dell' informazione: non ci si può permettere che il messaggio effettivo anticipi l' header.
Questo tipo di comunicazione mi permette di effettuare un' osservazione in merito al tipo di dati che si trasmettono. È noto che le socket non consentono la trasmissione di alcuna forma strutturata, ma tutto ciò che trasmettono lo interpretano come sequenza di caratteri; è però altresi vero che il C non distingue tra interi e caratteri ed in questo caso questa mancanza di tipo può essere utilizzata a proprio vantaggio. Facendo particolare attenzione alla lunghezza in bytes del messaggio da inviare e ricevere è possibile trasmettere oltre ai caratteri anche i numeri ed è proprio quello che accade nel caso dell' aggiornamento: l' header viene trasmesso come short e interpretato in ricezione come tale evitando così la necessità di conversioni di tipo.
L' argomento dei segnali preferisco non affrontarlo rimandando il lettore ai riferimenti bibliografici. L' impressione che emerge è che tutta la documentazione al riguardo miri ad evidenziare le insidie, piuttosto che le potenzialità di questo particolare meccanismo di comunicazione. Possono essere anche "divertenti " finchè si tratta di un figlio che dopo aver contato fino a dieci lo segnala al padre, o di un padre che genera figli affinchè leggano ciò che ha scritto su file (insomma esercitazioni alla "Calcolatori II") ma quando si tratta di utilizzarli con threads, primitive di comunicazione avanzate e memoria condivisa, possono divenire una insidia per l'esecuzione a causa dell' asincronismo. D'altra parte penso sia inevitabile un loro impiego tanta è la loro flessibilità, che consente di snellire un codice che diverrebbe troppo pomposo con l' utilizzo di pipe o fifo . Diventa in questo modo FONDAMENTALE riconoscere e gestire le regioni critiche del progetto nonchè il comportamento del kernel di fronte all' interruzione di una primitiva lenta.