Corso di Reti di Calcolatori
Interfaccia socket
Introduzione
• La programmazione di rete è ciò che permette a
due o più computer collegati da una rete fisica di
comunicare tra loro.
• Per poter comunicare con un computer ho
bisogno di:
1. Un indirizzo a cui connettermi: ogni computer connesso
in una rete dispone di un indirizzo univoco, che
consente di localizzarlo a livello globale
2. Una porta aperta: il concetto di porta permette ad un
computer di ricevere diverse connessioni, infatti tramite
diverse porte è possibile dallo stesso computer fornire
diversi servizi (posta, web, telnet, ecc.)
3. Un protocollo: che permetta di far capire al programma
in ascolto le mie richieste
[2]
TCP/IP
• TCP/IP non include una definizione di API.
• C’è in giro una quantità di API da usare col
TCP/IP:
–
–
–
–
Sockets
TLI, XTI
Winsock
MacTCP
[3]
Funzioni necessarie:
• Specificare gli endpoints, locale e remoto, della
comunicazione
• Initiare la connessione
• Attendere per le connessioni in arrivo
• Send e receive dei dati
• Terminare la connessione ‘gracefully’
• Gestione degli Errori
[4]
Comunicazione
• La comunicazione tra due pc funziona in questo modo:
– un CLIENT si collega ad un SERVER chiedendo un servizio
– il SERVER, se dispone del servizio, assolverà la richiesta del
CLIENT, inviandogli le risposte.
• Questa comunicazione avviene tramite un protocollo di
comunicazione (insieme di regole da rispettare).
• Al di sopra di questi protocolli esiste un livello di astrazione,
chiamato socket, che permette di trasmettere e ricevere
informazioni senza doversi preoccupare della correttezza
delle regole definite dal protocollo utilizzato.
[5]
Indirizzamento
• Ogni computer della rete è identificato da un hostname e
da un indirizzo IP unico a 32 bit es: “www.google.com” 
209.85.135.103
• L’indirizzo IP contiene sia l’identificativo della rete che
quello della macchina
• Il comando nslookup offre la lista di tutti i server alias per
un indirizzo.
– Esempio
>>> nslookup www.google.com
Risposta da un server non di fiducia:
Nome:
www.l.google.com
Addresses: 209.85.135.99, 209.85.135.103,
209.85.135.147, 209.85.135.104
Aliases: www.google.com
[6]
Come identificare i processi ?
• Si assegna ad ogni processo un numero di porta
• I numeri di porta sono assegnati a servizi
– di sistema (porte da 0-1023)
– dinamici o privati (1024 - 65535)
• I server (daemon) usano di solito porte ben note
– es HTTP = 80 , Telnet = 25, FTP = 21
• I client di solito usano porte dinamiche
• Questa associazione viene usata dai protocolli di
livello transport come TCP o UDP
[7]
TCP/IP: interfaccia di programmazione
[8]
Socket
• Un socket è un punto finale di un collegamento
bidirezionale tra due programmi in esecuzione sulla rete.
• Un socket è legato a un numero di porta così che il livello
TCP può identificare l’applicazione a cui i dati devono
essere inviati.
– Usando i socket la comunicazione in rete è molto simile all’ I/O su
file.
 La gestione dei socket viene trattata come la gestione di file.
 I comandi di lettura e scrittura sui flussi usati per l’I/O su file sono
applicabili anche all’I/O su socket.
– La comunicazione basata su socket è indipendente dal linguaggio di
programmazione.
– Un programma socket scritto nel linguaggio Java può comunicare
via rete con un programma non Java purché questo usi i socket.
[9]
Concetto di Socket
• Per attivare una connessione fra due processi si utilizza
dunque lo schema Host-Port
– il server è eseguito su un host e aspetta richieste su una
determinata porta
– il client viene eseguito su un host e richiede l’uso di una porta per
effettuare richieste ad un server
• Specificando ora anche il protocollo per la comunicazione
riusciamo a descrivere univocamente la connessione con
una quintupla detta Association:
(proto, ind_locale, proc_locale, ind_remoto, proc_remoto)
es: (TCP, 123.23.4.221, 1500, 234.151.124.2, 4000)
[10]
Concetto di Socket
• La quintupla viene in realtà costituita a partire da
due elementi simmetrici, un’associazione locale ed
una remota:
– Protocollo, Ind_locale, proc_locale
 (TCP, 123.23.4.221,1500)
