Programmazione socket
2-1
Programmazione socket
Obiettivo: imparare a costruire applicazioni
client/server che comunicano tramite socket
Socket API
 introdotte in UNIX BSD4.1,
1981
 create, utilizzate e
rilasciate esplicitamente
dalle applicazioni
 paradigma client/server
 due tipi di servizi di
trasporto via API socket:
 unreliable datagram
 reliable, byte streamoriented
socket
un’interfaccia situata
nell’host, creata
dall’applicazione e
controllata dal SO
attraverso la quale un
processo applicativo può
sia inviare che ricevere
messaggi a/da un altro
processo applicativo
situato in un altro host
2-2
I due tipi principali di socket
 SOCK_STREAM





 SOCK_DGRAM
TCP
affidabile
ordine dati garantito
orientato alla connessione
bidirezionale





App
3 2
1
UDP
inaffidabile
nessuna garanzia su ordine dati
nessuna nozione di “connessione”
– l’applicazione indica la
destinazione di ogni pacchetto
può inviare o ricevere
App
3
socket
2
D1
2
1
1
Dest.
3 2
1
socket
D2
3
D3
2-3
Creazione di socket in C: socket
 int s = socket (domain, type, protocol);
 s: socket descriptor, un intero (come un file-handle)
 domain: intero, dominio di comunicazione
• es., AF_INET (IPv4 protocol) – usato di solito

type: tipo di comunicazione
• SOCK_STREAM: affidabile, 2-vie, connection-based
• SOCK_DGRAM: inaffidabile, connectionless
• altri valori: servono permessi root, usati raramente o
obsoleti
protocol: specifica il protocollo, di solito settato a 0
(vedere file /etc/protocols per una lista di opzioni)
 NOTA: Una chiamata socket non specifica da dove
verranno i dati o dove andranno, crea solamente
un’interfaccia!

2-4
La funzione bind
 associa e riserva un port alla socket
 int status = bind (sockid, &addrport, size);
 status: error status, = -1 se il bind fallisce
 sockid: intero, socket descriptor
 addrport: struct sockaddr, l’indirizzo (IP) e il port
della macchina
• es. indirizzo: INADDR_ANY sceglie l’indirizzo locale
• es. port: 0 lascia al SO il compito di stabilire il port

size: la dimensione (in byte) della struttura
addrport
 usato dal server (opzionalmente dal client)
2-5
Quando usare il bind
 SOCK_DGRAM:
 in trasmissione il bind non è necessario. Il SO
trova un port ogni volta che la socket manda un
pacchetto
 in ricezione il bind è necessario
 SOCK_STREAM:
 la destinazione è determinata durante il setup
di connessione
 non occorre conoscere il port attraverso cui
vengono inviati i dati (durante il setup di
connessione l’estremità ricevente è informata
sul port del mittente)
2-6
Connection Setup
(SOCK_STREAM)
 Ricordare: nessun connection setup per il
SOCK_DGRAM
 I partecipanti alla connessione sono di due tipi:


passivo: aspetta che un partecipante attivo richieda la
connessione
attivo: inizia la richiesta di connessione verso il lato
passivo
 Una volta che la connessione è stabilita, i
partecipanti attivi e passivi sono “simili”


entrambi possono mandare e ricevere dati
ognuno può terminare la connessione
2-7
Connection setup (cont.)
 Participante passivo (es.
server)



step 1: listen (per arrivo di
richieste)
step 3: accept (una
richiesta)
step 4: trasferimento dati
 La connessione viene
accettata su una nuova
socket
 La vecchia socket continua
ad aspettare la connessione
di nuovi partecipanti
 Three way handshaking
 Participante attivo (es.
client)


step 2: richiede &
stabilisce connection
step 4: trasferimento dati
Passive Participant
a-sock-1
l-sock
a-sock-2
socket
socket
Active 1
Active 2
2-8
Connection setup: listen & accept
 Usate dal partecipante passivo (server)
 int status = listen (sock, queuelen);
 status: 0 se si mette in ascolto, -1 se dà errore
 sock: intero, socket descriptor
 queuelen: intero, numero di partecipanti attivi che possono
“aspettare” per una connessione
 listen è non-blocking: ritorna immediatamente
 int s = accept (sock, &name, namelen);
 s: intero, la nuova socket (usata per il trasferimento dati)
 sock: intero, la socket originale, usata come prototipo per s
 name: struct sockaddr, indirizzo del partecipante attivo
 namelen: sizeof(name): valore/risultato
