Cloud with rain
.:G
G:.
0 and 1 serie, black on white
pulled card
myjsp.feelinglinux.com
ver. 1.1.9-4
Hallo, welcome to my world.
Here you can find some stuff about computer science.
<<< Enjoy your visit! >>>
0 and 1 serie, white on black

I socket in C

        Scritto: Giansante Gabriele, 2001     

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.
Tutti gli esempi riguardano i protocolli usati da internet (AF_INET).



PROCESSO DI CREAZIONE/CHIUSURA
PROCESSO DI CONNESSIONE/ASCOLTO
int socket(...) #include <sys/types.h>
#include <sys/socket.h>
int setsockopt(...) #include <sys/types.h>
#include <sys/socket.h>
int bind(...) #include <sys/types.h>
#include <sys/socket.h>
int close(...)
-
int shutdown(...) #include <sys/types>
int connect(...) #include <sys/types.h>
#include <sys/socket.h>
int listen(...) #include <sys/socket.h>
int accept(...) #include <sys/types.h>
#include <sys/socket.h>
int select(...) #include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
PROCESSO DI LETTURA/SCRITTURA
ALTRE FUNZIONI
int send(...) #include <sys/types.h>
#include <sys/socket.h>
ssize_t write(...) #include <unistd.h>
int recv(...) #include <sys/types.h>
#include <sys/socket.h>
ssize_t read(...) #include <unistd.h>

E' sempre piu' comodo usare write e read.
Si usano esattamente come per i file, solo che si ha un descrittore di socket invece che di file.

int getsockname(...) #include <sys/types>
int getsockopt(...) #include <sys/types.h>
#include <sys/socket.h>
short htons(...) #include <sys/types>
#include <netinet/in.h>
short ntohs(...) #include <sys/types>
#include <netinet/in.h>
long htonl(...) #include <sys/types>
#include <netinet/in.h>
long ntohl(...) #include <sys/types>
#include <netinet/in.h>

La tipica modalita' di utilizzo dei socket e' la seguente:

  1. Creazione del socket
  2. Assegnazione indirizzo
  3. Connessione/Attesa di una connessione
  4. Scambio dati
  5. Chiusura socket
Come esempio, verranno mostrati due semplici programmi: un client ed un server. Il codice e' stato testato (compilato ed eseguito), nella forma in cui viene mostrato. La compilazione puo' avvenire con "gcc client.c -oclient" e "gcc server.c -o server". L'utilizzo e' semplice. Il server va solo eseguito; cio' deve essere fatto prima dell'avvio del client (pena un errore del tipo "broken pipe"). Il client puo' spedire due messaggi: "exit" ed un altro. L'altro messaggio e' fisso (es. "client"). Il messaggio "exit" indica al server di disattivarsi e viene passato come parametro (es. "client exit").
Pubblicita'

CLIENT (client.c):

#include <sys/types.h>
#include <sys/socket.h>
//"in" per "sockaddr_in"
#include <netinet/in.h>
//"netdb" per "gethostbyname"
#include <netdb.h>

void ChiudiSocket(int sock)
{
  close(sock);
  return;
}

int CreaSocket(char* Destinazione, int Porta)
{
  struct sockaddr_in temp; 
  struct hostent *h;
  int sock;
  int errore;
  
  //Tipo di indirizzo
  temp.sin_family=AF_INET;
  temp.sin_port=htons(Porta);
  h=gethostbyname(Destinazione);
  if (h==0) 
  {
    printf("Gethostbyname fallito\n");
    exit(1);
  }
  bcopy(h->h_addr,&temp.sin_addr,h->h_length);
  //Creazione socket. 
  sock=socket(AF_INET,SOCK_STREAM,0);
  //Connessione del socket. Esaminare errore per compiere azioni
  //opportune in caso di errore.
  errore=connect(sock, (struct sockaddr*) &temp, sizeof(temp));
  return sock;
}

void SpedisciMessaggio(int sock, char* Messaggio)
{
  printf("Client: %s\n",Messaggio);
  //Si puo' notare il semplice utilizzo di write: 
  //write(socket, messaggio, lunghezza messaggio)
  if (write(sock,Messaggio,strlen(Messaggio))<0)
  {
    printf("Impossibile mandare il messaggio.\n");
    ChiudiSocket(sock);
    exit(1);
  }  
  printf("Messaggio spedito con successo.\n");
  return;
}

int main(int argc,char* argv[])
{
  int DescrittoreSocket;
  
  //Creo e connetto il socket
  DescrittoreSocket=CreaSocket("127.0.0.1",1745);
  
  //Spedisco il messaggio voluto
  if ((argc==2)&&(strcmp(argv[1],"exit")==0))
    SpedisciMessaggio(DescrittoreSocket,"exit");
  else
    SpedisciMessaggio(DescrittoreSocket,"Un messaggio");

  //Chiudo il socket.
  ChiudiSocket(DescrittoreSocket);

  return 0;
}
Pubblicita'
SERVER (server.c):
#include <sys/types.h>
#include <sys/socket.h>
//"in" per "sockaddr_in"
#include <netinet/in.h>
//"fcntl" per la funzione "fcntl"
#include <fcntl.h>

int CreaSocket(int Porta)
{
  int sock,errore;
  struct sockaddr_in temp;

  //Creazione socket
  sock=socket(AF_INET,SOCK_STREAM,0);
  //Tipo di indirizzo
  temp.sin_family=AF_INET;
  temp.sin_addr.s_addr=INADDR_ANY;
  temp.sin_port=htons(Porta);

  //Il socket deve essere non bloccante
  errore=fcntl(sock,F_SETFL,O_NONBLOCK);

  //Bind del socket
  errore=bind(sock,(struct sockaddr*) &temp,sizeof(temp));
  //Per esempio, facciamo accettare fino a 7 richieste di servizio
  //contemporanee (che finiranno nella coda delle connessioni).
  errore=listen(sock,7);
 
  return sock;
}