– Protocollo, Ind_remoto, proc_ remoto
 (TCP ,234.151.124.2,4000)
• Ciascuna di queste parti è detta socket
– Nota:
1040 per TCP  1040 UDP
[11]
Concetto di Socket
Quindi un socket è
1. un descrittore di risorsa che consente ad una
applicazione di effettuare operazioni di lettura e
scrittura verso un particolare dispositivo di I/O.
2. Astrazione di un canale di comunicazione tra processi
distribuiti in rete
3. Interfaccia per la suite di protocolli TCP/IP
4. Punto di arrivo nella comunicazione in rete
[12]
Il protocollo TCP e l’interfaccia socket
• Nel modello TCP/IP l’interfaccia socket svolge tutte
le operazioni necessarie per stabilire una
connessione, ovvero:
– si occupa di creare i pacchetti TCP
– stabilisce una connessione con l’algoritmo del “three way
handshake”
– gestisce tutte le problematiche della trasmissione dei
pacchetti
[13]
Creazione di un socket
• Lato server
– Creazione del socket
– Bind ad una porta
– Listen, predisposizione a ricevere sulla porta
– Accept, blocca il server in attesa di una connesione
– Lettura - scrittura dei dati
– Chiusura
• Lato client
– Creazione del socket
– Richiesta di connessione
– Lettura - scrittura dei dati
– Chiusura
[14]
Comunicazione socket (1)
• Un server (programma) gira su un computer specifico e ha
un socket legato ad una porta specifica.
• Un server aspetta e ascolta sul socket finché un client non
esegue una richiesta di connessione.
[15]
Comunicazione socket (2)
• Se tutto funziona, il server accetta la connessione e
ottiene dal sistema operativo un nuovo socket legato a una
porta diversa.
– In questo modo un server può continuare ad ascoltare sul socket
originale per eventuali altre richieste di connessione, mentre
comunica con il client già connesso.
[16]
Indirizzi, porte e socket
• La metafora della posta
– L’utente è l’applicazione.
– Il suo indirizzo di casa è l’indirizzo IP.
– La casella postale è la porta.
– L’ufficio postale è la rete.
– Il socket è la chiave che dà all’utente l’accesso alla
casella postale giusta.
• Nota: si presuppone che anche la posta in uscita
venga depositata dall’utente nella sua casella
postale invece di imbucarla in una buca delle
lettere.
[17]
L'interfaccia dei socket
• La socket API (Application Program Interface) è l'interfaccia
standard tra i programmi applicativi e i protocolli TCP/IP
• La base per l'I/O di rete dell'API è costituita da una
generalizzazione del meccanismo UNIX di accesso ai file
che invece di un descrittore di file fornisce un terminale di
comunicazione.
• Quando un programma applicativo crea un socket e
stabilisce una connessione TCP dal socket a una
destinazione esterna, può usare write per inviare un flusso
di dati attraverso la connessione (il programma all'altro lato
può usare read per riceverla).
• Per rendere possibile usare le primitive come read e write
sia con i file sia con i socket, il sistema operativo si
assicura che se un intero è stato assegnato come
descrittore di file, non sarà assegnato anche come
descrittore di socket.
[18]
Creiamo un socket
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int
protocol);
• Ma cosa sono gli argomenti?
1. domain deve essere posto a "PF_INET".
2. type dice al kernel che tipo di socket è:
SOCK_STREAM o SOCK_DGRAM.
3. protocol = "0" per avere la socket()
• socket() restituisce
– un “socket descriptor” o
– -1 in caso di errore.
[19]
La specificazione di un indirizzo locale
• Inizialmente i socket vengono creati senza alcuna
associazione agli indirizzi locale o di destinazione.
• In molti casi i programmi applicativi non si preoccupano
dell'indirizzo locale che usano e vogliono consentire al
software del protocollo di sceglierne uno per loro conto.
• Tuttavia, i server che funzionano su una porta ben nota
devono essere in grado di indicare la porta al sistema.
• Una volta creato il socket, il server usa la funzione bind per
specificare un indirizzo locale. Bind ha la forma seguente:
– bind(socket, localaddr, addrlen)
• L'argomento socket è il descrittore intero del socket da
collegare all'indirizzo, localaddr è una struttura che
contiene l'indirizzo locale a cui il software dovrebbe essere
[20]
collegato e addrlen è la lunghezza di quest’ultima
Funzione bind -- What port am I on?
• Associa e può riservare (in modo esclusivo) una
porta TCP o UDP a uso di un socket.
int status = bind(sockid, &addrport, size);
– status: = -1, se bind non ha avuto successo;
– sockid: descrittore del socket, intero;
– addrport: struct sockaddr, l’indirizzo (IP) e la porta della
macchina;
 L’indirizzo di solito è impostato a INADDR_ANY
