IO Multiplex - Dipartimento di Informatica
Transcript
IO Multiplex - Dipartimento di Informatica
Problema a.a. 2003/04 Un’applicazione deve gestire più input simultaneamente I/O Multiplexing Es. il client echo gestisce due flussi di input Standard input (leggere da tastiera) un socket (leggere dal socket) Mentre l’applicazione è bloccata su un descrittore non si accorge di quello che succede sull’altro Prof. Vincenzo Auletta [email protected] http://www.dia.unisa.it/professori/auletta/ In genere una funzione di input si blocca se non ci sono dati da leggere Università degli studi di Salerno Laurea in Informatica può rimanere bloccata per molto tempo l’altro descrittore non può essere controllato Serve un meccanismo per poter esaminare più canali di 1 input contemporaneamente Il primo canale che produce dati viene letto Modelli di I/O Struttura delle Operazioni di Lettura UNIX implementa diversi modelli di I/O In una operazione di lettura da un canale di I/O Modelli sincroni: il processo quando esegue una operazione di lettura o scrittura si blocca fino al completamento dell’operazione Modelli asincroni: il processo può effettuare altre operazioni mentre l’operazione di lettura/scrittura viene effettuata possiamo distinguere due fasi 1. 2. Modelli sincroni 2 il client echo bloccato sulla lettura da stdin non legge il messaggio FIN ricevuto dal server I/O I/O I/O I/O bloccante non bloccante multiplexing guidato da segnali 3 Attesa per i dati da parte del kernel Copia dei dati dal kernel al processo che deve usarli I/O Bloccante Non ci sono dati pronti I/O Multiplexing EWOULDBLOCK 6 elabora i dati ATTESA BLOCCATA Restituisce descrittore pronto Non ci sono dati pronti dati pronti inizia copia setta handler per SIGIO Restituisce OK COPIA copia completata copia completata dati return Non ci sono dati pronti SIGIO dati pronti System call lettura 7 elabora i kernel sigaction system call SIGIO handler COPIA lettura applicazione kernel System call BLOCCATA (aspetta dati su un qualsiasi descrittore) Restituisce OK inizia copia I/O Guidato da Segnali System call select 5 elabora i dati dati pronti System call inizia copia COPIA applicazione System call lettura lettura copia completata ATTESA EWOULDBLOCK BLOCCATA 4 System call lettura COPIA Restituisce OK Non ci sono dati pronti EWOULDBLOCK dati pronti inizia copia elabora i dati kernel System call lettura ATTESA BLOCCATA ATTESA System call lettura applicazione kernel continua esecuzione applicazione I/O non Bloccante Restituisce OK copia completata I/O Asincrono Funzione select applicazione system call lettura asincrona Non ci sono dati pronti return continua esecuzione Restituisce dati pronti inizia copia invia segnale 8 elabora i COPIA handler del segnale #include <sys/select.h> #include <sys/time.h> int select(int maxfd, fd_set readset, fd_set writeset, fd_set exceptionset, struct timeval *timeout); kernel Permette di controllare contemporaneamente copia completata dati uno o più descrittori 9 Timeout della Select Insiemi di descrittori da controllare per individuare un descrittore pronto è una struct timeval struct timeval { long tv_sec; /* numero di secondi */ long tv_usec; /* numero di microsecondi */ }; readset, writeset e exceptionset sono variabili di tipo fd_set aspetta fino a quando un descrittore è pronto timeout = { 3; 5; } aspetta fino al timeout e poi esce anche se non ci sono descrittori pronti alcuni S.O. arrotondano a multipli di 10 microsecondi controlla i descrittori ed esce immediatamente (polling) readset: pronti per la lettura writeset: pronti per la scrittura exceptionset: condizioni di eccezione Arrivo di dati fuori banda su un socket Informazioni di controllo da uno pseudo terminale timeout = 0 10 timeout = { 0; 0; } per lettura, scrittura o gestione errori Insiemi di Descrittori timeout è il tempo massimo che la system call attende -1 se errore 0 se scaduto il timeout numero di descrittori pronti in genere è un array di interi in cui ogni bit rappresenta un descrittore primo elemento dell’array rappresenta descrittori da 0 a 31 secondo elemento dell’array rappresenta descrittori da 32 a 63 11 dettagli implementativi nascosti nella definizione Operazioni su Insiemi di Descrittori void FD_ZERO(fd_set *fdset) void FD_SET(int fd, fd_set *fdset) void FD_CLR(int fd, fd_set *fdset) int FD_ISSET(int fd, fd_set *fdset) Descrittori Pronti La select rileva i descrittori pronti Azzera la struttura fdset Mette a 1 il bit relativo a fd Un socket è pronto in lettura se Mette a 0 il bit relativo a fd Controlla se il bit relativo a fd è a 1 Macro utilizzate per operare sugli insiemi di descrittori La costante FD_SETSIZE è il numero di descrittori in definita in <sys/select.h> (solitamente 1024) in genere si usano meno descrittori [0, maxd] è l’intervallo di descrittori effettivamente utilizzati utilizzati Es. se siamo interessati ai descrittori 1,4,7,9 maxd = 10 12 i descrittori iniziano da 0 13 Il numero di byte liberi nel buffer di spedizione del socket è maggiore di LWM LWM selezionabile tramite opzioni del socket per default è 2048 L’operazione di scrittura restituisce il numero di byte effettivamente passati al livello di trasporto Il socket è stato chiuso in scrittura FD_ZERO(&rset); (1) for( ; ; ) { FD_SET(fileno(fd), &rset); FD_SET(sockd, &rset); (2) maxd = MAX(fileno(fd), sockd) + 1; (3) if( select(maxd, &rset, NULL, NULL, NULL) < 0 ) (4) err_sys("errore nella select"); C’è un errore L’operazione di scrittura ritornerà -1 e errno specificherà l’errore 14 Un socket è pronto per un’eccezione se Arrivo di dati fuori banda L’operazione di lettura ritornerà -1 void str_clisel_echo(FILE *fd, int sockd) { int maxd; fd_set rset; char sendline[MAXLINE], recvline[MAXLINE]; int n; Un’operazione di scrittura genera SIGPIPE Il socket è un socket di ascolto e ci sono delle connessioni completate c’è un errore pendente sul socket Client echo con select – 1 Un socket è pronto in scrittura se il socket è stato chiuso in lettura (è stato ricevuto il FIN) l’operazione di lettura ritorna EOF Descrittori Pronti ci sono almeno LWM (low-water mark) byte da leggere LWM selezionabile tramite opzioni del socket per default è 1 fd_set significato diverso per ciascuno dei tre gruppi 15 1. azzera l’array dei descrittori da controllare in lettura 2. setta il descrittore del socket e del file per lettura 3. calcola il massimo descrittore da controllare 4. invoca la select Condizioni Gestite dal Client echo Client echo&rset) con select – 2 if( FD_ISSET(sockd, ){ (5) if ( (n = readline(sockd, recvline, MAXLINE)) < 0) err_sys("errore nella readline"); if (n == 0) err_quit("str_clisel_echo: server morto prematuramente"); if( fputs(recvline, stdout) == EOF ) err_sys("errore nella fputs"); < 0) } 16 } Condizioni gestite da select in lettura su stdin ed un socket dal socket legge dal socket e stampa su stdout } if( FD_ISSET(fileno(fd), &rset) ) { (6) if( fgets(sendline, MAXLINE, fd) == NULL) return; if( (writen(sockd, sendline, strlen(sendline))) err_sys("errore nella write"); 5. controlla se il socket è leggibile 6. controlla se il file è leggibile legge da stdin e scrive sul socket se il server invia dati il socket è leggibile e readline restituisce > 0 se il server manda FIN il socket è leggibile e readline restituisce 0 se il server manda RST il socket è leggibile e readline restituisce < 0 Client Data o EOF stdin sock et da stdin 17 TCP se l’utente invia dati il file è leggibile e fgets restituisce > 0 se l’utente invia EOF il file è leggibile e fgets restituisce 0Condizioni gestite da select in lettura su stdin ed un socket RST data FIN } Strutture Dati Utilizzate dal Server I/O Multiplexing nel Server Possiamo usare l’I/O multiplexing anche nel Un insieme di descrittori rset (di tipo fd_set) server per ascoltare su più socket contemporaneamente un unico processo iterativo ascolta sia sul socket di ascolto che su tutti i socket di connessione Server Un array di interi client Client1 connd2() Client2 client connd3() 18 contiene i descrittori utilizzati entrambe di dimensione FD_SETSIZE listend() connd1() contiene la lista dei descrittori socket utilizzati dal server (sia di quello di ascolto che quelli di connessione) Client3 0 4 1 -1 2 -1 19 FD_SETSIZE-1 -1 rset fd0 fd1 0 0 fd2 0 maxd + 1 fd3 1 fd4 1 fd5 0 … Esempio – 1 Esempio – 2 Il server accetta due richieste di connessione e Il server crea il socket di ascolto crea i socket di connessione setta il bit corrispondente in rset (supp. sockd = 3) maxd = 3 Il server utilizza select per controllare la leggibilità di tutti i descrittori di rset salva i descrittori del socket nelle prime posizioni di client e setta i bit corrispondenti in rset maxd = 5 Il server utilizza select per controllare la leggibilità di tutti i descrittori di rset client 20 0 -1 1 -1 2 -1 client rset fd0 fd1 0 0 -1 FD_SETSIZE-1 fd2 0 fd3 1 fd4 0 fd5 0 21 maxd + 1 cancella il descrittore da client e azzera il bit corrispondente in rset maxd = 5 Il server utilizza select per controllare la leggibilità di tutti i descrittori di rset client 22 FD_SETSIZE-1 0 -1 1 5 2 -1 -1 1 5 2 -1 FD_SETSIZE-1 rset fd0 fd1 0 0 -1 fd2 0 fd3 1 fd4 1 fd5 1 … maxd + 1 Server echo con select – 1 Il server chiude la connessione sul socket 4 4 … Esempio – 3 0 rset fd0 fd1 0 0 fd2 0 fd3 1 maxd + 1 fd4 0 fd5 1 … 23 int main(int argc, char **argv) { int listend, connd, sockd; int i, maxi, maxd; int ready, client[FD_SETSIZE]; char buff[MAXLINE]; fd_set rset, allset; ssize_t n; struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; /* esegue socket(), bind() e listen() */ maxd = listend; client[0] = listend; maxi = -1; for ( i = 1; i < FD_SETSIZE; i++) client[i] = -1; FD_ZERO(&allset); FD_SET(listend, &allset); (1) (2) (3) (4) (5) 1. inizializza il numero di descrittori 2. inserisce listend in client 3. inizializza il resto dell’array client a –1 4. azzera l’insieme di descrittori registrati per la select 5. registra listend Server echo con select – 2 (5) Server echo con select – 3 for ( ; ; ) { rset = allset; if( (ready = select(maxd+1, &rset, NULL, NULL, NULL)) < 0 ) err_sys("errore nella select"); (6) 5.setta l’insieme dei descrittori da controllare in lettura 6.chiama la select esce quando un descrittore è pronto restituisce il numero di descrittori pronti 24 25 Server echo for( i = 0; i <= maxi; i++ ) { con select (12) – 4 if( (sockd = client[i]) < 0 ) continue; if ( FD_ISSET(sockd, &rset) ) { (13) if ( (n = readline(sockd, buff, MAXLINE)) == 0) { if( close(sockd) == -1 ) (14) err_sys("errore nella close"); FD_CLR(sockd, &allset); client[i] = -1; } else if( writen(sockd, buff, n) < 0 ) (15) err_sys("errore nella write"); if ( --ready <= 0 ) break; } } 26 } } 12. controlla tutti i socket di ascolto se sono leggibili 13. se un socket è leggibile invoca la readline 14. se ha letto l’EOF chiude il socket e lo cancella da client e allset 15. altrimenti fa l’echo if( FD_ISSET(listend, &rset) ) { (7) cliaddr_len = sizeof(cliaddr); if( (connd = accept(listend, (struct sockaddr *) &cliaddr, &cliaddr_len)) < 0) (8) err_sys("errore nella accept"); for(i = 0; i < FD_SETSIZE; i++) (9) if( client[i] < 0 ) { client[i] = connd; break; } if( i == FD_SETSIZE ) (10) err_quit("troppi client"); FD_SET(connd, &allset); (11) if( connd > maxd ) maxd = connd; if( i > maxi ) maxi = i; if( --ready <= 0 ) continue; } 7. controlla se il socket di ascolto è leggibile 8. invoca la accept 9. inserisce il socket di connessione in un posto libero di client 10. se non ci sono posti segnala errore 11. registra il socket ed aggiorna maxd Funzione poll #include <sys/poll.h> int poll(struct pollfd *fdarray, unsigned long nfds, int timeout); Restituisce -1 se errore 0 se scaduto il timeout numero di descrittori pronti simile a select 27 invece di utilizzare gli insiemi di descrittori fd_set utilizza un array di strutture pollfd consente di specificare le condizioni da testare per ogni descrittore Parametri della poll Struttura pollfd struct pollfd { int fd; short events; short revents; }; struct pollfd* fdarray array di strutture che contengono informazioni sui descrittori da controllare e sugli eventi da rilevare unsigned long nfds lunghezza dell’array fdarray per ogni descrittore è possibile definire gli eventi int timeout che devono essere verificati dalla poll timeout della poll specificato in millisecondi < 0 (aspetta per sempre) > 0 (aspetta per il numero di millisecondi specificato) 29 Eventi Rilevati da poll POLLIN POLLOUT POLLERR POLLHOP POLLNVAL eventi rilevati sul descrittore Server echo con poll – 1 /* dati normali o a priorità disponibili in lettura */ /* dati normali disponibili in scrittura */ /* è stato rilevato un errore */ /* è stata rilevata la chiusura della connessione */ /* descrittore non corrisponde ad un socket aperto */ solo POLLIN e POLLOUT possono essere specificati come eventi da rilevare gli altri eventi vengono settati in revents dalla poll() se rileva un errore POSIX 1.g prevede anche altri eventi che non sono supportati da Linux 30 events è un array di flag un flag per ogni tipo di evento quando la poll termina scrive in revents gli = 0 (esce immediatamente) 28 /* descrittore */ /* eventi da controllare */ /* eventi riscontrati dalla poll */ 31 int main(int argc, char **argv) { int listend, connd, sockd; int i, maxi, ready; char buff[MAXLINE]; ssize_t n; struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; struct pollfd client[OPEN_MAX]; /* invoca socket(), bind() e listen() */ client[0].fd = listend; client[0].events = POLLIN; for ( i = 1; i < OPEN_MAX; i++) client[i].fd = -1; maxi = 0; for ( ; ; ) { if( (ready = poll(client, maxi + 1, -1)) < 0 ) (3) err_sys("errore nella poll"); (1) (2) 1. registra listend in client[0] e specifica che deve essere controllato in lettura 2. inizializza il resto dell’array client a –1 3. invoca la poll Server echo con poll – 3 Server echo con poll – 2 32 if( client[0].revents & POLLIN ) { (4) cliaddr_len = sizeof(cliaddr); if( (connd = accept(listend, (struct sockaddr *) &cliaddr, &cliaddr_len)) < 0) (5) err_sys("errore nella accept"); for(i = 1; i < OPEN_MAX; i++) (6) if( client[i].fd < 0 ) { client[i].fd = connd; break; } if( i == OPEN_MAX ) (7) err_quit("troppi client"); client[i].events = POLLIN; (8) if( i > maxi ) maxi = i; (9) if( --ready <= 0 ) continue; } 4. controlla se il socket di ascolto è leggibile 5. invoca la accept 6. inserisce il socket di connessione in un posto libero di client 7. se non ci sono posti segnala errore 8. registra gli eventi da rilevare sul nuovo descrittore 9. aggiorna maxi (11) if ( (n = readline(sockd, buff, MAXLINE)) < 0) { if( errno = ECONNRESET ) { (12) if( close(sockd) == -1 ) err_sys("errore nella close"); client[i].fd = -1; } else err_sys("errore nella readline"); } else if( n == 0 ) { if( close(sockd) == -1 ) err_sys("errore nella close"); client[i].fd = -1; } else if( writen(sockd, buff, n) < 0 ) (13) 33 err_sys("errore nella writen"); if ( --ready <= 0 ) break; } 10. controlla tutti i socket di ascolto se sono leggibili 11. se un socket è leggibile o ha un errore pendente invoca la readline 12. se ha letto l’EOF o un RST chiude il socket e lo cancella da client 13. altrimenti fa l’echo Attacchi Denial of Service Soluzioni per Attacchi DOS Server iterativi che utilizzano l’I/O multiplexing Usare un singolo processo per ogni client Utilizzare un timeout sulle operazioni di I/O Usare I/O non-bloccante sono soggetti ad attacchi di tipo DOS (Denial of Service) un client malizioso può far in modo che il server non possa rispondere alle richieste degli altri client Esempio 34 for( i = 1; i <= maxi; i++ ) { (10) if( (sockd = client[i].fd) < 0 ) continue; if ( client[i].revents & (POLLIN | POLLERR) ) { Un client si connette, spedisce un solo byte (che non sia un newline) e non fa più nulla Il server rileva che il socket di connessione è leggibile ed invoca la readline la readline si blocca in attesa di un newline Il server è bloccato e nessun altro client riceverà il servizio 35 Stop-and-wait e Input Batch Shutdown della Connessione Il client echo opera in modalità stop-and-wait: Quando il client legge l’EOF dal file di chiude il Spedisce una linea di input e si blocca in attesa della risposta del server echo particolarmente inefficiente quando l’utente deve inviare molti dati socket solo in scrittura (half-close) Se l’utente fornisce l’input in modalità batch il L’operazione di half-close è implementata dalla programma segnala un errore 36 (es. client_sel_echo 127.0.0.1 < nome_file) quando la fgets legge l’EOF il client chiude la connessione e termina il server potrebbe ancora richiedere ritrasmissioni di segmenti o inviare risposte system call shutdown() 37 Funzione shutdown Client echo con shutdown – 1 #include <sys/socket.h> int shutdown(int sd, int howto); void str_cliselshut_echo(FILE *fd, int sockd) { int maxd, stdineof; fd_set rset; char sendline[MAXLINE], recvline[MAXLINE]; int n; Restituisce -1 se errore 0 se OK l’operazione della funzione dipende dal valore di howto 38 SHUT_RD: chiude il socket solo in lettura non riceve più nulla ed ignora anche i dati nel buffer eventuali altri dati sono riscontrati da TCP ma scartati SHUT_WR: chiude il socket solo in scrittura non scrive più nulla ma invia i dati attualmente nel buffer chiusura effettuata indipendentemente dal valore del reference counter SHUT_RDWR: entrambe le due opzioni rimane in ascolto per le risposte del server risponde alle eventuali richieste di ritrasmissioni di segmenti o di invii di ACK 39 stdineof = 0; (1) FD_ZERO(&rset); for( ; ; ) { if( stdineof == 0 ) (2) FD_SET(fileno(fd), &rset); FD_SET(sockd, &rset); maxd = MAX(fileno(fd), sockd) + 1; if( select(maxd, &rset, NULL, NULL, NULL) < 0 ) (3) err_sys("errore nella select"); 1. inizializza stdineof a 0 2. se l’utente non ha inviato l’EOF setta fd leggibile 3. invoca la select Client echo con shutdown – 2 0) if( FD_ISSET(sockd, &rset) ) { (4) 4. controlla se il if ( (n = readline(sockd, recvline, MAXLINE)) < (7) if( fgets(sendline, MAXLINE, fd) == NULL) { (8) socket è stdineof = 1; if( shutdown(sockd, SHUT_WR) < 0 ) err_sys("errore nella shutdown"); FD_CLR(fileno(fd), &rset); (9) continue; leggibile err_sys("errore nella readline"); if( n == 0 ) { 5. se il server ha (5) inviato l’EOF ed if( stdineof == 1 ) il client aveva return; già chiuso la else connessione err_quit("str_clisel_echo: server morto esce altrimenti prematuramente"); segnala errore } 6. altrimenti if( fputs(recvline, stdout) == EOF ) (6) stampa su err_sys("errore nella fputs"); } 40 Client echo &rset) con) { shutdown – 3 if( FD_ISSET(fileno(fd), 0)(10) } if( (writen(sockd, sendline, strlen(sendline))) < err_sys("errore nella write"); } } stdout } 41 7. controlla se fd è leggibile 8. se ha letto l’EOF setta stdineof ed esegue la shutdown chiude la connession e in scrittura 9. cancella fd da rset 10. altrimenti scrive sul socket