Scaricare la presentazione
La presentazione è in caricamento. Aspetta per favore
PubblicatoMatteo Roberti Modificato 11 anni fa
1
1 Reti di Calcolatori Esercitazione 1 Implementazione di un superserver Unix di rete Vedi: W.R. Stevens, Unix Network Programming, Prentice Hall Copyright © 2006-2013 by D. Romagnoli & C. Salati Alma Mater Studiorum - Universita' di Bologna Sede di Cesena II Facolta' di Ingegneria
2
2 Servizi nel mondo di Unix Uno host internet supporta tipicamente svariati servizi di rete (e.g. ftp, telnet, TFTP,...). Ogni servizio di rete, concorrente o sequenziale, dovrebbe comportarsi secondo uno degli schemi visti a lezione. essere attivato dal sistema alla partenza e rimanere in permanenza attivo in attesa di richieste di clienti; richieste che potrebbero pero anche non arrivare mai! (in gergo Unix un processo che si comporta in questo modo e chiamato daemon, tradotto impropriamente, come demone) In realta la struttura dei servizi standard nel mondo Unix non e quella degli schemi visti a lezione per i servizi di rete. Un servizio standard Unix ha la struttura di un filtro Unix. Quindi, quello che vorremmo davvero e che anche i servizi di rete fossero strutturati come dei normali filtri Unix. Cosi potremmo anche esportare automaticamente in rete tutti i servizi locali implementati come filtri Unix (e.g. il servizio cat ).
3
33 Struttura canonica di un server CO concorrente // trascuriamo la trattazione degli errori int sockfd, newSockfd; sockfd = socket(...); bind(sockfd,...); listen(sockfd, 5); for (;;) { newSockfd = accept(sockfd,...); if (fork() == 0) { // processo figlio/clone close(sockfd); doYourJob(newSockfd); close(newSockfd); exit(0); } else { // processo padre close (newSockfd); }
4
44 Struttura canonica di un server CO sequenziale // trascuriamo la trattazione degli errori int sockfd, newSockfd; sockfd = socket(...); bind(sockfd,...); listen(sockfd, 5); for (;;) { newSockfd = accept(sockfd,...); doYourJob(newSockfd); close(newSockfd); } Il server sa che opera su risorse reali di tipo socket Il server sa su quali risorse opera Il server e attivo in permanenza in attesa di nuovi clienti
5
55 Struttura canonica di un server CO: in realta … Il server e composto di 2 parti: 1.Una parte generica, indipendente dal particolare servizio che viene offerto, di attesa di nuovi clienti 2.Una parte specifica, dipendente dal particolare servizio che viene offerto, e che riguarda il servizio del (linterazione con il) singolo cliente Parte generica del server: Vede e gestisce esplicitamente laccesso al Servizio di Trasposto tramite system call specifiche dellAPI socket E sempre viva, in attesa di nuovi clienti (nuove richieste di servizio) E lei che determina se il servizio e sequenziale o concorrente Parte specifica del server: Vede laccesso al Servizio di Trasporto soltanto in modo opaco, tramite luso del file descriptor associato al socket di comunicazione con il cliente, e utilizzando solo le system call generiche read() e write() Il file descriptor tramite cui interagire con il cliente e lunico parametro di input della parte specifica La durata della sua vita e collegata a quella del servizio offerto al cliente cui e associata La parte generica potrebbe essere messa a fattor comune tra tutti i server
6
6 Struttura di un filtro Unix.1 E creato dinamicamente, quando e invocato dal cliente. Non e attivo in permanenza in attesa di nuovi clienti. E chi attiva il servizio (e.g. una shell) che determina se il servizio stesso sara eseguito in modo concorrente o sequenziale. Serve un singolo cliente, poi muore. Interagisce con il mondo esterno tramite standard input (in realta il file descriptor = 0) e standard output (in realta il file descriptor = 1), che accede tramite operazioni di read() e write(). Al momento della sua attivazione i file descriptor 0 e 1 sono gia aperti e collegati a risorse reali del sistema: e su queste risorse che il filtro opera, rispettivamente, in lettura ( read() ) e in scrittura ( write() ). E chi attiva il servizio che nel momento in cui lo fa (e.g. tramite command line/shell) definisce il significato dei file descriptor 0 e 1. Il processo filtro eredita dal processo padre i file descriptor 0 e 1 gia aperti sulle risorse di sistema che esso dovra utilizzare.
7
7 Struttura di un filtro Unix.2 Il filtro ignora quali siano le risorse che accede tramite i file descriptor 0 e 1. Il filtro ignora anche quale sia il tipo delle risorse che accede tramite i file descriptor 0 e 1: File vero e proprio Device driver Pipe... E quindi, anche: socket! Il filtro sa che puo operare su queste risorse tramite operazioni di read() e write(). Se la risorsa di sistema e un TSAP, questo deve essere: Gia bind -ato. Gia connesso ad un pari remoto.
8
8 Struttura di un filtro Unix: esempio duso #cat copia standard input in standard output #cat > dstFile copia standard input in dstFile #cat srcFile copia srcFile in standard output visualizza il contenuto di srcFile #cat srcFile > dstFile copia srcFile in dstFile #cat > dstFile Questo testo sara inserito in dstFile. ^D # # e il prompt della shell in esecuzione
9
99 Struttura canonica di un filtro Unix // linterazione con il mondo esterno avviene // tramite i due file descriptor 0 e 1 // che il server-filtro eredita gia aperti e // associati alle risorse di sistema opportune // dal processo padre doYourJob(0, 1); exit(0); N.B.: I file descriptor 0 e 1 sono di norma associati a risorse reali diverse ma niente impedisce che esse siano associati ad una stessa risorsa (che in questo caso deve essere capace di supportare contemporaneamente operazioni di read e di write!). In realta poi, un socket rappresenta davvero 2 risorse separate: Uno stream di byte in ingresso Uno stream di byte in uscita
10
10 Struttura di un server CO e filtri Unix int sockfd, newSockfd; sockfd = socket(...); bind(sockfd,...); listen(sockfd, 5); for (;;) { newSockfd = accept(sockfd,...); if (fork() == 0) { // processo figlio/clone close(sockfd); // la parte specifica del server e come un // filtro Unix, con 2 fd come parametri di // ingresso, input fd e output fd doYourJob(newSockfd /*in*/, newSockfd /*out*/); close(newSockfd); exit(0); } else { // processo padre close (newSockfd); }
11
11 Server di rete e filtri Unix.1 La parte generica dei server di rete puo essere messa a fattor comune: Gestisce lattesa di nuovi clienti per tutti i servizi di rete. Determina se lattivazione di ciascuno di questi servizi deve avvenire in modo sequenziale o concorrente. Attiva un server specifico quando vede arrivare dalla rete una richiesta di nuovo servizio indirizzata a lui. (quindi deve conoscere a priori il file name del programma eseguibile che implementa il servizio!) Si comporta rispetto ai clienti sulla rete come fa una shell rispetto alloperatore seduto al terminale! I servizi specifici di rete sono implementati come filtri Unix cosi come i servizi specifici locali. A questo punto, in realta, non ce nessuna differenza tra un servizio di rete e un servizio locale. cat puo ad esempio essere utilizzato per realizzare un servizio di eco TCP! Come?
12
12 Server di rete e filtri Unix.2 Quando si interagisce con i servizi tramite shell, si identifica il servizio che si vuole attivare con il suo nome. Indicando il file name del programma eseguibile (filtro Unix) che implementa il servizio e che deve essere messo in esecuzione. Quando si interagisce con i servizi tramite il superserver (generico) di rete, si identifica il servizio che si vuole attivare attraverso la sua porta (il suo TSAP) well known. Ovviamente rimane comunque necessario, per il superserver di rete, conoscere quale e il file name del programma eseguibile (filtro Unix) che implementa il servizio che e stato richiesto, altrimenti come potrebbe attivarlo? E ovvio che il superserver non puo implementare direttamente nessun servizio, nemmeno se questo e sequenziale. Come la shell il superserver non conosce la semantica e il protocollo dei diversi servizi che offre. Il superserver deve comunque rimanere in attesa di nuove richieste di servizio, relative ad altri servizi.
13
13 File e file descriptor in Unix Nel mondo Unix tutte le risorse del sistema, indipendentemente da quale sia il loro tipo reale, sono accedute in modo uniforme: 1.Tramite luso di un file descriptor (sono viste come dei file). 2.Tramite uno stesso insieme di system call (circa). Un file descriptor e una handle che consente ad un processo di accedere alla risorsa reale associata a quel file descriptor. 1.Lassociazione di una risorsa reale ad un file descriptor avviene (di norma) tramite linvocazione della system call open() (ma un filtro eredita i file descriptor su cui operare dal processo padre). 2.La disassociazione di una risorsa reale da un file descriptor avviene tramite linvocazione della system call close(). Implementativamente un file descriptor e costituito da un intero non negativo di piccole dimensioni ( a small, nonnegative integer, Linux man page ): attualmente con valore compreso tra 0 e 1023. Dal punto di vista del sistema operativo il file descriptor e un indice nel vettore User File Descriptor Table contenuto nel descrittore di ciascun processo.
14
14 La User File Descriptor Table Nella User File Descriptor Table sono contenuti i riferimenti a tutte le risorse reali utilizzate (accedibili) dal processo in un dato momento. Quando un processo esegue una system call open() Unix cerca nella User File Descriptor Table la prima entry libera a partire da 0 e la associa alla risorsa reale riferita nella system call open(). Quando un processo esegue una system call close () Unix dichiara libero il file descriptor riferito nella chiamata della system call. Eseguendo la system call fork() tutte le risorse del sistema che erano accedibili dal processo padre rimangono accedibili anche dal processo figlio, e cio utilizzando gli stessi valori di file descriptor. Al momento dellesecuzione della system call fork() la User File Descriptor Table del processo padre viene copiata nella User File Descriptor Table del processo figlio. Dal punto di vista dellutente e possibile clonare un file descriptor su un altro file descriptor tramite una delle due system call dup() e dup2().
15
15 Unix system programming int dup(int fd); fd è il file descriptor da duplicare Leffetto di una invocazione di dup() è di copiare lelemento fd della tabella dei file aperti nella prima posizione libera (quella con lindice minimo tra quelle disponibili). Restituisce il nuovo file descriptor (quello destinazione delloperazione di copiatura), oppure -1 (in caso di errore). int dup2(int oldfd, int newfd); oldfd è il file descriptor da duplicare. newfd è il file descriptor in cui deve essere duplicato. Leffetto di una invocazione di dup2() è di copiare lelemento oldfd della tabella dei file aperti nellelemento newfd della stessa tabella. N.B. se newfd era gia in uso al momento dellinvocazione di dup2() esso viene implicitamente chiuso (tramite close() ). Restituisce newfd oppure -1 (in caso di errore).
16
16 Protocollo di attivazione di un filtro Unix Per attivare un servizio standard (un filtro Unix), il processo padre (e.g. una shell) deve duplicare se stesso tramite linvocazione della system call fork() per poi mettere in esecuzione nel suo clone figlio il codice eseguibile del programma filtro. Per attivare correttamente un filtro Unix e quindi sufficiente che il processo padre: Si cloni tramite chiamata alla system call fork(). Nel processo figlio dup -lichi sui file descriptor 0 e 1 i file descriptor (le risorse reali) su cui vuole fare lavorare il server-figlio. (e chiuda tutti gli altri file descriptor che non interessano al server- figlio) Nel processo clone figlio metta in esecuzione il codice del filtro tramite invocazione della system call exec(). N.B.: I file descriptor 0 e 1 sono come due parametri formali a cui il chiamante associa come parametri attuali le risorse reali che il filtro Unix deve utilizzare per input e output.
17
17 La system call fork() int fork(void); Crea un nuovo processo che e un clone esatto del processo chiamante (padre). Il nuovo processo (figlio): Esegue lo stesso codice del processo padre e, al termine della system call, ha il program counter posizionato allistruzione successiva a quella contenente linvocazione della system call fork(). Come spazio dati ha una copia di quello del processo padre. Condivide le risorse di sistema accedibili dal processo padre: la sua User File Descriptor Table e una copia di quella del processo padre. La system call fork() ritorna: In caso di errore, un numero negativo al processo padre (il processo figlio non e nemmeno stato creato). In caso di terminazione corretta (esecuzione con successo), Il PID del processo figlio al processo padre, 0 al processo figlio. Cio permette di capire, al ritorno dalla fork(), se si e il processo padre oppure il processo figlio.
18
18 Il superserver inetd di Unix Il superserver inetd e un demone (un processo attivato dal sistema alla partenza e che rimane sempre attivo). inetd ha 3 scopi (il terzo conseguenza del secondo): 1.Evitare che tutti i diversi server di rete del sistema debbano essere attivi in permanenza. inetd rimane in attesa delle richieste dei clienti al posto dei (di tutti i) singoli server, e attiva un server solo al momento in cui ce effettivamente una richiesta di servizio pendente per lui. 2.Consentire ai singoli server di ignorare linterazione con la rete, comportandosi (essendo implementati) come normali filtri Unix. 3.Consentire quindi di esportare sulla rete qualunque servizio implementato come filtro Unix (in particolare, servizi gia esistenti). Per fare questo inetd deve conoscere quali sono i servizi di rete definiti sul sistema: Su quale servizio di trasporto (TCP o UDP) e su quale porta well known e offerto un servizio. Quale e il file eseguibile (filtro) che implementa il servizio. Il superserver acquisisce le informazioni sui servizi che deve supportare da un file di configurazione.
19
19 Il file di configurazione del superserver inetd Assumeremo che la sintassi del file di configurazione sia la seguente: Ogni riga del file descrive un servizio. Ogni servizio e descritto da una riga che ha la sintassi seguente: ::= e il pathname del codice eseguibile che implementa il servizio (come filtro). puo assumere i valori tcp o udp. e il numero della porta well known che identifica il servizio sulla rete. indica se il servizio deve essere offerto in forma sequenziale ( wait ) o concorrente ( nowait ). specifica una lista di parametri (N.B.: uguali per ogni invocazione) che il superserver deve passare al filtro che implementa il servizio nel momento in cui lo attiva.
20
20 Il file di configurazione del superserver: esercizio Nota che ci potrebbero essere diversi parametri che caratterizzano un Servizio Applicativo dal punto di vista del suo utilizzo del Servizio di Trasporto. Quali potrebbero essere ad esempio dei parametri significativi di Trasporto specifici di ciascun Servizio Applicativo nel caso di servizi Applicativi basati sul Servizio di Trasporto TCP? servizi Applicativi basati sul Servizio di Trasporto UDP? Come dovrebbe essere modificata di conseguenza la struttura del file di configurazione del superserver?
21
21 Esempio di file di configurazione del superserver inetd /bin/cat tcp 10001 nowait /bin/csh tcp 10002 nowait -i /bin/java tcp 10003 wait prog1 arg1 La prima linea descrive il servizio cat (copiatura da file a file) su TCP che utilizza la porta 10001 e funziona in modalita concorrente. N.B.: questo servizio essendo file di input e di output associati ad un unico socket implementa sulla rete un servizio di eco. La seconda linea descrive il servizio shell (csh) su TCP che utilizza la porta 10002, funziona in modalita concorrente e ha come parametro di ingresso -i. La terza linea descrive il servizio java prog1 su TCP che utilizza la porta 10003, funziona in modalita sequenziale e ha come parametro di ingresso arg1. Attenzione: per i servizi implementati in Java il nome delleseguibile che deve essere messo in esecuzione dal superserver e quello della Java VM, alla quale occorre passare il nome del servizio effettivo (prog1 nel nostro caso), piu gli eventuali parametri.
22
22 Servizi sequenziali e servizi concorrenti.1 Ogni servizio, implementato come un filtro, ignora la nozione di servizio sequenziale/concorrente. E il superserver che e responsabile, per ogni servizio, di implementare questa nozione, in base allindicazione relativa presente nel file di configurazione. N.B.: e ovvio che il superserver non puo comunque entrare nei dialoghi applicativi relativi ai diversi servizi, visto che non ne conosce nemmeno le regole. Se un servizio e definito come concorrente il superserver, anche dopo avere attivato una istanza del servizio, continuera ad attendere sulla porta well known del servizio richieste di altri clienti. Se un servizio e definito come sequenziale il superserver, una volta attivata una istanza del servizio, non attendera piu alcun evento (nuova richiesta di servizio) sulla porta well known del servizio fino a che listanza precedente non sia terminata. Nel caso di un servizio sequenziale il superserver deve avere la possibilita di capire quando il filtro che ha attivato per implementare il servizio ha terminato la sua esecuzione.
23
23 Servizi sequenziali e servizi concorrenti.2 Il superserver, dopo avere attivato una istanza di un servizio S, deve comunque rimanere attivo, anche se S e un servizio sequenziale: Il superserver deve comunque gestire nuove richieste, provenienti dalla rete, destinate ad S o ad un altro servizio. Se S e un servizio concorrente, listanza di S e il superserver saranno eseguiti in parallelo e in modo completamente indipendente. Nessuno dei due ha piu bisogno di avere a che fare con laltro. Se pero S e un servizio sequenziale il superserver deve essere informato della terminazione dellistanza di S che ha attivato. S e implementato da un normale filtro, sia che esso sia concorrente sia che esso sia sequenziale! In Unix ci sono normali meccanismi di sistema operativo che consentono ad un processo figlio di informare il processo padre della propria terminazione e anche di ritornargli un valore (intero)! L esecuzione della system call exit(n) o dellistruzione return(n) nella funzione main() del processo figlio lo terminano e consentono di informare il padre di cio e di passargli il valore n.
24
24 Sincronizzazione del superserver con i processi figli.1 In realta il superserver deve comunque gestire in qualche modo anche la morte di figli che implementano un servizio concorrente, altrimenti questi rimarrebbero nel sistema come zombi. Basta pero che il processo padre prenda atto della morte di un processo figlio per evitare che questo diventi uno zombi. Il superserver puo prendere atto della morte di un processo figlio invocando la system call wait(). La system call wait() permette di ricavare il PID (quindi lidentita) di un figlio che muore e anche la ragione di questa morte. Linterfaccia della funzione e: int wait(int* err); Il valore di ritorno e il PID del processo figlio che e morto. Il parametro di ritorno err fornisce lo stato di terminazione del figlio (in errore e, se del caso, quale, o meno). err e il valore ritornato dal processo figlio come argomento della system call exit() o dellistruzione return() che lo ha terminato.
25
25 Sincronizzazione del superserver con i processi figli.2 Poiche la system call wait() e bloccante, il superserver deve chiamarla solo quando e sicuro della morte (gia avvenuta) di un processo figlio. Altrimenti lintero superserver rimarrebbe bloccato in attesa della morte di un figlio, e verrebbe ad assumere il comportamento di superserver super-sequenziale! Il superserver e informato (in modo asincrono) della morte di un processo figlio perche, quando questo evento accade, il sistema operativo gli invia il segnale (interrupt software) SIGCLD (detto anche SIGCHLD ). Per poter catturare effettivamente il segnale, il superserver deve registrare sul sistema operativo, tramite chiamata della system call signal(), il suo interesse per levento (se non lo fa, il segnale non gli e inviato).
26
26 Unix system programming void (* signal(int sig, void (*func)()))(int); sig è lintero (o il nome simbolico) che individua il segnale da gestire il parametro func è un puntatore a una funzione che indica lazione da associare al segnale; in particolare func può: puntare alla routine di gestione dellinterruzione (handler) valere SIG_IGN (nel caso di segnale ignorato) valere SIG_DFL (nel caso di azione di default) ritorna un puntatore a funzione: al precedente gestore del segnale SIG_ERR (-1), nel caso di errore Una funzione che e uno handler di segnali per un processo deve avere il seguente prototipo: void signalHandler(int sig);
27
27 Struttura di uno handler di segnali Il parametro di ingresso della di un signal handler e lidentificatore numerico del segnale che e scattato: cio permette di registrare la stessa funzione per piu segnali e di gestire poi uno switch in funzione del segnale che si e manifestato. Nel nostro caso lo handler deve gestire solo il segnale SIGCLD. Per fare questo nel proprio corpo invochera la system call wait() che, dato il contesto della chiamata, sara non bloccante. Nel caso di morte di un figlio relativo ad un servizio concorrente la presa datto del fatto, tramite chiamata a wait(), e gia sufficiente ad evitare che il figlio morto diventi uno zombi. Nel caso di morte di un figlio relativo ad un servizio sequenziale il superserver dovra anche riattivare il servizio, dovra cioe tornare ad aspettare richieste di nuovi clienti sulla relativa porta well known. Lo handler viene attivato in seguito alla ricezione di uno dei segnali a cui e stato associato, e viene visto dal sistema come una thread secondaria del processo, in aggiunta a quella principale (che potrebbe eventualmente essere stata interrotta!). In uscita un signal handler non ritorna alcun valore.
28
28 Aspetti particolari dei segnali e di SIGCLD Un aspetto particolare della chiamata alla funzione wait() e che essa permette di rimuovere i processi terminati dallo stato di zombi. Infatti tutti i processi, quando terminano, vengono posti dal sistema operativo nello stato di zombi, in cui rimangono o fino alla morte del processo che li ha creati o fino a che questo prende atto dellevento tramite la chiamata della funzione wait(). Bisogna anche osservare che la ricezione di un segnale da parte di un processo interrompe lesecuzione di una system call eventualmente in corso (bloccata): in questo caso la system call termina con codice di errore EINTR. Poiche il superserver deve gestire il segnale di SIGCLD deve prendere in considerazione e gestire questa possibilita. La cosa e vera in particolare per la system call select(), che e bloccante e che costituisce il cuore del superserver. Infatti il superserver deve rimanere in attesa di richieste, contemporaneamente, sulle porte well known di tutti i servizi che esso supporta (e che sono attivi in quel momento).
29
29 Gestione del ritorno da select()... rs = select(..., NULL); if (rs<0) { if (errno == EINTR) { // morte di un figlio, non e un // errore vero, ma non ce neanche // nessun socket pronto... } else { // e un errore vero... } // tutto OK e ci sono socket pronti...
30
30 socket() bind() listen() (solo per TCP ) Per ogni servizio listato nel file di configurazione Test per lettura select() accept() (solo per TCP ) fork() close socketfd (solo per TCP) Se wait-flag==wait elimina dalla select il fd associato al servizio padre close di tutti i fd diversi da quello del socket; dup(sfd), dup(sfd) e dup(sfd); figlio execle() del server-program Per gestire la morte di un figlio. Alla morte di un figlio se il servizio era di tipo wait occorre reinserire nella select il socket-descriptor associato al servizio signal() Ciclo infinito Schema del superserver inetd
31
31 La system call exec() Negli schemi standard dei server di rete concorrenti visti a lezione il server padre, quando deve servire un nuovo cliente, esegue il fork() di un processo figlio, che puo immediatamente procedere a fornire il servizio al cliente perche il suo codice e identico a quello del padre. Nel caso del superserver, pero, il padre non contiene il codice capace di implementare ciascuno dei singoli servizi che esso offre, ma solo quello per la loro attivazione. Come puo il superserver arrivare a fornire il servizio richiesto dal cliente? Ci riesce perche (come una shell) il clone figlio del superserver non continua ad eseguire lo stesso codice del superserver ma, dopo una fase iniziale di housekeeping, mette in esecuzione nel proprio contesto un nuovo programma, quello del filtro Unix che implementa effettivamente il servizio desiderato dal cliente. N.B.: e evidente che tutto questo meccanismo si basa sulla nozione di porta well known (che indentifica il servizio desiderato)!
32
32 La system call exec() La funzione execle() permette di mettere in esecuzione, nel contesto del processo chiamante (cioe mantenendo il possesso delle stesse risorse reali possedute dal processo chiamante), un ben determinato programma. Noi la useremo nel processo clone-figlio per specializzarlo a fornire il servizio specifico per il quale e stato creato (sia per servizi concorrenti che per servizi sequenziali). La system call execle() e una funzione ad argomenti variabili di cui i primi due sono obbligatori e sono: nome del file contenente il programma eseguibile che si vuole eseguire, comprensivo dellintero path; nome del programma. segue poi la lista degli argomenti terminata da un NULL, infine lultimo argomento e un puntatore allenviroment. Quindi la chiamata: execle(/bin/csh, csh, -i, NULL, env); mette in esecuzione il programma csh passandogli il parametro -i.
33
33 La system call execle() int execle(char *pathname, char *arg0, …, char *argn, (char *)NULL, char **envp); La funzione, in effetti, ritorna al chiamante solo in caso di errore! Se non ce errore, leffetto della system call e di passare il controllo alla prima istruzione del programma contenuto nel file pathname. Dal punto di vista C la modalita di passaggio degli argomenti arg0.. argn equivale a passare un array NULL-terminato di puntatori, come e in effetti il parametro di ingresso argv della funzione main(). arg0 per convenzione deve essere il nome del programma, come indicato alla pagina precedente. Lultimo parametro e un puntatore ad una esplicita environment list, che e a sua volta implementata come un array NULL-terminato di puntatori a stringa. Quello che faremo e fare ereditare al processo figlio lo stesso environment del superserver.
34
34 Struttura del superserver.1 1.Il parametro di ingresso envp della funzione main() int main (int argc, char **argv, char **envp); ci consente di procurarci il riferimento envp allenvironment da utilizzare in execle(). 2.La funzione main() del programma apre in lettura il file di configurazione. 3.Per ogni linea di tale file ricava i parametri del servizio descritto in quella linea e li salva in una opportuna struttura dati: tutti i servizi sono inizialmente definiti come attivi. 4.Per ogni servizio, crea un socket e lo bind() -a alla porta well known su cui quel servizio e offerto (quella che e indicata nel file di configurazione, e il cui numero deve essere congruente con le indicazioni IANA). 5.Se il servizio utilizza il trasporto TCP il superserver mette anche in stato listen() -ing il socket relativo.
35
35 Struttura del superserver.2 7.Anche il file descriptor del socket well known deve essere salvato nella struttura dati di cui sopra. 8.Il superserver chiama la system call signal() per registrare il proprio interesse a gestire il segnale SIGCLD. 9.Terminata linizializzazione il superserver entra in un ciclo infinito in cui, tramite chiamata della system call select(), attende larrivo di nuove richieste di servizio da nuovi clienti; quando queste arrivano, attiva per ciascuna di esse (per il tramite di un corrispondente processo figlio) lopportuno server specifico (che e/deve essere strutturato come un filtro Unix). 10.Allinterno del ciclo (reinizializza e) riempie il parametro di ingresso readfds della select() con tutti e soli i file descriptor dei servizi attivi. Se un servizio nowait e gia in corso di fornitura, esso non puo essere nuovamente attivato in questo momento, e quindi non e attivo.
36
36 Struttura del superserver.3 11.Al termine della select(), trascurando i casi di errore, scandisce il parametro di ritorno readfds (N.B. quindi readfds e un parametro value-result!) che lista tutti i file descriptor sui quali e pendente una nuova richiesta di servizio da parte di un nuovo cliente. Per ciascuna di queste richieste mette in funzione il server specifico. 12.Se il servizio specifico utilizza il servizio di trasporto TCP, chiama la system call accept() per prendere in carico la nuova connessione e ricavare il corrispondente socket descriptor nfd : ad esso e associata la nuova richiesta di servizio. 13.Se il servizio specifico utilizza il servizio di trasporto UDP, registra come file descriptor nfd sul quale interagire con il cliente il file descriptor della relativa porta well known (ma e possibile? No!). Ma nel caso di un servizio UDP concorrente lo schema visto a lezione prevede che il servizio sia effettivamente fornito su una porta diversa da quella well known, altrimenti sarebbe impossibile utilizzare la porta well known per aspettare nuovi clienti! Allora?
37
37 Struttura del superserver.4 14.Chiama la system call fork() per generare il processo che fornira effettivamente il servizio alla richiesta corrente. 15.Nel ramo del padre, se il servizio considerato e di tipo TCP : Chiude il socket nfd. Se il servizio considerato e di tipo wait allora registra nella struttura dati di registrazione dei servizi il PID del processo figlio e marca il servizio come non attivo (se il servizio considerato e di tipo nowait non ce niente di specifico da fare). Passa a considerare la prossima richiesta pendente o, se non ce ne sono piu, ritorna allinizio del ciclo principale. 16.Nel ramo del figlio: Chiude i file descriptor 0 e 1. Duplica sui file descriptor 0 e 1 il file descriptor nfd. Chiude tutti i file descriptor del processo ad eccezione di 0, 1 e 2. Chiama la system call execle() per attivare il servizio richiesto.
38
38 Struttura dati di registrazione dei servizi Ci sono svariate modalita per salvare le informazioni relative ai servizi che devono essere supportati. La migliore e quella di utilizzare una coda linkata (doppiamente) di strutture (ogni struttura descrive un singolo servizio). Questa soluzione ha il vantaggio di non porre limiti al numero massimo di servizi supportabili dal superserver Unaltra possibilita e quella di utilizzare un vettore di strutture. Questa soluzione (quella consigliata) ha lo svantaggio di limitare ad un valore massimo il numero di servizi supportabili.
39
39 Esempio di struttura dati di registrazione dei servizi.1 typedef struct SrvElmT { chartr[4]; // tcp se il servizio usa il trasporto TCP, // udp se usa il trasporto UDP charconc[7]; // wait se il servizio e' di tipo sequenziale, // nowait altrimenti charport[8]; charsrvFullName[500]; // pathname completo del file di codice eseguibile // del programma charsrvName[20]; // nome del programma // continua...
40
40 Esempio di struttura dati di registrazione dei servizi.2 //... Continua: intnumArgs; // numero di argomenti da passare alla execle, // 0 in caso di assenza di essi charargv1[20]; charargv2[20]; charargv3[20]; charargv4[20]; charargv5[20]; intfd; // socket descriptor associato alla porta well // known del servizio intpid; // significativo solo per servizi di tipo wait (?) } SrvElmT, *SrvElmTP; SrvElmTsrvArray[20]; // massimo 20 servizi
41
41 Servizi UDP I servizi UDP non possono essere trattati in modo analogo a quelli TCP. La ragione di fondo e che mentre in TCP un socket rappresenta una connessione (e quindi una relazione gia instaurata con un singolo cliente), in UDP esso rappresenta una porta (e quindi ce leventualita di potere/dovere interagire su quella porta con piu clienti, vecchi e nuovi). Diverse conseguenze sul superserver: Un servizio concorrente UDP non puo essere trattato in modo analogo ad un servizio concorrente TCP. Se mantenesse il servizio attivo, alla prossima select() il superserver potrebbe trovare gia pendente come nuova richiesta quella del servizio appena attivato, perche il datagram che aveva richiesto il servizio alliterazione precedente potrebbe essere ancora da consumare. Per relizzare un servizio UDP sequenziale come un filtro Unix (ma con interazioni a messaggio, non a stream!) bisogna gestire il fatto che il server filtro deve interagire solo con un singolo cliente e non puo farlo esplicitamente (cioe eliminando lui le interazioni non volute sulla porta well known: i filtri Unix non lo fanno!).
42
42 Servizio CL concorrente.1 SuperServer p socket server di associazione porta UDP server well-knownClient UDP socket client porta UDP effimera
43
43 Servizio CL concorrente.2 SuperServer p socket server di associazione porta UDP server well-knownClient UDP socket client porta UDP effimera socket server di comunicazione porta UDP server effimera
44
44 Servizio CL concorrente.3 SuperServer p socket server di associazione porta UDP server well-knownClient UDP socket client porta UDP effimeraServer Figlio socket server di comunicazione porta UDP server effimera fork () Il server figlio, appena attivato, dopo la exec(), potrebbe andare subito a leggere il datagram pendente nel socket well known, ma chi dice che arriverebbe a farlo prima della nuova select() di SuperServer p E comunque, questo non sarebbe il comportamento di un filtro Unix!
45
45 Servizi UDP implementati tramite filtri Unix.1 Il servizio deve interagire con un singolo cliente, senza conoscerne necessariamente lidentita. Non deve nemmeno sapere che sta interagendo con il cliente tramite un socket UDP! Poiche deve poter ignorare lidentita del cliente remoto (infatti opera sui file descriptor 0 e 1 tramite operazioni di read() e write() ) bisogna che il socket UDP che e associato ai file descriptor 0 e 1 sia gia connesso al cliente! E il superserver che deve passare al server specifico una porta UDP gia connessa! Ma per un server UDP concorrente la porta con cui interagire con il cliente non puo essere la porta well known, deve essere una porta effimera. Quindi e questa porta che deve essere passata al server specifico come associata ai file descriptor 0 e 1, non la porta well known. Nota che la disciplina di utilizzo delle porte che va bene per un server UDP concorrente si puo applicare altrettanto bene ad un server sequenziale!
46
46 Servizio CL concorrente.1 SuperServer p socket server di associazione porta UDP server well-knownClient UDP socket client porta UDP effimera
47
47 Servizio CL concorrente.2 SuperServer p socket server di associazione porta UDP server well-knownClient UDP socket client porta UDP effimera socket server di comunicazione porta UDP server effimera
48
48 Servizio CL concorrente.3 SuperServer p ? socket server di associazione porta UDP server well-known ?Client UDP socket client porta UDP effimera socket server di comunicazione porta UDP server effimera recvfrom(MSG_PEEK?)
49
49 Servizio CL concorrente.4 SuperServer p ? socket server di associazione porta UDP server well-known ?Client UDP socket client porta UDP effimera socket server di comunicazione porta UDP server effimera connect(socket client)
50
50 Servizio CL concorrente.5 SuperServer p ? socket server di associazione porta UDP server well-known ?Client UDP socket client porta UDP effimeraSuperServer f ? socket server di comunicazione porta UDP server effimera fork ()
51
51 Servizio CL concorrente.6 SuperServer p ? socket server di associazione porta UDP server well-known ?Client UDP socket client porta UDP effimeraServer Figlio socket server di comunicazione porta UDP server effimera Il server figlio, dopo la exec(), non ha comunque piu il messaggio nella sua memoria dati centrale E ovviamente, secondo il protocollo di attivazione di un server-filtro Unix, non ha nemmeno piu accesso alla porta well known Come fa a vedere il messaggio? exec()
52
52 Servizi UDP implementati tramite filtri Unix.2 Pero il datagram del cliente che ha portato allattivazione del servizio UDP e registrato nella porta well known, non nella porta effimera che vogliamo passare al server specifico. Come puo il server specifico andarlo ad accedere? Oltretutto la sua presenza nella porta well known puo costituire un problema per il superserver che, nel caso di un servizio UDP concorrente, deve utilizzare la porta well known per aspettare richieste di nuovi clienti e non deve considerare 2 volte una stessa richiesta. Quindi bisogna che il superserver estragga il datagram UDP dalla porta well known e lo inserisca nella porta effimera prima di attivare il servizio. N.B.: Lutilizzo di una seconda porta (effimera) per interagire con il client e strettamente necessario solo per realizzare un servizio UDP concorrente, ma torna comodo anche nel caso di un servizio UDP sequenziale.
53
53 Servizio CL concorrente.1 SuperServer p socket server di associazione porta UDP server well-knownClient UDP socket client porta UDP effimera
54
54 Servizio CL concorrente.2 SuperServer p socket server di associazione porta UDP server well-knownClient UDP socket client porta UDP effimera socket server di comunicazione porta UDP server effimera
55
55 Servizio CL concorrente.3 SuperServer p socket server di associazione porta UDP server well-knownClient UDP socket client porta UDP effimera socket server di comunicazione porta UDP server effimera recvfrom()
56
56 Servizio CL concorrente.4 SuperServer p socket server di associazione porta UDP server well-knownClient UDP socket client porta UDP effimera socket server di comunicazione porta UDP server effimera sendto(porta server effimera)
57
57 Servizio CL concorrente.5 SuperServer p socket server di associazione porta UDP server well-knownClient UDP socket client porta UDP effimera socket server di comunicazione porta UDP server effimera connect(socket client)
58
58 Servizio CL concorrente.6 SuperServer p socket server di associazione porta UDP server well-knownClient UDP socket client porta UDP effimeraSuperServer f socket server di comunicazione porta UDP server effimera fork ()
59
59 Servizio CL concorrente.7 SuperServer p socket server di associazione porta UDP server well-knownClient UDP socket client porta UDP effimeraServer Figlio socket server di comunicazione porta UDP server effimera exec()
60
60 bind(), INADDR_ANY, e porte effimere Quando il superserver utilizza la system call bind() per asssociare il socket server UDP di comunicazione ad una porta effimera, assegna al parametro *myaddr il valore 0.0.0.0:0 (cioe INADDR_ANY:0 ). Notare che lindirizzo 0.0.0.0:0 non e un indirizzo valido (vero), e che non e utilizzabile come indirizzo destinazione in una operazione di rete: INADDR_ANY non e un indirizzo di rete valido. La porta numero 0 non esiste. Effettuare il bind allindirizzo 0.0.0.0:0 non significa chiedere effettivamente lassociazione a questo indirizzo (che non e un indirizzo valido), ma chiedere lassociazione a una qualunque porta effimera libera e a tutti gli indirizzi IP della macchina. Nella system call bind() il parametro *myaddr e di ingresso, non di ingresso uscita. Ma allora come posso sapere a quale porta effimera il socket e stato effettivamente associato, cosi da potergli inviare il datagram UDP del cliente? Vedi dispense, parte teoria. E quale indirizzo IP devo utilizzare come destinazione della sendto() ? Ovviamente 127.0.0.1! (perche?)
61
61 Servizi UDP implementati tramite filtri Unix.3 1.Il superserver acquisisce lindirizzo del cliente e il datagram UDP che ha attivato il servizio eseguendo una system call recvfrom() sul socket della porta well known. 2.Il superserver crea un nuovo socket e lo bind -a ad una porta effimera. 3.Il superserver invia il datagram UDP che ha portato allattivazione del servizio alla porta effimera appena creata. N.B.: per inviare il datagram il superserver puo utilizzare sia la porta effimera appena creata (la stessa che e destinazione del datagram!) che la porta well known del servizio UDP! 4.Il superserver connette la porta effimera appena creata allindirizzo del cliente. N.B.: lordine di queste due operazioni e fondamentale perche se il superserver connettesse la porta effimera prima di inviarle il datagram vedrebbe il suo datagram venire cestinato!
62
62 Servizi UDP implementati tramite filtri Unix.4 5.A questo punto il superserver puo comportarsi con un servizio UDP cosi come si comporta con un servizio TCP, utilizzando il socket descriptor della porta effimera come nfd. Vedi punto 13 della struttura del superserver. 6.Nota che avendo gia estratto dalla porta well known del servizio UDP il datagram che ha portato a questa attivazione del servizio, il superserver puo, nel caso di un servizio UDP concorrente, continuare a considerare attivo il servizio anche nelle iterazioni successive, andando ad attendere immediatamente larrivo di nuovi datagram di richiesta.
63
63 Servizi UDP implementati tramite filtri Unix.5 Dal punto di vista del server specifico lunico vincolo che ce, nel caso di utilizzo del servizio di trasporto UDP, e che bisogna inviare o ricevere un intero PDU applicativo tramite ogni singola operazione di write() e read(). Il servizio specifico non puo per esempio leggere da standard input singoli caratteri se il pari gliene manda molti in un singolo datagram. Ma se un server specifico avesse bisogno di sapere chi ce dallaltra parte della rete? Ovvio che in questo caso dovrebbe anche sapere a priori che sta interagendo con un cliente remoto tramite il servizio di trasporto UDP. Basterebbe che chiamasse la funzione getpeername(). N.B.: lo stesso discorso si applica ovviamente anche ad un server TCP (che ovviamente interagisce con il pari tramite una connessione. La cosa e meno scontata per un server UDP!).
64
64 Servizi TCP e setsockopt() Immaginiamo il seguente scenario: Il superserver riceve una richiesta per il servizio concorrente TCP S definito sulla port well known WK, e di conseguenza attiva il processo figlio P S per offrire il servizio. Il servizio e ovviamente offerto tramite una connessione che ha come end-point locale la porta WK. Mentre il processo P S e ancora attivo il superserver termina, per un motivo o per laltro, e viene riattivato. Alla partenza il superserver deve ovviamente appropriarsi di tutte le porte well known che gestisce. Questo costituisce un problema per la rete? Ci sono ambiguita? Questo scenario vi ricorda qualcosa descritto parlando di setsockopt() ? Durante limplementazione del superserver tenete conto di quanto detto qui.
65
65 Esercizio 1: superserver Realizzare un superserver di rete. Il superserver deve essere configurato tramite file di configurazione. Il superserver deve essere in grado di gestire sia servizi TCP che servizi UDP, sia servizi concorrenti (nowait) che servizi sequenziali (wait), indipendentemente dal tipo di servizio di trasporto utilizzato. Verificare il superserver interagendo con i servizi TCP cat e csh, e con un servizio UDP di echo (da realizzare a partire dagli esempi presentati nelle dispense). Come modulo cliente per il test dei servizi TCP si puo utilizzare un normale Hyperterm di Windows (o un client TELNET). Come modulo cliente per il test dei servizi UDP si puo utilizzare il client di echo visto a lezione con gli opportuni adattamenti. Attenzione: esiste il pericolo di una corsa critica tra la morte di un server sequenziale figlio e la registrazione di questo server come gia attivo da parte del superserver! Opzionale: Cercate in qualche modo di gestire questa situazione!
Presentazioni simili
© 2024 SlidePlayer.it Inc.
All rights reserved.