– size: le dimensioni (in byte) della addrport structure.
• Può essere evitata per entrambi i tipi di socket.
[21]
Uso di bind
SOCK_DGRAM (UDP)
– Se si spedisce solamente, bind non serve.
 Il sistema operativo trova lui una porta ogni volta che il socket
invia un pkt.
– Se si riceve, bind è indispensabile.
SOCK_STREAM (TCP)
– Destinazione determinata durante il setup della
connessione (three-way handshake).
– Non è necessario conoscere la porta da cui avviene
l’invio.
 Durante il setup della connessione, il lato ricevente viene
informato del numero di porta.
[22]
Chiamata della funzione connect-- Hey, you!
int status = connect(sock, &name, &namelen);
– status: 0 se connect ha successo, altrimenti -1 ;
– sock: intero, il socket che deve essere usato nella
connessione;
– name: struct sockaddr, indirizzo del partecipante
passivo;
– namelen: intero, sizeof(name).
• “connect” è bloccante.
[23]
listen()--Will somebody please call me?
• Come si comporta un server in attesa di ‘incoming
connections’ e come le gestisce ?
• Il processo si svolge in due step: first listen(), then accept()
int listen(int sockfd, int backlog);
– sockfd: è il solito ‘socket file descriptor’ restituito dalla socket()
system call.
– backlog: è il numero di connessioni contemporanee permesse sulla
coda di ingresso.
• Che cosa vuole dire ?
– Le ‘incoming connections’ restano in una coda di attesa finchè non
le si accept().
– Solitamente il limite delle connessioni accodate è tra 5 e 20
connessioni.
[24]
listen
• Come si può facilmente immaginare, c’è bisogno
di chiamare bind() prima di chiamare listen() o il
kernel sarà in ascolto su una porta random.
• Listening per connessioni in arrivo, la sequenza
delle system calls è:
socket();
bind();
listen();
/* accept() goes here */
[25]
accept()--"Thank you for calling port 3490."
•
Cosa succede ?
1. Qualcuno, da remoto cerca di connect() alla vs.
macchina su una porta sulla quale siete in listen()ing.
2. La connessione viene up waiting to be accept()ed.
3. You call accept() and you tell it to get the pending
connection.
4. It'll return to you a brand new socket file descriptor to
use for this single connection! That's right, suddenly
you have two socket file descriptors for the price of
one!
5. The original one is still listening on your port and the
newly created one is finally ready to send() and recv().
We're there!
[26]
sendto() e recvfrom() -- Talk to me, DGRAM style
• Poichè i datagram sockets non sono connessi ad un host
remoto, qual sarà l’informazione necessaria da fornire
prima di inviare il pacchetto ?
• That's right! l’indirizzo IP di destinazione ! :
int sendto(int sockfd, const void *msg, int
len, unsigned int flags, const struct
sockaddr *to, socklen_t tolen);
• Questa call è la stessa del caso send() con l’aggiunta di
due informazioni.
– to è un pointer ad una struct sockaddr che contiene l’ IP address di
destinazione e la porta.
– tolen, ponetela a sizeof(struct sockaddr).
[27]
close() e shutdown() -- Get outta my face!
• Whow! Abbiamo fatto send() e recv() di dati per
tutto il tempo, adesso siamo stufi. Siamo pronti a
chiudere la connessione e a liberare il ns. socket
descriptor. Facile: possiamo usare la funzione
regolare di Unix per chiudere un file:
close(sockfd);
• Questo inibirà ogni read/write addizionale sul
socket. Chiunque tenterà letture o scritture sul
socket del sistema remoto riceverà un errore.
[28]
Shutdown
• Se si vuole un maggiore controllo su come il socket viene
chiuso, si può usare la funzione shutdown(). Questa
funzione permette di chiudere la comunicazione solo in una
direzione, o in entrambe (come close())
• Synopsis:
int shutdown(int sockfd, int how);
– sockfd è il socket_descriptor che volete chiudere, e how è un
valore tra:
 0 – la ricezione è disabilitata
 1 – la trasmissione è disabilitata
 2 – sono disabilitate entrambe (come close())
