Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
vuoi
o PayPal
tutte le volte che vuoi
Architettura Client-Server
Nel modello client-server si può anche avere un client che gestisce solo l'input/output ma l'avvio dell'applicazione non avviene sull'host (server) ma sul client stesso; si ha quindi un capovolgimento di logica. Un'architettura di certo più sofisticata è l'architettura peer to peer (pari a pari) dove ho più processi che cooperano tra di loro ma non esiste un legame di tipo master/slave (non c'è alcuna gerarchia) ad esempio il processo A può richiedere l'attivazione del processo B, ricevuti i dati li può spedire a un processo C richiedendo la sua attivazione e così via. Questa è l'architettura che viene usata nelle macchine parallele.
Quindi posso dire che quando parlo di un'applicazione client-server mi riferisco ad un'architettura del tipo in figura 1, dove ho una rete locale (o geografica), tante macchine intelligenti (client) su cui gira
l'applicazione e infine ho il server su cui gira la restante parte dell'applicazione, in particolare si occupa di gestire le risorse condivise dai client.
La sofisticazione sta nella modalità con cui partiziono l'applicazione, vedi figura 2, anziché avere un sistema monolitico distribuisco l'applicazione tra la macchina client e la macchina server. Vediamo ora quali sono le possibilità di distribuzione intendendo con ciò che posso suddividere l'applicazione in diverse parti, di cui una si occuperà delle modalità di input/output, l'altra parte invece si occuperà della fase elaborativa (algoritmica), avrò inoltre la gestione dei dati (Data Base), quindi occorrerà un DBMS che è parte integrante della mia applicazione nonostante sia una piattaforma sulla quale si poggia l'applicazione.
Il livello di sofisticazione dipenderà da dove vado a tagliare questo discorso, cioè come
vado a di-stribuire i pezzi dell'applicazione una parte sul client e una parte sul server; la logica più banale è quella di dire: la logica di presentazione sta sul client e tutto il resto sul server. Ancora più banale è quella di utilizzare il client come emulatore di terminali e tutto quanto è poi gestito dal server, ma questo non è client-server in quanto sottoutilizzo le macchine client e demando tutto alla macchina server.
Tipodi suddivisione già fornisce un'applicazione client-server di un certo livello di sofisticazione. Altro esempio ancora più sofisticato è quello di avere sul client la presentation logic e una parte del business logic dove la parte algoritmica viene distribuita su 2 o 3 macchine che lavorano in paralle-lo, (sto' facendo riferimento in questo caso ad un modello pari a pari).
Siamo ora giunti al punto in cui nei fatti dobbiamo risolvere (indipendentemente dalla distribuzione dell'applicazione) questo problema: devo porre su di una macchina una procedura chiamante sull'al-tra macchina dello allocare una procedura chiamata e devo fare in modo di costruire un'enviroment intorno al chiamante e al chiamato in modo tale che invece di fare una normale call a procedura, devo far sì che scateni un insieme di attività tali che la call viene fatta ma non alla reale procedura bensì ad una procedura fittizia (in modo tale che il

chiamante non si accorga di nulla) che provvede poi ad implementare tutto il meccanismo di scambio di messaggi che permetterà di attivare un altro processo, sul server, il quale, a sua volta, farà in modo di lanciare la procedura chiamata senza che questa si renda conto che non è stata lanciata direttamente dal chiamante ma bensì in modo indiretto.
Per chiarire meglio le idee vediamo la figura 3. Come si può notare nel contesto del processo client viene eseguito il chiamante, la procedura chiamata viene eseguita nel contesto del processo server (come già più volte esposto), il problema fondamentale è che chi scrive l'applicazione non deve vedere questa situazione ma per lui le cose devono funzionare come se si trovasse di fronte alla macchina centralizzata: quindi esistono ambienti di sviluppo che consentono di fare ciò creando automaticamente degli stub (parti in grigio). Lo stub funge praticamente da interfaccia.
Quanto il chiamante esegue la normale call a procedura ma in realtà, a sua insaputa, viene attivato lo stub che si mette in contatto con lo stub del chiamato, gli passa i parametri con una delle procedure studiate (send e receive), quindi viene attivata la procedura chiamata che non si accorge di essere stata lanciata dallo stub, esegue le sue operazioni quindi restituisce i parametri allo stub, in modo del tutto analogo vengono ritornati i parametri alla procedura chiamante.
Figura 3 Stub del Server Stub del Client
Macchina del Client Macchina del Server
chiamata chiamata
Client Server
ritorno ritorno
KERNEL KERNEL
Impacchetta parametri
Spacchetta parametri
Facciamo ora un passo indietro:
Vediamo un esempio di codice client-server. Figura 10.6 da Tanenbound.
L'analisi di tali procedure è facilitato dai commenti inoltre sono anche auto esplicative.
Suppongo di avere un file-server (utility per creare, leggere, scrivere, cancellare un file). Il server ha una receive bloccante
in testa: al momento dell'accensione il server parte e attende, da buon servito-re, la richiesta. Questo praticamente è il send stub: quando riceve un messaggio viene sbloccato, quindi fa il case su M1 e lancia la routine che esegue il servizio. Fatta l'operazione mette il risultato in un campo di un messaggio il quale viene spedito con una send al processo client. Il fatto caratterizzate è la receive in testa e la send in coda. Notiamo che questo server è solo didattico in quanto un buon processo server deve essere multitrend (con più flussi di controllo in grado di evolvere parallelamente). Vediamo come si articola il client: il client vuole copiare un file, infatti fa read e write consecutivamente. Il codice a questo punto non ha bisogno di altre spiegazioni.
Indirizzamento del server: quando faccio una send devo individuare sia la macchina che il processo, vediamo come posso fare ciò:
- indirizzo assoluto: individuo un solo indirizzo
cui alcune cifre (generalmente quelle di maggior peso) individuano l'host e altre il processo. Questo tipo d'indirizzamento ha l'inconveniente di non fornire alcuna trasparenza (non possono spostare il processo server su un'altra macchina).
Identifico il nodo tramite un indirizzo e il processo tramite una mailbox. Ciò implica però che il server quando nasce deve dichiarare la mailbox. Anche in questo caso non ho trasparenza.
Identifico il server mediante un indirizzo assoluto e chiedo quindi al server di identificare il client in modo simbolico. È un tipo strano, lasciamolo stare.
Vediamo ora il caso più generale: identifico il processo server mediante un nome simbolico, risolvono la corrispondenza individuando nell'ambito della rete un particolare server che è un server di nomi, il quale possiede una tabella che restituisce la posizione dell'host e il numero di processo che mi serve. La stessa cosa è
fatta nell'esempio su visto con la routine INIZIALIZE.
Altra soluzione è quella di mandare i messaggi su tutta la rete, quello che risponde sarà ovviamente il server.
Altra cosa da sottolineare è il protocollo:
Quando faccio una send posso stare su rete locale o su rete geografica, quindi come faccio a decidere il protocollo da utilizzare?
In generale devo utilizzare un protocollo che si colloca al livello 5 della modello ISO-OSI; un messaggio per me rappresenta un qualcosa che deve essere suddiviso in pacchetti per cui il Kernel utilizzerà un protocollo che probabilmente (dipende dallo specifico Kernel) ha una routine che affetta il messaggio, mi crea dei pacchetti e li passa a IP sulla rete. L'applicativo sta a livello 5 chiama TCP/IP che sta a livello 5 fa la send al Kernel e spedisce il messaggio sulla rete.
Posso avere dei problemi nel senso dell'efficienza: se mi trovo su di una rete locale è inutile usare TCP/IP ma posso
usare UDP facendo solo l'affettamento e posso non mettere su IP scendendo diretta-mente a livello 2. Nello sviluppo dell'applicativo in genere non si fanno protocolli ad hoc per mantenere la genericità. Entriamo ora nel merito del problema da cui siamo partiti: Come si realizzano i client-stub e i server-stub; ovvero come tradurre una chiamata a procedura re-mota in modo tale che sembri una normale chiamata a procedura.252 Venerdì 16/5/97 Ing. FASOLINO
Esempio: Produttore-Consumatore
Descrizione del problema: Abbiamo un produttore e un consumatore che condividono un buffer. Abbiamo bisogno di due semafori: uno che indica che il buffer è libero e l'altro che indica che il messaggio è disponibile. Tutto il problema della mutua esclusione si riduce a due wait e due signal invertite tra produttore e consumatore; cioè il produttore fa prima la wait sul suo semaforo, scrive il messaggio nell'area di memoria e poi fa la signal sul semaforo del
<pre>
<code>
consumatore eviceversa per il consumatore.
Realizzazione:
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/ipc.h>
#include <sys/shm.h>/*primitive per la gestione della memoria condivisa*/
#include <sys/sem.h>/*primitive per la gestione dei semafori*/
#define DIM 16 /*dimenisone del buffer*/
#define NVOLTE 16 /*numero di volte in cui viene ripetuto il cicloproduzione/consumo*/
#define SEM_PROD 0 /*identificatore del semaforo del produttore*/
#define SEM_CONS 1 /*identificatore del semaforo del consumatore*/
main () {
pid_t pid; /*identificatore del processo figlio */
int status; /*modalità di terminazione del figlio */
key_t chiave=IPC_CREAT,c_sem=IPC_PRIVATE; /*chiavi cheidentificano rispettivamente, il buffer eil gruppo di due semafori che ci servono */
int id_shared,id_sem; /*identificatori delle due risorse, usati dai dueprocessi per referenziarle */
struct sembuf sem_buf[2]; /*array di due record
</code>
</pre>