Strumenti Utente

Strumenti Sito


roberto.alfieri:user:reti:socket:udp

Programmazione dei Socket

Riferimenti

I Berkeley Socket

Berkeley_sockets Introdotti nel 1983 dall'Universita' Berkeley della Califonia, in Unix BSD 4.2 intesi come astrazione fornita dal sistema operativo per gestire lo scambio di dati tramite le Reti con protocolli standard.

L'interfaccia dei Berkeley Socket

E' un'API, realizzata attraverso una libreria ( Socket Library), che permette la comunicazione tra processi sullo stesso host o host diversi utilizzando l'astrazione dei Socket. Sul canale di comunicazione tra 2 processi si possono leggere e scrivere dati analogamente a quanto avviene per le Pipe, ma non e' limitato a processi dello stesso host.

Tipi di comunicazione

I socket supportano 3 principali tipi di comunicazione:

  • SOCK_STREAM: canale bidirezionale, sequenziale, affidabile (flusso di byte). Gestito a livello di trasporto con il protocollo TCP
  • SOCK_DGRAM: Spedizione di pacchetti di lunghezza massima fissata (datagrammi). Gestito a livello di trasporto con il protocollo UDP
  • SOCK_RAW: accesso a basso livello delle interfacce di rete (utile ad esempio per debug)

(Altri tipi sono SOCK_SEQPACKET e SOCK_RDM)

Protocol Family (PF) e Address Family (AF)

Esistono diverse famiglie di socket. Ogni famiglia riunisce i socket che utilizzano gli stessi protocolli sottostanti, supporta un sottoinsieme di stili di comunicazione e possiede un proprio formato di indirizzamento (Address Family - AF). Esempi principali di PF e protocolli:

PF AF TIPI DESCRIZIONE
PF_UNIX AF_UNIX SOCK_STREAM stream loopback
PF_UNIX AF_UNIX SOCK_DGRAM datagram loopback
PF_INET AF_INET SOCK_STREAM TCP over IPv4
PF_INET AF_INET SOCK_DGRAM UDP over IPv4
PF_INET AF_INET SOCK_RAW IPv4 raw socket
PF_INET6 AF_INET6 SOCK_STREAM TCP over IPv6
PF_INET6 AF_INET6 SOCK_DGRAM UDP over IPv6
PF_INET6 AF_INET6 SOCK_RAW IPv6 raw socket
Indirizzi IPv4

Il socket della famiglia PF_INET utilizzano il formato di indirizzamento AF_INET a 32 bit. La struttura per memorizzare un indirizzo e':

struct in_addr{uint32_t  s_addr;}               

dove il tipo uint32_t e' un long unsigned int memorizzato in network order.

Esercizio: dig -x 160.78.48.10

Indirizzi IPv6

Il socket della famiglia PF_INET6 utilizzano il formato di indirizzamento AF_INET6 a 128 bit. La struttura per memorizzare un indirizzo e':

struct in6_addr{u_char  s6_addr[16];}          

memorizzato in network order.

Esercizio: dig -x 2001:760:ffff:ffff::aa

Endianess
  • Little Endian: Processori Intel, Digital, bus PCI
  • BIG Endian: Altri processori (IBM, Sun, Motorola, ..) , Network

Esercizio: endtest.c

Funzioni di Conversione

Funzioni di conversione del Byte Order per la comunicazione in rete. I Long servono per gli indirizzi Ipv4, gli Short per gli indirizzi di Porta.

ntohs( ) Network → Host ( uint16_t, 2 Byte)
htons( ) Host → Network ( uint16_t, 2 Byte)
ntohl( ) Network → Host ( uint32_t, 4 Byte)
htonl( ) Host → Network ( uint32_t, 4 Byte)
inet_aton(const char *src, struct in_addr *dest) ASCII → Numeric
char *inet_ntoa(struct in_addr *src) Numeric → ASCII
inet_pton(AF_INET,”1.2.3.4”, &mynet.sin_addr) Presentation → Numeric (32 bit network order)
inet_ntop(AF_INET, &mynet.sin_addr, stringa,sizeof(string)) Numeric → Presentation (stringa tipo "1.2.3.4")

Le funzioni inet_pton, inet_ntop sono piu' generali perche' non limitate ai soli indirizzi IPv4

