Skip to content


Come programmare le socket in ambienti GNU/Linux (Parte 1)

Introduzione

La socket (in inglese "presa") è una particolare astrazione software che permette ai processi messi in comunicazione di inviare e ricevere dati. Le socket sono nate intorno agli anni '80, il primo kernel a implementarle fu BSD 4.2 nel 1983.

Client/Server

La struttura base di funzionamento delle socket è di tipo Client/Server. Supponiamo di avere due processi p1 e p2. Il processo p2 ha bisogno del processo p1 per eseguire un determinato compito. Il processo p1 offrirà al processo p2 tale servizio, esso perciò sarà il "Servente" ovvero il Server. Il processo p2 che richiede il servizio sarà dunque il "Cliente", ovvero il Client.

Tipologie di socket

Esistono quattro tipologie di socket:

  1. Socket che utilizzano i protocolli ARPA di internet (come TCP e UDP).
  2. Gli Unix Domain Socket. Queste socket vengono usate in ambienti POSIX per la comunicazione in locale dei processi.
  3. Socket che utilizzano i protocolli di Xerox Network System.
  4. L'ultima tipologia è quella che utilizza i protocolli della Internationa Standard Association (fa riferimento al modello ISO/OSI).

In questo articolo andremo a vedere solo la prima tipologia.

Come programmare con le socket

Lato server:

- Si genera la socket e la si associa al processo (binding, in italiano "legare").
- Si mette in ascolto il server per delle nuove connessioni da parte dei client, specificandone il numero.
- A questo punto si accettano le connessioni e avviene la comunicazione.

Lato client:

