Académique Documents
Professionnel Documents
Culture Documents
}
Cos per intercetto tutti i segnale SIGPIPE indipendentemente da quale
socket (usato dal processo) lo provoca.
2) utilizzare al posto della write la system call send, in cui pu essere
specificato di non generare il segnale SIGPIPE ma di restituire -1
indicando come errore EPIPE.
In questo modo, solo il segnale SIGPIPE di quella particolare
invocazione viene intercettato.
I/O su Socket TCP: write() (2)
101c
I/O su Socket TCP: send() (3)
101d
int send (int fd, const void *buf, size_t count, int flags);
cerca di scrivere fino a count byte nel buffer di sistema
corrispondente al file descriptor fd perch siano poi trasmessi.
I byte vengono letti dal buffer puntato da buf.
- Se count zero la send restituisce zero e non
scrive nulla.
- Se count maggiore di zero viene effettuata
la scrittura e viene
restituito il numero di byte scritti.
- Se viene restituito -1 accaduto un errore e
viene settata la
variabile errno con un valore che indica
lerrore.
Il comportamento viene influenzato dal valore del parametro flags,
il cui valore pu essere 0 oppure viene assegnato mediante OR bit a
bit delle seguenti costanti: MSG_OOB, MSG_DONTWAIT,
MSG_NOSIGNAL.
Se il valore di flags zero la send si comporta come la write.
Se viene specificato MSG_DONTWAIT la send non si blocca
bens scrive il numero di byte possibili nel buffer di sistema e
termina restituendo il numero di byte scritti, eventualmente zero.
Se viene specificato MSG_NOSIGNAL la send non solleva
leccezione di tipo SIGPIPE e quindi non rischia di far terminare il
processo. Al contrario, in caso di chiusura anormale della
connessione, restituisce -1 e setta la variabile errno al valore
EPIPE.
I/O su Socket TCP: (2)
TCP Output
102
Ogni socket TCP possiede un buffer per loutput (send buffer) in cui
vengono collocati temporaneamente i dati che dovranno essere trasmessi
mediante la connessione instaurata. La dimensione di questo buffer pu
essere configurata mediante unopzione SO_SNDBUF.
Quando unapplicazione chiama write() per n bite sul socket TCP, il
kernel cerca di copiare n byte dal buffer dellappl. al buffer del socket.
Se il buffer del socket e pi piccolo di n byte, oppure gi parzialmente
occupato da dati non ancora trasmessi e non c spazio sufficiente,
verranno copiati solo nc<n byte, e verr restituito dalla write il numero
nc di byte copiati.
Se il socket ha le impostazioni di default, cio di tipo bloccante, la fine
della routine write ci dice che sono stati scritti sul buffer del socket
quegli nc byte, e possiamo quindi riutilizzare le prime nc posizioni del
buffer dellapplicazione. Ci non significa affatto che gi i dati siano
stati trasmessi allaltro end-system.
Per attendere di ricevere almeno un byte, o leggere i byte gi arrivati, si
usa la gi descritta
ssize_t read (int fd, void *buf, size_t n);
Per attendere di ricevere TUTTI i byte richiesti, si implementa la
seguente funzione readn, che
-restituisce -1 in caso di errore, e setta errno
-restituisce il numero di byte letti se laltro end system chiude la
connessione
- restituisce il numero di byte chieste (e letti) se tutto ok.
ssize_t readn (int fd, char *buf, size_t n)
{
size_t nleft; ssize_t nread;
nleft = n;
while (nleft > 0) {
if ( (nread = read(fd, buf+n-nleft, nleft)) < 0) {
if (errno != EINTR)
return(-1); // restituisco errore
}
else if (nread == 0) {
// EOF, connessione chiusa, termino
// esce e restituisco il numero di byte letti
break;
}
else // continuo a leggere
nleft -= nread;
}
return(n - nleft); // return >= 0
}
I/O su Socket TCP: utility (1)
103a
I/O su Socket TCP: utility (2)
Per attendere di consegnare al buffer di sistema TUTTI i byte richiesti
restituisce -1 in caso di errore e setta errno
restituisce il numero di byte da inviare ed inviati, se tutto OK.
ssize_t writen (int fd, const char *buf, size_t n)
{
size_t nleft; ssize_t nwritten; char *ptr;
ptr = buf; nleft = n;
while (nleft > 0)
{
if ( (nwritten = send(fd, ptr, nleft, MSG_NOSIGNAL )) < 0) {
if (errno == EINTR) nwritten = 0; /* and call write() again*/
else return(-1); /* error */
}
nleft -= nwritten; ptr += nwritten;
}
return(n);
}
Per consegnare da zero ad n byte da trasmettere, ma senza attendere:
restituisce -1 in caso di errore, se no restituisce il numero di byte scritti
ssize_t write_nowait (int fd, const char *buf, size_t n)
{
int nwritten;
do {
nwritten=send ( fd, buf, n, MSG_DONTWAIT|MSG_NOSIGNAL);
}while( (nwritten<0) && (errno==EINTR) );
return(nwritten);
}
103b
Per primo viene fatto partire il server, poi viene fatto partire il client che
chiede la connessione al server e la connessione viene instaurata.
Nellesempio (ma non obbligatorio) il client spedisce una richiesta al
server, questo risponde trasmettendo alcuni dati. Questa trasmissione
bidirezionale continua fino a che uno dei due (il client nellesempio)
decide di interrompere la connessione,
e tramite la close() chiude la
connessione. Infine il server
chiude a sua volta la connessione.
Interazioni tra Client e Server TCP
104
ephemeral
port
La funzione fork usata per duplicare un processo.
#include <unistd.h>
pid_t fork (void);
restituisce -1 in caso di errore. Se tutto va a buon fine restituisce 0
nel processo figlio ed un valore maggiore di zero (il pid process
identifier) nel processo padre.
Questa funzione viene chiamata nel processo (padre=parent), e
restituisce il risultato in due diversi processi (padre e figlio).
Se il figlio vuole conoscere il pid del padre user la funzione getppid().
I descrittori di file e di socket aperti dal padre prima della fork
sono condivisi col figlio, e possono perci essere usati da entrambi
per lI/O.
Inoltre, per come funziona la funzione close(), possibile per uno dei
processi (padre o figlio) chiudere una connessione aperta condivisa
(dai due processi) senza con questo impedire allaltro processo di
continuare ad utilizzare la connessione.
La fork viene usata per generare delle repliche del processo server, per
gestire in parallelo le connessioni che via via vengono instaurate.
funzione fork()
117
Un server banale in attesa su una porta TCP serializza le varie richieste
di apertura di una connessione dei client permettendo la connessione ad
un solo client per volta.
Server TCP pi evoluti invece, come i web server, una volta risvegliati
dalla richiesta di una connessione da parte di un client, effettuano una
fork() duplicando se stessi (il processo). Il processo figlio viene dedicato
a servire la connessione appena instaurata, il processo padre attende
nuove richieste di connessione sulla stessa porta.
A livello di interfaccia socket, questa situazione si ottiene cos:
Il server chiama la accept passando come argomento il socket
listening (socket in ascolto), e subito dopo chiama la fork.
il processo padre chiude il connected socket, e ripete la accept sul
listening socket, in attesa della prossima richiesta di connessione.
Invece il descrittore del connected socket (socket connesso) restituito
dalla accept resta aperto per il figlio, e viene utilizzato da questo
utilizzato per gestire lI/O con la connessione. Quando infine il figlio
termina il suo lavoro e chiude il connected socket con la close(), la
connessione viene finalmente terminata con la sequenza di FIN.
pid_t pid; int listenfd, connfd;
listenfd = socket (AF_INET, SOCK_STREAM, 0);
bind ( listenfd, (struct sockaddr*) &Local, sizeof(Local));
listen(listenfd, 10 );
for( ; ; ) {
connfd = accept ( listenfd, (struct sockaddr*) &Cli, &len);
pid = fork();
if ( pid !=0) close(connfd); /* processo padre */
else { /* processo figlio */
close(listenfd);
usa_nuova_connessione_indicata_da_newsockfd(connfd);
close(connfd); exit(0);
}
}
Server TCP Concorrenti (1)
118
Vediamo graficamente cosa capita a livello di TCP e di porte.
Entriamo un p nei dettagli del programma appena visto, per quanto
riguarda la scelta delle porte.
Consideriamo la situazione pi complicata, quella di unapplicazione
server collocata su un host con pi interfacce di rete, che vuole
permettere le connessioni su una certa porta convenzionale (la 6001)
da parte di client che accedono a una qualsiasi delle due interfacce
del server.
listenfd = socket (AF_INET, SOCK_STREAM, 0);
/* collega il socket ad un indirizzo IP locale e una porta TCP locale */
memset ( &Local, 0, sizeof(Local) );
Local.sin_family = AF_INET;
Local.sin_addr.s_addr = htonl(INADDR_ANY); /* wildcard */
Local.sin_port = htons(6001);
bind ( listenfd, (struct sockaddr*) &Local, sizeof(Local));
/* accetta max 100 richieste simultanee di inizio conness., da adesso */
listen(listenfd, 100 );
/* accetta la prima conness. creando un nuovo socket per la conness. */
for( ; ; ){
connfd = accept(listenfd, (struct sockaddr*) &Cli, &len);
pid = fork();
if ( pid !=0 ) /* processo padre */
close ( connfd );
else { /* processo figlio */
close ( listenfd ); /* chiuso il listening socket */
il figlio usa il connected socket ()
close ( connfd );
exit(0);
}
}
119
Server TCP Concorrenti (2)
120
La quaterna (IP locale, Port Number locale , IP remoto, Port Number remoto)
che identifica univocamente una connessione TCP viene di solito
chiamata socket pair.
SERVER CLIENT
IP = IP_A1 IP = IP_B
IP = IP_A2 port = 2222
Server TCP Concorrenti (3)
listening socket
( *, 6001 , * , * )
connected socket
( IP_A2, 6001 , IP_B , 2222 )
IP_A2
IP_A1
listening socket
( *, 6001 , * , * )
IP_A1
IP_A2
dopo la listen(), prima della accept()
dopo la accept() , prima della fork()
121
Server TCP
Concorrenti
(4)
listening socket
( *, 6001 , * , * )
connected socket
( IP_A2, 6001 , IP_B , 2222 )
IP_A2
IP_A1
listening socket
( *, 6001 , * , * )
IP_A1
IP_A2
dopo la fork()
dopo la close(connfd) del padre, e la close(listenfd) del figlio
connected socket
( IP_A2, 6001 , IP_B , 2222 )
padre
figlio
padre
figlio
Unapplicazione di rete pu avere la necessit di accedere
contemporaneamente a pi tipi di input e di output, ad es. input di tipo
streamdalla tastiera, input di tipo streamda rete eventualmente da pi
connessioni contemporaneamente, input di tipo datagramda rete
anchesso eventualmente da pi socket contemporaneamente.
Esistono vari modelli di I/O disponibili in ambiente Unix:
I/O Bloccante
I/O Non Bloccante
I/O tramite Multiplexing
I/O guidato da signal
I/O asincrono (per ora poco implementato)
Consideriamo per ora il modello di I/O standard, per cui quando viene
effettuata una richiesta di I/O mediante una chiamata ad una primitiva di
tipo read() o write(), la primitiva non restituisce il controllo al chiamante
fino a che loperazione di I/O non stata effettuata, ovvero o la read() ha
letto da un buffer del kernel dei dati, o la write() ha scritto dei dati
dellutente in un buffer del kernel.
Le funzioni di I/O finora analizzate sono state descritte nel loro
funzionamento proprio secondo la modalit standard (bloccante).
Il problema che, quando lapplicazione effettua una read su un certo
descrittore di file o di socket, se i dati non sono gi presenti nella coda
del kernel dedicata a quel descrittore, lapplicazione rimane bloccata
fino a che i dati non sono disponibili, ed impossibile leggere dati
eventualmente gi pronti sugli altri descrittori di file o socket.
Analogamente per la write() se i buffer del kernel in cui si deve scrivere
il dato gi occupato.
Un problema analogo, caratteristico dei socket, si ha quando
lapplicazione effettua una chiamata alla funzione accept(), che
restituisce il controllo solo quando una richiesta di inizio connessione
disponibile (o meglio gi stata soddisfatta ed nella coda delle
connessioni stabilite).
I/O Multiplexing
122
Quello che serve un modo di ordinare al kernek di avvertirci quando,
in un insieme di canali di I/O, si verifica una condizione di
disponibilit allI/O, che pu essere cos definita:
1) o dei dati sono pronti alla lettura in una coda del kernel, e si pu
accedere mediante una read che restituir immediatamente il controllo al
chiamante con i dati letti,
2) o una coda di output del kernel si svuotata ed pronta ad accettare
dati in scrittura mediante una write,
3) o si verificato un errore in uno dei dispositivi di I/O e quindi una
read() o write() restituirebbe il valore -1,
4) o quando un socket listening disponibile a fornire immediatamente
un connected socket in risposta ad una chiamata di tipo accept(), perch
ha ricevuto una richiesta di connessione da un client,
Posix.1g mette a disposizione una primitiva, detta select(), che:
1) permette di effettuare attesa contemporaneamente su pi tipi di canali
di I/O in modo da essere risvegliati quando uno di questi canali
disponibile allI/O in lettura o scrittura o ha verificato un errore, o
ancora nel caso dei socket quando sono disponibili i cosiddetti dati fuori
banda (usati solo in casi particolarissimi perch meno utili di quanto il
nome farebbe presupporre),
2) e permette di fissare un limite allattesa, in modo da essere risvegliati
se non accade nulla allo scadere di un certo tempio limite. Questultima
possibilit pu collassare in unattesa di durata nulla, ovvero permette di
non effettuare attesa alcuna, ma solo di controllare lo stato istantaneo
dei vari canali e restituire subito il controllo al chiamante.
I/O Multiplexing
123
124
La funzione select permette di chiedere al kernel informazioni sullo
stato di descrittori di tutti i tipi, riguardo a loro disponibilit in lettura
scrittura o condizioni eccezionali, e di specificare quanto tempo al
massimo aspettare.
#include <sys/select.h>
int select ( int maxfdp1, fd_set *readset, fd_set *writeset,
fd_set *exceptset, const struct timeval *timeout);
La funzione restituisce -1 in caso di errore,
0 se il timeout fissato scaduto,
altrimenti restituisce il numero di descrittori che hanno raggiunto la
condizione di disponibilit loro richiesta.
Lultimo argomento timeout dice al kernel quanto aspettare al massimo,
ed una struttura cos fatta:
struct timeval {
long tv_sec; /* secondi */
long tv_usec; /* microsecondi */
}
con questa struttura noi possiamo specificare alla select:
attesa infinita: attesa fino a che una delle condizioni si verificata. Si
passa un puntatore timeout nullo.
attesa limitata: attesa il numero di secondi e microsecondi specificati
nella struttura puntata dal puntatore timeout passato. In caso di timeout
la select restituisce 0.
attesa nulla: ritorna subito al chiamante dopo avere fotografato la
situazione dei descrittori. SI specifica settando a zero tv_sec e tv_usec.
NB: la timeval specifica microsecondi, ma i kernel non riescono a
discriminare solitamente sotto i 10 msec.
funzione select()
125
int select ( int maxfdp1, fd_set *readset, fd_set *writeset,
fd_set *exceptset, const struct timeval *timeout);
I tre argomenti centrali di tipo fd_set*, readset writeset exceptset
specificano i descrittori che si vuole controllare rispetivamente per
verificare disponibilit alla lettura scrittura o eccezioni (out of band data
only).
Il tipo fd_set (descriptor set) un array di interi, in cui ogni bit
corrisponde ad un descrittore. Se il bit settato il descrittore viene
considerato appartenente al set, altrimenti non vi appartiene.
Esistono delle macro per settare o resettare gli fd_set.
void FD_ZERO (fd_set *fdset); clear di tutti i bit di fd_set
void FD_SET ( int fd, fd_set *fdset); setta il bit fd in fd_set
void FD_CLR ( int fd, fd_set *fdset); clear del bit fd in fd_set
int FD_ISSET ( int fd, fd_set *fdset); !=0 se il bit fd settato in fd_set
0 se il bit fd non settato
Con queste macro posso settare pulire e controllare lappartenenza o
meno di un descrittore allinsieme. Es.:
fd_set readset; dichiaro la variabile fd_set
FD_ZERO ( &readset ); inizializzo, azzero tutto,
insieme vuoto
FD_SET ( 1, &readset ); 1 appartiene allinsieme
FD_SET ( 4, &readset ); 4
FD_SET ( 7, &readset );
FD_ISSET ( 4, &readset ) restituisce != 0
FD_ISSET ( 3, &readset ) restituisce 0
Ricordarsi di inizializzare il set (FD_ZERO) altrimenti risultati
impredicibili.
funzione select() (2)
int select ( int maxfdp1, fd_set *readset, fd_set *writeset,
fd_set *exceptset, const struct timeval *timeout);
Il primo argomento maxfdp1, specifica quali descrittori controllare, nel
senso che deve avere il valore pi alto tra i descrittori settati + 1.
Es.: se i descrittori settati sono 1, 4 , 7 maxfdp1 deve essere 8 = 7+1
I 3 descrittori di set passati per argomento contengono quindi i
descrittori da controllare, e vengono passati per puntatore perch la
select li modifica scrivendoci sopra il risultato.
Quando la select termina si controllano ciascuno dei 3 fd_set, chiedendo
tramite la macro FD_ISSET() quali descrittori sono settati.
Se un descrittore (es. 4) non settato (es. FD_ISSET(4, &readset )== 0)
significa che non pronto.
Se invece il descrittore settato (es. FD_ISSET(4, &readset ) != 0)
significa che pronto.
Il valore restituito dalla select dice quanti descrittori sono stati
settati.
se la select restituisce 0 significa che scaduto il timeout.
se la select restituisce -1 ce stato un errore o avvenuta una
signal.
N.B. esiste una define che specifica la costante FD_SETSIZE ovvero il
numero di descrittori che pu contenere la struttura fd_set.
Vediamo ora un esempio di uso della select, con cui implementiamo un
web server, che lavora in parallelo senza dover fare delle fork().
126
funzione select() (3)
Prima parte del server, inizializzazione:
typedef SA struct sockaddr ;
int main(int argc, char **argv)
{
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready, client [FD_SETSIZE];
ssize_t n;
fd_set rset, allset;
char line[MAXLINE];
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
listen(listenfd, LISTENQ);
maxfd = listenfd; /* initialize */
maxi = -1; /* index into client[] array */
for (i = 0; i < FD_SETSIZE; i++)
client[i] = -1; /* -1 indicates available entry */
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
127
esempio duso della select()
server che non utilizza la fork() (1)
for ( ; ; ) {
rset = allset; /* structure assignment */
nready = select(maxfd+1, &rset, NULL, NULL, NULL);
if ( FD_ISSET( listenfd, &rset) ) { /* new client connection */
clilen = sizeof(cliaddr);
connfd = accept( listenfd, (SA *) &cliaddr, &clilen);
for (i = 0; i < FD_SETSIZE; i++)
if (client[i] < 0) {
client[i] = connfd; /* save descriptor */
break;
}
if (i == FD_SETSIZE) err_quit("too many clients");
FD_SET(connfd, &allset); /* add new descriptor to set */
if (connfd > maxfd) maxfd = connfd; /* for select */
if (i > maxi) maxi = i; /* max index in client[] array */
if (--nready <= 0)
continue; /* no more readable descriptors */
}
for (i = 0; i <= maxi; i++) { /* check all clients for data */
if ( (sockfd = client[i]) < 0)
continue;
if (FD_ISSET(sockfd, &rset)) {
if ( (n = Readline(sockfd, line, MAXLINE)) == 0) {
/*connection closed by client */
close(sockfd);
FD_CLR(sockfd, &allset);
client[i] = -1;
} else
writen(sockfd, line, n);
if (--nready <= 0)
break; /* no more readable descriptors */
}
}
}
}
128
server che non utilizza la fork() (1)
Advanced TCP Socket
130
Le opzioni per i socket sono controllate mediante tre tipi di primitive:
1) le funzioni getsockopt() e setsockopt(), che permettono di
configurare alcune caratteristiche proprie solo dei socket, quali
dimensioni dei segmenti, controlli sulla funzionalit della connessione,
modalita di utilizzo degli indirizzi;
2) la funzione fcntl(), che invece consente di settare caratteristiche
comuni a tutti i descrittori di I/O, quali comportamento bloccante o
non bloccante e I/O guidato dai signal.
3) la funzione ioctl(), che ripete operazioni delle fcntl ed inoltre
effettua operazioni riguardanti ARP e routing.
Le opzioni tipiche per i socket possono essere settate solo quando il
socket reso disponibile allinterfaccia di programmazione.
Consideriamo ad es. il caso di un connected socket ottenuto da un
server in risposta ad una chiamata alla accept() a partire da un socket
listening. Il connected socket viene creato dopo la listen() quando arriva
una richiesta di connessione dal client, e tutto il procedimento di
instaurazione della connessione avviene senza che il programmatore
possa intervenire, in quanto il socket creato verr reso disponibile al
programmatore solo quando questo far eseguire la accept().
Per rendere possibile configurare il socket anche in questa situazione
temporanea, linterfaccia socket implementa la politica seguente: il
connected socket eredita dal listening socket alcune opzioni, invece
di assumere le opzioni di default. Queste opzioni sono:
SO_DEBUG, SO_DONTROUTE, SO_KEEPALIVE, SO_LINGER,
SO_OOBINLINE, SO_RCVBUF e SO_SNDBUF. In tal modo se
vogliamo che il connected socket abbia queste opzioni gia durante la
fase del thee-way-handshake dobbiamo settare in quel modo il listening
socket.
In http://www.cs.unibo.it/~ghini/didattica/sistemi3/SOCKOPTS/checkopts.c
e implementato un esempio di lettura delle opzioni di default di un socket.
Le Opzioni per i Socket
Il formato di queste due funzioni il seguente:
#include <sys/socket.h>
int getsockopt ( int sockfd, int level, int optname,
void *optval, socklen_t *optlen );
int setsockopt ( int sockfd, int level, int optname,
const void *optval, socklen_t optlen );
restituiscono 0 se tutto OK, -1 in caso di errore.
- Il primo argomento socketfd un descrittore di socket aperto con una
socket(), su cui operano le funzioni.
- Largomento level indica a che livello di protocollo deve agire
lopzione: a livello di socket generale (level=SOL_SOCKET), a livello
IPv4(IPPROTO_IP), IPv6(IPPROTO_IPV6), ICMPv6
(IPPROTO_ICMPV6), a livello TCP (level=IPPROTO_TCP).
- optname specifica lopzione da leggere /settare.
- optval un puntatore ad una variabile di tipo dipendente dallopzione
specificata, che contiene il valore nuovo da settare dellopzione da
configurare (caso setsockopt) o che conterr il valore attuale
dellopzione (caso getsockopt).
- optlen contiene la dimensione della variabile puntata da optval.
Le opzioni possono effettuare due diverse operazioni:
- settare o resettare un flag,
- assegnare o leggere un valore pi complesso.
Nel caso dei flags, il valore restituito o passato (optval) punta ad un
intero, che vale zero se lopzione e disabilitata, vale nonzero se
lopzione e abilitata.
Nel caso non flags optval punta ad un dato di tipo diverso, come
indicato nella seguente tabella:
131
funzioni getsockopt() e setsockopt()
T
a
b
e
l
l
a
d
e
l
l
e
O
p
z
i
o
n
i
S
o
c
k
e
t
G
e
n
e
r
i
c
h
e
,
c
i
o
d
i
l
i
v
e
l
l
o
S
o
c
k
e
t
,
p
e
r
g
e
t
s
o
c
k
o
p
t
(
)
e
s
e
t
s
o
c
k
o
p
t
(
)
1
3
2
P
e
r
q
u
e
s
t
e
o
p
z
i
o
n
i
,
n
e
l
l
e
f
u
n
z
i
o
n
i
g
e
t
s
o
c
k
o
p
t
(
)
e
s
e
t
s
o
c
k
o
p
t
(
)
d
e
v
e
e
s
s
r
e
u
t
i
l
i
z
z
a
t
o
c
o
m
e
s
e
c
o
n
d
o
a
r
g
o
m
e
n
t
o
l
e
v
e
l
=
S
O
L
_
S
O
C
K
E
T
.
Consideriamo le principali opzioni caratteristiche di tutti i socket,
quelle identificate dal livello SOL_SOCKET.
SO_BROADCASTquesta opzione abilita o disabilita la possibilit
per un socket di spedire messaggi broadcast. Viene applicato solo ai
socket datagram(DGRAM) e solo se la rete sottostante lo permette (es:
ethernet, non punto a punto). Per default questa opzione
disabilitata, per impedire ad un processo di spedire accidentalmente
un datagramin broadcast, ad es. se lindirizzo IP di destinazione viene
preso a linea di comando e si digita per errore un indirizzo di
broadcast. In tal caso il kernel si accorge di avere a che fare con un
datagramdi broadcast il cui invio disabilitato, e restituisce un errore
di tipo EACCES.
SO_DEBUG questa opzione supportata solo da TCP, e ordina al
kernel di mantenere informazioni su tutti i pacchetti spediti o ricevuti
da/a un certo socket, in una coda circolare. Il programma trcp
esaminera questa coda.
SO_DONTROUTE questa opzione e applicata per bypassare il
normale meccanismo di routing dei pacchetti in uscita, ad es: per farli
uscire da uninterfaccia di rete diversa da quella prevista dalle
tabelle interne di routing. Viene usata ad es. dai processi daemon del
routing (routed o gated) per instradare un pacchetto sullinterfaccia
giusta quando le tabelle di routing sono sbagliate.
SO_KEEPALIVE questa opzione applicata solo agli streamTCP,
per verificare se una connessione che da molto tempo non scambia
dati debba essere chiusa o no. Il motivo per cui una connessione
deve essere chiusa da un end systeme che laltro end systema) e
down, b) non e raggiungibile (rete partizionata o problema nel
routing), c) non e piu interessato a quella connessione.
Opzioni Socket Generiche (1)
133
SO_KEEPALIVE (continuazione) Quando un socket TCP ha questa
opzione settata, se nessun dato viene scambiato in una delle due
direzioni della connessione per 2 ore, il TCP spedisce un segmento,
detto keepalive probe, allaltro end-system, per verificarne la
situazione, e si aspetta di ricevere un ACK.
1) se il peer risponde con un ACK tutto e OK, ed il TCP mandera un
nuovo probe dopo altre due ore di inattivita.
2) se il peer risponde con un segmento RST (reset), significa che e
andato in crash e poi ha effettuato il reboot. Il socket allora viene
chiuso, ed la variabile derrore del socket settata a ECONNRESET.
3) se non ce risposta dal peer, il TCP riprova a mandare altri 8
segmenti keepalive probe, ogni 75 secondi, aspettando risposta.
3.1) Se non viene ricevuta alcuna risposta il socket e chiuso e la var.
derrore settata a ETIMEOUT.
3.2) se invece viene ricevuta un ICMP error in risposta ad uno dei
keepalive probe, il socket viene chiuso, e viene restituito lerrore
indicato dallICMP, che sara di tipo EHOSTUNREACH, ovvero
lhost non e raggiungibile.
La specifica Posix.1g stabilisce anche le modalita per settare
lintervallo di attesa (le 2 ore) ad un valore diverso, ma tale opzione e
implementata raramente.
Questa Opzione SO_KEEPALIVE serve a stabilire se il peer host e
andato in crash. Invece il crash dellapplicazione peer viene
individuato e notificato dal TCP peer. Cioe quando nellaltro end
systemil processo che gestiva il socket va in crash, il TCP di
quellhost spedisce un segmento FIN per chiudere la connessione, e
questa chiusura pu essere individuata con una read() o una
select() settata per verificare la possibilita di leggere da quel
socket. Non esiste altro modo di accorgersi di un crash se non
cercando di fare un test per lettura.
Opzioni Socket Generiche (2)
134
SO_RCVBUF e SO_SNDBUF Queste due opzioni servono a
modificare la dimensione dei buffer di ricezione e trasmissione del
TCP e dellUDP.
Per il TCP la dimensione del buffer di ricezione viene mandata allatto
dellinstaurazione della connessione. E quindi necessario che per il
server questa opzione sia settata prima della chiamata alla listen(),
mentre per il client deve essere settata prima della chiamata alla
connect().
Invece per UDP il buffer di ricezione determina la dimensione
massima dei datagramche possono essere accettati.
SO_REUSEADDR e SO_REUSEPORTQueste due opzioni servono
a permettere di effettuare la bind() su porte e indirizzi IP gia utilizzati
da qualche altro processo. La SO_REUSEADDR ad es. puo essere
utile nei seguenti casi:
a) si cerca di fare la bind per un listening socket che e stato chiuso e si
vuol fare ripartire, quando ancora esiste un connected socket nato dal
listening socket appena chiuso.
b) ci sono piu connected socket che lavorano sulla stessa porta di un
host ma con IP diversi. E il caso dei web server che devono lavorare
sulla stessa well know port 80 ma su interfacce diverse.
SO_TYPE Questa opzione puo essere usata solo in lettura, e
restituisce il tipo del socket, SOCK_STREAM o SOCK_DGRAM.
Opzioni Socket Generiche (3)
135
SO_LINGERQuesta opzione determina le modalita di chiusura
realizzate dalla funzione close(). Viene usato come argomento optval
della setsockopt() un puntatore ad una struttura di tipo:
struct linger { int l_onoff; /* 0=off , nonzero=on */
int l_linger; /* linger time, Posix.1g vuole secondi */
}
Per default la close() restituisce subito il controllo al chiamante, ma se
alcuni dati rimangono nei buffer di spedizione il TCP tenta di spedirli.
1) se l_onoff==0 lopzione SO_LINGER e disabilitata, quindi viene
settato il modo di default appena visto.
In questo modo non sappiamo se il TCP peer ha ricevuto tutti i dati, e
a maggior ragione non sappiamo se lapplication peer li ha ricevuti.
2) se l_onoff != 0 e l_linger==0 quando un socket chiama la close(), il
TCP chiude la connessione in modo traumatico, non spedendo i dati
eventualmente bufferizzati per la spedizione, e mandando un segment
RST (reset) allaltro end-system. Non si va nello stato TIME_WAIT e
si rischia di danneggiare lapertura di una nuova connessione con gli
stessi indirizzi IP e di porta.
Anche in questo caso non sappiamo se il TCP peer ha ricevuto
tutti i dati, e nemmeno se li ha ricevuti l application peer.
Opzioni Socket Generiche (4)
136
SO_LINGER(continuazione)
3) se l_onoff != 0 e l_linger !=0 quando un socket chiama la close(),
se il socket e di tipo bloccante (e il default) il TCP tenta di spedire i
dati eventualmente bufferizzati per la spedizione, fino a che si verifica
una di queste condizioni:
3.1) tutti i dati sono trasmessi e riscontrati dal TCP peer, ed allora la
funzione close() restituisce il controllo al chiamante con risultato 0,
passando dallo stato TIME_WAIT.
Anche in questo caso per, anche se sappiamo che il TCP ha ricevuto i
dati non abbiamo garanzie che lapplication peer riceva i dati. Puo
capitare infatti che dopo che il TCP peer ha spedito lACK per i dati ed
il FIN, e quindi la close() e terminata, lapplication peer vada in crash
e non riesca a leggere dalla coda del TCP peer.
3.2) oppure scade il tempo assegnato di attesa l_linger e la funzione
close restituisce -1 mandando un segment RST (reset) allaltro
end-system, e non passa dallo stato TIME_WAIT.
Opzioni Socket Generiche (5)
137
La funzione shutdown() e utilizzata per chiudere una connessione in
modo differente rispetto alla close().Infatti:
- mentre la close() decrementa solo il contatore dei processi che
utilizzano quel socket e quando il contatore e zero spedisce il
segmento FYN, la shutdown spedisce subito il segmento di FIN
(anche se il contatore e maggiore di zero) ovviamente senza passare
avanti agli altri dati bufferizzati per la spedizione.
- mentre la close() chiude entrambe le direzioni della connessione, e
quindi impedisce di usare con quel socket sia primitive di input sia di
output (no read no write dopo close() ), la shutdown da la possibilita di
effettuare chiusure asimmetriche di una connessione, specificando quale
direzione deve essere interrotta.
int shutdown (int sockfd, int howto);
restituisce 0 se tutto OK, -1 in caso di errore.
Largomento socketfd un descrittore di socket.
Largomento howto specifica lazione che deve essere effettuata sul
socket sockfd, ed e una delle seguenti:
- SHUT_WR lapplicazione chiude il socket sia in scrittura che in
lettura,, senza badare al contatore di processi per quel socket. Tutti i dati
eventualmente presenti nelle code di output verranno spediti, e poi
verra spedito un SYN segment, per terminare il lato di output della
connessione. Rimane possibile effettuare delle read() fino a che laltro
end-systemnon effettua a suo volta un\a close() che fa inviare il SYN
segment verso chi aveva effettuato la chiamata alla shutdown(). Questo
tipo di chiusura viene detta half-close.
- SHUT_RD lapplicazione chiude il socket in lettura, resta possibile
effettuare le write(). Non piu possibile effettuare le letture, e tutti i
dati eventualmente gia ricevuti dal TCP e presenti nelle code per linput
vengono scartati. Vengono scartati anche eventuali dati giunti
dopo la shutdown(). 138
funzione shutdown() (1)
- SHUT_RDWR lapplicazione chiude il socket sia in scrittura che in
lettura, come se effettuasse due chiamate alla shutdown, con parametro
SHUT_RD e poi con parametro SHUT_WR.
vediamo qui una rappresentazione di una half close (SHUT_WR).
139
funzione shutdown() (2)
Garanzie di
Trasmissione Completata (1)
140
Garanzia per il Client che il Server ha ricevuto tutti i dati.
Un modo sicuro per sapere se lapplicazione dellaltro end system(non
solo il TCP) ha ricevuto i dati sostituire la chiamata alla close() con
una chiamata alla shutdown() usando come secondo argomento la
costante SHUT_WR, e procedere poi con una chiamata alla read().
In questo modo la shutdown manda il segmento di FIN, ma lascia
aperto il socket in lettura, permettendo di effettuare la chiamata alla
read() che altrimenti restituirebbe immediatamente un errore. La read()
rimane bloccata fino a che lapplication peer termina la lettura di tutti i
dati e legge il FIN, quindi effettua la close() che manda il segmento
FIN di risposta.
La read() allora riceve lend-of-file e termina restituendo 0. Si ha
allora la garanzia che la applicazione peer ha ricevuto tutti i dati.
Garanzia per il Client che il Server ha ricevuto tutti i dati.
Un altro modo per garantire che lapplication peer ha ricevuto tutti i
dati e usare il cosiddetto application-level acknowledgment o
application ACK.
Il client dopo avere spedito tutti i suoi dati si blocca su una read
aggiuntiva, che rappresenta lattesa per un ACK a livello di
applicazione. Il server, dopo avere ricevuto tutti i dati effettua una
write di un byte, che rappresenta lACK a livello di applicazione.
Quando il client ritorna dalla read() effettua la close() che manda il
FIN segment. Solo allora lapplication peer effettua la close().
In questo modo si ha la garanzia che il processo server ha letto i dati
che gli sono stati inviati.
141
Garanzie di
Trasmissione Completata (2)
T
a
b
e
l
l
a
d
e
l
l
e
O
p
z
i
o
n
i
S
o
c
k
e
t
p
e
r
T
C
P
,
c
i
o
d
i
l
i
v
e
l
l
o
I
P
P
R
O
T
O
_
T
C
P
,
p
e
r
g
e
t
s
o
c
k
o
p
t
(
)
e
s
e
t
s
o
c
k
o
p
t
(
)
1
4
2
P
e
r
q
u
e
s
t
e
o
p
z
i
o
n
i
,
n
e
l
l
e
f
u
n
z
i
o
n
i
g
e
t
s
o
c
k
o
p
t
(
)
e
s
e
t
s
o
c
k
o
p
t
(
)
d
e
v
e
e
s
s
e
r
e
u
t
i
l
i
z
z
a
t
o
c
o
m
e
s
e
c
o
n
d
o
a
r
g
o
m
e
n
t
o
l
e
v
e
l
=
I
P
P
R
O
T
O
_
T
C
P
.
143
Opzioni Socket per TCP
Queste opzioni si usano specificando il livello IPPROTO_TCP.
TCP_KEEPALIVE Questa opzione specifica il tempo (in sec.) di
inattivita della connessione prima che venga fatto partire il segmento
di keepalive probe. Il valore di default e di 7200 sec. (2 ore). Questa
opzione e realmente attivata solo quando lopzione SO_KEEPALIVE
e abilitata.
TCP_MAXSEG Questa opzione restituisce o setta il maximum
segment size (MSS) per la connessione TCP.
TCP_NODELAY Per default il TCP abilita il cosiddetto Algoritmo di
Nagle (Nagle Algorithm) il cui scopo e di diminuire il numero di
segmenti piccoli trasmessi, come nel caso di client telnet che
prevedono lACK per ogni singolo carattere battuto a tastiera. Questo
algoritmo legato allalgoritmo dellACK ritardato (delayed ACK
algorithm) che fa aspettare il TCP per circa 50-200 msec) prima di dare
lack ad un segmento, sperando di poter accodare lACK ad un
segmento di dati.
Questi due algoritmi assieme cercano di minimizzare il numero di
segmenti trasmessi, ma producono ritardo per applicazioni che
scambiano piccoli dati e quasi solo in una direzione.
Lopzione TCP_NODELAY disabilita luso di questi algoritmi.
Nagle algorithmDISABLED
Nagle algorithm Enabled settata TCP_NODELAY opt.
Consente di inviare uno stesso datagram UDP a piu destinatari, i quali
devono essersi preventivamente registrati come membri del gruppo di
multicast caratterizzato da un certo indirizzo di multicast.
Indirizzi di multicast:
Classe D, 224.0.0.0 - 239.255.255.255
Indirizzi di Multicast Speciali:
224.0.0.1 (all-host group) vi si devono registrare tutti gli host di
una sottorete, che implementano il multicast.
224.0.0.2 (all-router group) vi si devono registrare tutti gli host di
una sottorete, che implementano il multicast.
Operazioni necessarie per ricevere datagram UDP via multicast:
creazione di un socket UDP
collegamento ad una porta del protocollo UDP (bind)
join ad un indirizzo di multicast (setsockopt).
Operazioni necessarie per smettere di ricevere via multicast:
leave del gruppo multicast (setsockopt).
N.B. Se con la bind non specifico un indirizzo IP, posso ricevere
datagram UDP sia originati da multicast che da unicast, per la
porta UDP specificata.
N.B. Se con la bind specifico lindirizzo IP di multicast a cui faro il
join, potro ricevere solo datagram UDP di multicast per quello
indirizzo di multicast.
Operazioni opzionali per spedire datagram UDP via multicast:
Specificare il TTL dei datagram UDP multicast.
Se non si specifica, il default e 1, non si esce dalla subnet.
Specificare se il mittente deve ricevere copia del datagram(se fa
parte del gruppo di multicast.
144
Opzioni Socket per IP: Multicast (1)
indica se i datagram
di multicast spediti
devono andare anche
al mittente, se questo
appartiene
al gruppo
u_char IP_MULTICAST_LOOP
specifica il TTL dei
datagramdi
multicast da spedire
u_char IP_MULTICAST_TTL
indica linterfaccia
di rete da usare per
spedire i datagram di
multicast
struct in_addr IP_MULTICAST_IF
toglie il socket UDP
dal gruppo di
multicast
struct ip_mreq IP_DROP_MEMBERSHIP
collega il socket
UDP ad uno
specifico gruppo di
multicast
struct ip_mreq IP_ADDR_MEMBERSHIP
uso tipo di dato opzione
Opzioni di livello IPPROTO_IP
Le opzioni utilizzate per configurare un socket UDP per la
trasmissione/ricezione di datagram per un certo indirizzo di multicast,
vengono settate mediante la primitiva setsockoption, utilizzando il
livello IPPROTO_IP, ed i comandi e le strutture dati specificati in
tabella.
145
Opzioni per il Multicast in IPv4
146
esempio: receiver di datagram UDP
per gruppo di Multicast
struct ip_mreq Mreq;
.....
socketfd = socket (AF_INET, SOCK_DGRAM, 0);
OptVal = 1;
setsockopt (socketfd, SOL_SOCKET, SO_REUSEADDR,
(char *)&OptVal, sizeof(OptVal) );
Local.sin_family = AF_INET;
Local.sin_addr.s_addr = htonl(INADDR_ANY);
Local.sin_port = htons( 6001); // local_port_number
bind ( socketfd, (struct sockaddr*) &Local, sizeof(Local));
/* join the multicast group. */
Mreq.imr_multiaddr.s_addr = inet_addr("234.5.6.7");
Mreq.imr_interface.s_addr = INADDR_ANY;
setsockopt(socketfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
(char *)&Mreq, sizeof(Mreq) );
for (j=1;j<=1000;j++) {
Fromlen=sizeof(struct sockaddr);
msglen = recvfrom( socketfd, msg, (int)SIZEBUF, 0,
(struct sockaddr*)&From, &Fromlen);
sprintf(string_remote_ip_address,"%s",inet_ntoa(From.sin_addr);
remote_port_number = ntohs(From.sin_port);
printf("ricevuto msg: \"%s\" len %d, from host %s, port %d\n",
msg, msglen, string_remote_ip_address, remote_port_number);
}
147
esempio: sender di datagramUDP
per gruppo di Multicast
struct ip_mreq Mreq; int ttl, int loopback;
.....
socketfd = socket (AF_INET, SOCK_DGRAM, 0);
OptVal = 1;
/* set TTL to traverse up to multiple routers */
ttl = TTL_VALUE; // per default 1
setsockopt(socketfd, IPPROTO_IP, IP_MULTICAST_TTL,
(char *)&ttl, sizeof(ttl));
/* join the multicast group, NON NECESSARIO per SPEDIRE */
Mreq.imr_multiaddr.s_addr = inet_addr("234.5.6.7");
Mreq.imr_interface.s_addr = INADDR_ANY;
setsockopt(socketfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
(char *)&Mreq, sizeof(Mreq) );
/* disable loopback , NON NECESSARIO se non si fa join */
loopback =0;
setsockopt(socketfd, IPPROTO_IP, IP_MULTICAST_LOOP,
(char *)&loopback, sizeof(loopback));
for (j=1;j<=1000;j++) {
To.sin_family = AF_INET;
To.sin_addr.s_addr = inet_addr("234.5.6.7" );
To.sin_port = htons( 6001 ); // remote_port_number
addr_size = sizeof(struct sockaddr_in);
/* send to the address */
sendto ( socketfd, msg, strlen(msg) , 0,
(struct sockaddr*)&To, addr_size);
}
La funzione fcntl significa file control e serve ad effettuare alcune
operazioni su vari descrittori non solo di socket ma in generale di file.
In particolare la funzione fcntl permette di:
configurare lI/O di un descrittore come non bloccante o bloccante
usando nella fcntl il comando F_SETFL con argomento
O_NONBLOCK (O_BLOCK),
configurare lI/O di un descrittore come guidato dai signal usando
nella fcntl il comando F_SETFL con argomento O_ASYNC, ottenendo
che in corrispondenza di ogni modifica della situazione del socket
venga scatenato un SIGIO signal.
settare un proprietario per il descrittore (comando F_SETOWN),
ovvero definire quale il processo che viene avvisato con il signal
SIGIO quando il descrittore e disponibile allI/O, ma questo solo se e
stato settato il socket come guidato dai signal. Si ricorda che un socket
appena creato con la socket() non ha proprietario, un connected socket
ha lo stesso proprietario del listenung socket
conoscere il proprietario corrente per il descrittore (F_GETOWN).
#include <fcntl.h>
int fcntl (int fd, int cmd, ... /* int arg */ );
restituisce -1 in caso di errore,
un altro valore dipendente dal comando in caso tutto OK.
Le proprieta di un descrittore di file (e di socket) sono definite
mediante alcuni flags, che la funzione fcntl permette di leggere (usando
come secondo argomento cmd=F_GETFL) e di settare (usando come
secondo argomento cmd=F_SETFL).
I due flags che ci interessano per i socket sono:
O_NONBLOCK non blocking I/O
O_ASYNC signal-driven I/O notification
148
Opzioni Socket tramite fcntl (1)
In generale la procedura corretta per settare un flags consiste nel
leggere i flags del descrittore mediante il comando F_GETFL,
modificare solo il flag che interessa con un OR logico, settare i nuovi
flags per il socket mediante il comando F_SERFL.
Nel seguente esempio viene settato il flag O_NONBLOCK, quindi
il socket diventa non bloccante.
Procedura corretta
int flags, sockfd;
sockfd = socket(....);
if ( (flags=fcntl(sockfd,F_GETFL,0)) <0 )
exit(1);
flags |= O_NONBLOCK;
if ( fcntl(sockfd,F_SETFL,flags) <0 )
exit(1);
Procedura errata, vengono azzerati tutti gli altri flags.
int sockfd;
sockfd = socket(....);
if ( fcntl(sockfd,F_SETFL, O_NONBLOCK )<0)
exit(1);
Infine in questo esempio viene resettato il flag O_NONBLOCK, quindi
il socket diventa bloccante.
int flags, sockfd;
sockfd = socket(....);
if ( (flags=fcntl(sockfd,F_GETFL,0)) <0 )
exit(1);
flags &= O_NONBLOCK;
if ( fcntl(sockfd,F_SETFL,flags) <0 )
exit(1);
149
Opzioni Socket tramite fcntl (2)
Se per un socket viene settato il flags O_ASYNC, quando il socket
diventa disponibile allI/O, o e in errore, viene lanciato un signal
SIGIO al processo proprietario del socket, o ai processi del gruppo
proprietario del socket, se il proprietario esiste.
Per settare il proprietario si usa la fcntl con secondo argomento
F_SETOWN passando come terzo parametro o il numero positivo pid
del processo proprietario, o il numero negativo ottenuto cambiando di
segno il numero di gruppo.
Per conoscere il proprietario si usa la fcntl con secondo argomento
F_GETOWN. La funzione restituisce un intero positivo (il pid del
proprietario) o un numero negativo diverso da -1, che e lidentificatore
del gruppo cambiato di segno.
settare il proprietario di un socket
int sockfd,pid;
sockfd = socket(....);
if ( fcntl(sockfd,O_SETOWN, pid ) <0 )
exit();
settare il gruppo di un socket
int sockfd, gid;
sockfd = socket(....);
if ( fcntl(sockfd,O_SETOWN , -gid ) <0 )
exit();
150
Opzioni Socket tramite fcntl (3)
Per default un socket e bloccante. Cio significa che quando il socket
deve effettuare unoperazione di I/O, se questa non puo essere
terminata immediatamente il processo si mette in attesa aspettando la
fine delloperazione. Se invece, mediante la fcntl vista prima, il socket
viene settato Nonblocking il comportamento delle funzioni cambia:
Operazioni di input (read, recvfrom). Con un socket Non
Bloccante, se loperazione di input non puo essere terminata (cioe
non ce nemmeno un byte per il socket TCP o non ce nemmeno un
datagramper il socket UDP) la funzione ritorna immediatamente al
chiamante restituendo il codice derrore EWOULDBLOCK.
Operazioni di output (write, sendto ).
Con un socket Non Bloccante di tipo TCP, se loperazione di
scrittura non puo essere effettuata nemmeno in parte perche manca
spazio nei buffer del TCP in cui andare a scrivere i dati, la funzione
ritorna immediatamente al chiamante restituendo il codice derrore
EWOULDBLOCK. Se invece e rimasto spazio, viene effettuata una
scrittura di una porzione di dati minore o uguale alla dimensione del
buffer libero, e la write restituisce il numero di byte scritti.
Con un socket Non Bloccante di tipo UDP invece il problema non
sussiste perche nessun datagramviene mantenuto in un buffer perche
non ce ritrasmissione, quindi il datagram UDP viene immediatamente
spedito e vada come vada. Quindi una primitiva di output di un socket
UDP non blocca mai il processo che leffettua.
Operazioni di Accettazione Richiesta Connessione (accept).
Con un socket Non Bloccante, se loperazione di accept non puo
restituire immediatamente una nuova connessione perche la coda delle
connessioni accettate e vuota, la funzione ritorna immediatamente al
chiamante restituendo il codice derrore EWOULDBLOCK.
151
I/O Non Bloccante (1)
Operazioni di Richiesta Connessione (connect).
Con un socket UDP la funzione connect non e realmente bloccante,
perche deve solo scrivere nel kernel lindirizzo IP e il numero di porta
dellaltro end-system, quindi anche nel caso di socket Non Bloccante la
connect per lUDP effettua sempre loperazione interamente e
restituisce il controllo al chiamante.
Invece per il TCP la chiamata del client alla connect() di un socket
bloccante deve aspettare che, allinterno del three way handshake, il
client abbia ricevuto lACK per il SYN segment, sia stato ricevuto, e
quindi puo veramente bloccare il processo.
Se il socket TCP e di tipo Non Bloccante, la chiamata del client alla
connect() fa iniziare il procedimento di inizio connessione, cioe fa
spedire il primo SYN segment, ma se la connessione non puo
immediatamente essere instaurata la connect() termina con un codice
derrore EINPROGRESS ad indicare che loperazione continua a
livello inferiore.
Con una select() sara possibile accorgersi di quando il socket
impegnato nella richiesta di connessione ha stabilitpo la connessione, e
quindi puo essere ripetuta la connect().
Si noti che puo capitare che la connect() restituisca OK perche e
riuscita ad avere immediatamente risposta ed ha instaurato la
connessione richiesta.
152
I/O Non Bloccante (2)
socketfd = socket(AF_INET, SOCK_STREAM, 0);
/* SETTO IL SOCKET COME NON BLOCCANTE */
set_socket_non_blocking(socketfd); /* ora socket e' non bloccante */
... qui va messo il necessario per la bind .......
bind(socketfd, (struct sockaddr*) &Local, sizeof(Local));
memset ( &Serv, 0, sizeof(Serv) );
Serv.sin_family = AF_INET;
Serv.sin_addr.s_addr = inet_addr(string_remote_ip_address);
Serv.sin_port = htons(remote_port_number);
/* connection request, NON BLOCCANTE */
ris = connect(socketfd, (struct sockaddr*) &Serv, sizeof(Serv));
if (ris != SOCKET_ERROR) { /* connessione riuscita subito */
set_socket_blocking(socketfd); /* ora socket e' bloccante */
.....................
}
else { /* (ris == SOCKET_ERROR) */
if(errno!=EINPROGRESS) exit(1); /* conness. non terminata */
FD_ZERO(&fdr); FD_SET(socketfd,&fdr);
fdw=fdr;
.....................
ris=select(socketfd+1,&fdr,&fdw,NULL,NULL);
if ((FD_ISSET(socketfd,&fdr))||(FD_ISSET(socketfd,&fdw))) {
ris=connect(socketfd,(struct sockaddr*) &Serv, sizeof(Serv));
if (ris == SOCKET_ERROR) {
if(errno==EISCONN) {
printf ("connessione gia' esistente, OK\n");
set_socket_blocking(socketfd); /* socket e bloc*/
}
else exit(1); /* connessione NON riuscita */
}
}
}
153
Connect Non Bloccante (1)
/* SETTO IL SOCKET COME NON BLOCCANTE */
int set_non_blocking(int socketfd) {
int flags;
if ( (flags=fcntl(socketfd,F_GETFL,0)) <0 ) return(0); /* errore */
flags |= O_NONBLOCK;
if ( fcntl(socketfd,F_SETFL,flags) <0 ) return(0); /* errore */
return(1);
}
/* SETTO IL SOCKET COME BLOCCANTE */
int set_blocking(int socketfd) {
int flags;
if ( (flags=fcntl(socketfd,F_GETFL,0)) <0 ) return(0); /* errore */
flags &= (~O_NONBLOCK);
if ( fcntl(socketfd,F_SETFL,flags) <0 ) return(0); /* errore */
return(1);
}
N.B.
per lesempio completo vedere nella home page
154
Connect Non Bloccante (2)