Esercizi inaddr.c

La struttura HOSTENT: i dati dell'host

Le informazioni relative ad un host:

  • Hostname
  • Host aliases
  • Address type (AF_UNIX, AF_INET o AF_INET6)
  • Lunghezza dell’indirizzo in byte (AF_INET=4, AF_INET6=16)
  • Indirizzo (o indirizzi) (struttura in_addr)

sono memorizzate in una opportuna struttura hostent:

#include <netdb.h>

struct hostent {
char  *h_name;     //hostname
char **h_aliases;  //alias list
int   h_addrtype;  //address type
int   h_length;    //address length
char **h_addr_list;//address list 
}

#define h_addr  h_addr_list[0] 
// indirizzo principale dell’host

Funzioni:

gethostbyname(<hostname>) interroga il DNS e ritorna la struttura hostent (IPv4)
gethostbyname2(<hostname>,AF_INET6) interroga il DNS e ritorna la struttura hostent (IPv4 o IPv6)
gethostbyaddr(<IPaddr>,4,AF_INET) ritorna hostent a partire dall’indirizzo IP

Esempi in C:

| gethostent.c - gethostbyaddr.c

Esempi in altri linguaggi:

gethostent.phps - gethostbyname.py - GetIPAddess.java

Lo Standard POSIX 1003.1-2001 e la struttura addrinfo

In questo standard sono state indicate come deprecate le varie funzioni gethostbyaddr, gethostbyname, ecc ed è stata introdotta una interfaccia completamente nuova, con la struttura addrinfo che sostituisce hostent, aggiungendo campi per le informazioni relative ai socket della comunicazione (socktype, sockaddr, ..):

(vedi http://en.wikibooks.org/wiki/C_Programming/POSIX_Reference/netdb.h/getaddrinfo )

 struct addrinfo 
 { 
   int ai_flags;                 /* Input flags.  */ 
   int ai_family;                /* Protocol family for socket.  */ 
   int ai_socktype;              /* Socket type.  */ 
   int ai_protocol;              /* Protocol for socket.  */ 
   socklen_t ai_addrlen;         /* Length of socket address.  */ 
   struct sockaddr *ai_addr;     /* Socket address for socket.  */ 
   char *ai_canonname;           /* Canonical name for service location.  */ 
   struct addrinfo *ai_next;     /* Pointer to next in list.  */ 
 }; 

Le nuove funzioni getaddrinfo e getnameinfo sostituiscono gethostbyname, getaddrbyname, ecc)

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
 
int getaddrinfo(const char *hostname,   //puo' essere un  nome ("nice.com") o un indirizzo ("127.0.0.1")
                const char *service,    //puo' essere un nome ("http") o un numero ("80")
                const struct addrinfo *hints, // struttura con info aggiuntive per la richiesta
                struct addrinfo **res);       // struttura addrinfo con le info richieste

int getnameinfo(const struct sockaddr *sa, 
                          socklen_t salen,
                               char *host, 
                           size_t hostlen,
                               char *serv, 
                           size_t servlen, 
                               int flags);

Esempi in C:

addrinfo.c (da Wikipedia )) - showip.c (da Bjee )

Esempi in Python:

addrinfo.py - addrinfo2.py

Librerie di Rete in altri linguaggi:

Programmazione

Modelli di programmazione

Client - Server

E' caratterizzato dalla presenza di due categorie di soggetti, i programmi di servizio, chiamati server, che ricevono le richieste e forniscono le risposte, ed i programmi di utilizzo, detti client.

In generale un server può (di norma deve) essere in grado di rispondere a più di un client, per cui è possibile che molti programmi possano interagire contemporaneamente, quello che contraddistingue il modello però è che l'architettura dell'interazione è sempre nei termini di molti verso uno, il server, che viene ad assumere un ruolo privilegiato.

Questo e' il modello nativo fornito dalla libreria Socket di Berkeley.

La comunicazione avviene tra 2 end-point (un client e un server) attraverso la rete. Ogni endpoint della comunicazione e' detto Socket ed e' identificato dai numeri dell'indirizzo di rete (IP) e dell'indirizzo logico (porta).

Peer-to-peer

Non c'e' nessun programma che svolga un ruolo preminente.