• Nota: shutdown() non chiude il file_descriptor – ne cambia
l’uso. Per liberare il socket_descriptor, usare close().
[29]
TCP Server–Client: Interazione
[30]
UDP Server–Client: Interazione
[31]
getpeername()--Who are you?
• La funzione getpeername() vi dice chi c’è all’altro
capo di uno stream socket (connesso).
• Synopsis:
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr
*addr, int *addrlen);
• Una volta recuperato l’indirizzo, si può usare
inet_ntoa() o gethostbyaddr() per avere maggiori
informazioni.
[32]
gethostname() -- Who am I?
• Ancora più facile di getpeername() è la funzione
gethostname(): restituisce il nome del computer su
cui sta girando il vs. programma. Il nome può
essere usato da gethostbyname() per
determinare l’indirizzo IP della macchina locale.
• Ecco un esempio:
#include <unistd.h>
int gethostname(char *hostname, size_t size);
[33]
DNS -- You say "whitehouse.gov", I say 63.161.169.137
• "DNS sta per "Domain Name Service". In poche parole, voi
dite al DNS un nome di un indirizzo (human-readable) di un
sito, e lui vi restituisce l’ IP address (lo potete usare con
bind(), connect(), sendto(), o per quello che vi serve.)
• Pertanto, se uno scrive:
– $ telnet whitehouse.gov
• telnet scoprirà che ha bisogno di connect()-ersi a
"63.161.169.137".
• Ma come funziona ? Basta usare la funzione
gethostbyname():
#include <netdb.h>
struct hostent *gethostbyname(const char
*name);
[34]
send() e recv() -- Talk to me, baby!
• Queste due funzioni sono per la comunicazione su
uno stream sockets o un datagram sockets di tipo
connesso.
• Se volete usare i regolari unconnected datagram
sockets, riferirsi alle funzioni sendto() e recvfrom().
• La send() call:
• int send(int sockfd, const void *msg, int
len, int flags);
[35]
Esempio di Codice
char *msg = "Beej was here!";
int len, bytes_sent;
.
.
.
len = strlen(msg);
bytes_sent = send(sockfd, msg, len, 0);
[36]
recv
• La recv() call è simile:
int recv(int sockfd, void *buf, int len,
unsigned int flags);
–
–
–
–
sockfd è il socket_descriptor da cui leggere,
buf è il buffer su cui memerizzare le informazioni,
len è la massima lunghezza del buffer e
flags può essere settato a 0.
• recv() restituisce il numero di bytes letti nel buffer, o -1 in
caso di errore (errno viene settata di conseguenza.)
• recv() può restituire 0. Questo vuole dire una sola cosa: il
sito remoto ha chiuso la connessione ! Il valore di 0 è il
modo di recv() di informarvi su questo evento.
[37]
Invio e ricezione di dati (1)
• Con una connessione TCP (SOCK_STREAM)
int count = send(socket, &buf, len, flags);
–
–
–
–
count: numero di byte trasmessi (-1 in caso di errore);
buf: char [], buffer che devono essere trasmessi;
len: intero, lunghezza di buffer (in byte) da trasmettere;
flags: intero, opzioni speciali, di solito solo 0.
int count = recv(socket, &buf, len, flags);
–
–
–
–
count: numero di byte ricevuti (-1 in caso di errore);
buf: void [], memorizza i byte ricevuti;
len: numero di byte ricevuti;
flags: intero, opzioni speciali, di solito solo 0.
• Le chiamate sono bloccanti: la chiamata ritorna solo dopo
che i dati sono stati inviati (al socket buffer) o ricevuti.
[38]
Invio e ricezione di dati (2)
• Senza connessione, via UDP (SOCK_DGRAM)
int count = sendto(sock, &buf, len, flags,
&addr, addrlen);
– count, sock, buf, len, flags: uguale all’invio;
– addr: struct sockaddr, indirizzo della destinazione;
– addrlen: sizeof(addr).
int count = recvfrom (sock, &buf, len, flags,
&name, namelen);
– count, sock, buf, len, flags: uguale alla ricezione;
– name: struct sockaddr, indirizzo della sorgente;
– namelen: sizeof(name): parametro valore/risultato.
• Le chiamate sono bloccanti: la chiamata ritorna solo dopo
che i dati sono stati inviati (dal buffer del socket) o ricevuti.
[39]
Funzione close
• Una volta terminato l’uso di un socket, lo si
dovrebbe chiudere.
status = close(s)
– status: 0 se l’operazione ha avuto successo, -1 in caso
di errore;
– s: il descrittore di file (il socket che viene chiuso).
• La chiusura di un socket:
– chiude la connessione TCP (nel caso SOCK_STREAM);
– libera la porta usata dal socket.
[40]
Esercizio inaddr
[41]
Winsock
•
Il winsock si basa su queste principali sette
funzioni:
1. Socket(): crea una socket che verrà utilizzata per la
comunicazione
2. Connect(): collega la socket ad un indirizzo remoto
3. Read(), Write(); Send(), Sendto(), Recv(), Recvfrom():
funzioni di invio e ricezione dati
4. Listen(): specifica le code di socket client
5. Accept(): attende delle connessioni in una specifica
porta
6. Bind(): collega la socket ad una porta specifica
7. Close(): chiude la socket
[42]
Programmare le socket
• Otto sono i passi fondamentali per programmare le
socket in windows:
– 1. Definizione delle librerie da utilizzare (Winsock.h)
– 2. Inizializzazione della libreria socket (WSAStartup())
– 3. Creazione della socket
– 4. Descrizione delle caratteristiche della socket
– 5. Connessione al sistema remoto
– 6. Operazioni sulla socket
– 7. Chiusura della socket
– 8. Rilascio delle risorse della libreria (WSACleanup() )
[43]
L’ereditarietà dei socket
• UNIX usa le chiamate di sistema fork ed exec per attivare
nuovi processi.
• È una procedura in due fasi: nella prima si crea una copia
del programma in esecuzione al momento, mentre nella
seconda la nuova copia è sostituita dal programma
applicativo desiderato.
• Quando un programma chiama fork, la copia appena
creata eredita l'accesso a tutti i socket aperti, così come
l'accesso a tutti i file aperti. Quando un programma chiama
exec la nuova applicazione mantiene l'accesso a tutti i
socket aperti.
• I server principali usano l'ereditarietà dei socket quando
creano server secondari per gestire una specifica
connessione.
[44]
La struttura sockaddr
• Si usa quando si passa un indirizzo TCP/IP all'interfaccia
dei socket.
• inizia con un campo ADDRESS FAMILY a 16 bit che
identifica la famiglia di protocolli a cui appartiene l'indirizzo.
È seguita da un indirizzo di 14 ottetti al massimo.
• Il valore nel campo ADDRESS FAMILY determina il formato
degli ottetti dell'indirizzo rimanente. Ad esempio, il valore
25 nel campo ADDRESS FAMILY significa che gli ottetti
degli indirizzi rimanenti contengono un indirizzo TCP/IP.
Ogni famiglia di protocolli definisce come userà gli ottetti
nel campo degli indirizzi.
• Per gli indirizzi TCP/IP l'indirizzo dei socket è noto come
sockaddr_in e comprende sia l'indirizzo IP sia il numero di
porta
[45]
La funzione gestsockopt
• La funzione gestsockopt consente all'applicazione
di richiedere informazioni su un socket. La
chiamata ha la forma seguente:
– getsockopt(socket, level, optionid, optionval, lenght)
 L'argomento socket specifica il socket per cui sono necessarie le
