aboutsummaryrefslogtreecommitdiff
path: root/articles/2009/20090602_socket_pt1.rhtml
blob: 754a18e7a0c249b22dd5d22fca1b953ef2c15000 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
<div class="article">
  
<h1>Programmare con le socket (Parte 1)</h1>
<p class="date-published">Alessandro Iezzi, 2 giugno 2009</p>


<h2>Introduzione</h2>
<p>La <em>socket</em> (in inglese “<em>presa</em>“) è 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.</p>


<h2>Client/Server</h2>
<p>La struttura base di funzionamento delle socket è di tipo <em>Client/Server</em>. 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.</p>


<h2>Tipologie di socket</h2>
<p>Esistono quattro tipologie di socket:</p>
<ol>
    <li>Socket che utilizzano i protocolli ARPA di internet (come TCP e UDP).</li>
    <li>Gli Unix Domain Socket. Queste socket vengono usate in ambienti POSIX per la comunicazione in locale dei processi.</li>
    <li>Socket che utilizzano i protocolli di Xerox Network System.</li>
    <li>L’ultima tipologia è quella che utilizza i protocolli della Internationa Standard Association (fa riferimento al modello ISO/OSI).</li>
</ol>
<p>In questo articolo andremo a vedere solo la prima tipologia.<span id="more-275"></span></p>



<h2>Come programmare con le socket</h2>
<h3>Lato server</h3>
<ul>
    <li>Si genera la socket e la si associa al processo (binding, in italiano “legare”).</li>
    <li>Si mette in ascolto il server per delle nuove connessioni da parte dei client, specificandone il numero.</li>
    <li>A questo punto si accettano le connessioni e avviene la comunicazione.</li>
</ul>
<h3>Lato client</h3>
<ul>
    <li>Si genera anche qui la socket (in questo caso si definisce l’indirizzo ip del server) e la si associa al processo.</li>
    <li>Si effettua una connessione al server, se viene accettata può incominciare la comunicazione.</li>
</ul>
<p>Al termine della sessione di comunicazione vengono chiuse tutte le socket aperte ed i vari stream (nel caso di invii di file).</p>


<h2>Le funzioni da utilizzare</h2>
<h3>Funzione Socket</h3>
<p>La prima funzione che si va ad analizzare è:</p>
<pre>int socket(int domain, int type, >int protocol);</pre>
<p>La funzione restituisce un intero che ne identifica la socket. Questo intero viene chiamato <em>socket descriptor</em>. La funzione
richiede tre argomenti:</p>
<h4>Argomento: <em>domain</em></h4>
<p>Questo parametro serve a identificare quale tipo di famiglia di protocolli si vuole utilizzare (vedi, "Tipologie di socket").</p>
<ul>
    <li>AF_INET: Protocolli ARPA di internet.</li>
    <li>AF_UNIX: Protocolli interni di sistemi POSIX.</li>
    <li>AF_NS: Protocolli di Xerox Network System.</li>
    <li>AF_ISO: Protocolli della International Standard Association.</li>
</ul>
<p>Il prefisso AF sta per address family. Esistono anche un altro gruppo con prefisso PF. L’uso dei due prefissi è indifferente.</p>
<h4>Argomento: <em>type</em></h4>
<p>Questo parametro serve a specificare quale tipo di connessione deve essere stabilita. Può assumere i seguenti valori:</p>
<ul>
    <li>SOCK_STREAM: Connessione sequenzia (TCP)</li>
    <li> SOCK_DGRAM: Connessione trmite datagramma (UDP)</li>
    <li>SOCK_RAW: Connessione tramite protocollo IP</li>
</ul>
<p>Esistono anche altri due tipi che non si vedranno.</p>
<h4>Argomento: <em>protocol</em></h4>
<p>Serve a specificare quale protocollo usare. Le possibili opzioni sono:</p>
<ul>
    <li>IPPROTO_TCP: Protocollo TCP.</li>
    <li>IPPROTO_UDP: Protocollo UDP.</li>
    <li>IPPROTO_ICMP: Protocollo ICMP.</li>
    <li>IPPROTO_RAW: Protocollo IP.</li>
    <li>0: Il sistema sceglie il protocollo adatto, in base alla coppia 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).</li>