Questo vuol dire che in generale ciascun programma viene ad agire come un nodo in una rete potenzialmente paritetica; ciascun programma si trova pertanto a ricevere ed inviare richieste ed a ricevere ed inviare risposte, e non c'è più la separazione netta dei compiti che si ritrova nelle architetture client-server.

In realtà, possono essere presenti server centralizzati o nodi con funzionalità diverse rispetto agli altri (supernodi), con il compito di facilitare l'interazione tra i peer (servizio di localizzazione).

Caratteristiche: sistema altamente distribuito, nodi dinamici e autonomi.

Applicazioni principali: distribuzione di contenuti (CDN), collaborazione e comunicazione (chat, voip, ..)

Problematiche: pubblicazione, ricerca e recupero.

Nel modello client-server della Socket library di Berkeley un nodo p2p e' composto almeno da un socket client e da un socket server.

Multi Tier

Si struttura, come dice il nome, su tre o piu' livelli. Questo consente di progettare e sviluppare applicazioni modulari distribuite in rete.

In una tipica architettura a 3 livelli (Three tier) il primo livello e' quello dei client che eseguono le richieste e gestiscono l'interfaccia con l'utente mentre la parte server viene suddivisa in due livelli, introducendo un middle-tier, su cui deve appoggiarsi tutta la logica di analisi delle richieste dei client per ottimizzare l'accesso al terzo livello, che è quello che si limita a fornire i dati dinamici che verranno usati dalla logica implementata nel middle-tier per eseguire le operazioni richieste dai client.

La modularita' dell'architettura richiede la progettazione di specifiche interfacce basate su tecnologie standard come RPC e Web service, come ad esempio il servizio Portmap di linux utilizzato da applicazioni come NFS.

Vedi: rpc

Il middle-tier e' un server verso il primo livello e un client verso il terzo.

I dati del socket: la struttura SOCKADDR

Nella libreria Berkeley Socket le informazioni relative al socket sono memorizzate nella struttura sockaddr:

struct sockaddr
{ sa_family_t sa_family;    //Address Family: AF
char  sa_data[14];      }      // port, IP address, ..

sockaddr e’ una struttura generica, a cui fanno riferimento le primitive di gestione dei socket (bind, connect, listen, accept, ..) indipendentemente dall’Address Family utilizzato. Il programmatore C dovra’ invece utilizzare le strutture specifiche, per poi fare casting al momento della chiamata:

IPv4:	struct sockaddr_in  
	{ 
	sa_family_t     sin_family;   // Address Family: AF_INET
	in_port_t       sin_port;     // port, 2 byte network-order
	struct in_addr  sin_addr;     // IPv4 address network-order
	} 
IPv6:	struct sockaddr_in6  
	{ 
	sa_family_t     sin6_family;   // Address Family: AF_INET6
	in_port_t       sin6_port;     // port, 2 byte network-order
	uint32_t	        sin6_flowinfo; //
	struct in6_addr sin6_addr;     // IPv6 address network-order
	uint32_t        sin6_scpe_id;  // 
	} 

Esempio di codifica in C in cui un client istanzia la sockaddr relativa al server che vuole contattare:

struct in_addr myaddr;
struct sockaddr_in servsockaddr;
int port 80; 
inet_aton("172.28.33.1", &myaddr);

bzero(&servsockaddr, sizeof(servsockaddr));
servsockaddr.sin_family = AF_INET;
servsockaddr.sin_port = htons(port);    
servsockaddr.sin_addr = myaddr; 

Vedi esempio: mysockaddr.c

Socketcall

Il supporto di Linux per la programmazione dei socket avviene con la chiamata socketcall(102) che e’ un multiplexer per le API:

socket() bind() listen() accept() sendto()/ recvfrom() connect() send() recv() getsockname() ..

socket ()

La socket() restituisce un File Descriptor che verra' utilizzato come handle. Si limita ad allocare le opportune strutture nella file table del kernel. Gli argomenti della chiamata socket() sono l'Address Family (IPv4 o IPv6) e il tipo di comunicazione (a datagrammi o a stream).

Il socket deve essere creato sia dal client che dal server.

Prototipo della funzione:

#include <sys/socket.h>
int socket(int domain, int type, int protocol)

Esempio di codifica in C:

int sockfd;

sockfd = socket(AF_INET, SOCK_DGRAM, 0)
if (sockfd == -1) printf("error: %s\n", strerror(errno));