void ChiudiSocket(int sock)
{
  close(sock);
  return;
}

int main()
{
  //N.B. L'esempio non usa la funzione fork per far vedere l'utilizzo di
  //     socket non bloccanti

  char  buffer[512];
  int DescrittoreSocket,NuovoSocket;
  int exitCond=0;
  int Quanti;

  DescrittoreSocket=CreaSocket(1745);
  printf("Server: Attendo connessioni...\n");
  while (!exitCond)
  {
    //Test sul socket: accept non blocca, ma il ciclo while continua
    //l'esecuzione fino all'arrivo di una connessione.
    if ((NuovoSocket=accept(DescrittoreSocket,0,0))!=-1)
    {
      //Lettura dei dati dal socket (messaggio ricevuto)
      if ((Quanti=read(NuovoSocket,buffer,sizeof(buffer)))<0)
      {
         printf("Impossibile leggere il messaggio.\n");
         ChiudiSocket(NuovoSocket);
      }
      else
      {
         //Aggiusto la lunghezza...
         buffer[Quanti]=0;
         //Elaborazione dati ricevuti
         if (strcmp(buffer,"exit")==0)
              exitCond=1;
         else printf("Server: %s \n",buffer);
      }
      //Chiusura del socket temporaneo
      ChiudiSocket(NuovoSocket);
    }
  }
  //Chiusura del socket
  ChiudiSocket(DescrittoreSocket);
  printf("Server: Terminato.\n");

  return 0;
}
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)..
Pubblicita'

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.
Per utilizzre, invece, un protocollo ben preciso, si puo' guardare il file "/etc/protocols" contenente i numeri associati ai tipi di protocolli possibili.

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.
Per capire meglio il significato, si pensi ad un ciclo "while" in cui all'interno vi e' una attesa per una connessione. Con un socket bloccante, il ciclo non continuera' e si blocchera' fino all'arrivo di una richiesta di connessione.
Con un socket non bloccante, invece, non si ha una attesa indefinita. Se al momento della verifica, non e' presente alcuna richiesta di connessione, allora il thread continuera' con la propria esecuzione, senza bloccarsi.
Il modo di rendere un socket non bloccante e' il seguente (si assume che il descrittore del socket sia nella variabile intera con nome "sock"):

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":
struct sockaddr
  {
    unsigned short int sa_family; // Fmiglia degli indirizzi.
    char sa_data[14]; // Dati dell'indirizzo.
  };
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:

  struct in_addr
  {
    unsigned int s_addr;
  };
  struct sockaddr_in
  {
    unsigned short int sin_family; // Famiglia degli indirizzi.
    unsigned short int sin_port; // Numero della porta (16 bit).
    struct in_addr sin_addr; // Indirizzi internet.
    // Campo per adattare la dimensione di sockaddr_in a quella della struttura "sockaddr".
    unsigned char sin_zero[sizeof (struct sockaddr) -
             sizeof (unsigned short int) -
             sizeof (unsigned short int) -
             sizeof (struct in_addr)];
  };
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:
#include <netdb.h>
...
struct sockaddr_in temp;
struct hostent *indirizzo_host;
int eventuale_errore;
int sock;
...
//Tipo di indirizzo
temp.sin_family=AF_INET;
//Porta d'ascolto sul server a cui ci si vuole connettere.
temp.sin_port=htons(321); //Numero casuale
//"gethostbyname" restituisce una struttura di tipo "hostent" in cui mette
//l'indirizzo IP. Riceve in ingresso il nome dell'host che puo' essere
//un nome, in indirizzo IPv4 o un indirizzo IPv6.
//Nel caso di IPv4 o IPv6, il campo "indirizzo_host->h_addr" contiene il nome stesso.
indirizzo_host=gethostbyname("nome.inventato.it");
//Se hp contiene il puntatore nullo, allora la chiamata a gethostbyname e' fallita.
if (indirizzo_host==0)
{
  printf("Gethostbyname fallito\n");
  if (h_errno==HOST_NOT_FOUND) printf("host not found\n");
  if (h_errno==NO_ADDRESS) printf("name is valid but no has IP address\n");
  if (h_errno==NO_RECOVERY) printf("non recoverable name server error occurred\n");
  exit(1);
}
//Copio l'indirizzo nella struttura di tipo "sockaddr_in", nel campo relativo
//all'indirizzo.
bcopy(indirizzo_host->h_addr,&temp.sin_addr,indirizzo_host->h_length);
//Connessione del socket
eventuale_errore=connect(sock,(struct sockaddr*) &temp, sizeof(temp));

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.
Pubblicita'
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".

Hai trovato utile questo articolo?
Aiutami a condividerlo o metti un "mi piace".
Grazie mille!


Gli strumenti di condivisione (Google+, Facebook) sono visibili in alto a destra solo dopo aver accettato la policy di utilizzo dei cookie per questo sito.
FAQ - Come faccio a cambiare la mia scelta?

 

Strumenti (myjsp.feelinglinux.com)
Gioco: allenamento con la tastiera Strumenti di codifica/decodifica URI (%-encoding) e Base64 Strumenti di calcolo online per IP e Reti
QUIZ GAME
Quiz game

Cerca @myjsp.feelinglinux.com

Pubblicita'