• deve essere settato in maniera appropriata prima della chiamata
• aggiustato dal SO quando la funzione ritorna

accept è blocking: aspetta una connessione prima di ritornare
2-9
connect call
 Usata dal partecipante attivo (client)
 int status = connect (sock, &name, namelen);
 status: 0 se connessione OK, -1 altrimenti
 sock: intero, socket da essere utilizzata nella
connessione
 name: struct sockaddr: indirizzo del partecipante
passivo
 namelen: intero, sizeof(name)
 connect è blocking
2-10
Sending / Receiving Data
 Con connessione (SOCK_STREAM):
 int count = send (sock, &buf, len, flags);
•
•
•
•

int count = recv (sock, &buf, len, flags);
•
•
•
•

count: Numero byte trasmessi (-1 se errore)
buf: char[ ], buffer da trasmettere
len: intero, lunghezza buffer (in byte) da trasmettere
flags: intero, opzioni speciali, di solito settate a 0
count: Num. byte ricevuti (-1 se errore)
buf: void[ ], immagazzina i byte ricevuti
len: intero, lunghezza buffer (in byte)
flags: intero, opzioni speciali, di solito settate a 0
Le chiamate sono blocking [ritornano solo dopo
che i dati sono inviati (al socket buf) / ricevuti]
2-11
Sending / Receiving Data
(cont.)
 Senza connessione (SOCK_DGRAM):
 int
count = sendto (sock, &buf, len, flags, &addr, addrlen);
• count, sock, buf, len, flags: stesse di send
• addr: struct sockaddr, indirizzo della destinazione
• addrlen: sizeof(addr)
 int
count = recvfrom (sock, &buf, len, flags, &addr, addrlen);
• count, sock, buf, len, flags: stesse di recv
• name: struct sockaddr, indirizzo della sorgente
• namelen: sizeof(name): valore/risultato
 Le
chiamate sono blocking [ritornano solo dopo che i dati
sono inviati (al socket buf) / ricevuti]
2-12
close
 Quando si finisce di utilizzare una socket, la
socket dovrebbe essere chiusa:
 status = close (s);
status: 0 se OK, -1 se errore
 s: il socket descriptor (della socket da chiudere)

 Chiusura di una socket
Chiude una connessione (per SOCK_STREAM)
 Libera il port utilizzato dalla socket

2-13
Client/server socket interaction: TCP
Server
Client
crea socket
socket()
crea socket
socket()
associa port
bind ()
aspetta richieste
listen ()
accetta richieste
accept ()
TCP
connection setup
richiedi connessione
connect ()
manda dati
send ()
ricevi dati
recv ()
manda dati
send ()
chiudi socket
close ()
ricevi dati
recv ()
chiudi socket
close ()
2-14
Client/server socket interaction: UDP
Server
Client
crea socket
socket()
crea socket
socket()
associa port
bind ()
manda dati
sendto ()
ricevi dati
recvfrom ()
manda dati
sendto ()
chiudi socket
close ()
ricevi dati
recvfrom ()
chiudi socket
close ()
2-15
La struct sockaddr
 Generica:
struct sockaddr {
u_short sa_family;
char sa_data[14];
};

sa_family
• specifica quale
famiglia di indirizzi
deve essere usata
• determina come i 14
byte rimanenti
saranno utilizzati
 Specifica Internet:
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
 sin_family = AF_INET
 sin_port: port # (0-65535)
 sin_addr: IP address
 sin_zero: non utilizzato
//Structure per ragioni storiche
struct in_addr {
u_long s_addr; //32-bit long
};
2-16
Byte-ordering di indirizzo e port
 Indirizzo e port sono memorizzati come interi
 u_short sin_port; (16 bit)
 in_addr sin_addr; (32 bit)
 Problema:
 Macchine/SO usano differenti modalità per memorizzare i dati
• little-endian: lower bytes first
• big-endian: higher bytes first

Queste macchine devono poter comunicare l’una con l’altra
attraverso la rete
128.119.40.12
128
Big-Endian
machine
119
40
12
Little-Endian
machine
128
119
12.40.119.128
40
12
2-17
Soluzione: Network Byte-Ordering
 Definizioni:
Host Byte-Ordering: il byte ordering usato
dall’host (big o little)
 Network Byte-Ordering: il byte ordering usato