BUFFER

Ogni socket ha un buffer di ricezione ed uno di trasmissione. Il buffer di ricezione è usato per mantenere i dati nel kernel prima di passarli all’applicazione.

Con TCP, lo spazio disponibile è quello pubblicizzato nella finestra di TCP

Con UDP, eventuali pacchetti in overflow vengono cancellati

SO_RCVBUF e SO_SNDBUF permettono di cambiare la grandezza dei buffer

Esempio:

sysctl -a | grep tcp
net.ipv4.tcp_rmem= 4096 87380 174760 (buffer ric.  min-init-max)
net.ipv4.tcp_wmem= 4096 16384 131072 (buffer sped. min-init-max)

La dimensione dei buffer puo' essere modificata  con setsockopt(). 
SO_RCVBUF SO_SNDBUF       Imposta dimensioni del Buffer di Ricezione/Trasm

 //Esempio di raddoppio del buffer del ricevente
 int rcvBufSize;
 int sockOptSize;
 sockOptSize = sizeof(rcvBuffersize);                  //preleva la dimensione di origine del buffer
 if(getsockopt(m_socket, SOL_SOCKET, SO_RCVBUF, &rcvBufSize, &sockOptSize) < 0)
 /*gestione errore*/
 printf(“initial receive buffer size: %d\n”, rcvBufSize);        
 RcvBufSize *=2;                                                    //raddoppia la dimensione del buffer
 if(setsockopt(m_socket, SOL_SOCKET, SO_RCVBUF, &rcvBufSize, sizeof(rcvBufSize)) < 0)
 /*gestione errore*/

send() e recv() bloccante e non bloccante

  • La send() e' normalmente bloccante; si blocca quando il buffer in trasmissione e' pieno.
  • Con un socket Non Bloccante di tipo TCP, se l’operazione di scrittura non può essere effettuata nemmeno in parte perché manca spazio nei buffer del TCP in cui andare a scrivere i dati, la funzione ritorna immediatamente al chiamante restituendo il codice d’errore EWOULDBLOCK.
  • Se invece e’ rimasto spazio, viene effettuata una scrittura di una porzione di dati minore o uguale alla dimensione del buffer libero, e la write restituisce il numero di byte scritti.
  • La recv() e' per default bloccante; si blocca quando il buffer in ricezione associato al socket è vuoto.
  • Ritorna quando ci sono dati nel buffer e il numero di byte letti puo' essere inferiore al numero di byte richiesto.
  • Ritorna 0 quando non ci sono dati nel buffer e l'altro peer ha chiuso la connessione.
  • Se il buffer e' vuoto e il socket è impostato come non bloccante non ci sarà nessun blocco ma ritornerà un -1 settando la variabile di errore EWOULDBLOCK Il programma dovra' ripetere successivamente la recv() per vedere se i dati sono effettivamente arrivati.

Esempio di codice per impostare il socket non bloccante:

 int flags, sockfd;
 sockfd = socket(....);
 if ( (flags=fcntl(sockfd,F_GETFL,0)) <0 ) exit(1);
 flags |= O_NONBLOCK;
 if ( fcntl(sockfd,F_SETFL,flags) <0 ) exit(1);

bind()

La funzione bind assegna un indirizzo locale ad un socket. Viene usata sul lato server per specificare la porta (e gli eventuali indirizzi locali) su cui poi ci si porrà in ascolto. Il prototipo della funzione è il seguente:

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen)

Esempio di programma in C in cui il server si mette in ascolto sulla porta 80 su qualsiasi interfaccia di rete del server (INADDR_ANY):

servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(80);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 

Comunicazione a Datagrammi

Le due funzioni principali usate per la trasmissione di dati attraverso i socket UDP sono sendto e revcform()

Prototipi:

//scrive i datagrammi sul socket
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen)

//Legge i datagrammi dal socket (*)
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *from, socklen_t *fromlen)

Esempi di codice in C lato client:

sendto(sockfd, msg, NumByteToSend , 0, (struct sockaddr *)&servaddr, socklen);

int MaxBytes=2000; 
int Len= recvfrom(sockfd, msg, MaxBytes, 0, (struct sockaddr *)&servaddr, (socklen_t *)&socklen);