- Si genera anche qui la socket (in questo caso si definisce l'indirizzo ip del server) e la si associa al processo.
- Si effettua una connessione al server, se viene accettata può incominciare la comunicazione.

Al termine della sessione di comunicazione vengono chiuse tutte le socket aperte ed i vari stream (nel caso di invii di file).

Le funzioni da utilizzare

Funzione Socket

La prima funzione che si va ad analizzare è:

int socket(int domain, int type, int protocol);

La funzione restituisce un intero che ne identifica la socket. Questo intero viene chiamato socket descriptor. La funzione richiede tre argomenti:

Argomento: domain

Questo parametro serve a identificare quale tipo di famiglia di protocolli si vuole utilizzare (vedi, "Tipologie di socket").

  • AF_INET: Protocolli ARPA di internet.
  • AF_UNIX: Protocolli interni di sistemi POSIX.
  • AF_NS: Protocolli di Xerox Network System.
  • AF_ISO: Protocolli della International Standard Association.

Il prefisso AF sta per address family. Esistono anche un altro gruppo con prefisso PF. L'uso dei due prefissi è indifferente.

Argomento: type

Questo parametro serve a specificare quale tipo di connessione deve essere stabilita. Può assumere i seguenti valori:

  • SOCK_STREAM: Connessione sequenzia (TCP)
  • SOCK_DGRAM: Connessione trmite datagramma (UDP)
  • SOCK_RAW: Connessione tramite protocollo IP

Esistono anche altri due tipi che non si vedranno.

Argomento: protocol

Serve a specificare quale protocollo usare. Le possibili opzioni sono:

  • IPPROTO_TCP: Protocollo TCP.
  • IPPROTO_UDP: Protocollo UDP.
  • IPPROTO_ICMP: Protocollo ICMP.
  • IPPROTO_RAW: Protocollo IP.
  • 0: Il sistema sceglie il protocollo adatto, in base alla comppia degli argomenti domain e type. Infatti, AF_INET e SOCK_STREAM identificano un protocollo TCP (IPPROTO_TCP), AF_INET e SOCK_DGRAM identificano un protocollo UDP (IPPROTO_UDP).

Funzione Bind

Questa funzione permette di associare una socket ad un processo. La sua sintassi è la seguente:

int bind(int sd, struct sockaddr *my_addr, int addr_lenght);

Come si può notare richiede 3 argomenti:

Argomento: sd

Ossia il socket descriptor ottenuto dalla funzione socket.

Argomento: *my_addr

È una struttura che contiene informazioni come l'indirizzo al quale si vuole connettere, la famiglia a cui appartiene (AF_xxx) e la porta. Tramite casting viene passata una struttura sockaddr_in:

struct sockaddr_in {
short int sin_family; /* famiglia AF_xxx */
unsigned short int sin_port; /* numero di porta */
struct in_addr sin_addr; /* l'indirizzo al quale ci si connette (dipende dal protocollo) */
char sin_zero[8] /* non usato */
};

Questa struttura attraverso il casting viene trasformata in quest'altra struttura:

struct sockaddr {
uint8_t sa_len;
sa_family_t sa_family; /* famiglia AF_xxx */
char sa_data[14]; /* l'indirizzo al quale ci si connette (dipende dal protocollo) */
};

Argomento: addr_lenght

È la dimensione della struttura passata come secondo argomento. La dimensione della struttura si ricava utilizzando la funzione sizeof.

Funzione Listen

 int listen(int sd, int queue);

Una volta creata la socket e associata al processo, quest'ultimo viene messo in ascolto per eventuali connessioni da parte dei client. Accetta solo due argomenti:

Argomento: sd

Il socket descriptor generato dalla funzione socket

Argomento: queue

Il numero di connessioni massime che ammette il processo.

Funzione Accept

Questa funzione consente di accettare le connessioni da parte dei client. Una volta messo in ascolto il processo viene eseguita questa funzione che genera un socket descriptor associato al client connesso.

int accept(int sd, struct sockaddr *client_addr, int client_addr_lenght);

Gli argomenti che vengono richiesti sono:

Argomento: sd

Il solito socket descriptor generato dalla funzione socket

Argomento: client_addr

Una struttura che conterrà alcune informazioni del client. Come quelle viste in precedenza nella funzione bind.

Argomento: client_addr_lenght

La dimensione della struttura che si passa come secondo argomento.

Funzione Connect

Consente ad un client di connettersi ad un server.

int connect(int sdc, sockaddr *server_addr, int server_addr_lenght);

Richiede tre argomenti:

Argomento: sdc

È il socket descriptor generato dalla funzione socket richiamata all'interno del client.

Argomento: server_addr

È la struttura che conterrà l'indirizzo a cui connettersi e la porta del server.

Argomento: server_addr_lenght

È la lunghezza della struttura passato come secondo argomento.

Le funzioni Recv e Send

Vengono usate sia dal server che dal client per inviare informazioni:

int recv(int sd, void *msg, int msg_lenght, int flag);
int send(int sd, void *msg, int msg_lenght, int flag);

Richiedono tre argomenti:

Argomento: sd

Il socket descriptor;

Argomento: msg

Per quanto riguarda la funzione recv sono i dati da ricevere, quindi è la variabile nella quale verrano memorizzati.
Per quanto riguarda la funzione send sono i dati da inviare.

Argomento: flag

Generalmente è posto a 0. Le opzioni possibili non saranno trattate in questo articolo.

Esempio

Di seguito sono mostrati i codici sorgente di un server e di un client. Basta rinominarli rispettivamente in server.c e client.c. Da terminale basta dare i seguenti comandi:

$gcc server.c -o server
$gcc client.c -o client

Ecco i sorgenti:

Il file server.c

  1. /*
  2.  * File: server.c
  3.  * Autore: Iezzi Alessandro
  4.  * Server d'esempio
  5.  */
  6. #include <stdio.h>
  7. #include <string.h>
  8. #include <sys/types.h>
  9. #include <sys/socket.h>
  10. #include <netinet/in.h>
  11. #include <netdb.h>
  12. #include <stdlib.h>
  13. /*#include <arpa/inet.h>*/ /* Se si vuole visualizzare l'indirizzo ip del client, decommentare questa riga */
  14.  
  15. #define MAX 8192 /* in bytes, 8KB */
  16.  
  17. int main()
  18. {
  19. char buff[MAX]; /* dati di invio e ricezione */
  20. struct sockaddr_in server_addr; /* indirizzo del server */
  21. struct sockaddr_in client_addr; /* indirizzo del client */
  22. int sd_server, sd_client; /* i socket descriptor usati per identificare server e client */
  23.  
  24. /*
  25.  * Creazione socket descriptor per il server.
  26.  * AF_INET + SOCK_STREAM --> TCP, utilizzo del protocollo TCP (IPPROTO_TCP)
  27.  */
  28. if((sd_server = socket(AF_INET, SOCK_STREAM, 0)) < 0)
  29. printf("Errore nella creazione del server\n");
  30.  
  31. /*
  32.  * Inseriamo nella struttura alcune informazioni
  33.  */
  34. server_addr.sin_family = AF_INET; /* la famiglia dei protocolli */
  35. server_addr.sin_port = htons(1745); /* la porta in ascolto */
  36. server_addr.sin_addr.s_addr = INADDR_ANY; /* dato che è un server bisogna associargli l'indirizzo della macchina su cui sta girando */
  37.  
  38. /*
  39.  * Assegnazione del processo alla socket tramite la funzione BIND
  40.  */
  41. if(bind(sd_server, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
  42. printf("Errore di binding\n");
  43.  
  44. /*
  45.  * Si mette in ascolto con un massimo di 20 connessioni
  46.  */
  47. listen (sd_server, 20);
  48.  
  49. /*
  50.  * Essendo un server monothreading, accetterà una sola connessione per volta
  51.  */
  52. int address_size = sizeof(client_addr); /* dimensione della struttura client_addr */
  53. /* Con opportune modifiche si potrebbe vedere anche l'ip del client */
  54. if((sd_client = accept(sd_server, (struct sockaddr *)&client_addr, &address_size)) < 0)
  55. printf("Errore nella chiamata accept\n");
  56. /* si ricevono i dati dal client */
  57. recv(sd_client, buff, sizeof(buff), 0);
  58. printf("Dati ricevuti: %s\n", buff);
  59. /* Decommentare queste due righe per visualizzare l'indirizzo ip del client */
  60. /*char *ip_address = inet_ntoa(client_addr.sin_addr);
  61. printf("<acronym title="Internet Protocol">IP</acronym> del client: %s\n", ip_address);*/
  62. /* si spedisce un messaggio */
  63. strcpy(buff, "Tutto OK!");
  64. send(sd_client, buff, strlen(buff), 0);
  65. /* chiusura del socket descriptor */
  66. close(sd_client);
  67. close(sd_server);
  68.  
  69. return EXIT_SUCCESS;
  70. }

Il file client.c

  1. /*
  2.  * File: client.c
  3.  * Autore: Iezzi Alessandro
  4.  * Client d'esempio
  5.  */
  6. #include <stdio.h>
  7. #include <string.h>
  8. #include <sys/types.h>
  9. #include <sys/socket.h>
  10. #include <netdb.h>
  11. #include <netinet/in.h>
  12. #include <stdlib.h>
  13.  
  14. #define MAX 8192 /* in bytes, 8KB */
  15.  
  16. int main() {
  17. int sd; /* Il socket descriptor del client */
  18. struct sockaddr_in server_addr; /* l'indirizzo del server */
  19. char buff[MAX]; /* dati di invio e ricezione */
  20.  
  21. /* Utilizzando la struttura hostent si definisce l'indirizzo del server */
  22. struct hostent *hp;
  23. hp = gethostbyname("127.0.0.1");
  24.  
  25. server_addr.sin_family = AF_INET;
  26. server_addr.sin_port = htons(1745);
  27. /* successivamente viene memorizzato nella struttura server_addr */
  28. server_addr.sin_addr.s_addr = ((struct in_addr*)(hp->h_addr)) -> s_addr;
  29.  
  30. /* Viene creato il socket descriptor */
  31. if((sd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
  32. printf("Errore nella creazione della socket\n");
  33.  
  34. /* Viene connesso al server */
  35. if(connect(sd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
  36. printf("Errore di connessione al server\n");
  37.  
  38. /* Si inviano alcuni dati */
  39. send(sd, "Dati inviati dal client", strlen("Dati inviati dal client"), 0);
  40.  
  41. /* Si riceve la risposta */
  42. recv(sd, buff, sizeof(buff), 0);
  43. printf("Risposta del server: %s\n", buff);
  44. close(sd);
  45. return EXIT_SUCCESS;
  46. }

Questo è tutto, alla prossima.

Be Sociable, Share!

Posted in C.

Tagged with , , .


23 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. Nagy says

    I’m quite pleased with the intormafion in this one. TY!

1 2

Continuing the Discussion

  1. Come programmare le socket in ambienti GNU/Linux (Parte 2) – OScene.net - Italiano linked to this post on 20 October 2010

    […] Come programmare le socket in ambienti GNU/Linux (Parte 2) Ti interesser&agrave anche…Come programmare le socket in ambienti GNU/Linux (Parte 1) Nel primo articolo abbiamo analizzato il funzionamento delle socket e di un server con rispettivo client. In questo vedremo un modo di programmare un tipico server concorrente utilizzando la funzione fork(). […]



Some HTML is OK

or, reply to this post via trackback.