dalla rete – sempre big-endian
 Ogni word inviata attraverso la rete dovrebbe essere
convertita in Network Byte-Order prima della
trasmissione (e viceversa in Host Byte-Order una
volta riceuta)
 D: Le socket devono effettuare la conversione
automaticamente?

 D: Dato che per le macchine big-endian non servono
routine di conversione e per le macchine little-endian sì,
come si può evitare di scrivere due versioni di codice?
2-18
Funzioni di byte-ordering
 u_long htonl(u_long x);
 u_long ntohl(u_long x);
 u_short htons(u_short x);
 u_short ntohs(u_short x);
 Sulle macchine big-endian, queste routine non fanno
nulla
 Sulle macchine little-endian, invertono il byte order
Big-Endian
Little-Endian12 40
128.119.40.12
119 128
machine
128
119
40
12
128.119.40.12
machine
119
40
12
128
119
40
ntohl
128
12
 Lo stesso codice funziona indipendentemente dal tipo
di “endian” della macchina
2-19
Altre funzioni utili
 atoi (char* s): converte la stringa s in un intero
 bcopy (void* s, void* d, int n): copia n byte di s in d
 bzero (char* c, int n): pone n byte a 0 a partire dal
valore puntato da c
 gethostname (char *name, int len): ritorna il nome
dell’host sui cui il processo risiede
 gethostbyname (char *name): converte l’hostname
