Post on 14-Jan-2016
description
transcript
2-1
Programmazione socket
2-2
Programmazione 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 stream-
oriented
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
socket
Obiettivo: imparare a costruire applicazioni client/server che comunicano tramite socket
2-3
I due tipi principali di socket SOCK_STREAM
TCP affidabile ordine dati garantito orientato alla
connessione bidirezionale
SOCK_DGRAM UDP inaffidabile nessuna garanzia su ordine
dati nessuna nozione di
“connessione” – l’applicazione indica la destinazione di ogni pacchetto
può inviare o ricevereApp
socket3 2 1
Dest.123
App
socket3 2 1
D1
D3
D2
1
2
3
2-4
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-5
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-6
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-7
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-8
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
l-socka-sock-1 a-sock-2
Active 1
socket
Active 2
socket
2-9
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-10
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-11
Sending / Receiving Data
Con connessione (SOCK_STREAM): int count = send (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
int count = recv (sock, &buf, len, flags);• 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-12
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-13
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-14
Client/server socket interaction: TCP
crea socketsocket()
Server
associa port
bind ()
aspetta richieste
listen ()
accetta richieste
accept ()
ricevi datirecv ()
manda dati
send ()
chiudi socket
close ()
Client
richiedi connessioneconnect ()
manda dati
send ()
ricevi dati
recv ()
chiudi socket
close ()
crea socketsocket()
TCP connection
setup
2-15
Client/server socket interaction: UDP
crea socketsocket()
Server
associa port
bind ()
ricevi dati
recvfrom ()
manda dati
sendto ()
chiudi socket
close ()
Client
manda dati
sendto ()
ricevi dati
recvfrom ()
chiudi socket
close ()
crea socketsocket()
2-16
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-17
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
119
40 12
12.40.119.128
128
119
40 12
Big-Endianmachine Little-Endian
machine
SBAGLIATO
2-18
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-19
Funzioni di byte-ordering
u_long htonl(u_long x); u_short htons(u_short x);
u_long ntohl(u_long x); u_short ntohs(u_short x);
Sulle macchine big-endian, queste routine non fanno nulla
Sulle macchine little-endian, invertono il byte order
Lo stesso codice funziona indipendentemente dal tipo di “endian” della macchina
128.119.40.12
128
119
40 12
128.119.40.12
128
119
40 12
Big-Endianmachine Little-Endian
machine
hton
l nto
hl
128
119
40 12 128
119
4012
2-20
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-21
#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); }
Server : Inizializzazione
2-22
server.sin_family = AF_INET; server.sin_addr.s_addr = INADDR_ANY; server.sin_port = htons(atoi(argv[1])); 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); }
Server: Creazione della coda
struttura peril bind
dimensionola coda dibacklog
2-23
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”);
close(sock); printf("server: ho chiuso il socket\n");} /* fine della funzione main */
Server: Gestione delle connessioni
qui ci va il codice che presta il servizio (segue)
2-24
{ /* 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 */ }
Server: Gestione del client
2-25
#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); }
Client: Inizializzazione
2-26
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));
Client: Connessione col server
2-27
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 */
Client: Gestione messaggi
2-28
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-29
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-30
select function call int status = select (nfds, &readfds, &writefds,
&exceptfds, &timeout); status: # 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 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-31
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-32
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);