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.