in una struttura (hostent) contenente l’indirizzo IP
(utilizzando il servizio di DNS)
2-20
Server : Inizializzazione
#include <sys/types.h>
[…altri include…]
#define MAX_CODA 5
/* massimo backlog */
main(int argc, char* argv[]) /* prende in input la porta */
{
int sock;
/* socket in attesa */
int sockmsg; /* socket servente */
struct sockaddr_in server;
if ( argc != 2 ) {
printf("uso: %s <numero-della-porta>\n", argv[0]);
exit(EXIT_FAILURE);
}
sock = socket(AF_INET,SOCK_STREAM,0); /* socket prototipo */
if( sock <0 ) {
printf("server: errore %s nella creazione del socket\n",
strerror(errno));
exit(EXIT_FAILURE);
}
2-21
Server: Creazione della coda
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port = htons(atoi(argv[1]));
struttura per
il bind
if( bind(sock, (struct sockaddr *)&server, sizeof(server)) )
{
printf("server: bind fallita\n");
exit(EXIT_FAILURE);
}
printf("server: rispondo sulla porta %d\n",
ntohs(server.sin_port));
if( listen(sock, MAX_CODA) <0 ) {
printf("server: errore %s nella listen\n",
strerror(errno));
exit(EXIT_FAILURE);
}
dimensiono
la coda di
backlog
2-22
Server: Gestione delle connessioni
int totale=0;
char input[256];
sockmsg = accept(sock, 0, 0);
if( sockmsg <0 ) {
printf("errore %s nella accept\n", strerror(errno));
exit(EXIT_FAILURE);
}
printf("server: accetto una nuova connessione\n”);
qui ci va il codice che presta il servizio (segue)
}
close(sock);
printf("server: ho chiuso il socket\n");
/* fine della funzione main */
2-23
Server: Gestione del client
{ /* questo e’ il codice di servizio del server */
int len;
printf("server %d: iniziato \n", getpid() );
while( len = recv(sockmsg, input, sizeof(input), 0) ) {
int numero;
char tot[256];
input[len]='\0'; /* termina la stringa*/
numero = atoi(input); /* converti in intero */
printf("server: arrivato il numero: %d\n", numero);
totale=totale+numero; /* calcolo totale*/
sprintf(tot, "%d", totale); /* prepara la stringa */
send(sockmsg, tot, sizeof(tot), 0); /* invia la stringa */
}
close(sockmsg); /* prima di uscire chiudi il socket */
printf("server: socket chiuso\n”);
exit(EXIT_SUCCESS); /* connessione terminata */
}
2-24
Client: Inizializzazione
#include <stdio.h>
[…altri include…]
main(int argc, char* argv[])
{
int sock;
/* descrittore del socket */
struct sockaddr_in server;
struct hostent *hp;
char input[256];
if(argc!=3) {
printf("uso: %s <host> <numero-della-porta>\n", argv[0]);
exit(1);
}
sock = socket(AF_INET, SOCK_STREAM, 0);
if( sock < 0 ) {
printf("client: errore %s nella creazione del socket\n",
strerror(errno));
exit(1);
}
2-25
Client: Connessione col server
hp = gethostbyname(argv[1]);
if( hp == NULL ){
printf("client: l'host %s non e' raggiungibile.\n",
argv[1]);
exit(1);
}
server.sin_family = AF_INET;
bcopy(hp->h_addr, &server.sin_addr, hp->h_length);
server.sin_port = htons(atoi(argv[2]));
if( connect(sock, (struct sockaddr *)&server, sizeof(server))
< 0 )
{
printf("client: errore %s durante la connect\n",
strerror(errno));
exit(1);
}
printf("client: connesso a %s, porta %d\n", argv[1],
ntohs(server.sin_port));
2-26
Client: Gestione messaggi
printf("client: num. o ‘quit’? ");
scanf("%s",&input);
while( strcmp(input,"quit") != 0 )
{
char result[256];
if( send (sock, (char *)&input, strlen(input), 0) <0) {
printf("errore %s durante la write\n", strerror(errno));
exit(1);
}
if( recv(sock,(char *)&result, sizeof(result), 0) < 0 ) {
printf("errore %s durante la read\n", strerror(errno));
exit(1);
}
printf("client: ricevo dal server %s\n", result);
printf("client: num. o \"quit\"? ");
scanf("%s",&input);
}
close(sock);
printf("client: ho chiuso il socket\n");
}
/* fine della funzione main */
2-27
Gestione del blocco delle funzioni
 Molte delle funzioni esaminate si bloccano finchè
accade un determinato evento




accept: fino all’arrivo di una connessione
connect: fino a quando la connessione non è stabilita
recv, recvfrom: fino a quando un pacchetto (di dati) non è
ricevuto
send, sendto: fino a quando i dati non vengono messi nel
buffer della socket
 Per semplici programmi il blocco è conveniente
 Cosa accade ai programmi più complessi?
 connessioni multiple
 invio e ricezione contemporaneo
 necessità di eseguire in contemporanea codice non legato
alla rete
2-28
Gestione blocco delle funzioni (cont.)
 Opzioni:
 creazione di codice multi-process o multi-threaded
 “eliminazione” del blocking (es., usando la funzione di
controllo del file descriptor fcntl)
 uso della funzione select
 Cosa fa la select?
 si può bloccare permanentemente, per un intervallo
limitato o non bloccarsi
 input: un set di file-descriptor
 output: info sullo stato dei file-descriptor
 cioè, può identificare le socket che sono “pronte all’uso”:
le funzioni che coinvolgono quelle socket ritornano
immediatamente
2-29
select function call
 int status = select (nfds, &readfds, &writefds,
&exceptfds, &timeout);
# di oggetti pronti, -1 se errore
 nfds: 1 + il numero del più grande file descriptor da
controllare
 readfds: lista dei descrittori “pronti alla lettura”
 writefds: lista dei descrittori “pronti alla scrittura”
 exceptfds: lista dei descrittori che registrano
un’eccezione
 status:

timeout: intervallo dopo il quale la select ritorna,
anche se non c’è niente di pronto – può essere tra 0
e
(settare il parametro timeout a NULL per )
2-30
Da utilizzare con la select
 select utilizza una struct fd_set per le liste dei
descrittori


è un vettore di bit
se il bit i è settato in [readfds, writefds, exceptfds], select
controllerà che il file descriptor (cioè la socket) i è
pronta per [reading, writing, exception]
 Prima di chiamare select:
 FD_ZERO (&fdvar): azzera la struttura
 FD_SET (i, &fdvar): aggiunge il file descriptor i alla lista
 FD_CLR (i, &fdvar): rimuove il file descriptor i dalla lista
 Dopo aver chiamato select:
 int FD_ISSET (i, &fdvar): booleano ritorna TRUE iff i è
“pronto”
2-31
Rilascio dei port
 Qualche volta un’uscita “rude” da un programma
(es. ctrl-c) non rilascia il port correttamente
 In ogni caso il port dovrebbe essere rilasciato
dopo alcuni minuti
 Per ridurre la probabilità di questo inconveniente,
includere il codice seguente:
#include <signal.h>
void cleanExit(){exit(0);}

nel codice della socket:
signal(SIGTERM, cleanExit);
signal(SIGINT, cleanExit);
2-32
Scarica

socket - Anghelos