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