</ul>
<h3>Funzione Bind</h3>
<p>Questa funzione permette di associare una socket ad un processo. La sua sintassi è la seguente:</p>
<pre>int bind(int sd, struct sockaddr *my_addr, int addr_lenght);</pre>
<p>Come si può notare richiede 3 argomenti:</p>
<h4>Argomento: <em>sd</em></h4>
<p>Ossia il <em>socket descriptor</em> ottenuto dalla funzione socket.</p>
<h4>Argomento: <em>*my_addr</em></h4>
<p>È 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:</p>
<pre>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 */
};</pre>
<p>Questa struttura attraverso il casting viene trasformata in quest’altra struttura:</p>
<pre>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) */
};</pre>
<h4>Argomento: <em>addr_lenght</em></h4>
<p>È la dimensione della struttura passata come secondo argomento. La dimensione della struttura si ricava utilizzando la funzione sizeof.</p>
<h3>Funzione Listen</h3>
<pre>int listen(int sd, int queue);</pre>
<p>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:</p>
<h4>Argomento: <em>sd</em></h4>
<p>Il <em>socket descriptor</em> generato dalla funzione socket</p>
<h4>Argomento: <em>queue</em></h4>
<p>Il numero di connessioni massime che ammette il processo.</p>
<h3>Funzione Accept</h3>
<p>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.</p>
<pre>int accept(int sd, struct sockaddr *client_addr, int client_addr_lenght);</pre>
<p>Gli argomenti che vengono richiesti sono:</p>
<h4>Argomento: <em>sd</em></h4>
<p>Il solito socket descriptor generato dalla funzione socket</p>
<h4>Argomento: <em>client_addr</em></h4>
<p>Una struttura che conterrà alcune informazioni del client. Come quelle viste in precedenza nella funzione bind.</p>
<h4>Argomento: <em>client_addr_lenght</em></h4>
<p>La dimensione della struttura che si passa come secondo argomento.</p>
<h3>Funzione Connect</h3>
<p>Consente ad un client di connettersi ad un server.</p>
<pre>int connect(int sdc, sockaddr *server_addr, int server_addr_lenght);</pre>
<p>Richiede tre argomenti:</p>
<h4>Argomento: <em>sdc</em></h4>
<p>È il socket descriptor generato dalla funzione socket richiamata all’interno del client.</p>
<h4>Argomento: <em>server_addr</em></h4>
<p>È la struttura che conterrà l’indirizzo a cui connettersi e la porta del server.</p>
<h4>Argomento: <em>server_addr_lenght</em></h4>
<p>È la lunghezza della struttura passato come secondo argomento.</p>
<h3>Le funzioni Recv e Send</h3>
<p>Vengono usate sia dal server che dal client per inviare informazioni:</p>
<pre>int recv(int sd, void *msg, int msg_lenght, int flag);</pre>
<pre>int send(int sd, void *msg, int msg_lenght, int flag);</pre>
<p>Richiedono tre argomenti:</p>
<h4>Argomento: <em>sd</em></h4>
<p>Il <em>socket descriptor;</em></p>
<h4>Argomento: <em>msg</em></h4>
<p>Per quanto riguarda la funzione recv sono i dati da ricevere, quindi è la variabile nella quale verrano memorizzati.<br>
Per quanto riguarda la funzione send sono i dati da inviare.</p>
<h4>Argomento: <em>flag</em></h4>
<p>Generalmente è posto a 0. Le opzioni possibili non saranno trattate in questo articolo.</p>



<h2>Esempio</h2>
<p>Di seguito sono mostrati i codici sorgente di un server e di un client. Basta rinominarli rispettivamente in <em>server.c</em> e
<em>client.c</em>. Da terminale basta dare i seguenti comandi:</p>
<pre>$ gcc server.c -o server</pre>
<pre>$ gcc client.c -o client</pre>
<p>Ecco i sorgenti:</p>
<h6><span style="color: #3366ff;"><strong>server.c</strong></span></h6>
<pre lang="c">/*
 * File: server.c
 * Autore: Alessandro Iezzi
 * Server d'esempio
 */
#include \&lt;stdio.h>
#include \&lt;string.h>
#include \&lt;sys/types.h>
#include \&lt;sys/socket.h>
#include \&lt;netinet/in.h>
#include \&lt;netdb.h>
#include \&lt;stdlib.h>
/*#include \&lt;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 --\&gt; TCP, utilizzo del protocollo TCP (IPPROTO_TCP)
   */
  if((sd_server = socket(AF_INET, SOCK_STREAM, 0)) \&lt; 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 *)\&amp;server_addr, sizeof(server_addr)) \&lt; 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 *)\&amp;client_addr, \&amp;address_size)) \&lt; 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;
}</code></pre>

<h6><span style="color: #3366ff;"><strong>client.c</strong></span></h6>
<pre lang="c">/*
 * File: client.c
 * Autore: Alessandro Iezzi
 * Client d'esempio
 */
#include \&lt;stdio.h>
#include \&lt;string.h>
#include \&lt;sys/types.h>
#include \&lt;sys/socket.h>
#include \&lt;netdb.h>
#include \&lt;netinet/in.h>
#include \&lt;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-\&gt;h_addr)) -\&gt; s_addr;

  /* Viene creato il socket descriptor */
  if((sd = socket(AF_INET, SOCK_STREAM, 0)) \&lt; 0)
    printf("Errore nella creazione della socket\\n");

  /* Viene connesso al server */
  if(connect(sd, (struct sockaddr *)\&amp;server_addr, sizeof(server_addr)) \&lt; 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;
}</pre>



</div>