|
||||||||||||||||||||||||||||||||||||||||||||||
| [IT] I socket in C | ||||||||||||||||||||||||||||||||||||||||||||||
|
I socket in C Un socket e', in pratica, l'equivalente dell'indirizzo di rete di un processo. Stabilire una connessione fra due socket, e quindi dare inizio ad una sessione, significa mettere in condizione di comunicare due processi "affacciati" sulla rete. In C vi sono diverse funzioni e strutture apposite per la creazione e l'uso dei socket. Le si possono trovare negli header "sys/socket.h", "sys/types.h", "sys/time.h", "unistd.h", "netinet/in.h", "netdb.h", che vanno inclusi a seconda delle funzioni e strutture che si intende usare. Nelle seguenti tabelle vengono mostrate le funzioni
principali (consultare il manuale per quelle
prive di link) e gli header (che si trovano
generalmente in "/usr/include") da includere per usarle. Successivamente
verra' mostrato come creare ed utilizzare i socket con queste funzioni.
La tipica modalita' di utilizzo dei socket e' la seguente:
CLIENT (client.c):
SERVER (server.c):
Quella che segue e' una descrizione delle principali
funzioni relative ai socket.
int socket(int dominio, int tipo, int protocollo) Crea un socket e ne restituisce un descrittore. Quest'ultimo puo' essere usato anche con le funzioni relative ai file. Se il valore del descrittore e' pari a "-1", allora la creazione del socket non e' andata a buon fine (la variabile "errno" contiene il codice d'errore appropriato).. Il parametro "dominio" indica la famiglia di protocolli da usare. Tre delle possibili famiglie sono AF_UNIX (protocolli interni di Unix), AF_ISO (protocolli ISO) e AF_INET (protocolli usati da internet). In <sys/socket.h> sono definiti anche gli altri. In "tipo", come dice lo stesso nome del parametro, viene indicato il tipo di comunicazione, cioe' in che modo debbano essere scambiati i dati. Puo' assumere diversi valori fra cui SOCK_STREAM (connessione permanente e bidirezionale basata su un flusso di byte: i dati vengono mantenuti in ordine e non sono persi o duplicati) e SOCK_DGRAM (scambio di datagram, ovvero pacchetti di byte di lunghezza massima fissata, non affidabile al 100% perche' la connessione non e' continua e quindi i pacchetti stessi possono essere duplicati e/o non arrivare in ordine). Infine, il "protocollo",
indica il particolare protocollo da usare con il socket. Normalmente assume
valore nullo (0),
cioe' il protocollo usato e' quello di default per la combinazione di dominio
e tipo specificata con gli altri due parametri.
Il valore restituito, come gia' detto, e' il descrittore del socket. Questo valore va conservato perche' serve per ogni riferimento al socket creato. Il socket creato e' "bloccante"
per default. Un socket e' bloccante quando, a seguito di una chiamata alla
funzione di attesa per una connessione, blocca il thread in cui e' stata
creata, fino all'arrivo di una richiesta di connessione.
eventuale_errore=fcntl(sock,F_SETFL,0,NONBLOCK);
int bind(int descrittore_socket, struct sockaddr* indirizzo, int lunghezza_record_indirizzo) Bind assegna un indirizzo al socket. In caso di errore viene restituito il valore "-1" (la variabile "errno" conterra' il codice d'errore appropriato). Vediamo come e' fatta la struttura "sockaddr":
Sia la struttura che i valori che puo' assumere "sa_family",
si trovano nel file "bits/socket.h",
incluso in "sys/socket.h".
Fra le famiglie possibili ci sono AF_UNSPEC,
AF_FILE,
AF_INET
(internet), AF_UNIX.
il contenuto del campo "sa_data" dipende dalla particolare famiglia di indirizzi usata. Nel caso di AF_INET, si avra' la struttura "sockaddr_in", definita in "netinet/in.h" (che quindi andra' incluso) ed equivalente, per mezzo di una conversione esplicita di formato, alla precedente: La famiglia degli indirizzi e' AF_INET. Per quel che riguarda la porta da usare, bisogna fare attenzione al fatto che l'ordine dei bit usato in internet e' diverso da quello usato normalmente dal computer. Quindi bisogna convertire il numero della porta voluto e poi assegnarlo al campo "sin_port". La funzione che consente questa conversione e' "htons". Fra gli indirizi internet utilizzabili nel campo "sin_addr" (definiti in "netinet/in.h") ci sono "INADDR_ANY" (vengono accettati dati da qualunque parte vengano), "INADDR_BROADCAST" (per mandare dati a tutti gli host), "INADDR_LOOPBACK" (loopback sull'host locale). Un tipico modo di usare la struttura "sockaddr_in" e' il seguente: //... int eventuale_errore; int sock; struct sockaddr_in temp; sock=socket(AF_INET,SOCK_STREAM,0); //Creazione del socket. temp.sin_family=AF_INET; temp.sin_addr.s_addr=INADDR_ANY; temp.sin_port=htons(123); //Numero casuale. eventuale_errore = bind(sock,(struct sockaddr*) &temp,sizeof(temp)); //...
int close (int descrittore_socket) Close consente di chiudere la comunicazione sul socket passato in "descrittore_socket". Ovviamente, la funzione "close" deve essere chiamata su entrambi i socket in comunicazione. Se uno venisse chiuso e l'altro no, e quello aperto cercasse di mandare dati a seguito della funzione "write" (o di altre funzioni), si otterrebbe un errore. Se il risultato della chiamata a "close" fosse "-1", allora si sarebbe in presenza di un errore.
int shutdown(int descrittore_socket, int modalita_chiusura) La funzione "shutdown" causa la chiusura totale o solo parziale di una connessione full-duplex (bidirezionale) sul socket associato al descrittore. Se l'operazione non va a buon fine, il risultato della chiamata a questa funzione e' "-1". La modalita' di chiusura puo' assumere tre valori. Se vale "0", allora il socket indicato non potra' piu' ricevere dati. Con il valore "1", il socket non potra' piu' spedire messaggi. In fine, con il valore "2", il socket non potra' piu' ricevere messaggi e neanche mandarne.
int connect (int descrittore_socket, struct sockaddr* indirizzo_server, int lunghezza_record_indirizzo) La funzione "connect" cerca (solo se SOCK_STREAM - vedi "socket") di effettuare la connessione fra il socket passato come parametro con il socket in ascolto all'indirizzo specificato. Per spiegarne l'uso, un esempio e' piu' efficacie:
int listen (int descrittore_socket, int dimensione_coda) Mentre il socket e' in ascolto, puo' ricevere delle richieste di connessione. Mentre viene servita una di queste richieste, ne possono arrivare altre. Il procedimento adottato per tenere conto di questo fatto, e' di mettere le richieste in una coda di attesa. Listen si occupa di definire la dimensione massima di questa coda ("dimensione_coda"). Come al solito, se il valore restituito e' "-1", allora vi e' stato un errore. La funzione Listen si applica solo ai socket di
tipo "SOCK_STREAM" o "SOCK_SEQPACKET" (vedi "socket").
int accept (int descrittore_socket, struct sockaddr* indirizzo, int* lunghezza_record_indirizzo) Ha il compito di accettare una connessione. Una volta che un socket e' stato creato (socket), collegato (bind) e messo in ascolto (listen), "accept" prende la prima connessione disponibile sulla coda delle connessioni pendenti (vedi listen), crea un nuovo socket con le stesse proprieta' di quello rappresentato da "descrittore_socket" e restituisce un nuovo descrittore. La connessione puo', allora, essere gestita con questo nuovo socket. Mediante un "fork" sul processo che gestisce la connessione, e' possibile servire la connessione accettata, aspettando contemporaneamente altre connessioni (e servendole se sono gia' nella coda). In "indirizzo" vengono messi i dati relativi al socket che ha richiesto la connessione. Se non ci sono connessioni presenti nella coda, allora, il comportamento di default (per default i socket sono bloccanti) prevede che il processo venga bloccato fino all'arrivo di una nuova. E' possibile rendere il socket non bloccante per modificare il comportamento di default di "accept". Seguendo il metodo gia' indicato, si forza "accept" a verificare la presenza di una connessione ed a sbloccare immediatamente il processo, anche in assenza di connessioni pendenti. Come sempre, in caso di errore, "accept" restituisce
il valore "-1"
e la variabile "errno"
ne contiene il codice.
int send (int descrittore_socket, const void* buffer, int lunghezza_messaggio, unsigned int opzioni) Con "send" e' possibile inviare messaggi dal socket rappresentato da descrittore al socket con cui e' connesso. Questa funzione puo' essere usata solamente in presenza di una connessione. Il parametro "buffer" contiene il messaggio e deve avere una dimensione non inferione a "lunghezza_messaggio" (cioe' alla dimensione del messaggio da spedire). "opzioni" puo' essere posto a "0". In caso di errore, la funzione "send" restituisce il valore "-1", altrimenti restituisce "0".
int recv (int descrittore_socket, const void* buffer, int dimensione_buffer, unsigned int opzioni) Serve a ricevere messaggi da un socket e puo' essere usato solamente in presenza di una connessione. Il risultato della chiamata a questa funzione, in caso di errore, e' "-1", altrimenti e' il numero di caratteri ricevuti. Il messaggio ottenuto e' contenuto nella memoria puntata da "buffer". Il parametro "len" non e' altro che la dimensione del buffer. "opzioni" puo' essere posto a "0". Se non e' presente alcun messaggio in arrivo, ed il socket e' "bloccante" (vedi "Il modo di rendere un socket non bloccante"), allora "recv" attende fino all'arrivo di un messaggio. Esistono, comunque, altre due funzioni simili
a "recv": sono recvfrom
e recvmsg.
int getsockname (int descrittore_socket, struct sockaddr* indirizzo, int* lunghezza_record_indirizzo) Permette di ottenere tramite "indirizzo" le informazioni sull'indirizzo locale del socket. Cioe' restituisce il record contenente, ad esempio nel caso di sockaddr_in, la famiglia di indirizzi, il numero della porta, gli indirizzi internet con cui il socket interagisce. Il parametro "lunghezza_record_indirizzo" deve puntare alla dimensione della struttura "sockaddr". In uscita conterra' un puntatore alla dimensione di tale struttura. In caso di insuccesso, viene restituito il valore "-1", altrimenti lo zero. La struttura "sockaddr" e' descritta con la funzione "bind". Vediamo un esempio di utilizzo di "getsockname". //... int eventuale_errore; int sock; struct sockaddr_in temp; int dim=sizeof(temp); ... eventuale_errore=getsockname(sock, (struct sockaddr*) &temp, &dim); //In temp ci sono le informazioni sul protocollo, porta e indirizzi //...
unsigned short int htons (unsigned short int valore) Su internet i numeri sono rappresentati con un ordine diverso di bit rispetto a quello dell'elaboratore. Questa funzione si occupa della conversione al formato internet per numeri di tipo "unsigned short int".
unsigned long int htonl (unsigned long int valore) Su internet i numeri sono rappresentati con un ordine diverso di bit rispetto a quello dell'elaboratore. Questa funzione si occupa della conversione al formato internet per numeri di tipo "unsigned long int".
unsigned short int ntohs (unsigned short int valore) Su internet i numeri sono rappresentati con un ordine diverso di bit rispetto a quello dell'elaboratore. Questa funzione si occupa della conversione dal formato internet per numeri di tipo "unsigned short int".
unsigned long int ntohl (unsigned long int valore) Su internet i numeri sono rappresentati con un ordine diverso di bit rispetto a quello dell'elaboratore. Questa funzione si occupa della conversione dal formato internet per numeri di tipo "unsigned long int".
|
||||||||||||||||||||||||||||||||||||||||||||||
(c) 1999-2006
|