Programmazione UDP in Pyhton - PHP - Java

Esempi con UDP

DGRAM : Invia un datagramma al server

ECHO : Invia un datagramma al server; il server rimanda al client il datagramma ricevuto

ECHO TIMEOUT : Invia un datagramma al server e attiva un timeout; il server rimanda al client il datagramma ricevuto con probabilita' P, tra 0 e 1, e lo scarta con probabilita' 1-P (simulando un disturbo sulla rete).

Protocollo simplex, per canali ideali. Trasmissione dati in una sola direzione, sorgente/destinazione sempre pronti, tempo di elaborazione dati trascurabile, memoria infinita.

Protocollo Stop-and-wait Il destinatario richiede tempo per l'elaborazione. Prima di inviare il prossimo dato il mittente deve ricevere un riscontro (Ack), per cui il destinatario, se sovraccarico, può moderare il tasso di invio dei dati ritardando l'invio di Ack (controllo di flusso con riscontro).

Protocollo Stop-and-wait ARQ : Per canali rumorosi. I datagrammi possono andare perduti, duplicati o consegnati fuori ordine. Ogni invio viene numerato (aggiungendo una intestazione con il numero <n> del blocco) e viene attivato un timeout. Prima di inviare il prossimo dato il mittente deve ricevere un riscontro (Ack <n>). Il server scarta alcuni datagrammi in modo casuale per simulare errori sulla linea.

Protocollo tftp Client tftp (per tftp vedi Wiki): l'applicazione tftp consente di trasferire file tra client e server utilizzando il trasporto a datagrammi di UDP. Il protocollo e' simile a Stop-and-wait ARQ.

DGRAM-P2P Programma per l'invio di un datagramma in modalita' peer-to-peer, integrando DGRAM server e client in un unica applicazione. Il server e' gestito tramite thread, mentre il client e' interattivo. Componenti:

- un thread sempre in ascolto riceve messaggi UDP e visualizza a schermo coordinate del mittente (host, porta) e messaggio (come dgram_server).

- il programma principale raccoglie interattivamente le coordinate del server da contattare (host, porta) e messaggio, quindi invia il messaggio all'host (come dgram_client).

Possibili opzioni:

- logging: salvare su file (o DB) il logging delle attivita (Send | Receive, Host, port, Time, Message)

p2p-udp

Portmapper Se i servizi di rete sono dinamici e' utile avere un registry server (portmapper) in cui vengono registrati i servizi attivi (tipo di servizio, indirizzo ip, porta). Il client esegue un lookup sul portmapper, quindi contatta il servizio desiderato.

Architettura della comunicazione:

1) Il server registra il proprio servizio sul portmapper, indicando IP, porta, NICname ecc.

2) Il client contatta il portmapper per conoscere la lista dei servizi disponibili.

3) Il client contatta il server utilizzando le informazioni ricevute dal portmapper.

4) Il server cancella la registrazione sul portmapper.

portmapper

p2p con directory centralizzata I sistemi p2p con directory centralizzata dispongono di un "supernodo" che ha il mapping dei nodi p2p: discovery dei peer attivi e delle relative risorse (ad esempio file shared).

Programma DGRAM-p2p con directory centralizzata:

- un thread/processo in ascolto sulla porta PORT, riceve messaggi di testo unicast da altri nodi p2p-udp in internet. I messaggi ricevuti vengono inviati a stdout.

- un thread/processo invia periodicamente un messaggio di hello al servizio p2p-udp-server del tipo "hello NIC PORT" e riceve la lista dei nodi attivi (NIC, ora:minuti:secondi, IP, porta | NIC, ora:minuti:secondi , | … ). La lista viene memorizzata in una struttura dati interna.

- un client interattivo puo' listare l'elenco dei nodi attivi e inviare un mesaggio unicast ad un NIC di destinazione del tipo "NICdest testo". Il client deve consultare la struttura dati per recuperare la coppia IP/PORT associata al NICdest e inviare un messaggio "NICmitt testo".

P2P con directory centralizzata

Note

Per vedere quali socket UDP sono in ascolto: netstat -una

Per abilitare l'invio in broadcast in C:

int yes = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &yes, sizeof(int) );
roberto.alfieri/user/reti/socket/udp.txt · Ultima modifica: 16/02/2021 09:28 da roberto.alfieri