HostState_t State; /* stato server */ - GENERALITA': ============ SERVER ------ Il server e' stato realizzato secondo uno schema concorrente a singolo processo, basato sulla gestione in multiplex dell'I/O ottenuta mediante utilizzo della funzione "select()". Questo evita di dover gestire l'accesso simultaneo al catalogo di biblioteca, e risulta essere piu' efficiente rispetto ad una soluzione multiprocesso in termini di utilizzo delle risorse di sistema. Le operazioni di scrittura sono effettuate in modo non bloccante (ottenuto mediante fcntl()) per garantire l'avanzamento delle richieste ed il ritorno al trattamento della select() anche in caso di riempimento dei buffer di comunicazione. Questo permette di evitare condizioni di blocco, sia per arresto della ricezione da parte del client, che durante fasi di trasmissione reciproca tra server. Il meccanismo di inoltro della risposta sfrutta viene quindi messo in moto dalla disponibilita di buffer sia in caso locale che remoto. Struttura Protocollo Il protocollo consiste in due funzioni che gestiscono l'input da socket, e due l'output. Le ReadMessage/WriteMessage si occupano della lettura/scrittura di un singolo messaggio da/su buffer della socket. Le HandleRead/HandleWrite gestiscono la logica di protocollo interno ed il controllo delle abilitazioni. L'alternarsi delle varie fasi di lettura e scrittura sulla stessa socket nel corso dell'elaborazione della query viene ottenuto mediante l'utilizzo di due funzioni: "SetWriteToken" e "SetReadToken" che impostano i bit delle rispettive strutture fds, e di variabili di stato associate alla socket. Per ogni singola fase di scrittura/lettura ci si preoccupa di disabilitare inizialmente il token e di abilitare correttamente in funzione dello stato corrente la fase successiva, Il collegamento tra server avviene attraverso registrazione esplicita ai server gia' attivi e comunicazione dei propri indirizzo e porta. La connessione utilizzata in questa fase viene poi mantenuta attiva allo scopo di segnalare quando interrota la caduta di uno dei due server. Da notare che i dati ed il buffer relativi ad ogni socket sono contenuti in una struttura di tipo SockInfo, i cui dettagli sono illustrati nel seguito. Passiamo a descrivere il ciclo tipico di utilizzo: la risposta a richiesta del client. Dopo la accept il token viene dato alla socket del client in lettura. Si effettua la lettura della query e si inizializza la SockInfo per la ricerca locale, abilitata mediante WriteToken. Ogni fase della ricerc locale viene pilotata dal completamento della precedente col meccanismo di abilitazione del token in scrittura. Terminata la ricerca locale si modifica la Sockinfo in modo da proseguire con la remota. Qui per ogni server remoto si inoltra la richiesta e si passa il token in lettura alla socket del server. Per ogni messaggio di risposta ricevuto vi e' un passaggio del token alla write su client, il cui completamento determina il passaggio del token nuovamente alla read da server per la successiva risposta. Ogni ciclo di interrogazione del server remoto termina quando si verifica la condizione di EOF, dando avvio alla fase di connect sul server successivo. Alla chiusura dell'ultimo server si chiude anche la connessione col client, segnalandogli cosi' il completamento della query. Le operazioni di aggiornamento del catalogo sono svolte dal bibliotecario tramite stdio, trattato in select in modo tale da permettere l'elaborazione concorrente. CLIENT ------ Il meccanismo illustrato sopra permette di realizzare un client che non sia a conoscenza delle problematiche della rete ma che semplicemente realizza la query tramite una operazione di write e successive letture delle risposte. La versione da noi realizzata alterna l'input a fasi di ricezione risposte. Questo protocollo rende comunque possibile espandere a piacere il client senza che siano necessarie modifiche al server. - PROTOCOLLO CONTROLLO SERVER: ============================ Server A Server N ======== ======== 1) listen() 2) listen() 3) connect() 4) accept() 5) write(control) 6) read(control) ... ... Caso A) ..... 7) close() 8) read(EOF) 9) close() Caso B) ..... 7) close() 8) read(EOF) 9) close() - PROTOCOLLO RICHIESTA/RISPOSTA: ============================== Client Server A Server N ====== ======== ======== 1) listen() 1) listen() ... ... 2) connect() 3) accept() 4) write(query) 5) read(query) 6) write(reply)* 7) read(reply)* per ogni server <-------------------------------+ 8) connect() | 9) accept() | 10) write(query) | 11) read(query) | 12) write(reply)* | 13) read(reply)* | | 14) write(reply)* | | 15) read(reply)* | 16) close() | >-------------------------------+ 17) close() 18) close() - STRUTTURE DATI: =============== SocketInfo_t:------------------------- Viene utilizzata come descrittore per le socket attive, mantenendone le informazioni di stato relative. In modo particolare, viene evidenziata la presenza di un buffer di I/O differenziato per ogni socket, che permette l'avanzamento parallelo di piu' richieste. Queste strutture vengono allocate dinamicamente e gestite tramite un array statico di puntatori, indicizzato dall'identificativo della socket. typedef struct SocketInfo_tag { SocketType_t Type; /* tipo di socket distinta in client/server/control */ SocketState_t State; /* stato operazioni i/o messaggi da/verso buffer */ SocketControl_t Control; /* stato avanzamento richiesta (se client) */ int Request; /* indice avanzamento scansione server */ long Offset; /* Offset avanzamento ricerca locale su file */ int Linked; /* socket destinazione (per server) */ int NoForward; /* flag per inibizione inoltro ad altri server */ Libro_t Libro; /* struttura dati query/riposte */ int Len; /* lunghezza messaggio */ int ToRead; /* numero bytes rimanenti da leggere */ int ToWrite; /* numero bytes rimanenti da scrivere */ MessageText_t Text; /* buffer messaggio */ } SocketInfo_t; HostInfo_t: ----------------------- Contiene alcuni dati relativi ai server collegati, tra cui l'indirizzo della socket in listen. Queste strutture sono memorizzate in un array statico a cui si accede in modo sequenziale durante lo svolgimento delle richieste. I dati del primo elemento si rifericono al server locale. typedef struct HostInfo_tag { int Inactive; /* flag annullamento host */ HostState_t State; /* stato server */ Biblio_t Biblio; /* nome descrittivo biblioteca */ int Socket; /* socket (connected/accepted) */ struct sockaddr_in Peer; /* host:port peer se accepted */ } HostInfo_t; Array Fds: ---------------------- Vengono utilizzati a coppie quattro array fd_set, due per le operazioni di lettura e due per la scrittura, differenziati in Active e Select, da utilizzarsi rispettivamente come salvataggio e input/output verso la select. Libro_t: ------------------- Definisce la struttura delle informazioni applicative, e viene utilizzata sia come formato di richiesta che per le risposte all'utente. typedef struct Libro_tag { char Titolo[LEN_TITOLO]; /* titolo libro */ char Autore[LEN_AUTORE]; /* autore libro */ char Anno[LEN_ANNO]; /* anno edizione libro */ } Libro_t; - MODULI: ======= CLIENT: client.c /* main e funzioni varie del client */ SERVER: main.c /* Parsing parametri ed inizializzazione */ server.c /* Gestione del lato server */ COMUNI: socket.c /* Strutture e funzioni gestione socket */ message.c /* Trattamento messaggi e conversione da/a buffer */ libro.c /* Gestione accesso catalogo biblioteca */ support.c /* Funzioni di utilita' varia */ - CENNI SULL'UTILIZZO: ==================== Avvio Server server -h attiva l'aiuto in linea. Attivare il primo server con server -f -b Es: server -f c1.txt -b biblio1 Attivare i successivi server comunicandogli anche porta ed indirizzo degli altri server precedentemente annotati. Es: server -f c3.txt -b biblio3 host1:port1 host2:port2 Funzione Bibliotecario A server attivo la pressione dell'enter elenca i comandi disponibili. Client Attivare col comando client. Quando richiesto immettere host e port di un server attivo, ed immettere la chiave di ricerca. Es: ( basato sui file c1.txt c2.txt c3.txt forniti a corredo ed utilizzati per i test ) client alla richiesta 'host:', immettere l'indirizzo di un server alla richiesta 'port:', immettere la porta di un server alla richiesta di chiave digitare: itolo 1 sul terminale appaiono di seguito i libri i cui titoli sono: 1Titolo 1 1Titolo 10 2Titolo 1 2Titolo 10 3Titolo 1 3Titolo 10 completi di tutti i dati nonche' del server di provenienza. NOTA: Chiaramente l'ordine di inoltro ai server puo' varire e di conseguenza l'ordine delle risposte.