Programmare i Socket

Transcript

Programmare i Socket
Programmare i Socket
Aspinall & MasterShadow
21 agosto 2003
1
Programmare i Socket
S.P.I.N.E Group
Indice
1 Il modello Client/Server
3
2 Il modello OSI/ISO
3
3 Il protocollo TCP/IP
4
4 Il protocollo IP
4.1 Struttura dell’header IPv4 . . . . . . . . . . . . . . . . . . .
5
5 Il protocollo TCP
5.1 Struttura dell’header TCP . . . . . . . . . . . . . . . . . . .
8
6 Il protocollo UDP
6.1 Struttura dell’header UDP . . . . . . . . . . . . . . . . . . .
11
7 Le funzioni principali
7.1 Socket(2) . . . . .
7.2 bind(2) . . . . . . .
7.3 listen(2) . . . . . .
7.4 accept(2) . . . . .
7.5 connect(2) . . . . .
7.6 send(2) . . . . . .
7.7 recv(2) . . . . . . .
7.8 gethostbyname(3)
7.9 gethostbyaddr(3) .
12
8 Le strutture di rete
8.1 SockAddr . . .
8.2 SockAddr_In .
8.3 HostEnt . . . .
8.4 In_addr . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
6
8
11
12
13
14
14
15
16
17
18
18
20
.
.
.
.
20
9 Programmi dimostrativi
9.1 UDP Packet sender . . . . . . . . . . . . . . . . . . . . . .
9.2 MiniTalk . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9.3 DNSLookup . . . . . . . . . . . . . . . . . . . . . . . . . . .
22
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
2
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
20
21
21
22
23
25
Programmare i Socket
S.P.I.N.E Group
1 Il modello Client/Server
Il tcp/ip e il modello client/server sono la base di internet.
La comunicazione tra questi e‘ effettuata grazie ad applicazioni definite
come: applicazioni socket. Questi possono essere programmi come telnet,ssh,ftp e quant’altro, e la loro comunicazione e‘ permessa dalle API
socket.
Le API socket, chiamate anche interfacce socket, vennero sviluppate
nei laboratori dell’universita‘ di Berkeley, ecco perche‘ spesso li abbiamo
letti come socket Berkeley. Il paradigma client-server si basa sull’assunto
che esiste un certo insieme di entita‘ che ha bisogno di ottenere un certo
servizio da un’altra entita‘ e che esiste un meccanismo di comunicazione
a scambio di messaggi. Ad ogni entita‘ che richiede il servizio viene dato
il nome di client, mentre a quella che lo offre il nome di server.
Con il termine entita‘ si intende un’ampia casistica che puo‘ andare da
un host in rete fino a un thread. Nel seguito intenderemo in modo implicito
che client e server siano dei task o dei thread.
2 Il modello OSI/ISO
Questo modello e‘ stato creato con lo scopo di soddisfare tutte le specifiche, non soltanto apparecchiature di rete ma anche pc, software, hardware
e sistemi operativi.
Questi standard sono concepiti seguendo un modello concettuale, strutturato in 7 strati:
Livello 7
Livello 6
Livello 5
Livello 4
Livello 3
Livello 2
Livello 1
Applicazione
Presentazione
Sessione
Trasporto
Rete
Collegamento dati - Datalink
Fisico
Livelli superiori (5-7)
Livelli inferiori (1-4)
Una connessione per far si che venga instaurata e quindi venga effettuato lo scambio dei dati deve soddisfare questi strati. Ogni strato superiore utilizza i dati provenienti dagli strati inferiori.
Iniziamo dagli strati inferiori:
Strato Fisico. Questo strato , il piu‘ basso, ha il compito di definire
un bit ( tramite la cifratura binaria 1 e 0. Ad esempio la frequenza e la
lunghezza, l’assenza di segnale, la tensione, le interfaccie di connessioni
3
Programmare i Socket
S.P.I.N.E Group
( spine, plug ), lunghezze massime e minime di cavi... sono definite in
questo strato.
Strato Datalink. Questo strato impacchetta i dati e li invia al mezzo di
trasmissione suddivisi in frame.In questo strato viene assegnato un indirizzo fisico ad ogni link. Un esempio di questo e‘ il MAC ( Media Access
Controll )
Strato Rete. Questo strato assegna un indirizzo unico ad ogni unita‘
e grazie ei routed protocol ( IP, IPX ) e routing protocol ( RIP, OSPF),
viene indicato al dato la strada da seguire per arrivare a destinazione.I
dati vengono spediti in pacchetti.
Strato Trasporto. In questo strato ci si prepara alla trasmissione dei
dati.Lo strato di trasporto gestisce il controllo del flusso dei dati,il checksum ( controllo dell’integrita‘ del dato ) e divide i dati in segmenti appropiati
agli strati inferiori. TCP e UDP lavorano ad esempio in questo livello. I dati
viaggiano in datagrammi. Questo datagramma e‘ formato da un header (
intestazione ) ed e‘ seguito dai dati.
Gli strati superiori in ordine sessione, presentazione e applicazione si occupano di sincronizzare la connessione, convertire i dati in formato standard in modo che le interfaccie ( API ) possano comunicare
indipendentemente dal software.
3 Il protocollo TCP/IP
Il protocollo TCP/IP consente a computer di tutti i tipi di comunicare fra loro.
La sua diffusione e‘ stata molto piu‘ ampia di quanto di prevedeva. E‘ stato
sviluppato negli anni settanta come un progetto di ricerca per lo scambio
di messaggi fra le reti ed oggi e‘ la piu‘ diffusa forma di comunicazione
delle reti di computer.
Si tratta di un protocollo aperto e molte sue implementazioni sono liberamente accessibili ed ha fondato le basi per quello che attualmente
chiamiamo Internet.
Il protocollo TCP/IP deriva dai due protocolli principali di Internet, progettati nel 1974 da Kahn e Cerf presso l’universita‘ di Berkeley ed e‘ suddiviso in 4 livelli che descrivono il processo di trasmissione ereditando parte
del modello OSI/ISO, composto da 7 livelli.
Livello 4
Applicazione
Livello 3
Trasporto
Livello 2
Rete (network)
Livello 1 Collegamento (link)
4
Programmare i Socket
S.P.I.N.E Group
Il livello Collegamento (link), a volte chiamato data-link, solitamente include il driver del dispositivo di collegamento e la corrispodente interfaccia
di rete del computer. Questo livello gestisce gli standard per gli hardware
di rete.
Il livello Rete (network) gestisce lo scambio dei pacchetti nella rete. L’instradamento dei pacchetti viene effettuato in questo livello e trovano posto in questo strato dello stack TCP/IP il protocollo IP, ICMP e IGMP,
propriamente incapsulati dalla suite di protocolli TCP/IP.
Il livello Trasporto fornisce un flusso di dati tra due host per il livello superiore. Nel protocollo TCP/IP possono essere utilizzati il protocollo TCP ed
il protocollo UDP.
Il livello Applicazione gestisce qualsiasi applicazione che possa implementare la suite TCP/IP.
La struttura del modello TCP/IP prevede una rete formata da sottoreti che
comunicano tramite gateway. Ogni macchina ha un indirizzo univoco a 32
bit rappresentato da una serie 4 di numeri da 0 a 255 separati da un punto.
Ogni sottorete e‘ definita da una quaterna di numeri da 0 a 255, in cui lo 0
specifica quale parte dell’indirizzo non e‘ stabilita a priori (puo‘ cambiare a
seconda delle condizioni).
4 Il protocollo IP
Il protocollo IP e‘ la base del TCP/IP. Tutti i protocolli incapsulabili dal protocollo TCP/IP vengono trasmessi come datagrammi IP, i quali hanno la
particolarita‘ di non effettuare una connessione fra gli host.
I datagrammi IP non danno infatti garanzie sul fatto che il pacchetto
arrivi a destinazione ma offrono una semplice gestione degli errori basata
sull’invio di messaggi ICMP di diagnostica. Per avere maggiore sicurezza
sulla trasmissione dei dati e‘ bene passare ad un livello superiore (TCP).
Il protocollo IP non fornisce nessuna informazione su eventuali datagrammi successivi ed ogni datagrammi e‘ gestito indipendentente dagli altri
datagrammi. Questo comporta che l’ordine di arrivo non puo‘ essere
previsto.
5
Programmare i Socket
4.1
S.P.I.N.E Group
Struttura dell’header IPv4
L’intestazione (header) di un datagramma IPv4 e‘ solitamente di 20 bytes
tranne nei casi in cui siamo presenti le opzioni del datagramma. I byte vengono inviati secondo il modo di ordinamento Big Endian ovvero in ordine
di successione nello schema sovrastante.
I primi 4 bit, ossia il campo Version, sono dedicati alla versione del protocollo IP; in questo caso stiamo analizzando un pacchetto IPv4.
Header Length corrisponde alla lunghezza dell’intestazione del pacchetto
ed essendo limitata a 4 bit possiamo avere al massimo 60 byte di header
(15 x 4).
Gli 8 bit individuati dal campo Type Of Service (TOS) settano le impostazioni del datagramma IP che possono essere: minimize delay (minimizza
il ritardo), maximize throughput (massimizza le prestazioni), maximize reliability (massimizza l’affidabilita‘), minimize monetary cost (minimizza il
costo).
Solo uno di questi quattro bit puo‘ essere portato ad 1 mentre gli altri bit
devono essere obbligatoriamente a 0. Se tutti e quattro i bit sono a 0 si
sta lavorando nelle condizioni normali. Ricordiamo che il TOS non e‘ piu‘
considerato valido dalla nuove implementazioni del protocollo TCP.
Il campo Total Lenght Field rappresenta la lunghezza totale del datagramma IP espressa in byte. Combinando questo campo con quello della lunghezza dell’intestazione possiamo trovare esattamente dove cominciano i
dati. Possiamo avere al massimo datagrammi di 65535 byte che vengono
rappresentati dai 16 bit del campo ma e‘ bene forgiare pacchetti di dimensione minore per evitare la frammentazione durante il tragitto da un host
all’altro.
Il campo Identification identifica univocamente ogni datagramma IP inviato
6
Programmare i Socket
S.P.I.N.E Group
da un host. Generalmente viene incrementato di un’unita‘ ogni volta che
viene inviato un pacchetto.
A questo punto troviamo 3 flags, dei quali uno e‘ settato a 0 di default.
Il flag DF (Don’t Fragment) e‘ un bit che impone ai sistemi di non frammentare il pacchetto (ritorna un errore ICMP in caso di frammentazione
obbligatoria) mentre il flag MF (More Fragments) dice al sistema che sono
in arrivo nuovi frammenti del datagramma. Il campo Fragment Offset contiene l’offset del frammento rispetto all’inizio del datagramma originale.
Il campo Time-To-Live (TTL) imposta un limita massimo agli instradamenti
effettuabili nel tragitto da un host all’altro. Questo campo limita la vita di
un datagramma IP. Viene inizializzato durante la creazione del pacchetto
ad un valore predefinito e viene decrementato ad ogni passaggio attraverso un router. Quando il TTL si azzera il pacchetto viene scartato e viene
inviato un segnale di diagnostica ICMP.
Il protocollo IP puo‘ ospitare altri tipi di protocollo nei suoi campi. Questa procedura viene chiamata incapsulamento ed e‘ molto diffusa nel
web perche‘ consente una grande flessibilita d’opera. Il campo Protocol
e‘ stato implementato per consentire questa procedura e viene usato per
il demultiplexing dei datagrammi incapsulati.
II campo Header Checksum e‘ una somma di controllo per evitare errori e
viene calcolata solo sulla base dell’header.
I campi Source IP Address e Destination IP Address contengono l’indirizzo IP del mittente e del destinatario espressi in una variabile long a 32
bit.
7
Programmare i Socket
S.P.I.N.E Group
5 Il protocollo TCP
Il protocollo TCP e‘ indubbiamente il protocollo di alto livello piu‘ usato.
Questo protocollo e‘ ingrado di trasformare l’inaffidabile servizio a pacchetti offerto dal layer Network dello stack TCP/IP in un affidabile flusso di
byte.
Le innovazioni introdotte da questo protocollo sono state notevoli. Prima di tutto la connessione fra gli host deve essere effettuata e i sistemi
devono assicurarsi che la connessione possa essere stabilita accidentalmente.
Il flusso di dati in uscita deve essere spezzato presso il lato del mittente
e deve essere riassemblato presso il destinatario. Il sistema che riceve il
flusso di dati deve prendersi cura della rimozione dei pacchetti duplicati e
di correggere l’ordine di arrivo, se necessario.
Per stabilire una connessione il protocollo TCP utilizza un meccanismo di
handshaking, chiamato three-way handshake (stretta di mano a 3 vie :-). I
pacchetti duplicati o scaduti vengono gestiti usando un numero di sequenza generato dal sistema mittente. I pacchetti dispersi vengono controllati
dal sistema destinatario tramita un meccanismo di riconoscimento e in caso di perdite viene richiesto l’invio al mittente. Il protocollo TCP fornisce
inoltre un sistema di controllo di flusso per prevenire la congestione in caso
di sistemi con diverse velocita‘.
5.1
Struttura dell’header TCP
Il protocollo TCP viene solitamente utilizzato in segmenti di dati incapsulati nei datagrammi IP e per essere utilizzato il datagramam deve essere
demultiplexato dal sistema che riceve il pacchetto. L’intestazione di un
segmento TCP va quindi ad accodarsi all’intestazione del datagramma IP,
nella sezione Data.
Ecco come si presenta l’header di un segmento TCP secondo lo standard:
8
Programmare i Socket
S.P.I.N.E Group
Ogni segmento TCP possiede due campi dove sono specificati la porta
sorgente e la porta di destinazione, che combinati con le informazioni del
datagramma IP rendono ogni connessione univoca. Sono due campi da
16 bit quindi le porte indirizzabili sono 65535.
Il campo Sequence Number identifica i byte nel flusso di dati fra il mittente ed il destinatario. Il protocollo TCP segna ogni byte con un numero di
sequenza calcolato dal sistema. Ad ogni nuova connessione, il segmento TCP inviato contiene il numero di sequenza iniziale scelto dall’host per
questa connessione.
Ogni segmento e‘ quindi identificato da un sequence number. Per ordinare i dati abbiamo bisogno di un riferimento che ci dica qual e‘ il segmento
seguente a quello appena arrivato. Questo compito e‘ svolto dal campo
Acknowledgment Number , che memorizza il numero di sequenza del pacchetto seguente.
Il campo Header Lenght indica la dimensione dell’intestazione in parole
binarie di 32 bit. L’header puo‘ quindi essere al massimo di 60 byte, anche
se nella norma e‘ di 20 byte.
Dopo un campo di 6 bit riservato troviamo una serie di bit che definiscono
i flag del protocollo TCP; analizziamoli insieme:
URG: il protocollo TCP fornisce una modalita‘ chiamata Urgent Mode, che
consente ad uno dei due estremi di dire all’altro che sono stati messi dati
urgenti nel flusso di dati. Questo flag ci dice che bisogna controllare l’Urgent Pointer.
ACK : Il numero di riconoscimento (Acknowledge Number) e‘ valido.
PSH: Il ricevente dovrebbe passare il dato segnato da questo flag prima
possibile.
RST : Reimposta la connessione
9
Programmare i Socket
S.P.I.N.E Group
SYN: Sincronizza i numeri di sequenza per dare vita ad una nuova connessione.
FIN: Il mittente ha concluso l’invio di dati.
Il protocollo TCP fornisce un meccanismo di controllo del flusso (Flow
Control) composto da un campo chiamato Window size. In questo campo
viene messo il numero di byte che il destinatario e‘ disposto ad accettare.
Possono essere accettati al massimo 65535 byte (dovuti alla limitazione di
16 bit del campo).
Il campo Checksum prende in considerazione si l’intestazione che i dati
ed e‘ un dato di controllo che deve essere calcolato e scritto dal mittente e
verificato dal destinatario.
L’Urgent Pointer e‘ valido solo se il flag URG e‘ impostato. Questi rappresenta un offset che deve essere aggiunto al numero di sequenza per
capire quando terminano i dati importanti.
10
Programmare i Socket
S.P.I.N.E Group
6 Il protocollo UDP
Le applicazioni che non hanno bisogno delle funzionalita‘ offerte dal protocollo TCP, possono usare un protocollo alternativo chiamato UDP.
L’invio dei dati, coeerenti con il protocollo UDP, avviene passando un’intestazione seguita dai dati in un datagramma IP. UDP e‘ un servizio che
non fornisce garanzie sull’arrivo a destinazione dei dati e i datagrammi
possono essere riorganizzati e duplicati.
6.1
Struttura dell’header UDP
Il protocollo UDP, come il TCP, identifica i processi di invio e di ricezione tramite l’utilizzo di numeri di porta. Nonostante sia una caratteristica
comune dei due sistemi di trasmissione le porte TCP sono totalmente indipendenti dalle porte UDP, poiche‘ durante il demultiplex del datagramma
IP il campo Protocol permette di separare i due protocolli.
Il campo Lenght rappresenta l’intera lunghezza del datagramma UDP e
la sua dimensione minima e‘ di 8 byte. E‘ infatti ammissibile avere un datagramma UDP senza dati. Questo campo e‘ rindondante in quanto le
informazioni necessarie sono presenti nel datagramma IP nel quale e‘ incapsulato.
Il campo Checksum viene calcolato sia sull’header che sui dati e puo’ contenere dati non reali ed e‘ quindi bene effettuare il padding a passi di 16
bit aggiungendo 0 (NULL) nel campo data.
11
Programmare i Socket
S.P.I.N.E Group
7 Le funzioni principali
7.1
Socket(2)
Sintassi:
int socket( int domain, int type, int protocol );
Valori di ritorno:
Minore di 0 se si verifica un errore
Maggiore o uguale a 0 se ritorna un file descriptor
Alla funzione socket(2), che si occupa di definire il protocollo, devono essere passati tre parametri:
domain
Definisce un dominio di comunicazione, ovvero la famiglia del protocollo
che sara‘ usato per la comunicazione.
PF_INET : ipv4 protocollo internet
PF_INET6 : ipv6 protocollo internet
PF_UNIX : Comunicazione locale
PF_NS : Protocolli di Xerox NS
PF_IMPLINK : Livello di collegamento IMP
*PF (protocol family) e AF (address family) sono equivalenti.
type
Indica il tipo di socket.
SOCK_STREAM : streaming connection (TCP)
SOCK_DGRAM : datagram communication (UDP)
SOCK_RAW : Accesso "grezzo" al protocollo di rete (IP)
protocol
Indica il particolare protocollo utilizzato dal socket: il sistema usa il protocollo piu‘ adatto al tipo di socket.
IPPROTO_UDP: udp
IPPROTO_TCP: tcp
IPPROTO_ICMP: icmp
IPPROTO_RAW: ip
12
Programmare i Socket
S.P.I.N.E Group
Esempio socket(2):
int sock;if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
perror(Error socket());
7.2
bind(2)
Sintassi:
int bind (int sock, struct sockaddr * my_addr, int addrlen);
Valore di ritorno:
0 in caso di successo
-1 se si verifica un errore
La funzione bind(2) associa alla socket un indirizzo locale, usata dal lato
server specifica la porta su cui si pone in ascolto.
A questa funzione vengono passati tre parametri :
sock
Socket descriptor restituito dalla funzione socket(2).
*my_addr
Puntatore all’indirizzo della struttura sockaddr_in.
addrlen
Dimensione dell’ indirizzo della struttura sockaddr_in.
Esempio bind() :
struct sockaddr_in myaddr;
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(PORTA);
my_addr.sin_addr.s_addr = INADDR_ANY;
// INADDR_ANY sta per qualsiasi indirizzo
bzero(&(my_addr.sin_zero), 8);
if (bind(sock, (struct sockaddr *)&my_addr,
sizeof(struct sockaddr)) == -1)
perror(Errore bind());
13
Programmare i Socket
S.P.I.N.E Group
/* sizeof : Funzione che indica la grandezza in
bytes di un qualunque tipo di dato. */
7.3
listen(2)
Sintassi:
int listen(int sock, int backlog);
Valore di ritorno:
0 in caso di successo
-1 se si verifica un errore
La funzione listen(2) usata dal lato server setta quante connessioni potra‘
contenere la coda di backlog.
Questa viene usata con 2 parametri:
sock
Il solito file descriptor.
backlog
Indica il numero di connessioni che potranno sostare nella coda.
Esempio listen(2):
if (listen
7.4
(sock, 5) == -1)
perror(Error listen());
accept(2)
Sintassi:
int accept(int sock, struct sockaddr *addr, int *addrlen);
Valori di ritorno:
Minore di 0 se si verifica un errore
Maggiore o uguale a 0 se ritorna un file descriptor
14
Programmare i Socket
S.P.I.N.E Group
La funzione accept(2) si occupa di accettare la prima connessione in listen, questa ritorna un socket descriptor del tutto nuovo ma uguale al primo, che si occupera‘ di riceve e mandare dati.
Questa ha 3 parametri :
sock
Il solito file descriptor.
*addr
Puntatore all’indirizzo della struttura sockaddr_in.
In questa struttura viene restituito l’indirizzo dell’host mittente.
*addrlen
Dimensione dell’indirizzo della struttura sockaddr_in
Esempio accept(2):
int sin_size;
sin_size = sizeof(struct sockaddr_in);
new_fd = accept(sockfd, (struct sockaddr *)&their_ addr,
&sin_size);
printf(ricevuto da: %s,inet_ntoa(their_addr));
/* inet_ntoa e` una funzione che prende come parametro
un unsigned long in network byte order e ritorna un array
di char che rappresenta l'ip un formato dotted decimal*/
7.5
connect(2)
Sintassi:
int connect(int sock, struct sockaddr *serv_addr, int addrlen);
Valore di ritorno:
0 in caso di successo
-1 se si verifica un errore
La funzione connect(2) permette di inizializzare la connessione.
Deve essere chiamata con tre parametri:
15
Programmare i Socket
S.P.I.N.E Group
sock
solito file descriptor.
*serv_addr
Puntatore alla struttura sockaddr_in,dove viene letto l’host a cui collegarsi.
addrlen
Dimensione dell’ indirizzo della struttura sockaddr_in.
Esempio connect(2) :
int sock;
struct sockaddr_in server;
if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
perror(Error socket());
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(PORTA);
my_addr.sin_addr.s_addr = inet_addr(212.122.223.56);
connect(sock, (struct sockaddr *)&server, sizeof(server));
// inet_addr: funzione che converte un indirizzo ip
// in un unsigned long.
7.6
send(2)
Sintassi:
int send(int sock, const void *msg, int len, int flags);
Valore di ritorno:
0 in caso di successo
-1 se si verifica un errore
La funzione send(2) permette l’invio di dati ed ha bisogno dei seguenti parametri:
sock
file descriptor.
16
Programmare i Socket
S.P.I.N.E Group
*msg
puntatore al msg che si vuole spedire.
len
grandezza in bytes del msg.
flags
eventuali opzioni da settare,diversamente settarlo a 0.
Esempio send() :
char *msg=ciao;
send(sockfd, msg, sizeof(msg), 0);
7.7
recv(2)
Sintassi:
int recv(int sock, void *buf, int len, unsigned int flags);
Valore di ritorno:
0 in caso di successo
-1 se si verifica un errore
La funzione recv permette la ricezione dai dati inviati ad un client.
Gli argomenti sono i seguenti:
sock
file descripton.
buffer
puntatore.
len
grandezza in bytes del msg arrivato.
flags
eventuali opzini di ricezione (settarlo a 0).
17
Programmare i Socket
7.8
S.P.I.N.E Group
gethostbyname(3)
Sintassi:
struct hostent *gethostbyname (char *name)
La funzione gethostbyname(3) permetta la risoluzione di un host in un indirizzo ip.
Questa restitusce un puntatore alla struttura hostent,in caso di errore restitusce NULL.
Esempio:
struct hostent *host;
if ((host=gethostbyname(host)) == NULL)
herror(gethostbyname);
// E` necessario usare la chiamata herror(), perche` la
// chiamata errno non viene usata.
7.9
gethostbyaddr(3)
Sintassi:
struct hostent *gethostbyaddr (const char *addr, int len, int type);
La funzione di rete gethostbyaddr(3) fornisce un metodo per la risoluzione
inversa del nome a partire dall’indirizzo ip. Questa restitusce un puntatore
alla struttura hostent o in caso di errore restitusce NULL.
Gli argomenti necessari all’utilizzo della funzione sono:
char *addr e‘ un puntatore ad un indirizzo ip (spesso una struttura del
tipo in_addr)
int len e‘ la lunghezza in byte dell’indirizzo
int type e‘ un intero che esprime la tipologia di indirizzo (e‘ attualmente supportato solo il tipo AF_INET).
18
Programmare i Socket
S.P.I.N.E Group
Esempio di gethostbyaddr(3):
struct hostent *host;
struct addr_in addr;
inet_aton(127.0.0.1, &addr);
host = gethostbyaddr((const char *) &addr, sizeof(addr), AF_INET);
if (host == NULL)
herror(gethostbyaddr);
19
Programmare i Socket
S.P.I.N.E Group
8 Le strutture di rete
La programmazione con le socket implica l’uso di determinate strutture.
Le principali che analizzeremo saranno 3 e siete invitati a studiarle nei
relativi header del kernel del sistema operativo.
8.1
SockAddr
struct sockaddr {
unsigned short sa_family;
/* address family, AF_xxx */
char
sa_data[14]; /* 14 bytes of protocol address */
};
Questa e‘ la struttura piu‘ generale. sa_family contiene la tipologia di
cui fa parte l’indirizzo espressa come unsigned shot mentre sa_data e‘ un
array di caratteri di dimensione 14, atto a contenere l’indirizzo.
8.2
SockAddr_In
La struttura SockAddr_in prevede principalmente l’utilizzo di tre variabili:
short int sin_family contiene la tipologia dell’indirizzo espressa in short
integer.
unsigned short int sin_port contiene la porta di collegamento all’indirizzo.
struct in_addr sin_addr contiene l’indirizzo vero e proprio, incapsulato in
una struttura del tipo in_addr.
struct sockaddr_in {
};
sa_family_t sin_family;
u_int16_t sin_port;
struct in_addr sin_addr;
Vediamo ora come associare ip e porta alla struttura sockaddr_in:
struct sockaddr_in their_addr;
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(2555);
their_addr.sin_addr = htonl(INADDR_ANY);
20
Programmare i Socket
S.P.I.N.E Group
bzero(&(their_addr.sin_zero), 8); // azzera la restante struttura
//INADDR_ANY : costante accetta ogni ip
//htons() : converte uno short da Host a Network
//htonl() : converte un long da Host a Network
8.3
HostEnt
Analizzando la chiamata gethostbyname(3) abbiamo utilizzato la struttura
hostent, questo appunto perche‘ la funzione in questione restituisce un
puntatore alla struttura hostent.
Vediamo di analizzarla meglio :
struct hostent
char
char
int
int
char
{
*h_name; /* official name of host */
**h_aliases; /* alias list */
h_addrtype; /* host address type */
h_length; /* length of address */
**h_addr_list; /* list of addresses from nameserver */
#define h_addr h_addr_list[0] /* address, for backward compatiblity */
};
La struttura hostent nel nostro caso e‘ riempita dall’argomento passato alla chiamata gethostbyname, ed e‘ indispensabile nel caso volessimo
risolvere un host in un indirizzo di rete.
Nel char puntato da *h_name e‘ immagazzinato il nome dell’host mentre
in **h_addrtype e‘ presente una lista di alias del nome. Il tipo di indirizzo
assegnato all’host e‘ espresso dal campo h_addrtype sottoforma di intero,
come d’altrocanto la lunghezza dell’indirizzo, immagazzinata in h_lenght.
La lista degli indirizzi provenienti dal nameserver viene puntata da **h_addr_list.
8.4
In_addr
struct in_addr {
unsigned long int s_addr;
};
Questa semplice struttura e‘ necessaria per molte chiamate inet_* ovvero tutte le chiamate di manipolazione degli indirizzi internet. La struttura
in questione e‘ in grado di memorizzare un indirizzo web nel formato long
senza segno.
21
Programmare i Socket
S.P.I.N.E Group
9 Programmi dimostrativi
9.1
UDP Packet sender
Semplice client che senda un pacchetto udp :
/* minitalk
codec : [email protected] */
#include
#include
#include
#include
#include
#include
#include
#include
#include
<stdio.h>
<stdlib.h>
<errno.h>
<string.h>
<sys/types.h>
<netinet/in.h>
<netdb.h>
<sys/socket.h>
<sys/wait.h>
int main(int argc, char *argv[])
{
int sockfd;
struct sockaddr_in their_addr;
struct hostent *host;
int numbytes;
if (argc<4) {
printf(Usare:
exit(-1);
}
minitalk hostname porta messaggio\n);
host=gethostbyname(argv[1]);
if (!host) {
herror(argv[1]);
exit(-1);
}
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror(Errore);
exit(1);
22
Programmare i Socket
S.P.I.N.E Group
}
their_addr.sin_family = AF_INET;
their_addr.sin_port = htons(atoi(argv[2]));
their_addr.sin_addr = *((struct in_addr *)host->h_addr);
bzero(&(their_addr.sin_zero), 8);
if ((numbytes=sendto(sockfd, argv[3], strlen(argv[3]), 0,
(struct sockaddr *)&their_addr, sizeof(struct
sockaddr))) == -1)
{
perror(Errore);
exit(1);
}
printf(Sendato il pacchetto a : %s\nporta :%d\n,
inet_ntoa(their_addr.sin_addr), atoi(argv[2]));
close(sockfd);
return 0;
}
9.2
MiniTalk
Semplice server che stampa i dati ricevuti dal client :
/* minitalk codec :
#include
#include
#include
#include
#include
#include
#include
#include
[email protected] */
<stdio.h>
<stdlib.h>
<errno.h>
<string.h>
<sys/types.h>
<netinet/in.h>
<sys/socket.h>
<sys/wait.h>
#define MAXBYTES 100
23
Programmare i Socket
S.P.I.N.E Group
int main(int argc, char *argv[])
{ int s;
struct sockaddr_in my_addr;
struct sockaddr_in their_addr;
int addr_len, numbytes;
int pid;
char buf[MAXBYTES];
if (argc < 2) {
printf(Usare: minitalk porta\n);
exit(1);
}
printf(Sono in ascolto sulla porta %d\n, atoi(argv[1]));
if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror(socket);
exit(1);
}
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(atoi(argv[1]));
my_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(my_addr.sin_zero), 8);
if (bind(s, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) ==
-1)
{
perror(Errore);
exit(1);
}
addr_len = sizeof(struct sockaddr);
if ((numbytes=recvfrom(s, buf, MAXBYTES, 0,(struct sockaddr
*)&their_addr, &addr_len)) == -1)
{
perror(Errore);
exit(1);
}
printf(ricevuto da :
%s\n,inet_ntoa(their_addr.sin_addr));
24
Programmare i Socket
S.P.I.N.E Group
buf[numbytes] = '\0';
printf(messaggio : \%s\\n,buf);
//close(s);
}
9.3
DNSLookup
Utile tool per la risoluzione dei nomi e del reverse lookup (da ip a dns) :
/* Simple DNSLookup and Whois
mailto:[email protected]
mailto:[email protected]
mailto:[email protected]
*/
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <assert.h>
#define ERROR -1
#define SUCCESS 0
int main(int argc, char *argv[]) {
int sockfd;
char stringa[100];
struct hostent *host;
struct in_addr addr;
}
if (argc<2) {
fprintf(stderr, Sintassi:
exit(ERROR);
%s <ip/dns>\n, argv[0]);
if((host = gethostbyname(argv[1])) == NULL) {
25
Programmare i Socket
}
}
S.P.I.N.E Group
herror(DNSLookup);
exit(ERROR);
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror(Socket);
exit(ERROR);
inet_aton(argv[1], &addr);
memset(&stringa, 0, sizeof(stringa));
inet_ntop(AF_INET, host->h_addr, stringa, sizeof(stringa));
}
if (strcmp(argv[1], stringa) == 0) {
memset(&host, 0, sizeof(host));
host = gethostbyaddr((const char *) &addr, sizeof(addr), AF_INET);
assert(host->h_addrtype==AF_INET && host->h_length==4);
fprintf(stdout, %s -> %s\n, argv[1], host->h_name);
}
else {
fprintf(stdout, %s -> %s\n, argv[1], stringa);
}
return SUCCESS;
26