From bd13286d87e6273db0f55e8fd95ba3730171def4 Mon Sep 17 00:00:00 2001 From: Alessandro Iezzi Date: Mon, 27 Dec 2021 15:58:27 +0100 Subject: Initial commit --- articles/2009/20090602_socket_pt1.rhtml | 285 ++++++++++++++++++++++++++++++++ 1 file changed, 285 insertions(+) create mode 100644 articles/2009/20090602_socket_pt1.rhtml (limited to 'articles/2009/20090602_socket_pt1.rhtml') diff --git a/articles/2009/20090602_socket_pt1.rhtml b/articles/2009/20090602_socket_pt1.rhtml new file mode 100644 index 0000000..754a18e --- /dev/null +++ b/articles/2009/20090602_socket_pt1.rhtml @@ -0,0 +1,285 @@ +
+ +

Programmare con le socket (Parte 1)

+

Alessandro Iezzi, 2 giugno 2009

+ + +

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. +
  3. Gli Unix Domain Socket. Queste socket vengono usate in ambienti POSIX per la comunicazione in locale dei processi.
  4. +
  5. Socket che utilizzano i protocolli di Xerox Network System.
  6. +
  7. L’ultima tipologia è quella che utilizza i protocolli della Internationa Standard Association (fa riferimento al modello ISO/OSI).
  8. +
+

In questo articolo andremo a vedere solo la prima tipologia.

+ + + +

Come programmare con le socket

+

Lato server

+ +

Lato client

+ +

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").

+ +

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:

+ +

Esistono anche altri due tipi che non si vedranno.

+

Argomento: protocol

+

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

+ +

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:

+
server.c
+
/*
+ * File: server.c
+ * Autore: Alessandro Iezzi
+ * Server d'esempio
+ */
+#include \<stdio.h>
+#include \<string.h>
+#include \<sys/types.h>
+#include \<sys/socket.h>
+#include \<netinet/in.h>
+#include \<netdb.h>
+#include \<stdlib.h>
+/*#include \<arpa/inet.h>*/ /* Se si vuole visualizzare l'indirizzo ip del client, decommentare questa riga */
+
+#define MAX 8192 /* in bytes, 8KB */
+
+int main()
+{
+  char buff[MAX]; /* dati di invio e ricezione */
+  struct sockaddr_in server_addr; /* indirizzo del server */
+  struct sockaddr_in client_addr; /* indirizzo del client */
+  int sd_server, sd_client; /* i socket descriptor usati per identificare server e client */
+
+  /*
+   * Creazione socket descriptor per il server.
+   * AF_INET + SOCK_STREAM --\> TCP, utilizzo del protocollo TCP (IPPROTO_TCP)
+   */
+  if((sd_server = socket(AF_INET, SOCK_STREAM, 0)) \< 0)
+    printf("Errore nella creazione del server\\n");
+
+  /*
+   * Inseriamo nella struttura alcune informazioni
+   */
+  server_addr.sin_family = AF_INET;         /* la famiglia dei protocolli */
+  server_addr.sin_port = htons(1745);       /* la porta in ascolto */
+  server_addr.sin_addr.s_addr = INADDR_ANY; /* dato che è un server bisogna associargli l'indirizzo della macchina su cui sta girando */
+
+  /*
+   * Assegnazione del processo alla socket tramite la funzione BIND
+   */
+  if(bind(sd_server, (struct sockaddr *)\&server_addr, sizeof(server_addr)) \< 0)
+    printf("Errore di binding\\n");
+
+  /*
+   * Si mette in ascolto con un massimo di 20 connessioni
+   */
+  listen (sd_server, 20);
+
+  /*
+   * Essendo un server monothreading, accetterà una sola connessione per volta
+   */
+  int address_size = sizeof(client_addr); /* dimensione della struttura client_addr */
+
+  /* Con opportune modifiche si potrebbe vedere anche l'ip del client */
+  if((sd_client = accept(sd_server, (struct sockaddr *)\&client_addr, \&address_size)) \< 0)
+    printf("Errore nella chiamata accept\\n");
+
+  /* si ricevono i dati dal client */
+  recv(sd_client, buff, sizeof(buff), 0);
+  printf("Dati ricevuti: %s\\n", buff);
+
+  /* Decommentare queste due righe per visualizzare l'indirizzo ip del client */
+  /*
+  char *ip_address = inet_ntoa(client_addr.sin_addr);
+  printf("IP del client: %s\\n", ip_address);
+   */
+
+  /* si spedisce un messaggio */
+  strcpy(buff, "Tutto OK!");
+  send(sd_client, buff, strlen(buff), 0);
+
+  /* chiusura del socket descriptor */
+  close(sd_client);
+  close(sd_server);
+
+  return EXIT_SUCCESS;
+}
+ +
client.c
+
/*
+ * File: client.c
+ * Autore: Alessandro Iezzi
+ * Client d'esempio
+ */
+#include \<stdio.h>
+#include \<string.h>
+#include \<sys/types.h>
+#include \<sys/socket.h>
+#include \<netdb.h>
+#include \<netinet/in.h>
+#include \<stdlib.h>
+
+#define MAX 8192 /* in bytes, 8KB */
+
+int main() {
+  int sd; /* Il socket descriptor del client */
+  struct sockaddr_in server_addr; /* l'indirizzo del server */
+  char buff[MAX]; /* dati di invio e ricezione */
+
+  /* Utilizzando la struttura hostent si definisce l'indirizzo del server */
+  struct hostent *hp;
+  hp = gethostbyname("127.0.0.1");
+
+  server_addr.sin_family = AF_INET;
+  server_addr.sin_port = htons(1745);
+
+  /* successivamente viene memorizzato nella struttura server_addr */
+  server_addr.sin_addr.s_addr = ((struct in_addr*)(hp-\>h_addr)) -\> s_addr;
+
+  /* Viene creato il socket descriptor */
+  if((sd = socket(AF_INET, SOCK_STREAM, 0)) \< 0)
+    printf("Errore nella creazione della socket\\n");
+
+  /* Viene connesso al server */
+  if(connect(sd, (struct sockaddr *)\&server_addr, sizeof(server_addr)) \< 0)
+    printf("Errore di connessione al server\\n");
+
+  /* Si inviano alcuni dati */
+  send(sd, "Dati inviati dal client", strlen("Dati inviati dal client"), 0);
+
+  /* Si riceve la risposta */
+  recv(sd, buff, sizeof(buff), 0);
+  printf("Risposta del server: %s\\n", buff);
+
+  close(sd);
+
+  return EXIT_SUCCESS;
+}
+ + + +
-- cgit v1.2.3