informazioni,
 level identifica se l'operazione è applicata al socket stesso o ai
protocolli sottostanti e
 optionid stabilisce l’opzione a cui si applica la richiesta.
 La coppia di argomenti optionval e lenght specifica due puntatori.
Il primo contiene l'indirizzo del buffer in cui il sistema pone il
valore richiesto e il secondo l'indirizzo di un intero in cui il sistema
pone la lunghezza del valore dell'opzione.
[46]
La funzione setsockopt
• La funzione setsockopt consente al programma
applicativo di impostare un'opzione di un socket
usando l'insieme di valori ottenuto con getsockopt.
Il chiamante specifica il socket e il nuovo valore
dell'opzione. La chiamata a setsockpt ha la forma
seguente:
– setsockpt(socket, level, optionid, optionval, lenght)
• Gli argomenti sono come quelli di getsockopt,
tranne lenght che contiene la lunghezza
dell'opzione che viene passata al sistema
[47]
Accettazione delle connessioni
• Come si è visto, i server usano le funzioni socket, bind e
listen per creare un socket, collegarlo a una porta di
protocollo ben nota e specificare una lunghezza della coda
per le richieste di connessione.
• La chiamata a bind associa il socket a una porta di
protocollo ben nota, ma il socket non è collegato a una
destinazione esterna specifica. La destinazione viene
specificata con un carattere jolly consentendo al socket di
ricevere richieste di connessione da client arbitrari.
• Una volta che il socket è stato attivato, il server deve
aspettare una connessione; per eseguire ciò utilizza la
funzione accept
[48]
Un client di esempio
• Il seguente programma C di esempio illustra come
usare l'API socket da programmare per accedere
ai protocolli TCP/IP. È una semplice
implementazione di un client e di un server whois.
• Come definito nella RFC 954, il servizio whois
consente a un client di ottenere informazioni su un
utente su un sistema remoto.
• In questa implementazione, il client è un
programma applicativo che l’utente attiva con due
argomenti: il nome di una macchina remota e il
nome dell'utente di quella macchina su cui si
desiderano le informazioni.
[49]
Un client d’esempio (II)
• Il client chiama gethostbyname per tradurre il
nome della macchina remota nell'indirizzo IP e
getservbyname per trovare la porta ben nota per il
servizio whois.
• Una volta che ha risolto i nomi di servizio e di host,
il client crea un socket, specificando che il socket
userà la consegna affidabile a flusso (cioè, TCP).
• Infine, il client collega il socket alla porta del
servizio whois sulla macchina di destinazione
specificata.
[50]
Codice del client (1)
/* whoisclient.c - main */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
/*----------------------------------------------------------* Programma: whoisclient
* Scopo: programma applicativo UNIX che diventa un client
* per il servizio Internet "whois".
* Utilizzo: whois hostname username
* Autore: Barry Shein, Università di Boston
* Data: Tanto tempo fa in un universo molto lontano
*-----------------------------------------------------------*/
[51]
Codice del client (2)
main(argc, argv)
int argc; /* dicharazioni di argomenti UNIX standard */
char *argv[];
{
int s;
/* descrittore di socket */
int len;
/* lunghezza dei dati ricevuti */
struct sockaddr_in sa;
/* struttura Internet socket addr.*/
struct hostent *hp;
/* risultato della ricerca del nome dell'host */
struct servent *sp;
/* risultato della ricerca del servizio */
char buf[BUFSIZ+1];
/* buffer per leggere informazioni whois */
char *myname;
/* puntatore al nome di questo programma */
char *host;
/* puntatore al nome dell'host remoto */
char *user;
/* puntatore al nome dell'utente remoto */
myname = argv[0];
[52]
Codice del client (3)
/* Controlla che vi siano due argomenti sulla riga di comando */
if(argc != 3) {fprintf(stderr, "Usage: %s host username\n", myname);
exit(1);}
host = argv[1];
user = argv[2];
/* Cerca il nome di host specificato */
if((hp = gethostbyname(host)) == NULL) { fprintf(stderr,"%s: %s: no such host?\n", myname, host);
exit(1);}
/* Pone l'indirizzo dell'host e il tipo di indirizzo nella struttura socket */
bcopy((char *)hp->h_addr, (char *)&sa.sin_addr, hp->h_length);
sa.sin_family = hp->h_addrtype;
/* Cerca il numero di socket per il servizio WHOIS */
if((sp = getservbyname("whois","tcp")) == NULL) { fprintf(stderr,"%s: No whois service on this host\n",
myname);
exit(1);}
/* Pone il numero di socket whois nella struttura socket. */
sa.sin_port = sp->s_port;
[53]
Codice del client (4)
/* Assegna un socket aperto */
if((s = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0)
{perror("socket"); exit(1);}
/* Si connette al server remoto */
if(connect(s, &sa, sizeof sa) < 0)
{perror("connect"); exit(1);}
/* Invia la richiesta */
if(write(s, user, strlen(user)) != strlen(user))
{ fprintf(stderr, "%s: write error\n", myname); exit(1);}
/* Legge la risposta e la mette nell'output dell'utente */
while( (len = read(s, buf, BUFSIZ)) > 0)
write(1, buf, len);
close(s);
exit(0);}
[54]
Un server di esempio
• Il server di esempio è solo leggermente più
complicato del client.
• Il server ascolta la porta ben nota "whois" e ritorna
le informazioni richieste in risposta alle
interrogazioni dei client.
• Le informazioni vengono prese dal file delle
password UNIX sulla macchina del server.
[55]
Un server di esempio (2)
/* whoisserver.c - main */
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <pwd.h>
/*----------------------------------------------------------* Program: whoisserver
* Scopo: programma applicativo UNIX che funge da server per
* il servizio "whois" sulla macchina locale. Ascolta la
* porta ben nota WHOIS (43) e risponde alle interrogazioni
* dei client. Questo programma deve essere eseguito con i
* privilegi del super-user.
* Utilizzo: whois hostname username
* Autore: Barry Shein, Università di Boston
* Data: Tanto tempo fa in un universo molto lontano
*------------------------------------------------------------*/
[56]
Un server di esempio (3)
#define BACKLOG 5
/* # di richieste che vogliamo accodare */
#define MAXHOSTNAME 32 /* lunghezza massima del nome di host */
main(argc, argv)
int argc;
/* dichiarazioni di argomenti UNIX standard */
char *argv[];
{ int s, t;
int i;
/* descrittori di socket */
/* intero */
struct sockaddr_in sa, isa; /* struttura socket address */
struct hostent *hp;
/*risultato della ricerca del nome dell'host */
char *myname; /* puntatore al nome di questo programma */
struct servent *sp;
/* risultato della ricerca del servizio */
char localhost[MAXHOSTNAME+1];/* nome di host locale */
[57]
Un server di esempio (4)
myname = argv[0];
/*Consulta l'immissione relativa al servizio WHOIS */
if((sp = getservbyname("whois","tcp")) == NULL)
{fprintf(stderr, "%s: No whois service on this host\n", myname); exit(1);}
/*Prende le informazioni sull'host locale */
gethostname(localhost, MAXHOSTNAME);
if((hp = gethostbyname(localhost)) == NULL)
{fprintf(stderr, "%s: cannot get local host info?\n", myname); exit(1);}
/* Pone il numero di socket WHOIS e le informazioni
* sugli indirizzi nella struttura socket */
sa.sin_port = sp->s_port;
bcopy((char *)hp->h_addr, (char *)&sa.sin_addr, hp->h_length);
sa.sin_family = hp->h_addrtype;
[58]
Un server di esempio (5)
/* Assegna un socket aperto per connessioni in ingresso */
if((s = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0)
{perror("socket"); exit(1);}
/* Associa il socket alla porta per rilevare le connessioni in ingresso */
if(bind(s, &sa, sizeof sa) < 0) {perror("bind");exit(1);}
/* Imposta il numero massimo di connessioni tralasciate */
listen(s, BACKLOG);
/* Entra in un ciclo infinito aspettando nuove connessioni */
while(1) {
i = sizeof isa;
/* Restiamo in accept() mentre aspettiamo nuovi client */
if((t = accept(s, &isa, &i)) < 0) { perror("accept"); exit(1); }
whois(t); /* esegue il servizio WHOIS effettivo */
close(t);
}
}
[59]
Un server di esempio (6)
/* Riceve la richiesta WHOIS dall'host remoto e invia una risposta */
whois(sock)
int sock;
{ struct passwd *p;
char buf[BUFSIZ+1];
int i;
/* Riceve una richiesta di una linea */
if((i = read(sock, buf, BUFSIZ)) <= 0) return;
buf[i] = '\0'; /* Terminatore null */
/* Cerca l'utente richiesto e formatta la risposta */
if((p = getpwnam(buf)) == NULL) strcpy(buf,"User not found\n");
else sprintf(buf, "%s: %s\n", p->pw_name, p->pw_gecos);
/* Restituisce la risposta */
write(sock, buf, strlen(buf));
return; }
[60]
Conclusioni
• Poiché il software del protocollo TCP/IP risiede
all'interno del sistema operativo, l'interfaccia esatta
tra un programma applicativo e i protocolli TCP/IP
dipende dai dettagli del sistema operativo e non è
specificata dallo standard TCP/IP.
• I socket hanno adottato il paradigma UNIX openread-write-close.
• L'API socket, che in origine fu progettata per BSD
UNIX, ma è diventata di fatto uno standard
utilizzato anche da fornitori come la Microsoft.
[61]
Scarica

Concetto di Socket