INGEGNERIA DEL WEB. VinX

Dimensione: px
Iniziare la visualizzazioe della pagina:

Download "INGEGNERIA DEL WEB. VinX"

Transcript

1 INGEGNERIA DEL WEB VinX

2 Indice 1 Programmazione di applicazioni di rete Applicazioni di rete Modello Client/Server Modello peer-to-peer Interazione del protocollo di trasporto con le applicazioni Application Programming Interface Socket Gestione degli errori Semi-Associazione: bind() Inizializzare indirizzo ip e numero di porta Header file La funzione connect() Gestione di errori transitori in connect() La funzione listen() La funzione accept() Socket d ascolto e socket connesso Associazione orientata alla connessione La chiusura di una connessione TCP Per leggere/scrivere dati Progettazione Client Server TCP/UDP Esempio: DayTime TCP Comunicazione senza connessione Uso di connect() con i socket UDP Esempio: daytime UDP Risoluzione dei nomi Funzioni rientranti Nomi dei protocolli Funzioni per altri servizi di risoluzione Formato dei dati Funzioni getsockname() e getpeername() Le opzioni dei socket Alcune opzioni generiche Server TCP iterativo (o sequenziale) Esempio: contatore accessi Server TCP ricorsivo (o concorrente) La funzione fork() Server echo ricorsivo Analisi applicazione echo I segnali Gestione dei segnali Segnale SIGCHLD Gestione SIGALRM Progettazione di applicazioni di rete robuste Multiplexing dei socket Funzioni bloccanti e soluzioni La funzione select() Operazioni sugli insiemi di descrittori Multiplexing dell I/O nel server Client tcp echo con select() Il demone inetd I

3 3 World Wide WEB: introduzione e componenti La filosofia e le attività del W3C Ingredienti del WEB WEB client e WEB server Meccanismi di naming delle risorse Protocollo HTTP Caratteristiche del protocollo HTTP Linguaggio di markup Linguaggio HTML XML XHTML Macro-componenti del WEB: client Parte Informativa Memorizzazione delle pagine Modelli architetturali di server WEB Macro-componenti del WEB: server proxy Il protocollo HTTP Header HTTP Codice di stato della risposta Autenticazione in HTTP/ Principali problemi di HTTP/ Protocollo HTTP/ Connessioni in HTTP/ Hosting Virtuale Negoziazione del contenuto Autenticazione digest in HTTP/ Ottimizzazione della banda Trasmissione del messaggio Caching Scadenza specificata dal server Validazione della risorsa in cache Interazione tra HTTP e TCP Timer di ritrasmissione Slow-start Restart Problema dei duplicati vecchi Stratificazione HTTP/TCP Algoritmo di Nagle Overhead sul server Web Ottimizzazione del server Il server Web Apache Apache: API Apache: Il servizio HTTP Le directory in Apache Apache: I moduli Multi-Processing Module (MPM) Ciclo richiesta-risposta Apache: Virtual Hosting Apache: I file di configurazione Apache: httpd.conf Utilità dei logfile di un server Web Informazioni estraibili dai logfile Common Logfile Format Logfile custom in Apache II

4 7 Tencologie per la generazione di contenuti dinamici Livelli logici di un servizio Web-based Come mappare i livelli logici sui processi e calcolatori Evoluzione delle architetture Web lato server Tecnologie per middle-tier Web Application Server Framework per applicazioni Web Tecnologia CGI Azioni ed implementazione di uno script Configurazione di Apache per CGI Java Servlet I compiti di una servlet Ciclo di vita di una servlet Servlet Container JSP Elementi JSP Apache Tomcat PHP Caratteristiche di PHP Apache e PHP Middleware Java EE Sistemi Web distribuiti localmente e geograficamente Sistemi Web Distribuiti Web Cluster Linux Virtual Server Oltre il front-end tier Delivery su scala geografica Sistemi Web Distribuiti Geograficamente Web Multi-Cluster Web Caching e sistemi per Web Content Delivery Le politiche di rimpiazzamento della cache Web Caching cooperativo Modalità di cooperazione Squid Collid di bottiglia del delivery Content Delivery Content Delivery Network Caching di contenuti Web dinamici Sistemi Peer-to-Peer Overlay Network Overlay Routing Reti non strutturate Case Study: Gnutella Case study: KaZaA Case study: BitTorrent Reti strutturate Distributed Hash Table III

5 11 Un introduzione ai Web service Web Service Service Oriented Architecture Standard per i Web service Simple Object Access Protocol Descrizione dei servizi Web Service Description Language Descrizione di un servizio Universal Description, Discovery, Integration Composizione di servizi Piattaforme per Web Service Architetture Stili architetturali Evoluzione degli stili architetturali Architetture di sistema Architetture Centralizzate Architetture decentralizzate e ibride Architetture e Middleware Sistemi distribuiti autonomici Processi e Virtualizzazione Processori, Processi e Thread Implementazione dei Thread Virtualizzazione Macchina Virtuale Tipi di virtualizzazione Migrazione del codice Comunicazione Tipi di comunicazione Semantica della comunicazione Programmazione di applicazioni di rete Chiamate a procedura remota (RPC) Architettura delle RPC Problemi di RPC RPC asincrona Implementazione del supporto RPC Definizione del programma RPC Implementazione del programma RPC RPC e socket Caratteristiche di SUNRPC Identificazione di messaggi RPC Livelli di SUNRPC external Data Rapresentation Server: registrazione di una procedura remota Port Mapper Processo di sviluppo Java RMI Stub e Skeleton Architettura Java RMI Passi per l utilizzo di Java RMI Confronto tra SUNRPC e Java RMI Comunicazione orientata ai messaggi Message Passing Interface (MPI) Sistemi a code di messaggi IV

6 Architettura di sistema a code di messaggi Comunicazione orientata agli stream Stream Comunicazione multicast Multicast Strutturato Multicast Non Strutturato Sistemi di Naming Naming semplice Approccio semplice Approcci home-based Approcci gerarchici Naming strutturato Risoluzione dei nomi Naming basato su attributi LDAP Sincronizzazione Modello della computazione Algoritmo di Cristian Algoritmo di Berkeley NTP Tempo in un sistema asincrono Tempo logico Clock Logico Scalare Clock Logico Vettoriale Sistemi Concorrenti e Mutua Esclusione Algoritmo di Dijkstra Algoritmo del panificio di Lamport Mutua esclusione nei sistemi distribuiti Panoramica sugli algoritmi per la mutua esclusione distribuita Algoritmi basati su autorizzazione Algoritmi basati su token Algoritmi basati su quorum Algoritmi di elezione Consistenza e Replicazione Consistenza dei dati Modelli di consistenza data-centrici Raggruppare le operazioni Modelli di consistenza client-centrici Protocolli di distribuzione Posizionamento dei server replica Replica e posizionamento dei contenuti Distribuzione dei contenuti Protocolli di consistenza Protocolli per consistenza continua Protocolli per consistenza sequenziale Protocolli per la consistenza client-centrica Tolleranza ai guasti nei sistemi distribuiti Modelli di failure Ridondanza Resilienza dei processi Modelli di replicazione Gruppi e mascheramento dei guasti V

7 Rilevamento dei fallimenti Comunicazione affidabile Argomenti non trattati VI

8 1 Programmazione di applicazioni di rete 1.1 Applicazioni di rete Le applicazioni forniscono servizi di alto livello utilizzati dagli utenti. Determinano, inoltre, la percezione di qualità del servizio (QoS) che gli utenti hanno della rete sottostante. Tali applicazioni sono dei processi che girano sugli host (sistemi terminali) della rete, tipicamente nello spazio utente. La comunicazione avviene utilizzando i servizi offerti dal sottosistema di comunicazione a scambio di messaggi. La cooperazione può essere implementata secondo vari modelli Modello Client/Server Le applicazioni sono composte da due parti: il client ed il server. Il Client è l applicazione che richiede il servizio (inizia il contatto con il server). Il Server è l applicazione che fornisce il servizio richiesto (attende di essere contattato dal client). L applicazione per identificare in rete un altra applicazione ha bisogno dell indirizzo ip dell host su cui è in esecuzione l altra applicazione ed il numero di porta (consente all host ricevente di determinare a quale applicazione locale deve essere consegnato il messaggio). Abbiamo due tipologie di server: iterativo (o sequenziale): gestisce una richiesta client per volta. ricorsivo (o concorrente): gestisce più richieste client contemporaneamente. In un server ricorsivo, viene creato un nuovo processo/thread di servizio per gestire ciascuna richiesta client. 1.3 Modello peer-to-peer Nel modello p2p non vi è un server sempre attivo, ma coppie arbitrarie di host, chiamati peer (ossia pari) che comunicano direttamente tra loro. Nessuno degli host che prende parte dell architettura p2p deve essere necessariamente sempre attivo. Ciascun peer può ricevere ed inviare richieste e può ricevere ed inviare risposte. Le applicazioni p2p sono molto scalabili, ma possono essere difficili da gestire a causa della loro natura altamente distribuita e decentralizzata. Alcune applicazioni di rete sono organizzate come un ibrido delle architetture Client/Server e p2p. 1.4 Interazione del protocollo di trasporto con le applicazioni Se una porta viene assegnata ad un servizio, nel caso di multitasking/multithreading ci potrebbero essere più processi/thread server attivi per lo stesso servizio. D altro canto, le richieste di un client devono essere consegnate al processo/thread server corretto. In questo caso, si devono usare sia le informazioni del server, sia le informazioni del client per indirizzare i pacchetti. I protocolli di trasporto tcp e udp usano quattro informazioni per identificare una connessione: indirizzo e porta del servizio lato server; indirizzo e porta del servizio lato client. Il client ed il server utilizzano un protocollo di trasporto (tcp o udp) per comunicare. Il software di gestione del protocollo di trasporto si trova all interno del sistema operativo. Per poter comunicare, le due applicazioni devono interagire con i rispettivi sistemi operativi utilizzando un meccaniscmo che svolge il ruolo di ponte (interfaccia) tra sistema operativo ed applicazione di rete: api. 1 Il più diffuso è il modello client-server. 1

9 1.5 Application Programming Interface Standardizza l interazione con il sistema operativo (so). Consente ai processi applicativi di utilizzare protocolli di rete, definendone le funzioni consentite e gli argomenti per ciascuna funzione. Le api di Internet sono i socket. Sono stati inizialmente definiti per i sistemi Unix TM bsd per utilizzare i protocolli tcp/ip sfruttando un estensione del paradigma di I/O di questi sistemi. Nel corso del tempo è divenuto uno standard di riferimento per tutta la programmazione su reti ed è disponibile su vari sistemi operativi. I due tipi di trasporto più famosi sono udp, datagramma a flusso non affidabile, e tcp, flusso di byte con connessione ed affidabile. 1.6 Socket È un interfaccia tra il processo applicativo ed il protocollo di trasporto End-to-End (udp o tcp). Tale interfaccia è locale all host e viene controllata dal sistema operativo. Viene creata dall applicazione che utilizza per inviare/ricevere messaggi a/da un altro processo applicativo (remoto o locale). I socket sono delle api che consentono ai programmatori di gestire le comunicazioni tra processi 2. A differenza degli altri meccanismi di ipc (code di messaggi, fifo/pipe e memoria condivisa), i socket consentono la comunicazione tra processi che possono risiedere su macchine diverse e sono collegati tramite una rete. Insomma costituiscono lo strumento di base per realizzare servizi di rete. I socket consentono ad un programmatore di effettuare trasmissioni tcp ed udp senza curarsi dei dettagli di più basso livello che sono uguali per ogni comunicazione. Quindi un socket è un astrazione del so, viene creato su richiesta dinamicamente, persiste solo durante l esecuzione dell applicazione ed il suo ciclo di vita è simile a quello di un file. Il descrittore del socket è un numero intero, ne esiste uno per ogni socket attivo ed è significativo solo per l applicazione che possiede il socket. Ogni associazione è una quintupla di valori { protocollo, indirizzo locale, porta locale, indirizzo remoto, porta remota}. Tale associazione deve essere completamente specificata affinché la comunicazione abbia luogo. 1 int socket(int domain, int type, int protocol); Figura 1: Dichiarazione socket() Nel listing 1 vediamo la dichiarazione dell api socket. È la prima funzione eseguita sia dal client che dal server e ne definisce un socket. Crea e riserva le risorse necessarie per la gestione di connessioni. Restituisce un intero che va interpretato come un descrittore di file: un identificatore dell entità appena creata. Se la creazione del socket fallisce, viene restituito il valore -1. I parametri della funzione socket() sono: domain: rappresenta la famiglia di protocolli (AF INET, AF UNIX). type: rappresenta il tipo di comunicazione (SOCK STREAM, SOCK DGRAM, SOCK RAW). protocol: rappresenta lo specifico protocollo richiesto. Solitamente si pone a 0 per selezionare il protocollo di default indotto dalla coppia domain e type. Il client utilizzerà il socket direttamente, specificando il descrittore in tutte le funzioni che chiamerà. Mentre il server utilizzerà il socket indirettamente, come se fosse un modello o prototipo per creare altri socket che saranno quelli effettivamente usati. 2 È un meccanismo di Inter-Process Comunication (ipc). 2

10 1.7 Gestione degli errori In caso di successo le funzioni dell api socket restituiscono un valore positivo. Altrimenti, restituiscono un valore negativo (-1) assegnando alla variabile globale errno un valore positivo. Ogni valore di tale varibile identifica un tipo di errore ed il significato dei nomi simbolici che identificano i vari errori è specificato in sys/errno.h. Il valore contenuto in errno si riferisce all ultima chiamata di sistema effettuata. Dopo aver invocato una funzione dell api si deve verificare se il codice di ritorno è negativo, ed in caso di errore leggere immediatamente il valore di errno. Le funzioni strerror() e perror() permettono di riportare in opportuni messaggi la condizione di errore che si è verificata. 1.8 Semi-Associazione: bind() Per inizializzare l indirizzo locale e processo locale lato server usiamo la definizione del listing 2. 1 int bind(int sockfd, const struct sockaddr *addr, socklen_t len); Figura 2: Dichiarazione bind() Tale funzione dice al so a quale processo vanno inviati i dati ricevuti dalla rete, dove: sockfd è il descrittore del socket; addr è il puntatore ad una struct contenente l indirizzo locale; len è la dimensione in byte della struct contenente l indirizzo locale. Nella funzione bind() si può assegnare un indirizzo ip specifico appartenente ad un interfaccia di rete della macchina. Per indicare un indirizzo ip generico ( ) si usa la costante INADDR ANY, mentre per indicare l interfaccia di loopback ( ) si usa la costante INADDR LOOPBACK. 1 struct sockaddr { 2 sa_family_t sa_family; 3 char sa_data[14]; 4 } Figura 3: Struttura degli indirizzi generica 1 struct sockaddr_in { 2 sa_family_t syn_family; 3 in_port_t syn_port; 4 struct in_addr sin_addr; 5 char sin_zero[8]; 6 } Figura 4: Struttura degli indirizzi ipv4 1 struct in_addr { 2 in_addr_t s_addr; 3 } Figura 5: Struttura dati in addr 3

11 1.9 Inizializzare indirizzo ip e numero di porta L indirizzo ip ed il numero di porta in sockaddr in devono essere nel network byte order 3. Esistono funzioni di conversione da rappresentazione testuale/binaria dell indirizzo/numero di porta a valore binario da inserire nella struttura sockaddr in. 1 unsigned short htons( int hostshort); 2 unsigned long htonl( int hostlong); Figura 6: Le funzioni htons() e htonl() Per inizializzare i campi di sockaddr in usiamo le funzioni descritte nel listing 6. Mentre per il riordinamento (da rete a macchina locale) vengono usate ntons() ed ntonl(). Per convertire gli indirizzi ip dalla notazione puntata in formato ascii al network byte order in formato binario usiamo la funzione inet aton(), mentre per fare l esatto contrario usiamo inet ntoa(). Se usiamo indirizzi di tipo ipv6 allora le funzioni saranno inet pton() e inet ntop() rispettivamente. Queste funzioni sono descritte nel listing 7. 1 int inet_aton(const char *src, struct in_addr *dest); 2 char * inet_ntoa( struct in_addr addrptr); 3 int inet_pton(int af, const char *src, void *addr_ptr); 4 char *inet_ntop(int af, const void *addr_ptr, char *dest, size_t len); Figura 7: Le funzioni inet aton(), inet ntoa(), inet pton(), inet ntop() Header file Gli header file utilizzati per queste api sono: sys/socket.h definisce i simboli che iniziano con PF e AF ed i prototipi delle funzioni. netinet/in.h definisce i tipi di dato per rappresentare gli indirizzi Internet. arpa/inet.h definisce i prototipi delle funzioni per manipolare gli indirizzi ip. in più usiamo sys/types.h, netdb.h ed unistd.h. 1 #include <sys/types.h> 2 #include <sys/socket.h> 3 #include <netinet/in.h> struct sockaddr_in sad; 6 int sd; if ((sd = socket(af_inet, SOCK_STREAM, 0)) < 0) { 9 perror(" errore in socket"); 10 exit(-1); 11 } 12 memset((void *)&sad, 0, sizeof(sad)); 13 sad.sin_family = AF_INET; 14 sad. sin_port = htons(1234); 15 sad.sin_addr.s_addr = htonl(inaddr_any); 16 if (bind(sd, (struct sockaddr *)&sad, sizeof(sad)) < 0) { Figura 8: Esempio funzione bind() 3 Ovvero ordinamento Big Endian, il byte più significativo viene prima. 4

12 1.11 La funzione connect() La funzione connect() descritta nel listing 9 permette al client tcp di parire la connessione con un server tcp. 1 int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen); Figura 9: Funzione connect() (client) Questa funzione è bloccante e termina solo quando la connessione viene creata. I parametri della funzione sono: sockfd: è il descrittore del socket; servaddr: rappresenta l indirizzo del server sul quale vogliamo collegarci; addrlen: è la dimensione in byte della struttura con l indirizzo remoto del server. Restituisce 0 in caso di successo e -1 in caso di errore. Se viene generato un errore, la variabile errno può assumere i valori: ETIMEDOUT: è scaduto il timeout del syn; ECONNREFUSED: nessuno in ascolto (rst in risposta a syn); ENETUNREACH: errore di indirizzamento (messaggio icmp di destinazione non raggiungibile) Gestione di errori transitori in connect() Se la chiamata di connect() fallisce, il processo attende per un breve periodo e poi tenta di nuovo la connessione, incrementando progressivamente il ritardo, fino ad un massimo di circa 2 minuti. 1 #include <sys/socket.h> 2 3 # define MAXSLEEP int connect_retry(int sockfd, const struct sockaddr *addr, socklen_t alen) 6 { 7 int nsec; 8 for (nsec=1; nsec <= MAXSLEEP; nsec <<=1) { 9 if (connect (sockfd, addr, alen) == 0) 10 return(0); /* connessione accettata */ 11 /* Ritardo prima di un nuovo tentativo di connessione */ 12 if (nsec <= MAXSLEEP/2) 13 sleep(nsec); 14 } 15 return(-1); 16 } Figura 10: Funzione connect() (client) 1.12 La funzione listen() 1 int listen(int sockfd, int backlog); Figura 11: Funzione listen() (server) La funzione listen(), descritta nel listing 11, mette il socket in ascolto di eventuali connessioni, ossia passa dallo stato closed allo stato listen. 5

13 Inoltre specifica quante connessioni possono essere accettate dal server e messe in attesa di essere servite dalla coda di richieste di connessione (backlog). Le connessioni possono essere accettate o rifiutate dal so senza interrompere il server. Storicamente, nel backlog c erano sia le richieste di accettazione, sia quelle accettate ma non ancora passate al server. In Linux, il backlog identifica solo la lunghezza della coda delle connessioni completate (per prevenire l attacco syn-flood); la lunghezza massima è pari a SOMAXCONN La funzione accept() 1 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); Figura 12: Funzione accept() (server) La funzione accept(), descritta nel listing 12, permette ad un server di prendere dal backlog la prima connessione completa sul socket specificato. Se il backlog è vuoto, il server rimane bloccato sulla chiamata finché non viene accettata una connessione. I parametri della funzione sono: sockfd: che rappresenta il descrittore del socket originale; addr: viene riempito con l indirizzo del client (indirizzo ip + porta); addrlen: viene riempito con la dimensione dell indirizzo del client e prima della chiamata viene inizializzato con la lunghezza della struttura. Tale funzione restituisce -1 in caso di errore oppure un nuovo descrittore di socket creato ed assegnato automaticamente dal so e l indirizzo del client connesso Socket d ascolto e socket connesso Il server utilizza due socket diversi per ogni connessione con un client. Il socket d ascolto (listening socket), creato dalla funzione socket(), viene utilizzato per tutta la vita del processo server. In generale viene usato solo per accettare richieste di connessione e resta per tutto il tempo nello stato listen. Il socket connesso è quello creato dalla funzione accept() e viene usato solo per la connessione e lo scambio di dati con un dato client. Tale socket si trova automaticamente nello stato established. I due socket identificano due connessioni distinte Associazione orientata alla connessione In un flusso di dati orientato alla connessione, l impostazione delle varie componenti dell associazione è effettuata dalle seguenti chiamate di sistema: un socket sulla macchina del server; per ogni client, un socket sulla macchina del server; per ogni client, un socket sulla macchina del client La chiusura di una connessione TCP Il processo che invoca per primo la funzione close(), descritta nel listing 13, avvia la chiusura attiva della connessione. Il descrittore del socket è marcato come chiuso, quindi il processo non può più utilizzare il descrittore ma la connessione non viene chiusa subito, perché il tcp continua ad utilizzare il socket trasmettendo i dati che sono eventualmente nel buffer. 6

14 1 int close(int sockfd); Figura 13: Funzione close() Dall altro capo della connessione, dopo che è stato ricevuto ogni dato eventualmente rimasto in coda, la ricezione del fin viene segnalata dal processo come un eof in lettura. Rilevato l eof, il secondo processo invoca close() sul proprio socket, causando l emissione del secondo fin. Tale funzione restituisce 0 in caso di successo e -1 in caso di errore. Un altra funzione che permette la chiusura di una connessione tcp è la funzione shutdown(), descritta nel listing int shutdown(int sockfd, int how); Figura 14: Funzione shutdown() Anche la funzione shutdown() permette la chiusura asimmetrica. Il parametro how può essere uguale a: SHUT RD che chiude il lato in lettura del socket; SHUT WR che chiude il lato in scrittura del socket; SHUT RDWR che chiude sia il lato in scrittura che quello in lettura del socket. Questa funzione non è uguale a close(). La sequenza di chiusura del tcp viene effettuata immediatamente, indipendentemente dalla presenza di altri riferimenti al socket (ad esempio il descrittore ereditato dai processi figli oppure duplicato tramite dup()). Tramite la funzione close(), la sequenza di chiusura del tcp viene innescata solo quando il numero di riferimenti del socket si annulla Per leggere/scrivere dati Per leggere e scrivere su socket si usano le funzioni read() e write(), descritte nel listing int read(int sockfd, void *buf, size_t count); 2 int write(int sockfd, const void *buf, size_t count); Figura 15: Funzioni read() e write() Non è detto che le funzioni read() e write() terminano avendo sempre letto o scritto il numero di caratteri richiesto, tali funzioni restituiscono il numero di caratteri effettivamente letti o scritti, oppure -1 in caso di errore. Se la funzione read() restituisce 0 la parte remota dell associazione è stata chiusa quindi si ha un eof. Se il processo prova a scrivere su un socket la cui parte remota è stata chiusa, il so invia il segnale SIGPIPE e la chiamata write() restituisce -1 con la variabile errno impostata a EPIPE. In alternativa a write() e read(), si possono usare le funzioni send() e recv(), descritte nel listing int send (int sockfd, const void* buf, size_t len, int flags); 2 int recv(int sockfd, void *buf, size_t len, int flags); Figura 16: Funzioni send() e recv() Se flags=0 allora send() e recv() equivalgono a write() e read(). 7

15 1 # include <errno.h> 2 #include <unistd.h> 3 int readn(int fd, void *buf, size_t n) 4 { 5 size_t nleft; 6 ssize_t nread; 7 char *ptr; 8 9 ptr = buf; 10 nleft = n; 11 while (nleft > 0) { 12 if ( (nread = read(fd, ptr, nleft)) < 0) { 13 if (errno == EINTR) 14 nread = 0; 15 else 16 return -1; 17 } else if (nread == 0) 18 break; 19 nleft -= nread; 20 ptr += nread; 21 } 22 return nleft; 23 } Figura 17: Funzione readn() 1 # include <errno.h> 2 #include <unistd.h> 3 ssize_t writen(int fd, const void *buf, size_t n) 4 { 5 size_t nleft; 6 ssize_t nwritten; 7 const char *ptr; 8 ptr = buf; 9 nleft = n; 10 while (nleft > 0) { 11 if((nwritten = write(fd, ptr, nleft)) <= 0) { 12 if ((nwritten < 0) && (errno == EINTR)) 13 nwritten = 0; 14 else 15 return -1; 16 } 17 nleft -= nwritten; 18 ptr += nwritten; 19 } 20 return nleft; 21 } Figura 18: Funzione writn() 8

16 2 Progettazione Client Server TCP/UDP Per poter progettare un client tcp dobbiamo creare un endpoint, una connessione, poter leggere e scrivere sulla connessione ed infine chiuderla. Per progettare, invece, un server tcp bisogna creare un endpoint e collegarlo ad una porta. Poi ascoltare le connessione su questa porta, accettare le richieste di un client, scrivere e leggere sulla connessione ed infine chiuderla. 2.1 Esempio: DayTime TCP Il client interroga il server per ottenere la data e l ora. Il server ottiene l informazione dal SO e la invia al client. Il client stampa l informazione su stdout. Assumiamo che il server invia la risposta in un unica stringa alfanumerica. Il client legge tale stringa, la visualizza sullo schermo e termina. Sia il client che il server utilizzano una connessione tcp. 1 #include <sys/types.h> 2 #include <sys/socket.h> 3 #include <netinet/in.h> 4 #include <arpa/inet.h> 5 #include <unistd.h> 6 # include <stdio.h> 7 #include <stdlib.h> 8 #include <string.h> 9 # define SERV_PORT # define MAXLINE int main(int argc, char *argv[]) 12 { 13 int sockfd, n; 14 char recvline[ MAXLINE + 1]; 15 struct sockaddr_in servaddr; 16 if (argc!= 2) { 17 fprintf(stderr, "utilizzo: daytime_clienttcp <indirizzo IP server>\n"); 18 exit(-1); 19 } 20 if ((sockfd = socket(af_inet, SOCK_STREAM, 0)) < 0) { 21 perror(" errore in socket"); 22 exit(-1); 23 } 24 memset((void *)&servaddr, 0, sizeof(servaddr)); 25 servaddr. sin_family = AF_INET; 26 servaddr. sin_port = htons( SERV_PORT); 27 if(inet_pton(af_inet, argv[1], &servaddr.sin_addr) <= 0) { 28 perror(" errore in inet_pton"); 29 exit(-1); 30 } 31 if(connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) { 32 perror(" errore in connect"); 33 exit(-1); 34 } 35 while ((n = read(sockfd, recvline, MAXLINE)) > 0) { 36 recvline[n] = 0; 37 if(fputs(recvline, stdout) == EOF) { 38 perror(" errore in fputs"); 39 exit(-1); 40 } 41 } 42 if (n < 0) { 43 perror("errore in read"); 44 exit(-1); 45 } 46 exit(0); 47 } Figura 19: Client tcp daytime 9

17 Per eseguire il client lanciamo il comando: daytime lienttcp <indirizzo IP server> 1 #include <sys/types.h> 2 #include <sys/socket.h> 3 #include <netinet/in.h> 4 #include <arpa/inet.h> 5 #include <unistd.h> 6 # include <stdio.h> 7 #include <stdlib.h> 8 #include <string.h> 9 # include <time.h> 10 # define SERV_PORT # define BACKLOG # define MAXLINE int main(int argc, char *argv[]) 14 { 15 int listensd, connsd; 16 struct sockaddr_in servaddr; 17 char buff[maxline]; 18 time_t ticks; 19 if((listensd = socket(af_inet, SOCK_STREAM, 0)) < 0) { 20 perror(" errore in socket"); 21 exit(-1); 22 } 23 memset((void *)&servaddr, 0, sizeof(servaddr)); 24 servaddr. sin_family = AF_INET; 25 servaddr.sin_addr.s_addr = htonl(inaddr_any); 26 servaddr. sin_port = htons( SERV_PORT); 27 if(bind(listensd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) { 28 perror("errore in bind"); 29 exit(-1); 30 } 31 if(listen(listensd, BACKLOG) < 0 ) { 32 perror(" errore in listen"); 33 exit(-1); 34 } 35 while (1) { 36 if((connsd = accept(listensd, (struct sockaddr *) NULL, NULL)) < 0) { 37 perror(" errore in accept"); 38 exit(-1); 39 } 40 ticks = time(null); 41 snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks)); 42 if(write(connsd, buff, strlen(buff))!= strlen(buff)) { 43 perror(" errore in write"); 44 exit(-1); 45 } 46 if(close(connsd) == -1) { 47 perror(" errore in close"); 48 exit(-1); 49 } 50 } 51 exit(0); 52 } Figura 20: Server tcp daytime Per eseguire il server lanciamo il comando: daytime servertcp 2.2 Comunicazione senza connessione Per creare un socket usiamo la seguente sintassi: 10

18 dove socket(af INET, SOCK DGRAM, IPPROTO UDP); SOCK DGRAM è il tipo di protocollo per datagram; IPPROTO UDP è lo specifico protocollo richiesto (anche 0). Il server invoca bind() per associare al socket l indirizzo locale e la porta locale. Il numero della porta è prestabilito per l applicazione. Il client invoca bind() per associare al socket l indirizzo locale e la porta locale, dove il numero della porta è in qualche modo arbitrario 4. Sia il server che il client hanno così impostato la semi-associazione locale del socket; l impostazione delle altre due componenti dell associazione può essere fatta per ogni pacchetto. 1 int sendto(int sockfd, const void *buf, size_t len, int flags, 2 const struct sockaddr_in * to, socklen_t tolen); Figura 21: Funzione sendto() La funzione sendto(), descritta nel listing 21, restituisce il numero di byte inviati (oppure -1 in caso di errore). La trasmissione non è affidabile, quindi questa funzione non restituisce errore se il pacchetto non raggiunge l host remoto. Abbiamo solo errori locali. I parametri della funzione sono: sockfd: rappresenta il descrittore del socket a cui si fa riferimento; buf: è un puntatore al buffer contenente il pacchetto da inviare; len: è la dimensione in byte del pacchetto da inviare; flags: rappresenta un numero intero usato come maschera binaria per impostare una serie di modalità di funzionamento della comunicazione; to: è l indirizzo IP e la porta della macchina remota che riceverà il pacchetto inviato; tolen: rappresenta la dimensione in byte della struttura sockaddr. 1 int recvfrom(int sockfd, const void *buf, size_t len, int flags, 2 struct sockaddr *from, socklen_t *fromlen); Figura 22: Funzione recvfrom() La funzione recvfrom(), descritta nel listing 22 restituisce il numero di byte ricevuti, oppure -1 in caso di errore. I parametri di questa funzione sono: sockfd: è il descrittore del socket a cui si fa riferimento. len: è la dimensione del buffer (il numero massimo di byte da leggere). Se il pacchetto ricevuto ha dimensione maggiore di len, si ottengono i primi len byte ed il resto del pacchetto viene perso. from: rappresenta il puntatore alla struttura contenente l indirizzo IP e la porta della macchina remota che ha spedito il pacchetto. Viene usato per ottenere l indirizzo del mittente del pacchetto. fromlen: è un putatore alla dimensione in byte della struttura sockaddr. Questa funzione permette di ricevere i pacchetti da qualunque macchina remota e non è possibile rifiutare un pacchetto. Pertanto, per verificare l indirizzo del client, si usa la flag MSG PEEK che non toglie il pacchetto dalla coda del SO. 4 Il numero di porta 0 lascia al SO la scelta della porta. 11

19 2.3 Uso di connect() con i socket UDP La funzione connect() può essere usata anche in applicazioni client di tipo senza connessione per impostare una volta per tutte le due componenti remote dell associazione del client e gestire la presenza di errori asincroni. Usando questa funzione, il client può lasciare nulli gli ultimi campi in sendto() oppure usare la write() o la send(). La funzione connect impedisce la ricezione sul socket di pacchetti non provenienti dalla macchina remota registrata. Ciò nonostante non ha senso usare questa funzione sul server. 2.4 Esempio: daytime UDP 1 #include <sys/types.h> 2 #include <sys/socket.h> 3 #include <arpa/inet.h> 4 #include <unistd.h> 5 # include <stdio.h> 6 #include <stdlib.h> 7 #include <string.h> 8 # define SERV_PORT # define MAXLINE int main(int argc, char *argv[ ]) 11 { 12 int sockfd, n; 13 char recvline[ MAXLINE + 1]; 14 struct sockaddr_in servaddr; 15 if (argc!= 2) { 16 fprintf(stderr, "utilizzo: daytime_clientudp <indirizzo IP server>\n"); 17 exit(1); 18 } 19 if((sockfd = socket(af_inet, SOCK_DGRAM, 0)) < 0) { 20 perror(" errore in socket"); 21 exit(-1); 22 } 23 memset((void *)&servaddr, 0, sizeof(servaddr)); 24 servaddr. sin_family = AF_INET; 25 servaddr. sin_port = htons( SERV_PORT); 26 if(inet_pton(af_inet, argv[1], &servaddr.sin_addr) <= 0) { 27 perror(" errore in inet_pton"); 28 exit(-1); 29 } 30 if(sendto(sockfd,null,0,0,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0) 31 { 32 perror(" errore in sendto"); 33 exit(-1); 34 } 35 n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL); 36 if(n < 0) { 37 perror(" errore in recvfrom"); 38 exit(-1); 39 } 40 if(n > 0) { 41 recvline[n] = 0; 42 if(fputs(recvline, stdout) == EOF) { 43 perror(" errore in fputs"); 44 exit(-1); 45 } 46 } 47 exit(0); 48 } Per eseguire il client lanciamo il comando: Figura 23: Client udp Daytime 12

20 daytime clientudp <indirizzo IP server> 1 #include <sys/types.h> 2 #include <sys/socket.h> 3 #include <arpa/inet.h> 4 #include <unistd.h> 5 # include <stdio.h> 6 #include <stdlib.h> 7 # include <time.h> 8 #include <string.h> 9 # define SERV_PORT # define MAXLINE int main(int argc, char *argv[]) 12 { 13 int sockfd, len; 14 struct sockaddr_in addr; 15 char buff[maxline]; 16 time_t ticks; 17 if((sockfd = socket(af_inet, SOCK_DGRAM, 0)) < 0) { 18 perror(" errore in socket"); 19 exit(-1); 20 } 21 memset((void *)&addr, 0, sizeof(addr)); 22 addr. sin_family = AF_INET; 23 addr.sin_addr.s_addr = htonl(inaddr_any); 24 addr.sin_port = htons(serv_port); 25 if(bind(sockfd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { 26 perror("errore in bind"); 27 exit(-1); 28 } 29 while (1) { 30 if ((recvfrom(sockfd,buff,maxline,0,(struct sockaddr *)&addr,&len)) < 0) 31 { 32 perror(" errore in recvfrom"); 33 exit(-1); 34 } 35 ticks = time(null); 36 snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks)); 37 if(sendto(sockfd,buff,strlen(buff),0,(struct sockaddr *)&addr,sizeof(addr))<0) 38 { 39 perror(" errore in sendto"); 40 exit(-1); 41 } 42 } 43 exit(0); 44 } Per eseguire il server usiamo il comando: daytime serverudp 2.5 Risoluzione dei nomi Figura 24: Server udp Daytime Il resolver è un inseieme di routine fornite con le librerie del C per gestire il servizio di risoluzione di nomi associati a identificativi o servizi relativi alla rete. I nomi di domini, servizi, protocolli e rete sono definiti nei seguenti file di configurazione: /etc/hosts e /etc/resolv.conf. Per determinare l indirizzo IP corrispondente al nome del proprio host usiamo la funzione gethostname(), descritta nel listing 25. Per determinare invece l indirizzo IPv4 corrispondente al nome di dominio di un host tramite DNS usamo la funzione gethostbyname(), descritta nel listing 25. La dichiarazione dei prototipi delle funzioni e degli altri simboli si trova nell header file netdb.h. 13

21 1 int gethostname(char *name, size_t size); 2 struct hostent *gethostbyname(const char *name); 3 struct hostent *gethostbyaddr(const char *addr, int length, int addrtype); Figura 25: Funzioni gethostname(), gethostbyname() 1 struct hostent { 2 char *h_name; 3 char ** h_aliases; 4 int h_addrtype; 5 int h_length; 6 char ** h_addr_list; 7 # define h_addr h_addr_list[0] 8 }; Figura 26: Struttura hostent In caso di errore, la funzione gethostbyname() restituisce NULL ed imposta la variabile globale h errno ad un valore corrispondente all errore. Per conoscere l errore, occorre valutare il valore di h errno oppure usare la funzione herror(). Per determinare il nome di un host corrispondente ad un dato indirizzo IP usiamo la funzione gethostbyaddr(), descritta dal listing 25, dove: length rappresenta la dimensione in byte di addr, ed addrtype è il tipo di indirizzo dell host. 1 #include <sys/types.h> 2 #include <sys/socket.h> 3 #include <netinet/in.h> 4 #include <arpa/inet.h> 5 # include <netdb.h> 6 # include <stdio.h> struct hostent * hp; 9 struct sockaddr_in * sin; 10 if((hp == gethostbyname(argv[1])) == NULL) { 11 herror(" errore in gethostbyname"); 12 exit(1); 13 } 14 memset((void *)&sin, 0, sizeof(sin)); 15 sin.sin_family = hp->h_addrtype; 16 memcpy(&sin.sin_addr, hp->h_addr, hp->h_length); 17 printf("host name %s\n", hp->h_name); 18 printf("ip address %s\n", inet_ntoa(sin.sin_addr)); Figura 27: Esempio: risoluzione dei nomi 2.6 Funzioni rientranti Una funzione è rientrante se può essere interrotta in un punto qualunque della sua esecuzione ed essere chiamata da un altro thread senza che questo comporti nessun problema nell esecuzione della funzione. Tutto ciò rappresenta una tipica problematica della programmazione multi-thread. Una funzione che usa soltato variabili locali è rientrante, mentre una funzione che utilizza la memoria non nello stack non è rientrante. Una funzione che usa un oggetto allocato dinamicamente può essere rientrante o meno. Nella glibc esistono due macro per il compilatore ( REENTRANT e THREAD SAFE) che attivano le versioni rientranti delle funzioni di libreria. 14

22 La funzione gethostbyname() non è una funzione rientrante, infatti, la struttura hostent è allocata in un area statica di memoria, che può essere sovrascritta da due chiamate successive della funzione. Per risolvere questo problema si può allocare una struttura hostent e passarne l indirizzo usando la versione rientrante gethostbyname r(), oppure usare la versione getipnodebyname() che alloca dinamicamente la struttura hostent. In quest ultimo caso bisogna invocare la funzione freehostent() per deallocare la memoria occupata dalla struttura hostent una volta che questa non serve più. 2.7 Nomi dei protocolli I nomi dei protocolli supportati da un host sono memorizzati in un file. Per determinare informazioni corrispondenti al nome di un protocollo usiamo la funzione getprotobyname(), descritta dal listing struct protoent * getprotobyname( const char * name); Figura 28: Funzione getprotobyname() La dichiarazione dei prototipi delle funzioni e degli altri simboli si trovano nell header file netdb.h. 1 struct protoent { 2 char *p_name; 3 char ** p_aliases; 4 int p_proto; 5 }; Figura 29: Struttura protoent 2.8 Funzioni per altri servizi di risoluzione Esistono anche altre funzioni del tipo getxxxbyname e getxxxbyaddr per interrogare gli altri servizi di risoluzione dei nomi. Se XXX = serv allora per risolvere i nomi dei servizi noti definiti in Linux nel file /etc/services usiamo la funzione getservbyname() per risolvere il nome di un servizio nel corrispondente numero di porta ed usa la struttura servent che contiene i relativi dati. Mentre se XXX = net allora risolverà il nome delle reti. 2.9 Formato dei dati La comunicazione deve tener conto della diversa rappresentazione dei dati. Possiamo avere o la rappresentazione Big Endian oppure quella Little Endian. Per risolvere questo problema, la rappresentazione usata dai socket è il network byte order (Big Endian). Per il formato dei dati, i socket utilizzano solo sequenze di caratteri. Se esistono valori numerici allora le singole cifre vengono convertite in caratteri e successivamente si invia la stringa. Il peer conosce il tipo di dati che deve aspettarsi e lo converte nel formato opportuno, in alternativa si definisce una rappresentazione standard dei dati Funzioni getsockname() e getpeername() Le funzioni getsockname() e getpeername(), descritte nel listing 30, forniscono indirizzo ip/porta locali associati al socket (semiassociazione locale) e l indirizzo ip/porta remoti associati al peer (semiassociazione remota) rispettivamente. La funzione getsockname() viene utilizzata dal client per conoscere l indirizzo ip ed il numero di porta locali assegnati dal so. Queste funzioni sono utilizzate dal server per conoscere l indirizzo ip su cui ha ricevuto la richiesta (dopo la funzione accept()), la porta del socket di connessione. Invece, per sapere l indirizzo ip e la porta del client, il server usa la funzione accept(). 15

23 1 int getsockname(int sockfd, struct sockaddr *name, socklen_t *namelen); 2 int getpeername(int sockfd, struct sockaddr *nameddr, socklen_t *namelen); Figura 30: Funzioni getsockname() e getpeername() servaddr_len = sizeof( servaddr); 3 getsockname( listensd, ( struct sockaddr *)& servaddr, & servaddr_len); 4 printf("socket di ascolto: indirizzo IP %s, porta %d\n", 5 (char *)inet_ntoa(servaddr.sin_addr, ntohs(servaddr.sin_port)); 6 for ( ; ; ) { 7 cliaddr_len = sizeof( cliaddr); 8 if((connsd = accept(listensd, (struct sockaddr *)&cliaddr, &cliaddr_len)) < 0) { 9 perror(" errore in accept"); 10 exit(1); 11 } 12 getsockname(connsd, (struct sockaddr *) &servaddr, &servaddr_len); 13 printf("socket di connessione: indirizzo IP %s, porta %d\n", 14 (char *)inet_ntoa(servaddr.sin_addr), ntohs(servaddr.sin_port)); 15 printf("indirizzo del client: indirizzo IP %s, porta %d\n", 16 inet_ntoa(&cliaddr.sin_addr, buff, sizeof(buff)), ntohs(cliaddr.sin_port)); 17 if( (pid = fork()) == 0 ) { Figura 31: Esempio getsockname() (server) if (connect(sockd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) { 3 perror(" errore in connect"); 4 exit(1); 5 } 6 localaddr_len = sizeof( localaddr); 7 getsockname(sockd, (struct sockaddr *) &localaddr, &localaddr_len); 8 printf("indirizzo locale: indirizzo IP %s, porta %d\n", 9 (char *)inet_ntoa(localaddr.sin_addr), ntohs(localaddr.sin_port)); 10 peeraddr_len = sizeof( peeraddr); 11 getpeername(sockd, (struct sockaddr *) &peeraddr, &peeraddr_len); 12 printf("indirizzo del peer: indirizzo IP %s, porta %d\n", 13 (char *)inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port)); Figura 32: Esempio getsockname() (client) 2.11 Le opzioni dei socket L API socket mette a disposizione due funzioni per gestire il comportamento dei socket. 1 int setsockopt(int sockfd, int level, int optname, const void *optval, 2 socklen_t optlen); 3 int getsockopt(int sockfd, int level, int optname, void *optval, 4 socklen_t * optlen); Figura 33: Funzioni setsockopt() e getsockopt() La funzione setsockopt() serve ad impostare le caratteristiche del socket, mentre la funzione getsockopt() serve a conoscere tali caratteristiche. Entrambe le funzioni, descritte nel listing 33, restituiscono 0 in caso di successo e -1 in caso di errore. La funzione setsockopt() possiede i seguenti parametri: sockfd: che è il descrittore del socket a cui si fa riferimento. 16

24 level: rappresenta il livello del protocollo (SOL SOCKET, SOL TCP). optval: è un puntatore ad un area di memoria contenente i dati che specificano il valore dell opzione da impostare per il socket a cui si fa riferimento. optlen: rappresenta la dimensione in byte dei dati puntati da optval Alcune opzioni generiche Analizziamo alcune opzioni generiche da usare come valore per optname: SO KEEPALIVE, serve a controllare l attività della connessione (in particolare per verificare la persistenza della connessione), SO RCVTIMEO, imposta un timeout in ricezione (sulle operazioni in lettura di un socket), SO SNDTIMEO, imposta un timeout in trasmissione (sulle operazioni in scrittura di un socket), SO REUSEADDR, riutilizza un indirizzo locale; modifica il comportamento della funzione bind() che fallisce nel caso in cui l indirizzo locale sia già in uso da parte di un altro socket int reuse = 1; 3 if((listensd = socket(af_inet, SOCK_STREAM, 0)) < 0) { 4 perror(" errore creazione socket"); 5 exit(1); 6 } 7 if(setsockopt(listensd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)) < 0) { 8 perror(" errore setsockopt"); 9 exit(1); 10 } 11 if(bind(listensd, (struct sockaddr *)&sad, sizeof(sad)) < 0) { 12 perror(" errore bind"); 13 exit(1); 14 } Figura 34: Esempio opzione SO REUSEADDR 2.12 Server TCP iterativo (o sequenziale) Questo server gestisce una connessione per volta, mentre è impegnato a gestire la connessione con un determinato client, possono arrivare altre richieste di connessione. Il so stabilisce le connessioni con i client; tuttavia queste rimangono in attesa di servizio nella coda di backlog finché il server non è libero. È il più semplice da progettare, implementare e mantenere, ma è il meno diffuso. È adatto in situazioni in cui il numero di client da gestire è limitato, il tempo di servizio per un singolo servizio è limitato. 1 int listensd, connsd; 2 listensd = socket(af_inet, SOCK_STREAM, 0); 3 bind(listensd,...); 4 listen(listensd,...); 5 for (; ;) { 6 connsd = accept(listensd,...); 7 do_it(connsd); 8 close(connsd); 9 } Figura 35: Struttura di un server tcp iterativo 17

25 Esempio: contatore accessi Questo esempio di applicazione client/server è orientato alla connessione. Il server, di tipo iterativo, conta il numero di client che accedono al suo servizio, mentre il client contatta il server per conoscere tale numero. Viene stampato un messaggio ASCII. Il server crea il socket di ascolto e si pone in attesa. Il client apre la connessione con il server. Fin quando non riceve il carattere di EOF, allora il client stampa i caratteri ricevuti. Il server, con un ciclo infinito, accetta una nuova connessione, usa i socket di connessione, incrementa il contatore, invia il messaggio ed infine chiude il socket di connessione. Una volta che il client finisce di ricevere la stringa, chiude la connessione ed esce. Per quanto riguarda i socket, il client chiude il socket dopo l uso, mentre il server non chiude mai il socket d ascolto, ma chiude il socket di connessione dopo aver risposto al client Server TCP ricorsivo (o concorrente) Il server tcp ricorsivo gestisce più client (connessioni) nello stesso istante. Utilizza una copia del processo/thread di se stesso per gestire ogni connessione. I processi server padre e figlio sono eseguiti contemporaneamente sulla macchina server. Il processo figlio gestisce la specifica connessione con un dato client, mentre il processo padre può accettare la connessione con un altro client, assegnandola ad un altro processo figlio per la gestione. Il numero massimo di processi figli che possono essere generati dipende dal so. Nel listing 38, nessuna delle due chiamate a close() causa l innesco della sequenza di chiusura della connessione tcp perché il numero di riferimenti al descrittore non si è annullato La funzione fork() Questa funzione, descritta dal listing 39, permette di creare un nuovo processo figlio. Il processo figlio è una copia esatta del processo padre e riceve una copia dei segmenti testo, dati e stack del padre. Esegue esattamente lo stesso codice del padre, ma dato che la memoria è copiata il padre ed il figlio vedono valori diversi delle variabili. In caso di successo restituisce un risultato sia al padre che al figlio, ossia al padre restituisce il pid (process ID) del figlio, mentre il figlio restituisce Server echo ricorsivo Il server replica un messaggio inviato dal client. Il client legge una riga di testo dallo standard input e la invia al server. Il server legge la riga di testo dal socket e la rimanda al client. Il client a sua volta legge la riga di testo dal socket e la invia allo standard output Analisi applicazione echo Il comando netstat permette di ottenere informazioni sullo stato delle connessioni instaurate. Qui di seguito sono descritte alcune opzioni di questo comando: -a serve a visualizzare anche lo stato dei socket non attivi; -Ainet specifica la famiglia di indirizzi Internet; -n visualizza gli indirizzi numerici, invece di quelli simbolici, degli host e delle porte. Il comando ps (process state) permette di ottenere le informazioni sullo stato del processo. Qui di seguito sono descritte alcune opzioni di questo comando: l stampa in formato lungo; w fornisce un output largo. 18

26 1 #include <sys/types.h> 2 #include <sys/socket.h> 3 #include <netinet/in.h> 4 #include <arpa/inet.h> 5 # include <netdb.h> 6 # include <stdio.h> 7 #include <string.h> 8 #include <stdlib.h> 9 #include <unistd.h> 10 # define PROTOPORT int main(int argc, char **argv) 12 { 13 struct hostent * ptrh; 14 struct protoent * ptrp; 15 struct sockaddr_in sad; 16 int sd; 17 int port; 18 char *host; 19 int n; 20 char buf[1000]; 21 char localhost[] = "localhost"; 22 memset((void *)&sad, 0, sizeof(sad)); 23 sad.sin_family = AF_INET; 24 port = (argc > 2)? atoi(argv[2]) : PROTOPORT; 25 if(port > 0) 26 sad.sin_port = htons(port); 27 else { 28 fprintf(stderr,"bad port number %s\n",argv[2]); 29 exit(1); 30 } 31 host = (argc > 1)? argv[1] : localhost; 32 ptrh = gethostbyname( host); 33 if(ptrh == NULL ) { 34 herror(" gethostbyname"); 35 exit(1); 36 } 37 memcpy(&sad.sin_addr, ptrh->h_addr, ptrh->h_length); 38 if((ptrp = getprotobyname("tcp")) == NULL) { 39 fprintf(stderr, "cannot map \"tcp\" to protocol number"); 40 exit(1); 41 } 42 if((sd = socket(af_inet, SOCK_STREAM, ptrp->p_proto)) < 0) { 43 perror(" socket creation failed"); 44 exit(1); 45 } 46 if(connect(sd, (struct sockaddr *)&sad, sizeof(sad)) < 0) { 47 perror(" connect failed"); 48 exit(1); 49 } 50 n = recv(sd, buf, sizeof(buf), 0); 51 while (n > 0) { 52 write(1, buf, n); 53 n = recv(sd, buf, sizeof(buf), 0); 54 } 55 close(sd); 56 exit(0); 57 } Figura 36: Client tcp count 2.16 I segnali I segnali sono interruzioni software inviate ad un processo. Questi permettono di notificare ad un processo l occorrenza di qualche evento asincrono. Sono inviati dal kernel o da un processo e vengono usati dal kernel per notificare situazioni eccezzonali oppure per notificare eventi. Sono anche usati come forma elementare di ipc. 19

27 1 #include <sys/types.h> 2 #include <sys/socket.h> 3 #include <netinet/in.h> 4 # include <netdb.h> 5 # include <stdio.h> 6 #include <stdlib.h> 7 #include <string.h> 8 #include <unistd.h> 9 # define PROTOPORT # define BACKLOG int visits = 0; 12 int main(int argc, char **argv) 13 { 14 struct protoent * ptrp; 15 struct sockaddr_in sad; 16 struct sockaddr_in cad; 17 int listensd, connsd; 18 int port; 19 int alen; 20 char buf[1000]; 21 memset((void *)&sad, 0, sizeof(sad)); 22 sad.sin_family = AF_INET; 23 sad.sin_addr.s_addr = htonl(inaddr_any); 24 port = (argc > 1)? atoi(argv[1]) : PROTOPORT; 25 if (port > 0) sad.sin_port = htons(port); 26 else { 27 fprintf(stderr,"bad port number %s\n",argv[1]); 28 exit(1); 29 } 30 if((ptrp = getprotobyname("tcp")) == NULL) { 31 fprintf(stderr, "cannot map \"tcp\" to protocol number"); 32 exit(1); 33 } 34 if((listensd = socket(af_inet, SOCK_STREAM, ptrp->p_proto)) < 0) { 35 perror(" socket creation failed"); 36 exit(1); 37 } 38 if(bind(listensd, (struct sockaddr *)&sad, sizeof(sad)) < 0) { 39 perror(" bind failed"); 40 exit(1); 41 } 42 if (listen(listensd, BACKLOG) < 0) { 43 perror(" listen failed"); 44 exit(1); 45 } 46 while (1) { 47 alen = sizeof(cad); 48 if((connsd=accept(listensd, (struct sockaddr *)&cad, &alen)) < 0) { 49 perror(" accept failed"); 50 exit(1); 51 } 52 visits ++; 53 snprintf(buf, sizeof(buf), "This server has been contacted %d time%s\n", 54 visits, visits==1?".":"s."); 55 if(write(connsd, buf, strlen(buf))!= strlen(buf)) { 56 perror("error in write"); 57 exit(1); 58 } 59 close( connsd); 60 } 61 } Figura 37: Server tcp count Ogni segnale ha un nome, che inizia con SIG, ad esempio: SIGCHLD inviato dal so al processo padre quando un processo figlio è terminato o fermato. 20

28 1 int listensd, connsd; 2 pid_t pid; 3 listensd = socket(af_inet, SOCK_STREAM, 0); 4 bind(listensd,...); 5 listen(listensd,...); 6 for (; ;) { 7 connsd = accept(listensd,...); 8 if((pid = fork()) == 0) { 9 close( listensd); 10 do_it( connsd); 11 close( connsd); 12 exit(0); 13 } 14 close( connsd); 15 } Figura 38: Struttura di un server tcp ricorsivo 1 pid_t fork(void); Figura 39: Funzione fork() SIGALRM generato quando scade il timer impostato con la funzione alarm(). SIGKILL per terminare immediatamente il processo. SIGSTOP per fermare il processo. SIGUSR1 e SIGUSR2 sono a disposizione dell utente per implementare una forma di comunicazione tra processi Gestione dei segnali Un processo può decidere quali segnali gestire, ovvero le notifiche di segnali che accetta. Per ogni segnale da gestire, deve essere definita un apposita funzione di gestione (signal handler). Il segnale viene consegnato al processo quando viene eseguita l azione per esso prevista. Fin quando la generazione del segnale e la sua consegna non è ultimata, il segnale rimane pendente. Ogni segnale ha un gestore (handler) di default. Alcuni segnali non possono essere ignorati e vengono gestiti sempre (SIGKILL e SIGSTOP). Altri segnali vengono ignorati per default (SIGCHLD). Per tutti i segnali non aventi un azione specificata che è fissa, il processo può decidere di ignorare, catturare oppure accettare l azione di default propria del segnale. La scelta riguardante la gestione del segnale può essere specificata mediante le funzioni signal() e sigaction() Segnale SIGCHLD Quando un processo termina (evento asincrono) il kernel manda un segnale SIGCHLD al padre ed il figlio diventa zombie. Questo viene mantenuto dal so per consentire al padre di controllare il valore di uscita del processo e l utilizzo delle risorse del figlio. Per default, il padre ignora il segnale SIGCHLD ed il figlio rimane zombie finché il padre non termina. Per evitare di riempire di zombie la tabella dei processi bisogna fornire un handler per SIGCHLD. Il processo zombie viene rimosso quando il processo padre chiama le funzioni wait() o waitpid(). Queste due funzioni sono definite nell header file sys/wait.h. La funzione wait() sospende il processo corrente finché non termina un qualunque processo figlio, mentre la funzione waitpid() è una funzione non bloccante, se è stata utilizzata l opzione WNOHANG, e permette di specificare quale processo attendere. Chiamata all interno di un cliclo, consente di catturare tutti i segnali. 21

29 La funzione waitpid() permette di scegliere quale figlio attendere sulla base del valore dell argomento pid. Quando un figlio termina e lancia il segnale SIGCHLD, waitpid() lo cattura e restituisce il pid; il processo figlio può essere rimosso Gestione SIGALRM Per evitare che un client udp o un server udp rimangono indefinitamente bloccati su recvfrom() si può usare il segnale di allarme SIGALARM. È il segnale del timer della funzione alarm(), descritta nel listing 44. La funzione alarm() predispone l invio di SIGALARM dopo seconds secondi, calcolati sul tempo reale trascorso (il clock time). Restituisce il numero di secondi rimanenti all invio dell allarme programmato in precedenza. Se impostiamo seconds a 0 allora si cancella la programmazione precedente del timer Progettazione di applicazioni di rete robuste Nel progettare applicazioni di rete robuste si deve tener conto di varie situazioni anomale che si potrebbero verificare 5. Nell applicazione echo abbiamo due situazioni critiche: la terminazione precoce della connessione effettuata dal client (invio rst) prima che il server abbia chiamato la funzione accept(); e la terminazione precoce del server, ad esempio del processo figlio per un errore fatale: il server non ha il tempo di mandare nessun messaggio al client Multiplexing dei socket 2.20 Funzioni bloccanti e soluzioni La funzione accept() e le funzioni per la gestione dell I/O sono bloccanti. Un server ricorsivo tradizionale si blocca sulla accept() aspettando una connessione. Quando arriva la connessione il server chiama fork(), il processo figlio gestisce la connessione ed il processo padre si mette in attesa di una nuova richiesta. Per ovviare questo problema, si possono usare le opzioni dei socket per impostare un timeout, oppure utilizzare un socket non bloccante tramite la funzione fcntl(), descritta nel listing 45. In alternativa, si usa la funzione select(), descritta nel listing 46, che permette di esaminare più canali di I/O contemporaneamente e realizzare quindi il multiplexing dell I/O. Nel caso del server, invece di avere un processo figlio per ogni richiesta, c è un solo processo che effettua il multiplexing tra le richieste, servendo ciascuna richiesta il più possibile. Il vantaggio principale consiste nel gestire tutte le richieste tramite un singolo processo, quindi non abbiamo bisogno di utilizzare memoria condivisa e le primitive di sincronizzazione tra processi. Ma in questo modo il server non può agire come se ci fosse un unico client. Nel caso del client permette di gestire più input simultaneamente La funzione select() Per poter utilizzare la funzione select() dobbiamo usare i seguenti header file: sys/time.h, sys/types.h, unistd.h. Permette, quindi, di controllare contemporaneamente lo stato di uno o più descrittori degli insiemi specificati. Si blocca fin quando non vi è attività (in lettura o scrittura) su un descrittore appartenente ad un dato insieme di descrittori, oppure non viene generata un eccezione, o non scade un timeout. Questa funzione restituisce -1 in caso di errore, 0 se il timeout è scaduto, altrimenti il numero totale dei descrittori pronti. I parametri della funzione select() sono tre insiemi di descrittori da controllare: readfds (operazioni in lettura), writefds (operazioni in scrittura), exceptfds (eccezioni). 5 La rete è inaffidabile!! 22

30 Questi tre insiemi sono puntatori di variabili di tipo fd set, ossia una bit mask implementata con un array di interi. I restanti due parametri sono: numfds: rappresenta il numero massimo di descrittori controllati da select(). Se maxd è il massimo descrittore usato allora numfds = maxd + 1. Può assumere il valore costante FD SETSIZE. timeout: specifica il valore massimo che la funzione select() attende per individuare un descrittore pronto. Se impostato a NULL si blocca indefinitamente fino a quando è pronto un descrittore. Se invece è impostato a zero non si attende affatto, quindi si effettua un polling dei descrittori senza bloccare. Se è diverso da zero si attende il tempo specificato. In questo caso la select() ritorna se è pronto uno (o più) dei dei descrittori specificati (restituisce un numero positivo) oppure se è scaduto il timeout (restituisce 0) Operazioni sugli insiemi di descrittori Le macro utilizzate per manipolare gli insiemi di descrittori sono descritte nel listing 47, dove: FD ZERO inizializza l insieme dei descrittori con l insieme vuoto. FD SET aggiunge all insieme di descrittori mettendo ad 1 il bit relativo a fd. FD CLR rimuove fd dall insieme di descrittori mettendo a 0 il bit relativo a fd. FD ISSET controlla se fd appartiene all insieme di descrittori, verificando il bit relativo a fd è pari a 1 (restituisce 0 in caso negativo, un valore diverso da 0 in caso affermativo). Descrittori pronti in lettura La funzione select() rileva i descrittori in lettura, scrittura ed eccezione. Un descrittore è pronto in lettura nei seguenti casi: nel buffer di ricezione del socket sono arrivati dati in quantità sufficiente; per il dato in lettura è stata chiusa la connessione; si è verificato un errore sul socket; se un socket è nella fase di listening e ci sono delle connessioni completate, è possibile controllare se c è una nuova connessione completata ponendo nel socket d ascolto nell insieme readfds. Descrittori pronti in scrittura Un descrittore è pronto in scrittura nei seguenti casi: nel buffer di invio del socket è disponibile uno spazio in quantità sufficiente ed il socket è già connesso (tcp) oppure non necessita di connessione (udp); per il lato in scrittura è stata chiusa una connessione (dall operazione di scrittura viene generato il segnale SIGPIPE); si è verificato un errore nel socket Multiplexing dell I/O nel server Il multiplexing dell I/O può essere usato sul server per ascoltare su più socket contemporaneamente usando un unico processo. La struttura generale di un server che usa select() riempie strutture fd set con i descrittori dai quali si intende leggere e scrivere. Chiama select() ed attende finché non avviene qualcosa. Quando la select() si sblocca, bisogna controllare se uno dei descrittori ha causato il ritorno. In questo caso il descrittore viene gestito in base al servizio offerto dal server. Questo procedimento viene ripetuto all infinito. 23

31 Client tcp echo con select() Il client deve controllare due diversi descrittori in lettura: lo standard input, da cui leggere il testo da inviare al server ed il socket connesso con il server, su cui scriverà il testo e dal quale riceverà la risposta. L implementazione con I/O multiplexing consente al client di accorgersi di errori sulla connessione mentre è in attesa di dati immessi dall utente sullo standard input Il demone inetd Il demone inetd (Internet super-server) è un programma sempre in esecuzione che attende, mediante la funzione select(), messaggi su un insieme specifico di porte. Quando arriva un messaggio, inetd accetta la connessione (se necessario) ed effettua il fork di un processo figlio che esegue il programma server corrispondente al servizio richiesto. Le porte ed i programmi corrispondenti sono specificati nel file /etc/inetd.conf Una tipica riga del file di configurazione del file inetd.conf ha il seguente formato: service style protocol wait username program arguments 24

32 1 #include <sys/types.h> 2 #include <sys/socket.h> 3 #include <netinet/in.h> 4 #include <arpa/inet.h> 5 # include <stdio.h> 6 #include <stdlib.h> 7 #include <unistd.h> 8 #include <strings.h> 9 # include <time.h> 10 # define SERV_PORT # define BACKLOG # define MAXLINE int main(int argc, char **argv) 15 { 16 pid_t pid; 17 int listensd, connsd; 18 struct sockaddr_in servaddr, cliaddr; 19 int len; 20 if((listensd = socket(af_inet, SOCK_STREAM, 0)) < 0) { 21 perror(" errore in socket"); 22 exit(1); 23 } 24 memset((char *)&servaddr, 0, sizeof(servaddr)); 25 servaddr. sin_family = AF_INET; 26 servaddr.sin_addr.s_addr = htonl(inaddr_any); 27 servaddr. sin_port = htons( SERV_PORT); 28 if((bind(listensd, (struct sockaddr *)&servaddr, sizeof(servaddr))) < 0) { 29 perror("errore in bind"); 30 exit(1); 31 } 32 if(listen(listensd, BACKLOG) < 0 ) { 33 perror(" errore in listen"); 34 exit(1); 35 } 36 for ( ; ; ) { 37 len = sizeof(cliaddr); 38 if((connsd = accept(listensd, (struct sockaddr *)&cliaddr, &len)) < 0) { 39 perror(" errore in accept"); 40 exit(1); 41 } 42 if((pid = fork()) == 0) { 43 if(close(listensd) == -1) { 44 perror(" errore in close"); 45 exit(1); 46 } 47 printf("%s:%d connesso\n", inet_ntoa(cliaddr.sin_addr), 48 ntohs( cliaddr. sin_port)); 49 str_srv_echo( connsd); 50 if(close(connsd) == -1) { 51 perror(" errore in close"); 52 exit(1); 53 } 54 exit(0); 55 } 56 if(close(connsd) == -1) { 57 perror(" errore in close"); 58 exit(1); 59 } 60 } 61 } 62 void str_srv_echo( int sockd) 63 { 64 int nread; 65 char line[maxline]; 66 for( ; ; ) { 67 if((nread = readline(sockd, line, MAXLINE)) == 0) 68 return; 69 if(writen(sockd, line, nread)) { 70 fprintf(stderr, "errore in write"); 71 exit(1); 72 } 73 } 74 } 25 Figura 40: Esempio: Server echo

33 1 #include <sys/types.h> 2 #include <sys/socket.h> 3 #include <netinet/in.h> 4 #include <arpa/inet.h> 5 # include <stdio.h> 6 #include <stdlib.h> 7 #include <unistd.h> 8 #include <strings.h> 9 # include <time.h> 10 # define SERV_PORT # define BACKLOG # define MAXLINE int main(int argc, char **argv) 14 { 15 int sockfd; 16 struct sockaddr_in servaddr; 17 if(argc!= 2) { 18 fprintf(stderr, "utilizzo: echo_client <indirizzo IP server>\n"); 19 exit(1); 20 } 21 if((sockfd = socket(af_inet, SOCK_STREAM, 0)) < 0) { 22 perror(" errore in socket"); 23 exit(1); 24 } 25 memset((void *)&servaddr, 0, sizeof(servaddr)); 26 servaddr. sin_family = AF_INET; 27 servaddr. sin_port = htons( SERV_PORT); 28 if(inet_pton(af_inet, argv[1], &servaddr.sin_addr) <= 0) { 29 fprintf(stderr, "errore in inet_pton per %s", argv[1]); 30 exit(1); 31 } 32 if(connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) { 33 perror(" errore in connect"); 34 exit(1); 35 } 36 str_cli_echo( stdin, sockfd); 37 close( sockfd); 38 exit(0); 39 } 40 void str_cli_echo(file *fd, int sockd) 41 { 42 char sendline[maxline], recvline[maxline]; 43 int n; 44 while(fgets(sendline, MAXLINE, fd)!= NULL) { 45 if((n = writen(sockd, sendline, strlen(sendline))) < 0) { 46 perror(" errore in write"); 47 exit(1); 48 } 49 if((n = readline(sockd, recvline, MAXLINE)) < 0) { 50 fprintf(stderr, "errore in readline"); 51 exit(1); 52 } 53 fputs(recvline, stdout); 54 } 55 } Figura 41: Esempio: Client echo 26

34 1 #include <signal.h> 2 #include <sys/wait.h> 3 void sig_chld_handler( int signum) 4 { 5 int status; 6 pid_t pid; 7 8 while((pid = waitpid(wait_any, &status, WNOHANG)) > 0) 9 printf ("child %d terminato\n", pid); 10 return; 11 } if((listensd = socket(af_inet, SOCK_STREAM, 0)) < 0) { 14 fprintf(stderr, "errore in socket"); 15 exit(1); 16 } if((bind(listensd, (struct sockaddr *)&servaddr, sizeof(servaddr))) < 0) { 19 fprintf(stderr, "errore in bind"); 20 exit(1); 21 } 22 if(listen(listensd, QLEN) < 0) { 23 fprintf(stderr, "errore in listen"); 24 exit(1); 25 } 26 if(signal(sigchld, sig_chld_handler) == SIG_ERR) { 27 fprintf(stderr, "errore in signal"); 28 exit(1); 29 } Figura 42: Handler per SIGCHLD 1 unsigned int alarm( unsigned int seconds); Figura 43: Funzione alarm() 27

35 1 # define TIMEOUT 20 2 void sig_alrm_handler( int signo) 3 { 4 } int main(int argc, char *argv[]) 7 { struct sigaction sa; sa. sa_handler = sig_alrm_handler; 12 sa. sa_flags = 0; 13 sigemptyset(& sa. sa_mask); 14 if (sigaction(sigalrm, &sa, NULL) < 0) { 15 fprintf(stderr, "errore in sigaction"); 16 exit(1); 17 } 18 if ((sockfd = socket(af_inet, SOCK_DGRAM, 0)) < 0) { 19 fprintf(stderr, "errore in socket"); 20 exit(1); 21 } if(sendto(sockfd,null,0,0,(struct sockaddr *)&servaddr, 24 sizeof(servaddr)) < 0) { 25 fprintf(stderr, "errore in sendto"); 26 exit(1); 27 } 28 alarm( TIMEOUT); 29 n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL); 30 if(n < 0) { 31 if(errno!= EINTR) alarm(0); 32 fprintf(stderr, "errore in recvfrom"); 33 exit(1); 34 } 35 alarm(0); Figura 44: Client udp daytime con SIGALRM 1 int fcntl(sockfd, F_SETFL, O_NONBLOCK); Figura 45: La funzione fcntl() 1 int select (int numfds, fd_set *readfds, fd_set *writefds, 2 fd_set * exceptfds, struct timeval * timeout); Figura 46: La funzione select() 1 void FD_SET(int fd, fd_set *set); 2 void FD_CLR(int fd, fd_set *set); 3 int FD_ISSET(int fd, fd_set *set); Figura 47: Le macro della funzione select() 28

36 1 void str_cli_echo_sel(file *fd, int sockfd) 2 { 3 int maxd, n; 4 fd_set rset; 5 char sendline[maxline], recvline[maxline]; 6 FD_ZERO(& rset); 7 for ( ; ; ) { 8 FD_SET(fileno(fd), &rset); 9 FD_SET(sockfd, &rset); 10 maxd = (fileno(fd) < sockfd)? (sockfd + 1): (fileno(fd) + 1); 11 if (select(maxd, &rset, NULL, NULL, NULL) < 0 ) { 12 perror(" errore in select"); 13 exit(1); 14 } 15 if (FD_ISSET(fileno(fd), &rset)) { 16 if (fgets(sendline, MAXLINE, fd) == NULL) 17 return; 18 if ((writen(sockfd, sendline, strlen(sendline))) < 0) { 19 fprintf(stderr, "errore in write"); 20 exit(1); 21 } 22 } 23 if (FD_ISSET(sockfd, &rset)) { 24 if ((n = readline(sockfd, recvline, MAXLINE)) < 0) { 25 fprintf(stderr, "errore in lettura"); 26 exit(1); 27 } 28 if (n == 0) { 29 fprintf( stdout, " str_cli_echo_sel: il server ha chiuso la connessione"); 30 return; 31 } 32 recvline[n] = 0; 33 if (fputs(recvline, stdout) == EOF) { 34 perror(" errore in scrittura su stdout"); 35 exit(1); 36 } 37 } 38 } 39 } Figura 48: Client tcp echo con select() 29

37 1 #include "basic.h" 2 #include "echo_io.h" 3 int main(int argc, char **argv) 4 { 5 int listensd, connsd, socksd; 6 int i, maxi, maxd; 7 int ready, client[fd_setsize]; 8 char buff[maxline]; 9 fd_set rset, allset; 10 ssize_t n; 11 struct sockaddr_in servaddr, cliaddr; 12 int n; 13 if((listensd = socket(af_inet, SOCK_STREAM, 0)) < 0) { 14 perror(" errore in socket"); 15 exit(1); 16 } 17 memset((void *)&servaddr, 0, sizeof(servaddr)); 18 servaddr. sin_family = AF_INET; 19 servaddr.sin_addr.s_addr = htonl(inaddr_any); 20 servaddr. sin_port = htons( SERV_PORT); 21 if((bind(listensd, (struct sockaddr *)&servaddr, sizeof(servaddr))) < 0) { 22 perror("errore in bind"); 23 exit(1); 24 } 25 if(listen(listensd, QLEN) < 0 ) { 26 perror(" errore in listen"); 27 exit(1); 28 } 29 maxd = listensd; 30 maxi = -1; 31 for(i = 0; i < FD_SETSIZE; i++) 32 client[i] = -1; 33 FD_ZERO(& allset); 34 FD_SET(listensd, &allset); 35 for( ; ; ) { 36 rset = allset; 37 if((ready = select(maxd+1, &rset, NULL, NULL, NULL)) < 0) { 38 perror(" errore in select"); 39 exit(1); 40 } 41 if(fd_isset(listensd, &rset)) { 42 len = sizeof(cliaddr); 43 if((connsd = accept(listensd, (struct sockaddr *)&cliaddr, &len)) < 0) { 44 perror(" errore in accept"); 45 exit(1); 46 } 47 for(i=0; i<fd_setsize; i++) 48 if (client[i] < 0) { 49 client[i] = connsd; 50 break; 51 } 52 if(i == FD_SETSIZE) { 53 fprintf(stderr, "errore in accept"); 54 exit(1); 55 } 56 FD_SET(connsd, &allset); 57 if(connsd > maxd) maxd = connsd; 58 if(i > maxi) maxi = i; 59 if(--ready <= 0) 60 continue; 61 } Figura 49: Server tcp echo con select() - parte 1 30

38 1 for(i = 0; i <= maxi; i++) { 2 if((socksd = client[i]) < 0 ) 3 continue; 4 if(fd_isset(socksd, &rset)) { 5 if((n = readline(socksd, buff, MAXLINE)) == 0) { 6 if(close(socksd) == -1) { 7 perror("errore in close"); 8 exit(1); 9 } 10 FD_CLR(socksd, &allset); 11 client[i] = -1; 12 } else if(writen(socksd, buff, n) < 0 ) { 13 fprintf(stderr, "errore in write"); 14 exit(1); 15 } 16 if(--ready <= 0) break; 17 } 18 } 19 } 20 } Figura 50: Server tcp echo con select() - parte 2 31

39 3 World Wide WEB: introduzione e componenti I motivi alla base del successo del WEB consistono nel: digitalizzare l informazione; trasportare l informazione ovunque, in tempi rapidissimi ed a costi bassissimi; diffusione dei personal computer a basso costo; semplicità di utilizzo e trasparenza dell allocazione delle risorse. Il WEB è un sistema ipermediale, distributito globalmente che supporta accessi interattivi a risorse e servizi. Ipermediale perché esistono diverse forme di rappresentazione delle risorse tra loro collegate, mentre distribuito globalmente perché le risorse distribuite e scalate sull intera Internet. Il WEB è un sistema aperto, quindi può essere esteso ed implementato con nuove modalità senza alterare le sue funzionaltità esistenti. Il suo funzionamento è basato su standard di comunicazione e di documenti che sono pubblicamente disponibili ed ampiamente implementati. L aspetto più interessante per gli utenti è che a differenza di altri mezzi informativi, il WEB è on demand e rappresenta un applicazione dell infrastruttura di Internet. 3.1 La filosofia e le attività del W3C Le attività del W3C si articolano in vari domini: architettura, interazione, tecnologia e società, WEB ubiquo, accessibilità. La filosofia del W3C è guidata dal suo direttore. Il ruolo del W3C consiste in una visione del WEB come spazio universale dell informazione. Il design ne specifica la coerenza nel disegno dell architettura. La standardizzazione è un azione legislativa che influisce sull evoluzione. I principi del W3C sono: interoperabilità (compatibilità fra tutti i tipi di software e di hardware), evolubilità (cambiamenti graduali), decentralizzazione (struttura indipendente da autorità centrali). Gli obiettivi del W3C garantiscono un accesso universale alla rete, autenticità dei dati, sicurezza, affidabilità, interpretabilità dei dati da parte delle macchine. 3.2 Ingredienti del WEB L informazione è digitalizzata e l architettura è di tipo client/server. I meccanismi di comunicazione e naming delle risorse (a livello di nodi) di Internet sono basati sul tcp/ip e Domain Name System (dns). Le tre principali componenti tecnologiche standard alla base del WEB sono: uri (meccanismo di naming universale per identificare le risorse), html (linguaggio di markup ipertestuale) e http (protocollo per il trasferimento delle risorse). 3.3 WEB client e WEB server I WEB browser sono i client usati dagli utenti per il WEB. Guida l interazione client/server nei confronti di un server per volta. Permette di visualizzare la pagina WEB richiesta dall utente; fornisce molte caratteristiche per la navigazione e configurazione. Implementa il client nel protocollo http. Il WEB server fornisce su richiesta risorse WEB memorizzate su disco o generate dinamicamente. Implementa il server nel protocollo http. 32

40 3.4 Meccanismi di naming delle risorse L insieme di tutti i meccanismi standard di naming delle risorse (fisiche o astratte) è detto Uniform Resource Identifier (uri). Le risorse sono considerate accessibili tramite l utilizzo di protocolli esistenti, inventati appositamente o ancora da inventare. Gli uri si orientano a risolvere il problema di creare un meccanismo ed una sintassi di accesso unificata alle risorse disponibili. Tutte le istruzioni d accesso alle varie specifiche risorse disponibili sencondo un dato schema di accesso sono codificate come una stringa di indirizzo. Gli uri sono, per definizione: url (Universal Resource Locator): una sintassi che, oltre ad identificare la risorsa, fornisce informazioni immediatamente utilizzabili per accedere alla risorsa. urn (Universal Resource Names): una sintassi che permette un etichettatura permanente e non ripudiabile della risorsa, indipendetemente dal riportare informazioni sull accesso. Lo schema di naming indica il servizio con cui accedere alla risorsa, ossia quale protocollo usare per interagire con il server che controlla la risorsa; il protocollo di accesso più comune è http. L url può essere assoluto o relativo. 3.5 Protocollo HTTP Il WEB è un architettura client/server, tra i cui componenti standard basilari vi è un protocollo per il trasferimento di informazioni: HyperText Transfer Protocol (http). http è un protocollo di livello applicativo, nativo del WEB. È basato sul paradigma client/server dove il client (browser) richiede, riceve e visualizza le risorse WEB; mentre il server invia risorse in risposta a richieste Caratteristiche del protocollo HTTP http è un protocollo di richiesta/risposta. Il client invia un messaggio di richiesta, in conseguenza del quale il server risponde con un messaggio di risposta. Questo protocollo definisce la sintassi dei messaggi ed il modo in cui dovrebbero essere interpretati i campi in ciascuna linea del messaggio. Utilizza textsctcp come protocollo di trasporto. Il client inizia la connessione tcp verso il server sulla porta 80; il server accetta la connessione tcp dal client. Successivamente avviene uno scambio di messaggi di richiesta e risposta http tra browser e WEB server. Infine la connessione viene chiusa. tcp offre un servizio di trasferimento affidabile: i messaggi di richiesta/risposta sono consegnati integri al destinatario. http è un protocollo stateless (senza stato), ossia il server non mantiene nessuna informazione tra una richiesta di un client e la successiva. 3.6 Linguaggio di markup Il linguaggio di markup descrive i meccanismi di rappresentazione (strutturali, semantici o presentazionali) del testo che, utilizzando convenzioni standardizzate, sono utilizzabili su più supporti. Un documento è composto dall informazione pura del testo e da informazioni strutturali, ossia tutto ciò che serve per rendere efficacemente intellegibile l informazione. sgml (Standard Generalized Markup Language) è un meta-linguaggio di markup descrittivo standardizzato che definisce dei metodi di rappresentazione del testo in forma elettronica in modo indipendente dall hardware e dal sistema utilizzato. Per permettere la corretta visualizzazione dell informazione su qualsiasi piattaforma hardware connessa in rete, con la nascita del WEB è stato necessario definire un nuovo linguaggio di markup per descrivere la struttura ipermediale delle pagine WEB. Ossia fornire delle linee guida generali per la rappresentazione del contenuto, in forma aperta (leggibile da tutti i calcolatori e scrivibile senza bisogno di fare ricorso a programmi particolari). Inoltre non specifica esattamente il formato e la posizione del testo, lasciando ai browser la definizione dei dettagli. 33

41 3.6.1 Linguaggio HTML Sebbene siano state proposte modifiche ed altri standard, in questo momento l accezione comune html (HyperText Markup Language) rimane ancora il linguaggio del WEB. html è un linguaggio di markup non proprietario basato su sgml ed ideato nel La risorsa html è un file di solo testo ascii. Il contenuto del testo e le specifiche del formato sono inseriti nello stesso file. Quando un browser riceve una risorsa da un WEB server, la visualizza nella stessa forma in cui la ha ricevuta se la risorsa è un immagine, altrimenti la interpreta e la visualizza in base a determinate istruzioni in esso contenute se la risorsa è un documento html. html non è un linguaggio di programmazione. Un linguaggio di programmazione permette di comuptare qualcosa, di usare salti condizionali, loop e di entrare su dati contenuti in strutture di dati astratte. html è semplicemente un linguaggio di makup usato per definire una struttura. Un documento html è composto da un intestazione contenente informazioni riguardanti il documento e da un corpo che contiene il documento vero e proprio XML XML (extensible Markup Language) è un meta-linguaggio di markup, progettato per lo scambio e la interusabilità di documenti strutturati sul WEB. Possiede una sintassi semplificata rispetto a sgml e definisce una serie di linguaggi associati. XML si propone di integrare, arricchiere e nel lungo periodo sostituire html come linguaggio di markup standard per il WEB. È basato su uno standard aperto, quindi chiunque può realizzare strumenti che lo usino come formato di dati. Un meta-linguaggio non è una grammatica, ma è una grammatica di grammatiche, in modo tale da generare grammatiche personalizzate. I documenti sono autodescrittivi, ossia si scegli un vocabolario in modo da facilitare la comprensione del ruolo strutturale di ogni documento. Possiede una sintassi universale, minimale e rigorosa. La rigida struttura ad albero e la facilità di accesso alle varie sottoparti facilitano la visualizzazione e la programmazione di applicazioni. Pertanto è facile convertire questo formato in formati WEB XHTML xhtml (extensible HyperText Markup Language) è un linguaggio di markup che associa alcune proprietà dell XML con le caratteristiche dell html. Questo formato usa un formato più ristrettivo dei tag html, solo la struttura della pagina è scritta in xhtml mentre il layout è imposto dai CSS (Cascading Style Sheets). 3.7 Macro-componenti del WEB: client Esistono diversi tipi di WEB client (user agent nel protocollo http). Abbiamo browser WEB, spider o robot (bot) ed agenti software. Un browser WEB è un applicazione software che svolge il ruolo di interfaccia fra l utente ed il WEB, mascherando la complessità di Internet e del WEB. Questa applicazione consente di specificare le richieste (url), implementa il client del protocollo http, visualizza in modo appropriato il contenuto delle risposte sullo schermo e consente la navigazione. Invia informazioni al server per la generazione di risorse dinamiche, gestisce i cookie. Può avere un caching locale delle risorse, ossia il browser gestisce uno spazio sul disco (di definizione predefinita e configurabile) in cui memorizza le risorse recuperate dalla rete. Prima di effettuare una richiesta al WEB server, il browser controlla se nella cache vi è una copia della risorsa. L utente può forzare il prelievo della risorsa dal server o comunque la sua rivalidazione mediante un opportuno pulsante del browser. Le risorse nella cache possono avere un timestamp che indica il periodo di validità della risorsa (stabilito dal server). Il browser analizza l indirizzo inserito esplicitamente nella finestra indirizzo o il link selezionato nella pagina allo scopo di individuare la risorsa specificata nell url. Controlla se la risorsa richiesta è contenuta 34

42 nella cache disco del browser (mediante ricerca hash in un file indice), se la copia è valida il browser la visualizza, se l utente ha esplicitamente effettuato un reload, un buon browser cercherà di risparmiare tempo effettuando solo una richiesta http con il metodo GET al server con header If-Modified-Since per controllare se la copia nella cache è ancora valida o meno. Si passa alla fase successiva se la risorsa deve essere acquisita. Il browser, a questo punto, invoca il resolver per conoscere, tramite il sistema di naming del dns, la risoluzione dell indirizzo ip corrispondente all hostname specificato nell url. Se la risoluzione esiste, il dns restituisce l indirizzo ip. Il lookup è quasi sempre implementato con una chiamata di sistema bloccante, per cui il browser non può effettuare altre operazioni fino a che l operazione di lookup si conclude con successo o insuccesso. Ottenuta la risoluzione, il browser apre una connessione tcp con l host avente l indirizzo ip individuato. Sfruttanto questa connessione, il browser richiede mediante il protocollo http la risorsa specificata nell url. Il server WEB invia la risorsa richiesta ed il browser effettua un operazione di parsing del file html. Ossia analizza se vi sono risorse collegate nella pagina (embedded url); in caso affermativo, il browser effettua una richiesta per ciascuna risorsa collegata. Una volta individuate tutte le risorse (http/1.1) o dopo aver inviato ciascuna risorsa (http/1.0), il server chiude la connessione tcp. Non appena riceve la prima risorsa, il browser analizza come visualizzare sullo schermo il testo contenuto nella pagina. Se la risorsa ricevuta è in qualche formato non direttamente interpretabile dal browser, questi può attivare un apposito programma che ne consente la visualizzazione. Il browser, di per sé, è in grado solo di visualizzare correttamente file html e immagini GIF, JPEG. Può ricorrere ad applicazioni esterne (plug-in) per visualizzare o comunque per elaborare specifiche parti di una pagina WEB. Cookie Il protocollo http è stato progettato senza stato (stateless) per evitare il sovraccarico dei server. Per gestire lo stato sul server (mantenere traccia sulle richieste del client), il server memorizza sul disco del client un cookie (una stringa di testo). Il meccanismo dei cookie è un estensione del protocollo http. I cookie sono utilizzati per la gestione delle sessioni, tracking nei siti, personalizzazione dei contenuti in base alle preferenze dell utente, pubblicità su misura, ecc... Il funzionamento dei cookie avviene con i seguenti passi: Il server invia al client un cookie in risposta usando l header Set-Cookie. Il client include il cookie nelle richieste successive usando l header Cookie. Il server confronta il cookie del client con i propri cookie memorizzati. 3.8 Parte Informativa La parte informativa è organizzata generalmente in pagine ipermediali con collegamenti verso altre pagine (interne o esterne al sito). L organizzazione delle pagine è tipicamente gerarchica (ad albero), in cui vi è un punto di partenza e tutte le altre pagine sono poste al di sotto della radice dell albero. L albero delle pagine WEB è organizzato in modo simile ad un filesystem gerarchico con una radice e directory che contengono altre directory o file. Ogni pagina WEB ha un nome unico, che è il cammino assoluto dalla radice (/) dell albero delle pagine. Questo cammino è quello specificato nella parte path dell url richiesto dal client. La parte informativa del sito è creata e mantenuta dal content provider. 3.9 Memorizzazione delle pagine L albero delle pagine WEB non riflette necessariamente la vera organizzazione dei file all interno del filesystem. L organizzazione fisica che rispecchia fedelmente quella logica è solo una delle possibli alternative. 35

43 Per motivi di efficienza organizzativa (gruppi differenti possono creare o fornire le informazioni per le pagine) o di efficienza della risposta, l albero delle pagine WEB può essere partizionato tra due o più dischi della stessa piattaforma o addirittura tra piattaforme differenti, utilizzando o meno meccanismi di Network File System. Si può utilizzare in questi casi il mirroring, ossia l intero albero è replicato su più dischi o piattaforme. Altre soluzioni intermedie possono replicare solo le pagine che vengono frequentemente richieste, oppure altre pagine possono essere o meno partizionate. Ciascuna di queste organizzazioni deve essere trasparente per l utente che deve poter navigare e richiedere le pagine nello stesso identico modo, indipendentemente dall organizzazione fisica dei file e dei servizi. Le risorse possono essere statiche quando il contenuto è relativamente stabile nel tempo, volatili quando il contenuto viene modificato da eventi in corso, dinamiche quando il contenuto è creato dinamicamente sulla base delle richieste del client, attive quando le risorse contengono codice che viene eseguito dal client Modelli architetturali di server WEB Esistono diversi approcci per l architettura di un server WEB. Abbiamo server basati su processi, i quali utilizzano il metodo fork() ed il metodo helper(); basati su thread, ibridi, basati su eventi, interni al kernel. Per ogni nuova richiesta che arriva ad un server basato su processi, il processo padre crea una copia di se stesso alla quale affida la gestione della richiesta tramite fork(). Si mette subito in attesa di nuove richieste. Il processo figlio si occupa di soddisfare la richiesta e poi termina. Usando questo approccio il server rimane semplice poiché la generazione del nuovo processo e la copia dei dati, heap e stack è demandata interamente dal sistema operativo. Ma l overhead di fork() può penalizzare l efficienza del sistema: il tempo di generazione del processo figlio può non essere trascurabile rispetto al tempo di gestione della richiesta. Quindi, in mancanza di un limite superiore al numero di richieste che possono essere gestite correttamente, nel caso di un elevato numero di richieste i processi figli possono esaurire le risorse del sistema. Esistono alcuni processi per il servizio delle richieste (processi helper) ed un processo dispatcher (o listener), all avvio del servizio il dispatcher effettua il preforking dei processi helper, il dispatcher rimane sempre in ascolto delle richieste di connessione. Quando arriva una richiesta, il dispatcher la assegna ad un helper per la gestione e trasferisce la connessione all helper prescelto. Quando l helper termina la gestione della richiesta si rende disponibile per gestire una nuova richiesta. A regime il dispatcher svolge compiti di supervisione e controllo. Usanto il metodo helper() i processi vengono creati e poi riutilizzati, evitando così l overhead dovuto alla fork() all avvio di ogni nuova richiesta. È molto più semplice rispetto ad un server basato su eventi, più robusto e portabile rispetto al metodo multithread. Ma il processo dispatcher può diventare un potenziale collo di bottiglia, si usa più memoria rispetto al metodo multithread e necessita di meccanismi di locking quando si devono condivider informazioni tra i processi helper. Lo schema con preforking considerato finora usa il dispatcher per chiamare la funzione accept() per poi passare il descrittore del socket di connessione ad un helper. Questo schema viene anche chiamato job-queue dove il dispatcher è il produttore, mentre gli helper sono i consumatori. In alternativa, si può usare lo schema leader-follower dove dopo aver effettuato il preforking, ogni helper chiama la funzione accept() sul socket di ascolto. Questa tecnica funziona solo su kernel Unix TM derivati da BSD e il socket di ascolto è una risorsa che si deve accedere in mutua esclusione. Per questo motivo si effettua un file locking prima di chiamare tale funzione. Gli helper che sono nello stato idle competono per accedere al socket d ascolto: al più uno, detto leader, si può trovare in ascolto mentre gli altri, detti follower, sono accodati in attesa di poter accedere alla sezione critica per il socket di ascolto. Server basati su thread Il metodo basato sui thread possiede una sola copia del server che genera thread multipli di esecuzione. Ossia, il thread principale rimane sempre in ascolto delle richieste. Quando arriva una richiesta, esso genera un nuovo thread, un request handler, che la gestisce ed eventualmente termina. Ogni thread possiede una copia privata della connessione gestita, ma condivide con gli altri thread uno spazio di memoria. 36

44 1 static int nchildren; 2 static pid_t * pids; 3 int main(int argc, char **argv) 4 { 5 int listenfd, i; 6 socklen_t addrlen; 7 void sig_int( int); 8 pid_t child_make(int, int, int); pids = calloc(nchildren, sizeof(pid_t)); 11 my_lock_init("/tmp/lock.xxxxxx"); 12 for (i = 0; i < nchildren; i++) 13 pids[i] = child_make(i, listenfd, addrlen); } 16 pid_t child_make(int i, int listenfd, int addrlen) 17 { 18 pid_t pid; 19 if((pid = fork()) > 0) 20 return(pid); 21 child_main(i, listenfd, addrlen); 22 } 23 void child_main(int i, int listenfd, int addrlen) 24 { 25 int connfd; 26 socklen_t clilen; 27 struct sockaddr * cliaddr; 28 cliaddr = malloc( addrlen); 29 printf("child %ld starting\n", (long) getpid()); 30 for( ; ; ) { 31 clilen = addrlen; 32 my_lock_wait(); 33 connfd = accept(listenfd, cliaddr, &clilen); 34 my_lock_release(); 35 web_child( connfd); 36 close( connfd); 37 } 38 } Figura 51: Preforking con file locking Tramite questo metodo si mantiene una maggiore semplicità rispetto ad un server basato su eventi e si ottiene un minore overhead per il context switching. Ma abbiamo anche una maggiore complessità del codice del server per gestire i vari thread, minore robustezza rispetto al metodo helper() dato che i thread non sono protetti uno dall altro, mancato supporto da parte del sistema operativo e maggiori limitazioni sul numero di risorse rispetto al metodo helper(). Dimensione del pool Il comportamento della dimensione del pool di processi o thread è una scelta significativa nell architettura software di un server basato su processi o thread. Il pool può avere dimensione statica e quindi se il carico del server è alto e tutti i processi o thread del pool sono occupati, una nuova richiesta deve attendere che uno dei processi o thread si liberi. Se invece il carico del server è basso la maggiorparte dei processi o thread sono in idle mode e quindi abbiamo uno spreco di risorse. Il pool può avere dimensione dinamica. Ciò comporta che il numero dei processi o thread varia con il carico, ossia cresce se il carico è alto e diminuisce se il carico è basso. Tipicamente c è sempre un numero minimo di processi o thread in idle mode. Server ibridi Un server ibrido è basato su processi e thread, ogni singolo processo di controllo lancia i processi figli. Ciascun processo figlio crea un certo numero di thread di servizio ed un thread listener. Quando arriva 37

45 una richiesta, il thread listener la passa ad un thread di servizio che la gestisce. Questo approccio combina i vantaggi dell architettura basata su processi e quella basata su thread, riducendo i loro svantaggi. Il grado di servire un maggior numero di richieste usando una minore quantità di risorse rispetto all architettura basata solamente da processi. Conserva, comunque, in gran parte, la robustezza e stabilità dell architettura basata su processi. Server basato su eventi In questo modello abbiamo un solo processo che gestisce le richieste in modo event-driven, anziché servire una singola richiesta nella sua interezza, il server esegue una piccola parte di servizio per conto di ciascuna richiesta. Utilizza la funzione select() con opzioni non bloccanti sui socket e quindi l I/O viene gestito in modo asincrono. Il server continua l esecuzione mentre aspetta di ricevere una risposta alla chiamata di sistema da parte del sistema operativo. Questo approccio è molto veloce, usa un unico processo quindi non sono necessari meccanismi di lock, nessun overhead dovuto al contex switching o consumi extra di memoria. Ma presenta una maggiore complessità nella progettazione ed implementazione, è meno robusto dato che un failure può fermare l intero server. Inoltre possiamo avere limiti delle risorse per processo e non è detto che tutti i sitemi operativi supportino l I/O asincrono. Server interni al kernel In questo caso, viene generato un thread del kernel dedicato alle richieste http. Può essere implementato tutto il server all interno del kernel o in alternativa gestire solo le richieste statiche GET nel kernel, mentre le richieste dinamiche vengono gestite da un server nello spazio utente. Un esempio di server interno al kernel è Tux. Attualmente non è ufficialmente portato sul kernel Macro-componenti del WEB: server proxy Nel caso più semplice la comunicazione tra client e WEB server avviene direttamente. Più in generale, dove non sia possibile o non conveniente che il client contatti direttamente il server, vengono utilizzati degl intermediari, tra cui il più diffuso è il server proxy. Il proxy agisce sia da server nei confronti del client WEB, sia da client nei confronti del server WEB. I proxy sono nati come gateway, ossia sono gli intermediari per consentire la comunicazione tra client all interno di Intranet con WEB server esterni. Successivamente sono stati usati principalmente come locazioni per il caching delle risorse. Dato che c è una probabilità elevata che gli utenti condividino la cache possano essere interessati alle medesime risorse WEB in un intervallo di tempo. Quando una risorsa viene reperita per la prima volta da un WEB server, può essere memorizzata sul disco del proxy, in modo che richieste successive per la stessa risorsa indirizzate al proxy possono essere soddisfatte localmente. Una cache WEB memorizza una copia locale delle risorse WEB richieste (più recentemente) e reagisce come un proxy alle richieste degli utenti. In caso di cache hit abbiamo una riduzione della latenza percepita dall utente, una riduzione del traffico Internet una riduzione del carico sui WEB server. Il caching ha luogo in diverse locazioni del WEB: una richiesta WEB può attraversare diversi sistemi di caching lungo il suo percorso verso il WEB server. Abbiamo il caching a livello del browser, caching a livello del WEB server, caching a livello dei proxy. Possiamo avere due tipi di proxy: il forward proxy, che svolge il compito di intermediario tra client e WEB server, localizzato in prossimità del client oppure in punti strategici della rete; oppure il reverse proxy localizzato in prossimità del WEB server per accellerare il traffico http. Possiamo separare inoltre i proxy in altre due categorie: i proxy non trasparenti, dove l utente è a conoscenza dell esistenza del proxy, ed in proxy trasparenti, in cui la presenza di un elemento di rete che intercetta tutto il traffico WEB da client a server lo redirige verso un proxy server. Esistono varie modalità per configurare l uso di un proxy non trasparente da parte di un client. Abbiamo la configurazione esplicita del browser, la configurazione automatica del browser tramite un file di configurazione, discovery automatico del file di configurazione. L uso dei proxy però fornisce un trade-off tra contattare WEB server ed altri proxy dato che non c è garanzia di prestazioni migliori, non cè garanzia di migliore prossimità Internet. La consistenza delle 38

46 informazioni nella cache non è garantita. Senza contare i problemi economico-legali come la privacy, copyright, acquisizioni di informazioni personalizzate, siti con banner pubblicitari. 39

47 4 Il protocollo HTTP Il protocollo HTTP permette uno scambio di messaggi di richiesta e risposta. È un protocollo di tipo stateless ed è basato sul meccanismo di naming degli URI per identificare le risorse Web. Le informazioni sulla risorsa sono incluse in ogni trasferimento, ad esempio come la classificazione MIME. Sono presenti due versioni del protocollo, la 1.0 (vedi RFC1945) e la 1.1 (vedi RFC2616). I messaggi HTTP possono essere di due tipi: di richiesta e di risposta. Ogni messaggio è composto dall header (o intestazione) e da un corpo (opzionale). Una richiesta HTTP comprende: un metodo, che specifica il tipo di operazione che il client richiede al server. GET è il metoo usato più frequentemente dato che serve ad acquisire una risorsa Web. URL, che identifica la risorsa locale rispetto al server. informazioni addizionali, come la data e l ora di generazione della richiesta, il tipo di software utilizzato dal client (User Agent), i tipi di dato che il browser è in grado di visualizzare. Una risposta HTTP comprende l identificativo della versione del protocollo HTTP, il codice di stato e l informazione di stato in forma testuale, un insieme di possibili altre informazioni riguardanti la risposta e l eventuale contenuto della risorsa richiesta. Se la pagina Web richiesta dall utente è composta da molteplici risorse, ciascuna di esse sarà identificata da un URL differente: è necessario che il browser invii un esplicito messaggio di richiesta per ognuna delle risorse incorporate alla pagina. Un metodo HTTP può essere sicuro, ossia non altera lo stato della risorsa, ed idempotente, ossia l effetto di più richieste identiche è lo stesso di quello di una sola richiesta. I metodi principali di una richiesta HTTP sono: GET, è il più importante metodo di HTTP e richiede una risorsa ad un server. È sicuro ed idempotente. Può essere assoluto quando la risorsa viene richiesta senza altre specificazioni, condizionale se la risorsa corrisponde ad un criterio indicato negli header If-Match, If-Modified-Since, If-Range,...; parziale se la risorsa richiesta è una sottoparte di una risorsa memorizzata. HEAD è una variante di GET usata principalmente per scopi di controllo e debugging. Il server deve rispondere soltanto con i metadati associati alla risorsa richiesta (solo header) senza il corpo della risorsa. È sicuro ed idempotente. Viene usato per verificare la validità di un URI, ossia se la risorsa esiste ed ha lunghezza non nulla; l accessibilità di un URI, dove controlla se la risorsa è accessibile presso il server e non sono richieste procedure di autenticazione; la coerenza di cache di un URI, se la risorsa non è stata modificata rispetto a quella in cache, non ha cambiato lunghezza, valore hash o data di modifica. POST permette di trasmettere delle informazioni dal client al server, ossia aggiorna una risposta esistente o fornisce dati in input, oppure sono dati nel corpo della richiesta. Non è né sicuro né idempotente. Il server può rispondere positivamente in tre modi: 200 Ok ai dati ricevuti e sottomessi alla risorsa specificata è stata data una risposta; 201 Created ai dati ricevuti, la risposta non esisteva ed è stata creata; 204 No content ai dati ricevuti e sottomessi alla risorsa specificata non è stata data una risposta. PUT serve a trasmettere delle informazioni dal client al server, creando o sostituendo la risorsa specificata. A differenza del metodo POST, l URI di questo metodo identifica la risorsa inviata nel corpo della richiesta, ossia è la risorsa che ci si aspetta di ottenere facendo un GET in seguito con lo stesso URI. Non è sicuro, ma è idempotente. Non offre alcuna garanzia di controllo degli accessi o locking. 40

48 DELETE serve a cancellare la risorsa specificata dall URI. OPTIONS permette al client di conoscere le capacità del server. TRACE permette al client di conoscere il contenuto del messaggio di richiesta effettivamente ricevuto dal server. CONNECT esiste ma non viene attualmente utilizzato. 4.1 Header HTTP Gli header sono righe testuali free-format che specificano caratteristiche: generali della trasmissione. Gli header generali si applicano solo al messaggio trasmesso e si applicano sia ad una richiesta che ad una risposta, ma non necessariamente alla risposta trasmessa. dell identità trasmessa. Gli header dell entità forniscono informazioni sul corpo del messaggio, o, se non vi è un corpo, sulla risorsa specificata. Questi header sono: Content-Type, Content-Length, Content-Encoding, Allow, Last-Modified ed Expires. della richiesta effettuata. Gli header della richiesta sono impostati dal client per specificare informazioni sulla richiesta e su se stesso al server. Questi header sono: From, User-Agent, If-Modified-Since, Referer, Authorization. e della risposta generata. Gli header della risposta sono impostati dal server per specificare informazioni sulla risposta e su se stesso al client. Questi header sono: Server, Location, WWW-Authenticate. 4.2 Codice di stato della risposta Ogni risposta possiede un proprio codice. Questo è un numero di tre cifre, di cui la prima indica la classe della risposta e le atre due la risposta specifica. Abbiamo le seguenti classi: 1xx (Informational): una risposta temporanea alla richiesta, durante il suo svolgimento. 2xx (Successful): il server ha ricevuto, capito ed accettato la richiesta. Ad esempio il codice 200 OK significa che la risorsa cercata è presente nel corpo del messaggio. 3xx (Redirection): il server ha ricevuto e capito la richiesta, ma possono essere necessarie altre azioni da parte del client per portare a termine la richiesta. Ad esempio il codice 301 Moved Permanently indica che la risorsa è stata spostata, oppure il codice 304 Not Modified indica che la risorsa non è modificata. 4xx (Client Error): la richiesta del client non può essere soddisfatta per un errore da parte del client (errore sintattico o richiesta non autorizzata). Ad esempio il codice 401 Unauthorized rappresenta l accesso non autorizzato ad una data risorsa. Il codice 403 Forbidden dichiara che l accesso è vietato per una data risorsa. Invece il codice 404 Not Found specifica che la risorsa cercata non esiste. 41

49 5xx (Server Error): la richiesta può anche essere corretta, ma il server non è in grado di soddisfarela richiesta per un problema interno (suo o di applicazioni invocate per generare dinamicamente risorse). Ad esempio il codice 500 Internal Server Error specifica un errore inatteso che impedisce il servizio richiesto, oppure il codice 501 Not Implemented indica che il server non supporta la funzionalità richiesta. 4.3 Autenticazione in HTTP/1.0 Lo scopo dell autenticazione è quello di controllare gli accessi alle risorse del server. Data l assenza di stato propria del linguaggio HTTP, il client deve presentare l autorizzazione in ogni richiesta. La procedura di autorizzazione utilizza solitamente userid e password codificati in base64 mandati tramite l header Authorization. Se non c è autorizzazione, il server rifiuta l accesso ed invia l header WWW-Authenticate nella risposta. GET condizionale in HTTP/1.0 Il GET condizionale ha lo scopo di non inviare un oggett se il client ha una versione aggiornata già memorizzata nella sua cache. Il client specifica la data della copia nella cache con l header di richiesta If-Modified-Since. Il server invia una risposta che non contiene l oggetto se la copia posseduta dal client è aggiornata. 4.4 Principali problemi di HTTP/1.0 Le connessioni sono spesso lente e congestionate, per questo motivo si usa l hack delle connessioni multiple. Abbiamo un solo indirizzo IP per ciascun server Web. I meccanismi di caching sono primordiali e l autenticazione viaggia sulla rete in chiaro. 4.5 Protocollo HTTP/1.1 A differenza del protocollo HTTP/1.0 sono stati aggiunti: il meccanismo hop-by-hop; connessioni persistenti e pipelining; hosting virtuale; autenticazione crittografata; nuovi metodi di accesso, come l aggiornamento delle risorse sul server e diagnostica; miglioramento dei meccanismi di caching, ossia permette una gestione più sofisticata della cache, una maggiore accuratezza nella specifica validità e l header Cache-Control per le direttive di caching; il chunked encoding, dove la risposta può essere inviata al client suddivisa in sottoparti, anche prima di conoscerne la dimensione totale. Gli header sono diventati i seguenti: Header generali: Date, Pragma, Cache-Control, Connection, Trailer, Transfer-Encoding, Upgrade, Via, Warning. Header di entità: Allow, Content-Length, Content-type, Expires, Last-Modified, Content-Language, Content-Location, Content-MD5, Content-Range. Header di richiesta: Authorization, From, Referer, User-Agent, If-Modified-Since, Accept, Accept-Charset, Accept-Encoding, Accept-Language, TE, Proxy-Authorization, If-Match, If-None-Match, If-Range, If-Unmodified-Since, Expect, Host, Max-Forwards, Range. 42

50 Header di risposta: Location, Server, WWW-Authenticate, Proxy-Authenticate, Retry-After, Accept-Ranges, Age, ETag, Vary Connessioni in HTTP/1.0 La transazione HTTP è composta da uno scambio di richiesta e risposta. Il client apre una connessione TCP separata per ogni risorsa richiesta, e la connessione non è persistente. Ossia, il client apre una connessione TCP con il server ed invia il messaggio di richiesta HTTP sulla connessione. Il server invia il messaggio di risposta HTTP sulla connessione. Infine la connessione viene chiusa. Questo approccio permette di avere una massima concorrenza, ma dato che non abbiamo connessioni persistenti otteniamo un overhead per l instaurazione e l abbattimento della connessione TCP, overhead per il meccanismo di slowstart del TCP. Per questo motivo sono stati proposti tre approcci per risolvere questo problema: Uso di connessioni multiple in parallelo. Questa però è una soluzione parziale ai problemi delle connessioni non persistenti. Consiste nell apertura simultanea di più connessioni TCP, una per ogni richiesta. Ciò riduce il tempo di latenza percepito dall utente, ma congestiona la rete. Il servere serve un minor numero di client diversi contemporaneamente. Inoltre ci sono richieste di pagine interrotte dall utente e la riduzione della latenza non è garantita dato che ogni rchiesta è indipendente dalle altre. È stata proposta l introduzione di nuovi metodi per ottenere risorse multiple sulla stessa connessione. Connessioni TCP perstenti e pipelining. Una connessione persistente consiste nel trasferire coppie multiple di richiesta e risposta entro una stessa connessione TCP. Questo tipo di connessioni permettono una riduzione di costi (instaurazione ed abbattimento) delle connessioni TCP. Abbiamo una riduzione della latenza poiché si evitano gli slowstart multipli del TCP. Il pipelining permette una trasmissione di più richieste senza attendere l arrivo della risposta alle richieste precedenti. Le risposte devono essere date dal server nello stesso ordine in cui sono state fatte le richieste. HTTP non fornisce un meccanismo di riordinamento esplicito, quindi il server può processare le richieste in un ordine diverso da quello di ricezione. Con questa tecnica si riduce ulteriormente il tempo di latenza ed ottimizzazione del traffico di rete, sopratutto per richieste che riguardano risorse molto diverse per dimensioni o tempi di elaborazione. Nel protocollo HTTP/1.1 viene utilizzata la modalità SHOULD per usare le connessioni persistenti. L header generale Connection: close segnala la chiusura della connessione persistente. L implementazione della chiusura non è specificata nel protocollo e può essere specificata o dal client o dal server Hosting Virtuale Ad uno stesso IP possono corrispondere nomi diversi e server diversi, pertanto non sono più sufficienti IP e porta ad identificare il server. L header Host serve a specificare il nome (e la porta) del server. Questo permette l implementazione del virtual hosting senza manipolazioni del routing e multi-addressing IP. 43

51 4.5.3 Negoziazione del contenuto In HTTP/1.1 sono stati introdotti nuovi header di richiesta per la negoziazione del contenuto. I seguenti header rappresentano l esigenza del client di negoziare con il server la rappresentazione preferita di una risorsa: Accept: tipo di contenuto multimediale preferito; Accept-Charset: insiemi di charset preferiti; Accept-Encoding: codifica del contenuto preferita; Accept-Language: linguaggio preferito; TE: codifica del trasferimento preferita. La modalità di negoziazione del contenuto è guidata dal client quando sceglie tra le alternative possibili ed indica la propria preferenza al server. Mentre è guidata dal server quando sceglie la rappresentazione della risorsa in base alle informazioni ricevute dal client Autenticazione digest in HTTP/1.1 Il processo di autenticazione avviene quando il server invia al browser una stringa univocamente generata. Il client risponde con il nome dell utente ed un valore crittografato con MD5 a 128 bit basato sui seguenti dati: nome dell utente, password, URI e la stringa generata dal server. Il browser ricorda l autorizzazione. Con questo approccio la password non viene trasmessa in chiaro rispetto all autenticazione basic di HTTP/1.0. Ma non è sicuro al 100% dato che è possibile intercettare la richiesta con URI, stringa generata dal server e risposta per poter accedere alle risorse protette. La soluzione considerata attualmente più sicura è HTTPS. Infatti HTTPS trasmette i dati in HTTP semplice su un protocollo di trasporto (SSL che usa la crittografia a chiave pubblica ed è trasparente) che crittografa tutti i pacchetti. Il server ascolta su una porta diversa (443) e si usa uno schema di URI diverso Ottimizzazione della banda I meccanismi introdotti in HTTP/1.1 per ottimizzare l uso della banda, evitando sprechi, sono: trasmissione delle sole parti necessarie della risorsa tramite il meccanismo di range request. Questo meccanismo è utile perché richiede parti specifiche di una risorsa, anziché l intera risorsa. Ciò permette di riprendere un download interrotto prima della terminazione, oppure prendere solo una parte della risorsa che interessa al client. L header di richiesta utilizzato è Range e permette al client di specificare i byte della parte desiderata. L header di entità utilizzato è Content-Range e permette al server di specificare la parte di risorsa inviata. Il codice di risposta è 206 Partial Content dove il server indica al client che la risposta non è completa. L header di risposta utilizzato è Accept-Range e permette al server di indicare che non accetta richieste di parti; in alternativa, il server può inviare direttamente la risorsa intera. trasformazione della risorsa in modo da ridurne la dimensione tramite la compressione. In HTTP viene compressa solo la risorsa e non gli header. L header di entità utilizzato è Content-Encoding ed indica una trasformazione che è stata applicata o può essere applicata ad una risorsa. In HTTP/1.0 la compressione avviene end-to-end. Mentre in HTTP/1.1 avviene anche hop-by-hop e sono stati aggiunti nuovi header di richiesta come TE e Accept-Encoding. L header di richiesta Accept-Encoding permette di restringere l insieme delle codifiche accettabili nella risposta. 44

52 scambio di informazioni di controllo per evitare trasmissioni inutili tramite il meccanismo expect/- continue. Questo meccanismo è stato introdotto per gestire le richieste costose. Permette al client di sapere se il server non è in grado di servire la richiesta prima di inviare il corpo della richiesta con il metodo POST. L header di richiesta utilizzato è Expect. Se il server può gestire la richiesta risponde con HTTP/ Continue. A questo punto il client può continuare inviando il corpo della richiesta. Se il server, invece, non può gestire la richiesta risponde con HTTP/ Expectation Failed Trasmissione del messaggio In HTTP/1.0 l unico meccanismo per garantire al client di aver ricevuto correttamente l intero messaggio di risposta è fornito dall header di entità Content-Length. Tale dimensione è facile da conoscere per le risorse statiche (usando la funzione stat()), ma per le risorse dinamiche il server deve aspettare che la risorsa venga generata. Ciò genera un eccessiva latenza, pertanto in HTTP/1.0 la connessione viene chiusa appena terminato l invio della risorsa dinamica. In HTTP/1.1 tramite le connessioni persistenti si utilizza il chunked encoding. Il messaggio di risposta viene inviato in pezzi, detti chunk, specificando la dimensione, in esadecimale, di ogni chunk ed inviando al termine un chunk di dimensione nulla. L header di entità utilizzato è Transfer-Encoding: chunked. L ultimo chunk può essere seguito da un trailer opzionale che contiene gli header di entità Caching Il caching può avvenire lato client, lato server o lato intermediario 6. Il caching lato server riduce i tempi di processamento della risposta ed il carico sui server, senza effetti sul carico di rete. Il caching lato client o lato intermediario riduce il carico di rete ed in parte il carico sui server. In HTTP/1.0 si utilizzano tre header: Expires, dove il server specifica la scadenza della risorsa. Tale scadenza è specificata con la data, quindi il server e la cache devono essere sincronizzati. È possibile che il server specifichi una scadenza immediata per evitare il caching della risorsa. Tale fenomeno viene detto cache busting. If-Modified-Since, dove il client o il proxy richiede la risorsa solo se modificata dopo la data indicata nella richiesta. no-cache, dove il client indica ai proxy sul cammino di accettare solo la risposta dal server. In HTTP/1.1 è stato introdotto l header generale Cache-Control per permettere a client e server di specificare direttive per il caching. Le direttive sono sulla cacheability, validazione e coerenza di risposte in cache. Tale header può contenere molteplici direttive che controllano l uso di tutte le cache situate tra il client che ha originato la richiesta e il server. Il valore dell header utilizza una lista di direttive. Ogni direttiva consiste in una parola chiave che identifica la direttiva ed, opzionalmente, il valore della direttiva. È un header generale usato sia nelle richieste sia nelle risposte. Le direttive nella richiesta permettono al client di sovrascrivere la gestione di default delle risorse attuata dalle cache: no-cache: la richiesta non può essere soddisfatta usando una risorsa in cache; si forza la rivalidazione. no-store: la risposta alla richiesta e la richiesta stessa non possono essere memorizzate in cache (protezione di dati sensibili). 6 Su un proxy interposto tra client e server. 45

53 max-age <seconds>: solo risorse in cache più fresche dell età massima specificata dovrebbero essere usate per soddisfare la richiesta. min-fresh <seconds>: solo risorse in cache più fresche almeno per l età specificata dovrebbero essere usate per soddisfare la richiesta. max-stale <seconds>: risorse in cache che non scadono oltre l età specificata possono essere usate per soddisfare la richiesta (accettate anche risorse già scadute). no-transform: la cache non può fornire una versione modificata della risorsa, ma solo la versione originale. only-if-cached: in caso di cache miss, il proxy non dovrebbe inoltrare la richiesta. Le direttive nella risposta permettono al server di controllare la gestione delle risorse attuata delle cache: no-store e no-transform: come direttive nella richiesta. no-cache: la risorsa può essere memorizzata in cache ma deve essere rivalidata prima di fornire la risposta. private: la risposta può essere riusata solo dal client che ha originato la richiesta. public: la risposta può essere memorizzata in cache e condivisa tra i client. must-revalidate: la cache deve sempre rivalidare la risorsa se questa è scaduta. proxy-revalidation come must-revalidate ma si applica solo ai proxy. max-age <seconds>: la cache dovrebbe rivalidare la risorsa se questa ha raggiunto l età massima specificata. s-maxage <seconds>: come max-age ma si applica solo ai proxy. Sono disponibili due meccanismi per controllare la scadenza di una risorsa: o la scadenza viene specificata dal server o è di tipo euristico. Con questa versione del protocollo sono stati introdotti nuovi header: di identità ETag per identificare univocamente la versione di una risorsa e quindi versioni diverse della stessa risorsa hanno differente ETag. ETag sta a significare entity tag. Può essere generato dal server usando la funzione di crittografia hash MD5. Questo header fornisce un meccanismo di identificazione delle versioni di una risorsa più fine rispetto all header Last-Modified. Consente, inoltre, di disaccoppiare la validazione della risorsa in cache dalla scadenza della risorsa. di risposta Vary per elencare un insieme di header di richiesta da usarsi per selezionare la versione appropriata in una cache. Via per conoscere la catena di proxy tra client e server Scadenza specificata dal server Il server stabilisce una scadenza (Time To Live o TTL) della risorsa tramite l header Expires o con la direttiva max-age nell header Cache-Control. La direttiva max-age specifica il valore del TTL relativo in secondi, mentre l header Expires specifica il valore del TTL assoluto con la data. Se il TTL è scaduto, la risorsa dovrebbe essere rivalidata prima di essere fornita in risposta. Tuttavia, se la richiesta accetta anche risposte scadute, o se il server è irraggiungibile o se il proxy è configurato per sovrascrivere il TTL, la cache può rispondere con la risorsa scaduta con l header generale Warning: 110 Response is stale. Se Cache-Control nella risposta del server specifica la direttiva must-revalidate, la risorsa scaduta non può mai essere rispedita e quindi la cache deve rivalidare la risorsa con il server. Se il server non risponde, la cache manderà un codice 504 Gateway time-out al client. Se Cache-Control nella richiesta specifica la direttiva no-cache la richiesta deve essere sempre fatta dal server. 46

54 Scadenza Euristica Ad una risorsa può non essere associato dal server un valore esplicito del TTL. La cache stabilisce un valore euristico di durata della risorsa, dopo la quale assume che sia scaduta. Queste assunzioni possono essere avvolte ottimistiche e risultare in risposte scorrette. Se non è valida con sicurezza una risorsa assunta fresca, allora la cache deve fornire nella risposta l header generale warning: 113 Heuristic expiration Validazione della risorsa in cache Anche dopo la scadenza, nella maggior parte dei casi, una risposta sarà ancora non modificata, e quindi la risorsa in cache è valida. Il metodo più semplice per rivalidare la risorsa consiste nell usare il metodo HEAD. Il client o il proxy effettua la richiesta con HEAD e verifica la data di ultima modifica, usando l header di entità Last-Modified e l header ETag. In caso di risorsa non valida sono necessarie due richieste. Il metodo migliore, invece, consiste nel fare una richiesta condizionale. Ossia, il client o il proxy effettua la richiesta condizionale, usando gli header di richiesta If-Modified-Since, If-Match ed If-None-Match per specificare il valore dell ETag. Se la risorsa è stata modificata, viene fornita la risposta con codice di stato 304 Not Modified senza corpo della risposta. In caso di risorsa non valida è necessaria una sola richiesta. 47

55 5 Interazione tra HTTP e TCP Il protocollo TCP usa alcuni timer per attivare alcune operazioni: ritrasmissione dei pacchetti persi: attivata dal mittente TCP allo scadere di un timer. ripetizione della fase di slow-start: in alcune implementazioni del TCP, il mittente TCP ripete lo slow-start dopo un periodo di inattività. recupero dello stato da una connessione terminata: il mittente TCP che ha iniziato la chiusura della connessione rimuove lo stato della connessione allo scadere di un timer. 5.1 Timer di ritrasmissione Il timer di ritrasmissione scatta quando c è ritardo nello stabilire una connessione TCP. Questo timer viene chiamato ReTransmission Timeout (RTO) usato dal mittente TCP per individuare la perdita di un pacchetto. Se abbiamo un RTO grande allora avremo una latenza considerevole per gestire la perdita di pacchetti, mentre se abbiamo un RTO piccolo otterremo molte ritrasmissioni inutili. Per ottenere un buon tradeoff, il mittente TCP sceglie l RTO sulla base del Round Trip Time (RTT) misurato verso il destinatario. Il valore iniziale dell RTO è impostato a 3 secondi, successivamente si usa la tecnica del backoff esponenziale se sono presenti perdite multiple. Ossia, se il mittente non riceve il SYN-ACK dal destinatario entro l RTO, allora il valore corrente dell RTO viene raddoppiato. La perdita di pacchetti dipende dal traffico sulla rete e dal carico sul server 7. Il comportamento dell utente di tipo interrompi-ricarica equivale ad una ritrasmissione immediata del SYN da parte del client, il che vuol dire che riduce la latenza percepita dall utente ma aumenta il carico sulla rete e sul server. Per timeout di ritrasmissioni elevati nel mezzo di un trasferimento Web sono meno frequenti a causa di un valore minore dell RTO, dato che a regime il valore dell RTO stabilito dinamicamente dal mittente TCP si avvicina al valore dell RTT tra mittente e destinatario; acknowledgment duplicati, ossia è un altro meccanismo del TCP per gestire la perdita di pacchetti; in caso di mancata ricezione di un pacchetto, il destinatario non incrementa il numero di acknowledgment; il mittente riceve pacchetti con ACK duplicati; alla ricezione di 3 ACK duplicati il mittente ritrasmette il pacchetto mancante senza aspettare lo scadere dell RTO. Solitamente le risposte Web hanno dimensione media di 10 KB e quindi la maggiorparte del trasferimento avviene durante la fase di slow-start del controllo di congestione. Dopo un timeout di ritrasmissione viene ripetuta la fase di slow-start Slow-start Restart Le connessioni persistenti aiutano ad evitare lo slow-start, aumentando di conseguenza la finestra di congestione. Tuttavia, il traffico Web solitamente consiste da una raffica di richieste seguiti da periodi di idle. Il mittente TCP ripete la fase di slow-start dopo un periodo di inattività (slow-start restart), quindi si deve evitare di sovraccaricare la rete alle ripresa del trasferimento. La ripetizione della fase di slow-start dopo un periodo di inattività inizia dopo la ricezione dell ultimo ACK ed il timer è basato sul valore corrente dell RTO. Dato che la dimensione della finestra di congestione viene rinizializzata allo scadere del timer, tale meccanismo riduce il benificio delle connessioni persistenti. Per ridurre il degrado delle prestazioni causato dallo slow-start restart sono state proposte diverse soluzioni tecniche: disabilitare tale meccanismo sul server, ciò limita il rischio di una raffica inaspettata di richieste se il server chiude la connessione dopo un breve periodo di inattività. usare un timeout maggiore per lo slow-start restart, ma è possibile che si verifichi un inconsistenza tra la dimensione della finestra di congestione e lo stato corrente della rete. 7 Ossia se la codadelle connessioni TCP è piena. 48

56 diminuire gradualmente la finestra di congestione, in proporzione alla durata del periodo di inattività. mitigare la trasmissione dei pacchetti per evitare la generazione di raffiche di pacchetti Problema dei duplicati vecchi In Internet i pacchetti vengono duplicati arbitrariamente, ritardati e riordinati. Quando arriva un pacchetto duplicato appartenente ad una connessione ormai chiusa, per evitare di usare la stessa tupla, uno dei due host tiene traccia dell esistenza della connessione precedente. Il compito viene assegnato dal TCP all host che inizia la chiusura della conessione. Lo stato TIME WAIT della connessione TCP ha durata pari a 2 volte l MSL (Maximum Segment Lifetime). Tipicamente nel Web, il server rappresenta l host che chiude la connessione. In questo caso nascono problemi di asimmetria del modello client-server (molti client), maggior consumo di risorse sul server (incentivo a chiudere la connessione), ma il server deve mantenere traccia della connessione per la durata dello stato TIME WAIT. Questo problema esiste anche sui proxy. L uso delle connessioni persistenti riduce l entità del problema, ossia vengono generate un minor numero di connessioni in TIME WAIT. Per attenuare il problema del TIME WAIT sul server sono state proposte due soluzioni: 1. ridurre la quantità di risorse di sistema usate per tenere traccia delle connessioni in TIME WAIT. Il SO mantiene la minima quantità possibile di informazioni. Viene scandita periodicamente, alla scadenza dei timer, la lista delle connessioni TCP mettendo alla fine della lista le connessioni in TIME WAIT. Ciò migliora sensibilmente le prestazioni dei server soggetti ad un traffico elevato. 2. modificare le specifiche del protocollo HTTP. Ossia, forzare il client a chiudere la connessione ribaltando così l host che gestisce le connessioni in TIME WAIT. Ma questa modifica non è stata apportata poiché il client non può avere incentivi a chiudere la connessione o ad assumersi la responsabilità dello stato TIME WAIT. 5.2 Stratificazione HTTP/TCP Le funzioni implementate a livello del protocollo di trasporto possono avere un impatto significativo sulle prestazioni Web. L interruzione di trasferimenti HTTP richie la rispettiva chiusura della connessione TCP sottostante. Nel protocollo TCP viene usato l algoritmo di Nagle che limita il numero di pacchetti piccoli trasmessi dal mittente TCP, ritardando il trasferimento dell ultimo pacchetto del messaggio HTTP. Il destinatario TCP può ritardare l invio di un acknowledgment sperando di farne il piggybacking in un pacchetto di dati in uscita. L assenza di un meccanismo di interruzione precoce dell HTTP richiede la terminazione della connessione TCP. L effetto dell operazione di interruzione sulle prestazioni Web consiste nell evitare di caricare il resto della risorsa, il client deve ristabilire la connessione TCP per servire la prossima richiesta. Gli effetti collaterali consistono in un accoppiamento stretto tra richieste in pipeline sulla stessa connessione TCP, ossia interrompendo una richiesta si interrompono tutte le richieste inviate in pipeline; i proxy richiedono di mantenere uno stato dettagliato; l interruzione causata dall utente non blocca immediatamente il trasferimento dei dati; accoppiamento dei dati trasferiti tra proxy e client e tra proxy e server. Quando viene interrotta una connessione il client invia un segmento con impostato il flag FIN mediante la chiamata di sistema close(). Alla ricezione del FIN, il SO invia l EOF all applicazione server. Il SO continua ad inviare dati prelevandoli dal buffer di trasmissione. L EOF comporta che il server smetta di scrivere nuovi dati, ma i dati nel buffer di invio saranno inviati al client. Il client invia un segmento con impostato il flag RST (ReSeT) mediante la close(), solo se è stata impostata l opzione sul socket SO LINGER con setsockopt(). Il SO scarta ogni dato in uscita rimanente per la connessione, inclusi i dati nel buffer di invio. In questo modo si evita il trasferimento di dati addizionali quindi evita al server il bisogno di gestire le richieste in pipeline. Ma non vi è una chiusura pulita della connessione e dato che vengono scartati tutti i dati nel buffer in ricezione non vi è ritrasmissione dei pacchetti persi. 49

57 5.2.1 Algoritmo di Nagle Il TCP prevede un meccanismo di bufferizzazione dei segmeti con un utilizzo non ottimale della banda disponibile; inviando tanti segmenti piccoli, infatti, si ha un maggior consumo di banda e un maggior uso del preprocessore. L algoritmo di Nagle implementa un meccanismo in cui i dati vengono accumulati fino a che non si raggiunge una dimensione sufficiente per eseguire la trasmissione di un singolo segmento. L algoritmo prevede che il mittente non invii il prossimo segmento piccolo se è ancora in attesa di ricevere l ACK di un segmento piccolo già inviato. Di conseguenza il mittente deve accumulare i dati in spedizione fino a che sia soddisfatta le seguenti condizioni: la dimensione dei dati pronti per l invio ha superato l MSS ed è stato ricevuto l ACK per tutti i segmenti piccoli in sospeso. Tale algoritmo può, tuttavia, avere un effetto negativo sulle connessioni persistenti se i trasferimenti Web richiedono il trasferimento di segmenti piccoli. Quindi per disabilitarlo si opera a livello di applicazione, ossia si usa l opzione socket TCP NODELAY abbinata alla funzione setsockopt(). Ritardando la trasmissione di un ACK, aumenta la probabilità di poterne fare il piggybacking in un pacchetto dati. A tal proposito viene usato l algoritmo delayer ACK, che invia ACK ogni due segmenti ricevuti oppure dopo 200 ms dalla ricezione di un singolo segmento; mentre l invio immediato di un ACK si ha solo per segmenti fuori sequenza. Usando l algoritmo di Nagle e gli ACK ritardati causano una situazione di deadlock temporaneo. Supponiamo che il mittente vuole inviare 1.5 segmenti e manda il primo segmento completo. A questo punto l algoritmo di Nagle impedisce l invio del secondo segmento dato che non ha la dimensione completa e quindi il mittente attende di ricevere l ACK del primo segmento. Il mittente attende l ACK ritardato dal destinatario, mentre il destinatario aspetta il secondo segmento per inviare l ACK. Per ovviare a questo problema bisogna disabilitare l algoritmo di Nagle. 5.3 Overhead sul server Web Le operazioni effettuate dal server Web consumano risorse, molte di queste operazioni sono legate al TCP. Per ridurre in parte l overhead sul server o sui proxy si possono combinare o eliminare alcune operazioni. Consideriamo le operazioni per servire nello spazio utente una richiesta HTTP con il metodo GET. Il server notifica al SO di essere interessato a servire richieste Web. Alloca il socket di ascolto e ne effettua il bind() alla porta 80. Chiama listen() sul socket per indicare la sua volontà di ricevere richieste. A questo punto si può usare setsockopt() per disabilitare l algoritmo di Nagle. Successivamente chiama la funzione bloccante accept() per attendere una richiesta e quando accept() ritorna, il nuovo socket di connessione rappresenta la nuova connessione di un client. A questo punto tramite getsockname(), gethostbyname() e gettimeofday() vengono utilizzati per scopi di logging. La read() viene chiamata sul socket di connessione per ottenere la richiesta. La richiesta è individuata effettuando il parsing dei dati letti. Viene usata la funzione stat() per controllare il path della risorsa, ossia verificare se il file esiste ed è accessibile, ed inoltre viene usata anche per ottenere i meta-dati sul file (dimensione del file, last modified time). Assumendo che sia andato tutto per il verso giusto viene chiamata la funzione open() per aprire il file. Una volta che il file è stato aperto, usando le funzioni read(), write() e close() per leggere, scrivere e chiudere il socket Ottimizzazione del server Esiste un elevato grado di località nelle richieste Web e nel traffico Web. Una buona parte del lavoro visto per il server non deve essere eseguito necessariamente per ogni richiesta. Abbiamo due categorie di ottimizzazioni: 1. Caching: l idea consiste nello sfruttare la località delle richieste. Ossia, invece di aprire e chiudere gli stessi file, conviene mettere i descrittori dei file aperti e gestirli con la politica LRU. La stessa cosa può essere fatta per le informazioni sulle caratteristiche dei file oppure per generare gli header HTTP. Per attuare questa ottimizzazione si utilizza la chiamata di sistema mmap() che permette di mappare in memoria il file senza che ne esistano duplicati sia nella memoria utente che in quella del kernel. 50

58 2. Uso di primitive del SO che consentono di combinare più operazioni. Ossia, invece di chiamare accept(), getsockname() e read() si può usare (se disponibile) la nuova chiamata acceptextended() che combina queste tre funzioni. Oppure, al posto di chiamare gettimeofday() si può utilizzare un contatore mappato in memoria, che richiede un accesso più economico in termini di istruzioni macchina. La funzione write() può essere sostituita con la funzione writev() (vedi listing 52) che consiste nell implementazione dell I/O vettorializzato, scrivendo più buffer con una sola chiamata. Invece di chiamare read() e write() (o write() con un file mappato in memoria), si usa la chiamata di sistema sendfile() (o transmitfile()) per copiare direttamente da un descrittore di file ad un altro. In questo modo i byte rimangono nello spazio kernel, anziché essere copiati nello spazio utente per poi essere copiati nel buffer di invio. Inoltre, vi è un trasferimento diretto via DMA dal controller del disco alla scheda di rete. Se possibile (non su Linux), si aggiunge l opzione OPT CLOSE WHEN DONE in modo tale da chiudere la connessione evitando di chiamare esplicitamento la funzione close(). L uso di queste chiamate richiede un adeguato supporto da parte del SO. 1 int writev(int fd, const struct iovec *vector, int count); Figura 52: La funzione writev() 51

59 6 Il server Web Apache Apache sta per l acronimo A PAtCHy server ed è stato sviluppato sul server NCSA a partire dal È un progetto Free ed opensource, supportando vari tipi di SI come Linux, Unix, Microsoft Windows, OS/2, ecc. L architettura di questo server Web è di tipo modulare, ossia è composto da un nucleo (core) piccolo che realizza le funzionalità di base, mentre per estendere queste funzionalità di base si utilizzano i moduli (scritti usando l Apache module API). Questi moduli possono essere compilati staticamente nel nucleo oppure caricati dinamicamente a tempo di esecuzione. È conforme al protocollo HTTP/1.1. È efficiente e flessibile, garantendo stabilità, affidabilità e robustezza. Apache rende disponibili anche le seguenti funzionalità: Autenticazione Negoziazione dei contenuti in base alle capacità del client Virtual Hosting (più siti Web sullo stesso server) Logfile personalizzabili Personalizzazione dei messaggi di errore Possibilità illimitata di URL rewriting, aliasing e redirecting Aliasing (mod alias): crea degli alias per accedere ad una risorsa reale sul server. Questa tecnica è trasparente per il client. Redirecting (mod alias e mod rewrite): redirezione della richiesta verso un altra URL locale o remota. Questa funzionalità non è trasparente per il client. URL rewriting (mod rewrite): manipolazione e riscrittura flessibile dell URL. 6.1 Apache: API Apache fornisce un API per la programmazione di nuovi moduli, supportando linguaggi come C, C++ e Perl. L API permette allo sviluppatore del modulo di disinteressarsi dei dettagli implementativi legati al protocollo HTTP o alla gestione delle risorse del sistema. L API è costituita da un insieme di strutture e funzioni da utilizzare per creare i moduli aggiuntivi. 6.2 Apache: Il servizio HTTP Il servizio HTTP è fornito dal demone httpd che viene eseguito continuamente in background per gestire richieste. I file di configurazione vengono letti al momento dell avvio di httpd. In SO Unix-based, il demone httpd può essere eseguito lanciando lo script apachectl, che configura alcune variabili d ambiente dipendenti dal SO. Con questo comando è possibile specificare quattro operazioni: start: avvia il server; stop: blocca l esecuzione del server; restart: riavvia il server; graceful: riavvia il server senza interrompere le connessioni aperte. 52

60 6.3 Le directory in Apache Apache utilizza le seguenti directory fondamentali: ServerRoot, rappresenta il punto originale dei file di amministrazione del server; bin, contiene gli eseguibili come httpd, apachectl, ab (il benchmark di apache), htpasswd; conf, contiene i file di configurazione come httpd.conf logs, contiene i file di log come apache.log, apache.err; DocumentRoot, rappresenta il punto di origine dei documenti; ScriptCGI, contiene gli script CGI; UserDir, contenente le pagine Web degli utenti del sistema. 6.4 Apache: I moduli L architettura modulare di Apache permette di aggiungere o eliminare funzionalità semplicemente attivando o disattivando moduli software. All avvio del server, è possibile scegliere i moduli che devono essere caricati, indicandoli in un opportuno file di configurazione. Alcuni moduli possono essere inclusi di default nel core del server, questi moduli vengono chiamati moduli standard. I moduli possono essere caricati, quindi staticamente oppure dinamicamente. I moduli caricati dinamicamente sfruttando il meccanismo detto Dynamic Shared Objects (DSO) che permette di costruire un pezzo di codice di programma in un formato speciale e di caricarlo a run-time nello spazio di indirizzamento del programma eseguibile. I moduli di Apache sono scritti in linguaggio C o Perl Multi-Processing Module (MPM) A partire da Apache 2.0 è stato inserito questo modulo. Apache è stato progettato per essere flessibile su ogni tipo di piattaforma e con ogni configurazione d ambiente. È possibile aggiungere moduli MPM per la gestione di operazioni quali il binding, gestione dei processi/thread, connessioni. Un modulo MPM è compilato nel server e permette a SO differenti di fornire moduli appropriati per una maggiore efficienza. All amministratore permette di applicare politiche di gestione diverse in base alle proprie esigenze. Solo un modulo MPM alla volta può essere caricato nel server. Alcuni MPM sono: prefork, mpm winnt, worker, event. Modulo Prefork Il server Apache con il modulo prefork è molto stabile ed è stato implementato per Apache 1.3 che per la versione 2.2. L architettura di questo modulo è di tipo multi-process, ossia esiste un processo principale (padre) ed alcuni processi ausiliari (figli) per il servizio delle richieste. Il padre manda in esecuzione i processi figli (pre-forking dei processi figli), i processi figli attendono le connessioni e le servono quando arrivano. Il preforking è basato sullo schema leader-follower. Questa architettura permette di creare una sola volta un pool di processi figli e di riutilizzarli. La sua implementazione è molto semplice garantendo stabilità e portabilità rispetto ad un server multi-thread puro. I problemi però nascono per gestire il numero di processi figli, e quindi gestire dinamicamente il pool dei processi figli. Le direttive di default per la gestione dei processi figli in Apache vengono definite nel suo file di configurazione, dove: la variabile StartServers indica il numero di processi figli in preforking; 53

61 la variabile MaxClients determina il limite massimo sul numero di processi figli che può essere creato; la variaile MinSpareServers indica il limite minimo di processi figli in idle mode; mentre la variabile MaxSpareServers indica il limite massimo di questi processi; la variabile MaxRequestsPerChild indica il numero massimo di richieste HTTP servite da ciascun processo figlio; allo scadere del numero di richieste il processo child termina. Se questa variabile è impostata a 0 allora il processo figlio non termina. Modulo worker Questo modulo possiede un architettura ibrida. In questo caso abbiamo un processo padre, molteplici processi figli, ciascuno dei quali genera multipli thread di esecuzione. Il procedimento è il seguente: Il processo padre manda in esecuzione i processi figli; Ciascun processo figlio crea un numero fissato di thread worker ed un thread listener; Quando arriva una richiesta, il listener la assegna ad un thread worker che la gestisce. Tale architettura permette di ottenere un alta scalabilità e minor consumo di risorse di sistema. La stabilità è simile ad un server di tipo prefork puro. Purtroppo questo approccio implica che il codice del server è più complesso ed il supporto multithreading dipende dal tipo di SO utilizzato. Usando questo modulo nel file di configurazione abbiamo le seguenti direttive: StartServer contiene i processi figli da creare in fase di inizializzazione; MinSpareThreads e MaxSpareThreads rappresentano il numero minimo e massimo di thread idle; ThreadsPerChild rappresenta il numero di thread creati da ciascun processo child; MaxClients che setta il limite sul numero totale di thread; ServerLimit imposta il numero di processi figli attivi; ThreadLimit che setta il limite sul numero di thread creati da ogni processo figlio. 6.5 Ciclo richiesta-risposta La sequenza di fasi in cui Apache prende decisioni in base alla richiesta ricevuta è gestita dal suo core oppure dai moduli. Nel caso in cui non viene definito alcun modulo gestore per una determinata fase, allora Apache manda in esecuzione il gestore di default. Questa sequenza di fasi è riassunta nel modo seguente: Post-Read-Request: analisi dei principali header presenti nella richiesta HTTP e inizializzazione delle strutture dati utilizzate successivamente dai moduli che implementano le fasi di gestione. URI Translation: l URI può riferirsi ad un file fisico, ad una risorsa dinamica prodotta da uno script esterno, oppure ad una risorsa generata da un modulo interno. Il server deve sapere come individuare la risorsa, prima di poter effettuare decisioni successive: è necessaria la conversione da URI a risorsa presente sul server. Le direttive standard Alias, ScriptAlias e DocumentRoot permettono di tradurre l URI nel nome di un file presente nell albero dei documenti. I moduli esterni come mod rewrite possono assumere il controllo di questa fase ed effettuare traduzioni più sofisticate. Header Parsing: consiste nel fare l analisi dell header della richiesta HTTP, al fine di estrarre informazioni riguardanti il client. 54

62 Access Control: identifica la locazione di provenienza della richiesta. Authentication: richiesta di autenticazione del client. Authorization: controllo dell autenticazione. Mime Type Checking: individua il tipo MIME della risorsa richiesta. Il server deve sapere il tipo della modalità di elaborazione richiesta prima di poter preparare la risposta. Una volta noto il tipo di risorsa, Apache individua il gestore opportuno per la fase di risposta. Fixup: è una fase introdotta per permettere l esecuzione di un qualunque tipo di operazione prima della fase di risposta (ad esempio impostare un cookie). Response: le informazioni riguardanti la risosrsa sono passate al gestore opportuno (content handler), che costruisce l header della risposta HTTP e lo invia al client. Successivamente, la generazione o lettura del contenuto ed invio al client (o errore). Logging: scrittura su logfile dell esito delle operazioni effettuate. Cleanup: operazioni di chiusura, con cui si rilasciano le risorse allocate per la gestione della richiesta (ad esempio liberare la memoria principale, chiudere i file). 6.6 Apache: Virtual Hosting Il virtual hosting, anche detto multi-homing, consente di avere più siti Web ospitati su di una singola macchina. Possiamo avere due tipi di architetture: molteplici demoni httpd oppure un singolo demone httpd. Il virtual hosting può essere di due tipi: basato sul nome di dominio (name-based): al singolo indirizzo IP sono associati più nomi di dominio a livello di DNS. Una o più NIC a cui sono associati più nomi logici, utilizzando l alias CNAME a livello di DNS. Per fare ciò è necessario che il client supporti HTTP/1.1. basato su indirizzo IP (IP-based): il server è dotato di uno o più indirizzi IP (reali o virtuali). Una o più NIC a cui sono associati uno o più indirizzi IP, utilizzando il comando ipconfig alias. 6.7 Apache: I file di configurazione I file di configurazione contengono le informazioni di configurazione, dette direttive, del server stesso e dei moduli usati. Le direttive sono analizzate in sequenza, quindi dobbiamo porre molta attenzione all ordine in cui sono scritte. Il file httpd.conf è il file di configurazione principale, il quale configura il demone (numero di porta, utente, ecc.) e le sue funzionalità. Il file mime.types contiene le definizioni di tipi MIME. I file.htaccess consentono di modificare la configurazione per ciascuna directory del Web tree Apache: httpd.conf In questo file, le direttive sono raggruppate in tre sezioni principali: le direttive che controllano le impostazioni globali: ServerRoot, connessioni persistenti (KeepAlive, MaxKeepAliveRequests, KeepAliveTimeout), Listen, LoadModule, impostazioni sui processi e thread (in base al modulo MPM scelto), ecc. direttive che controllano le impostazioni del server principale: ServerName, DocumentRoot, <Directory>, AllowOverride, Allow, Deny, UserDir, Logging, ecc. parametri di configurazione per il virtual hosting. 55

63 1 ######################################### 2 # Section 1: Global Environment 3 # Many of the values are default values, so the directives could be omitted. 4 ServerType standalone 5 ServerRoot "/etc/httpd" 6 Listen 80 7 Listen Timeout KeepAlive On 10 MaxKeepAliveRequests KeepAliveTimeout MinSpareServers 5 13 MaxSpareServers StartServers 5 15 MaxClients MaxRequestsPerChild ######################################### 19 # Section 2: " Main" server configuration 20 ServerAdmin org 21 ServerName 22 DocumentRoot "/var/www/html" 23 # a very restrictive default for all directories 24 #. htaccess files are completely ignored 25 <Directory /> 26 Options FollowSymLinks 27 AllowOverride None 28 </ Directory > 29 <Directory "/var/www/html"> 30 Options Indexes FollowSymLinks MultiViews 31 AllowOverride None 32 Order allow, deny 33 Allow from all 34 </ Directory > ######################################### 37 # Section 3: virtual hosts 38 <VirtualHost 39 # all hosts in the domain are allowed access; 40 # all other hosts are denied access 41 <Directory /> 42 Order Deny, Allow 43 Deny from all 44 Allow from 45 </ Directory > 46 # " Location" directive will only be processed if mod_status is loaded 47 # To enable status reports only for browsers from foo.com domain 48 < IfModule mod_status.c> 49 <Location /server-status> 50 SetHandler server- status 51 Order Deny, Allow 52 Deny from all 53 Allow from.foo.com 54 </ Location > 55 </ IfModule > 56 </ VirtualHost > Figura 53: Esempio di httpd.conf 6.8 Utilità dei logfile di un server Web I logfile permettono il monitoraggio degli accessi ad un server Web e dello stato del server. Le informazioni memorizzabili nel logfile sono quelle che viaggiano negli header HTTP di messaggi di richiesta e risposta. Generalmente, i server Web permettono di definire quali campi dei messaggi devono essere memorizzati. Questi file inoltre permettono di ottenere informazioni sul capacity planning, billing e riconoscimento di attacchi. 56

64 6.8.1 Informazioni estraibili dai logfile Le informazioni che maggiormente si vogliono trarre da un logfile riguardano: il numero di utenti del sisto e la loro provenienza geografica; i browser utilizzati; giorni ed orari di maggior affluenza; pagine più popolari; errori verificatisi per determinare la presenza di link sbagliati all interno delle pagine del sito; siti che fanno riferimento al propio sito. Ovviamente se abbiamo come client dei proxy possono falsare i risultati ottenuti Common Logfile Format Il W3C ha definito uno standard per il logfile, denominato Common Logfile Format (CLF). Nel Common Logfile, ogni riga rappresenta una richiesta e si compone di più campi, separati da uno spazio. Se il campo non assume alcun valore, viene indicato con il simbolo -. Ogni riga termina con CRLF Logfile custom in Apache La definizione in Apache di un logfile custom avviene mediante l uso della direttiva LogFormat in httpd.conf, come si vede nel listing LogFormat string 2 LogFormat "%h %l %u %t \"%r\" %>s %b" common 3 LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" combined Figura 54: Direttiva LogFormat In questo listing vediamo che gli elementi del campo stringa possono essere: %h: remote host; %l: remote logname; %u: remote user; %t: timestamp della richiesta; \ %r\ : prima riga della richiesta; %>s: codice della risposta; %b: byte trasmessi; \ %{Header}i\ : header della richiesta; \ %{Header}o\ : header nella risposta. 57

65 7 Tencologie per la generazione di contenuti dinamici I contenuti dinamici rappresentano le risorse Web che richiedono l esecuzione di una o più applicazioni prima di poter inviare la risposta al client. Tale risposta può essere generata anche sulla base della richiesta del client. L utente non si accorge che la risorsa identificata dall URL richiesto necessita di un processamento per la generazione del contenuto, pertanto il server trasmette il risultato dell esecuzione. Le risorse dinamiche non sono risorse attive, le risorse attive contengono codice che viene eseguito sul client. I contenuti dinamici possono essere utilizzati per creare un interazione personalizzata sulla base di diversi parametri, sia client-side, sia server-side, oppure permettono l accesso ad informazioni gestite da server non HTTP, oppure effettuare interrogazioni a motori di ricerca. Con le risorse dinamiche, il Web è divenuto l interfaccia per la fornitura di servizi sofisticati come i Web service. 7.1 Livelli logici di un servizio Web-based In un servizio Web-based si possono identificare, a livello logico i seguenti strati: Interfaccia utente: rappresenta ciò che l utente percepisce interagendo con il servizio; Logica di presentazione: rappresenta quello che accade quando l utente interagisce con l interfaccia del servizio Web-based; Logica di applicazione, o business logic: rappresenta tutte le funzionalità offerte dal servizio; costituisce il filtro bidirezionale tra logica di presentazione e logica dei dati; Logica dei dati: rappresenta la gestione fisica dei dati (memorizzazione, ricerca ed aggiornamento di dati), compresa la verifica della loro integrità e completezza. Ovviamente questi livelli logici non vanno confusi con i processi e le macchine che li realizzano Come mappare i livelli logici sui processi e calcolatori Per mappare i livelli logici sui processi esistono quattro alternative: 1. con un solo processo (è un alternativa teorica); 2. con due processi: il processo client gestisce il livello logico dell interfaccia utente, mentre il processo server gestisce tutti e tre i livelli logici restanti (presentazione, applicazione, dati); 3. con tre processi: il processo client gestisce il livello logico dell interfaccia utente, il primo processo server gestisce il livello logico di presentazione ed il secondo processo server gestisce i livelli logici di applicazione e dei dati; 4. con quattro processi: il processo client gestisce il livello logico dell interfaccia utente, il primo processo server gestisce il livello logico di presentazione, il secondo processo server gestisce il livello logico di applicazione e il terzo processo server gestisce il livello logico dei dati. I processi logici possono essere mappati sul calcolatore nei seguenti modi: 1. 2 o 3 processi su 2 calcolatori 2. 4 processi su 2 o più calcolatori 58

66 7.2 Evoluzione delle architetture Web lato server Il livello di logica di presentazione agisce come un interfaccia tra il livello di interfaccia utente ed il livello di logica dell applicazione. Tale livello vine implementato mediante un server HTTP e da eventuali plug-in di business logic. Il livello di logica dei dati gestisce i dati strutturati su supporti di memorizzazione permanente. Questo livello viene implementato tramite Database Management System (DBMS) come MySQL, PostgreSQL, Microsoft SQL server, IBM DB2, Oracle. Il livello di logica di applicazione utilizza tecnologie software per realizzare il middle-tier, come processi esterni al server HTTP, linguaggi di scripting server-side oppure tecnologie distribuite ad oggetti Tecnologie per middle-tier Gli albori La tecnologia che prevede processi esterni al server HTTP implementa la logica di presentazione e la logica di applicazione da 2 processi server separati. I 3 processi server possono essere mappati su 2 calcolatori, dove i processi server per la logica di presentazione e di applicazione si trovano sullo stesso calcolatore. Questa tecnologia è basata su Common Gateway Interface (CGI). Ossia le applicazioni sono realizzate in un qualsiasi linguaggio di programmazione (prevalentemente C o Perl). Il server HTTP crea un nuovo processo per ogni invocazione dell applicazione CGI, quindi per ogni richiesta dinamica. Tale tecnologia permette di appoggiarsi ad un interfaccia standard, isolare la logica di elaborazione dell applicazione dal server HTTP e fornire flessibilità nella scelta del linguaggio. Ma è di tipo stateless, genera molto overhead e non è scalabile pertanto questa soluzione non è accettabile per server che devono gestire un elevato numero di richieste. Prima evoluzione La prima evoluzione ha portato a tecnologie che evitano la creazione di nuovi processi, quindi la logica di presentazione e la logica di applicazione sono integrate nello stesso processo server e i due processi server possono essere mappati su due calcolatori. Tra le tecnologie abbiamo: Fast CGI: permette la condivisione dell istanza di un processo CGI (persistenza del processo). Questi processi persistenti vengono organizzati in un pool, con strategia di dimensionamento del pool tipicamente configurabile. Permette anche l esecuzione di applicazioni Fast CGI remote e quindi tre processi possono essere mappati su tre calcolatori differenti. Server API proprietarie: permettono di inserire librerie condivise nel server HTTP in modo da servire richieste multiple senza creare nuovi processi. Ad esempio NSAPI, ISAPI, le API di Apache. Questa tecnologia però è vulnerabile ed hanno scarsa portabilità. mod perl: è un modulo di Apache in grado di interpretare script Perl all interno del server HTTP. L interprete Perl è persistente all interno di Apache, pertanto non c è overhead come nel caso dell invocazione di un interprete esterno. È stato scelto questo linguaggio dato che è un linguaggio interpretato, portabile e con elevate potenzialità. Java Servlet: rappresenta la risposta di Java alla programmazione CGI. Le componenti software Java vengono compilate e successivamente eseguite in ambiente server-side. Quindi viene creato un thread per richiesta, anziché un processo come avveniva con CGI. Ciò permette di ottenere una maggiore efficienza di esecuzione rispetto al modello CGI. Utilizza è un estensione standard di Java, pertanto è portabile, effettua il caching delle computazioni precedenti (persistenza in memoria della servlet), connessioni persistenti a DB, sicurezza, robustezza. Ma con questa tecnologia il livello di presentazione non è separata dalla generazione dei contenuti e richiede una conoscenza approfondita da parte dello sviluppatore di Java. 59

67 Seconda evoluzione La seconda evoluzione delle tecnologie middle-tier consiste nell introduzione di linguaggi di scripting HTMLembedded. Ossia viene il codice di scripting viene incluso all interno del testo HTML statico utilizzando una sintassi orientata ai tag. Tale script HTML-embedded viene processato da un interprete che elabora l intero template HTML ed incorpora l output nel testo HTML; quindi al client viene restituita la risorsa finale. La logica di presentazione e di applicazione sono generalmente integrate nello stesso processo server. Il predecessore dei linguaggi di scripting HTML-embedded è la tecnologia Server Side Includes (SSI). Questa permette tramite semplici direttive incluse come commenti SGML all interno del file HTML 8 e processate dal server prima di inviare la risposta HTTP. Questa tecnologia però include un numero ridotto di funzionalità offerte. La seconda evoluzione è rappresentabile dai seguenti linguaggi di scripting: PHP Hypertest Preprocessor (PHP): è un linguaggio di scripting general-purpose. I file hanno estensione.php. Per poter funzionare integra un modulo all interno del server Web. Con questa tecnologia, però, la logica di presentazione e di applicazione risiedono sullo stesso calcolatore. Java Server Pages (JSP): ogni pagina JSP viene convertita e compilata in una servlet Java alla prima richiesta di accesso. I file hanno estensione.jsp. Utilizzando il linguaggio Java si ottiene un alta portabilità del codice e maggiore potenza per applicazioni complesse che richiedono componenti riusabili. La logica di presentazione e di applicazione, inoltre, possono trovarsi anche su calcolatori diversi. Active Server Pages (ASP): è la soluzione proprietaria di Microsoft. I file hanno estensione.asp. Utilizza script di diversi linguaggi come VBScript. Questa tecnologia, però, è specifica per il server IIS e ci sono grossi problemi di manutenibilità dell HTML/script integrato. Terza evoluzione La terza evoluzione per tecnologie middle-tier consente di usare tecnologie distribuite ad oggetti ed a componenti. Con l evoluzione tecnologica delle piattaforme hardware, aumentano le aspettative sui servizi erogabili via Web. Il che significa un considerevole aumento della complessità del middle-tier diventando una vera e complessa business logic, permettendo inoltre di accedere a DB multipli, insiemi di file XML, directory service, ecc. Le applicazioni diventano sempre più complesse, quindi nasce la necessità di rendere l applicazione modulare, portabile sia a livello spaziale che temporale, manutenibile e riusabile. Le tecnologie per lo scripting HTML-embedded mirano principalmente all incremento delle prestazione, permettono un rapido sviluppo di applicazioni ma offrono scarsi strumenti di ingegnerizzazione del software. La logica di applicazione complessa rende necessaria una sua separazione dalla logica di presentazione. Come abbiamo precedentemente detto, le tecnologie distribuite ad oggetti e a componenti rispondono a requisiti di modularità, portabilità e manutenibilità di una business logic complessa, ovvero per portabilità intendiamo che il codice può essere eseguito dove serve, migliora la generalità cioè molte applicazioni possono usare business logic comuni, migliora la manutenibilità con una buona interfaccia (insieme di metodi pubblici) e i cambiamenti influenzano solo l oggetto. Esempi di tecnologie distribuite ad oggetti sono rappresentati da framework generali non specifici per il Web come Java EE e Microsoft.NET Web Application Server I Web Application Server (WAS) implementano il livello di business logic dell applicazione Web. Ossia fornisce l ambiente di esecuzione per il supporto dei linguaggi che si vogliono utilizzare per realizzare il servizio basato su Web, comprende tutte le possibili funzioni che sottendono le operazioni dinamiche, 8 I file hanno estensione.shtml. 60

68 traduce le richieste dell utente in operazioni che interagiscono con applicativi e/o con il livello logico dei dati. Un WAS è implementato mediante una miriade di tecnologie. I più conosciuti WAS sono: Apache Tomcat, JBoss, IBM WebSphere Application Server, Oracle Application Server Framework per applicazioni Web Alcuni framework per applicazioni Web sono: Apache Struct: applicazioni in Java (piattaforma JEE), design pattern architetturale MVC, open source. WebWork: applicazioni in Java. Apache Tapestry: applicazioni in Java, open source. Apache Cocoon: open source, separazione dei componenti dell applicazione. Apache Shale: basato su JavaServer Faces 9, open source. 7.3 Tecnologia CGI Permette al server Web di interfacciarsi con un applicazione e di passargli la richiesta ed i parametri provenienti dal client. Questa tecnologia possiede una logica simile ad un filtro Unix. È stata implementata da NCSA per ottenere la generazione di contenuti dinamici ed è ancora abbastanza diffusa per la sua flessibilità. Un applicazione CGI stabilisce la modalità di integrazione tra server HTTP ed applicazione. Permette al client di eseguire un applicazione sulla piattaforma server. Di conseguenza, permette al server di connettersi ad altri servizi per eseguire applicazioni esterne in grado di creare risorse dinamicamente e di poter recuperare informazioni dall utente. Per script intendiamo un qualsiasi programma, tipicamente, ma non necessariamente, implementato in un linguaggio interpretato, che viene eseguito dal server. Il gateway è uno script che fornisce accesso ad un servizio svolgendo un ruolo di interfaccia tra il server Web ed un altro server Azioni ed implementazione di uno script Uno script deve tradurre l input fornito dal client con una richiesta HTTP in forma comprensibile al servizio a cui lo script si collega. Invoca l attivazione di altri programmi eseguibili e traduce l output del programma in una forma comprensibile al client. Lo script può essere implementato in un qualsiasi linguaggio di programmazione. È sufficiente che lo script sia in grado di leggere da standard input, scrivere su standard output e leggere le variabili d ambiente. Quando un browser richiede una URL che identifica un applicazione da eseguire, il server HTTP svolge un semplice ruolo: avvia lo script e passa i dati dal browser allo script e viceversa. Il passaggio corretto dei dati tra il server HTTP e lo script è garantito da CGI. Tali script vengono tipicamente inseriti in un apposita directory. CGI definisce un insieme di variabili di ambiente utili all applicazione. Il server Web deve permettere la gestione delle variabili di ambiente. Passi nell esecuzione CGI Il client specifica nell URL il nome del programma. Il server HTTP deve determinare che la risorsa richiesta è un programma. Ne trova la sua posizione e controlla se può essere eseguito. In quest ultimo caso, lancia in esecuzione lo script passandogli l eventuale input fornito dal client; infatti, il server decodicica i parametri inviati dal client ed assegna i valori delle variabili d ambiente. L applicazione può utilizzare le variabili d ambiente, stampa la sua risposta sullo standard output. 9 JavaServer Faces è una tecnologia che permette di semplificare lo sviluppo dell interfaccia utente dell applicazione Web 61

69 Il server HTTP deve leggere da standard output l output dello script e redirezionarlo verso il client. Chiude la connessione solo quando l esecuzione dello script è terminata. Interazione tra client e server HTTP Una URL viene invocata direttamente tramite il metodo GET della richiesta MTTP. L utente specifica l URL di un CGI o seleziona un collegamento all URL di un CGI. Per inviare un form vengono usati i metodi GET e POST nella richiesta HTTP. Il browser visualizza il form all utente mediante una pagina speciale HTML detta fill-in form, acquisisce i dati di input inseriti dall utente e spedisce al server il form compilato. Tag form HTML L uso del tag <FORM> permette di acquisire l input inserito dall utente. Tramite l attributo ACTION del tag <FORM> si identifica l applicazione CGI, passando come parametro l input inserito dall utente. L invio dei dati avviene tramite l attributo SUBMIT del tag <INPUT>, vedi listing <FORM ACTION="http://servername/cgi-bin/test" METHOD= "GET"> 2 <INPUT TYPE="TEXT" NAME="var1" VALUE=""> 3 <INPUT TYPE="SUBMIT" NAME="Submit" VALUE="SEND"> Figura 55: Form di tipo GET L informazione della fill-in form è inviata al server come parte dell URL se il metodo specificato nell attributo METHOD del tag <FORM> è uguale a GET, oppure come corpo della richiesta se l attributo METHOD è uguale a POST. Invio di parametri con metodo GET I parametri con il metodo GET sono codificati nell URL specificato con ACTION. Sono recuperabili dallo script CGI tramite la variabile d ambiente QUERY STRING. Dall esempio del listing 55, l URL generata è: mentre la variabile QUERY STRING è pari a: var1=value1&var2=value2 Invio di parametri con metodo POST I dati vengono inviati al server in una linea separata, senza limite al numero di caratteri inviati. Questi caratteri sono recuperabili tramite standard input. La dimensione dell input viene specificata nell header Content-Length della richiesta HTTP. È possibile inviare anche informazioni non testuali, specificando il tipo nell header Content-Type della richiesta HTTP. 1 <FORM ACTION="http://servername/cgi-bin/test" METHOD="POST"> 2 <INPUT TYPE="TEXT" NAME="Var1" VALUE=""> 3 <INPUT TYPE="TEXT" NAME="Var2" VALUE=""> 4 <INPUT TYPE="SUBMIT" NAME="Submit" VALUE="SEND"> Figura 56: Form di tipo POST Dall esempio descritto nel listing 56, lo script CGI riceve dallo standard input il seguente valore: 62

70 Variabili d ambiente Una variabile d ambiente è un parametro con un nome per trasferire informazioni dal server allo script CGI. Questa variabile non è una variabile dell ambiente del sistema operativo, anche se questa è l implementazione più comune. Le principali variabili d ambiente sono: SERVER SOFTWARE: nome e versione del server; SERVER NAME: hostname o indirizzo del nodo server; GATEWAY INTERFACE: versione CGI; QUERY STRING: informazione contenuta dopo il? nell URL; REQUEST METHOD: metodo della richiesta HTTP; REMOTE ADDR: indirizzo IP del nodo client che effettua la richiesta; REMOTE USER: che lo script è protetto da autenticazione utente; HTTP USER AGENT: nome e versione del client; CONTENT TYPE: tipo di input inviato con il metodo POST; CONTENT LENGTH: dimensione dell input inviato con il metodo POST. Output da CGI Quando il server HTTP restituisce al client una risorsa (statica o generata dinamicamente), include nell header di risposta alcune informazioni sulla risorsa. Queste informazioni sono inviate indipendentemente dal risultato dell applicazione CGI. Quindi, alcuni header sono generati dal server Web mentre altri sono generati dallo script CGI. Lo script CGI può aggiungere tre tipi di informazioni: Content-type: formato MIME della risorsa; Location: URL alternativa per localizzare la risorsa; Status: risposta HTTP. 1 # include <stdio.h> 2 void main() { 3 printf("content-type: text/html\n\n"); 4 printf("<html>\n"); 5 printf("<head><title>cgi Output</title></head>\n"); 6 printf("<body>\n"); 7 printf("<h1>hello, world.</h1>\n"); 8 printf("</body>\n"); 9 printf("</html>\n"); 10 exit(0); 11 } Figura 57: Script CGI in C Configurazione di Apache per CGI Per abilitare gli script CGI sul server Apache si usa la direttiva ScriptAlias. Gli script CGI vengono spesso confinati nella directory di default per evitare vulnerabilità di sicurezza. In alternativa, per abilitare una directory arbitraria si usano le direttive AddHandler ed Options +ExecCGI. 63

71 1 #!/usr/bin/perl -w 2 use CGI; 3 $query = new CGI; 4 $secretword = $query->param( w ); 5 $remotehost = $query- > remote_host(); 6 print $query->header; 7 print "<p>the secret word is 8 <b>$secretword</b> and your IP is 9 <b>$remotehost</b>.</p>"; Figura 58: Script CGI in Perl 7.4 Java Servlet Una servlet è un componente software scritto in Java, gestito da un container, in grado di ricevere in modo strutturato i parametri e generare dinamicamente la risorsa richiesta. Una servlet interagisce con un client in base al paradigma richiesta/risposta. Le servlet sono una tecnologia cross-platform, in quanto le API non fanno assunzioni sull ambiente di esecuzione o sul protocollo. Le servlet sono indipendenti dalla piattaforma dato che usano una JVM; si integrano con le altre componenti ed API di Java. Utilizzano un paradigma orientato agli oggetti, pertanto possiedono molte funzionalità di alto livello. Sono efficienti dato che usano i thread Java anziché processi. Ogni servlet è persistente, ossia dopo il caricamento, la servlet rimane in memoria e può rispondere a richieste multiple mantenendo alcune informazioni. È in grado di gestire sessioni ed è una tecnologia robusta e sicura I compiti di una servlet Una servlet legge i dati impliciti inviati dal client, ossia presenti nella linea di richiesta e negli header della richiesta HTTP. Legge i dati espliciti inviati dal client nel form. Genera il risultato. Invia i dati impliciti al client tramite una linea di risposta e gli header della risposta HTTP. Invia i dati espliciti come file HTML. Le servlet usano le classi e le interfacce definite nei package javax.servlet e javax.servlet.http. L interfaccia pubblica è Servlet, mentre le interfacce generiche per le richieste e le risposte sono ServletRequest e ServletResponse. Una servlet generica estende la classe astratta javax.servlet.genericservlet, mentre le servlet HTTP estendono la classe astratta javax.servlet.http.httpservlet. Le Le interfacce pubbliche per HTTP sono HttpServletRequest e HttpServletResponse, queste forniscono alla servlet HTTP informazioni rispettivamente sulla richiesta e sulla risposta. L interfaccia pubblica per le sessioni HTTP è HttpSession che fornisce i meccanismi per identificare una sessione dell utente per memorizzare informazioni sull utente Ciclo di vita di una servlet Il servlet container carica ed inizializza la servlet mediante il metodo init(). Questo viene invocato solo quando la servlet è caricata per la prima volta, non viene chiamato per ogni richiesta (a meno che la servlet non venga ricaricata). È possibile costruire un metodo init() in overriding. La servlet gestisce zero o più richieste dai client. Mediante il metodo service() legge la richiesta e produce una risposta dai suoi parametri di tipo ServletRequest e ServletResponse. Chiamato un nuovo thread dal servlet container per gestire ogni richiesta; in service() avviene il dispatching verso doget(), dopost(), doxxx() in base al metodo HTTP della richiesta. Su questi ultimi metodi non è possibile effettuare overriding. Il servlet container rilascia l istanza della servlet mediante il metodo destroy(). Non viene chiamato per ogni richiesta, ma permette alla servlet il clean-up di qualsiasi risorsa prima che la servlet sia scaricata. È possibile costruire un metodo destroy in overriding. 64

72 HttpServletRequest e HttpServletResponse L interfaccia HttpServletRequest consente l accesso agli header HTTP, ai metodi di richiesta, ai cookie, ai parametri della richiesta. L interfaccia HttpServletResponse costruisce l header di risposta ed il testo della risposta (che può essere testuale o binario). 1 import java.io.*; 2 import javax. servlet.*; 3 import javax.servlet.http.*; 4 public class HelloServlet extends HttpServlet { 5 public void doget( HttpServletRequest request, 6 HttpServletResponse response) 7 throws ServletException, IOException { 8 response. setcontenttype(" text/ html"); 9 PrintWriter out = response. getwriter(); 10 String doctype = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD" + 11 "HTML 4.0 Transitional//EN\">\n"; 12 out.println(doctype + "<HTML>\n" + "<HEAD><TITLE> " + 13 "This is the output from HelloServlet</TITLE></HEAD>\n" + 14 "<BODY BGCOLOR=\"#FDF5E6\">\n" + 15 "<H1>Hello</H1>\n" + "</BODY></HTML>"); 16 } 17 } Figura 59: Esempio Java Servlet Servlet Container Il servlet container consente l esecuzione della servlet e gestisce l interazione con il server Web. Le tipologie di servlet container sono: Autonomo: componente software separata dal server Web, che include un supporto nativo per le servlet. La comunicazione avviene tra due entità gestite da un connector. Servlet container autonomi possono essere: Apache Tomcat, Jetty. Aggiuntivo: integrabile direttamente in server Web come modulo. Un servlet container aggiuntivo è Macromedia JRun. Incorporato in un application server: può anche essere in modo indipendente dal server Web, se nell application server è implementato HTTP. Alcuni servlet conainer incorporati in application server sono: Apache Tomcat, Caucho Resin, Oracle Application Server, IBM WebSphere Application Server. 7.5 JSP È un linguaggio di scripting HTML-embedded server-side. Una pagina JSP contiene sia HTML che codice. Il client effettua la richiesta per la pagina JSP, la parte HTML viene passata al client senza trasformazione. Il codice viene eseguito sul server e viene generato il contenuto dinamico. La pagina così creata viene inviata al server. La tecnologia JSP condivide molte caratteristiche con le Java Servlet come l integrazione con Java, sicurezza e portabilità. Ma rispetto alle Java Servlet sono molto più semplici da sviluppare. Permette di creare liberie di tag JSP che fungono da estensioni dei tag standard. Il JSP container ha il compito di eseguire le pagine JSP. Queste pagine vengono trasformate in Java Servlet dal JSP container. Le classi di implementazione sono conservate in una cache per migliorare le prestazioni. 65

73 1 <HTML > 2 <HEAD > 3 <TITLE>hello jsp</title> 4 <%! String message = "Hello, World, from JSP"; %> 5 </HEAD> 6 <BODY > 7 <h2><font color="#aa0000"><%= message%></font></h2> 8 <h3><font color="#aa0000"> 9 Current time: <%= new java.util.date() %> 10 </font></h3> 11 </BODY> 12 </HTML> Figura 60: Esempio di pagina JSP 1 public class HelloWorldServlet implements Servlet { 2 public void service( ServletRequest request, 3 ServletResponse response) 4 throws ServletException, IOException { 5 response.setcontenttype( text/html ); 6 PrintWriter out = response. getwriter(); 7 out.println( <html><head><title>hello</title></head> ); 8 out.println( <body><h2>hello, World, from Java Servlet</h2> ); 9 out.println( It s + (new java.util.date()).tostring()); 10 out.println( </body></html>); 11 } 12 } Figura 61: Esempio Java Servlet relativo al listing Elementi JSP Una pagina JSP è un alternanza di modelli di testo (HTML o altro) ed elementi JSP. Le pagine JSP permettono di utilizzare elementi di programmazione differenti (HTML, EJB, servlet). Gli elementi di direttiva impostano le informazioni sulla pagina che viene generata e sono gli stessi per ogni richiesta. Gli elementi di azione gestiscono le informazioni disponibili al momento della richiesta. Forniscono un livello di astrazione per incapsulare agevolmente compiti comuni; generalmente sono impiegati per creare o manipolare oggetti, sopratutto JavaBeans. Gli elementi di scripting permettono di includere porzioni di codice vero e proprio (tipicamente codice Java). 7.6 Apache Tomcat Apache Tomcat è l implementazione di riferimento per le JSP. È free ed opensource e viene utilizzato in concomitanza con le Java Servlet e le JavaServer Pages. Questo software è svuluppato dalla Apache Software Foundation. In modalità standalone può essere usato in modo indipendente dal server Web Apache. Questa modalità include il supporto del protocollo HTTP e serve anche pagine statiche. In alternativa, può essere usato insieme al server Web Apache tramite il modulo mod jk. Apache serve le pagine statiche, mentre Tomcat gestisce solo le pagine JSP. Viene usato, in questo caso, il protocollo AJP per gestire la comunicazione tra Apache e Tomcat, dove le connessioni sono TCP persistenti in formato binario. Con Apache di fronte a Tomcat aumentano le possibilità di configurazione sofisticate come URL rewriting, bilanciamento del carico, ecc. 7.7 PHP Il PHP è un linguaggio di scripting HTML-embedded server-side. In realtà, è un linguaggio general-purpose dato che è possibile creare applicazioni shell tramite un opportuna interfaccia a riga di comando. 66

74 È il sistema più usato e l architettura tipica viene detta LAMP (Linux Apache MySQL PHP). Per eseguire del codice PHP viene utilizzato il PHP preprocessor, il quale elabora la pagina eseguendo il codice PHP prima di inviarla al client Caratteristiche di PHP Il linguaggio non strettamente tipato. La sua sintassi di base è simile a Perl e C. Comprende costrutti base per i salti condizionali (if, elseif, else), cicli di interazione (for, while), definizione di funzioni. Non necessaria la dichiarazione esplicita di variabili dato che ogni variabile inizia con $, successivamente l engine tiene traccia del primo tipo di dato assegnato. Un altra caratteristica è data dalla presenza di array associativi che contengono coppie nome-valore. Per default, uno script PHP genera un file HTML. Si possono fornire anche altri tipi di contenuto, come ad esempio file binari o modifiche dell header. Tramite le variabili $PHP AUTH USER e $PHP AUTH PW si gestisce il controllo degli accessi. Con la variabile PHPSESSID e la funzione setcookie() si interagisce con i cookie. Questo linguaggio supporta molti DB, per ognuno sono state specificate delle funzioni PHP proprie. 1 <html > 2 <head > 3 <title>php Hello</title> 4 </head> 5 <body > 6 <? echo "Hello world!";?> 7 <? if(strstr($http_user_agent,"msie")) {?> 8 <center><b>you are using Internet Explorer</b></center> 9 <? } else {?> 10 <center><b>you are not using Internet Explorer</b></center> 11 <? }?> 12 </body> 13 </html> Figura 62: Esempio PHP 7.8 Apache e PHP In Apache, l interprete PHP è un modulo chiamato mod php. Nel file di configurazione httpd.conf di Apache dobbiamo aggiungere le seguenti righe: LoadModule php5 module modules/libphp5.so AddType application/x-httpd-php.php DirectoryIndex index.php index.htm index.html Il modulo ridefinisce la fase di fixup nel servizio delle richieste per risorse di tipo x-httpd-php. Tale fase avviene subito prima della fase di risposta. Durante la fase di fixup viene invocato l interprete PHP. In questa modalità di funzionamento, PHP deve essere integrato nel server Web. Pertanto la logica di presentazione e di applicazione si trovano sullo stesso calcolatore. 7.9 Middleware Un middleware letteralmente significa software che sta in mezzo. Consortium è la seguente: La definizione dell ObjectWeb In un sistema distribuito, il midleware è il livello software che si trova tra il sistema operativo e le applicazioni su ogni lato del sistema Un middleware viene usato per sviluppare sistemi distribuiti, rappresentando il software di supporto alle applicazioni distribuite tra il supporto di comunicazione e le applicazioni. Pertanto è un collante per realizzare sistemi informatici compressi. 67

75 Il middleware permette di mascherare molti dettagli dipendenti dalle diverse piattaforme hw/sw e la distribuzione, facilita l integrazione e consente allo sviluppatore di applicativi di lavorare sul problema ad un più alto livello di astrazione. Essendo i sistemi veramente complessi, il primo, e spesso, unico obiettivo consiste proprio nel farli funzionare. Quindi la maggior parte del processo di svilupo è speso nell integrazione delle componenti e nel farle funzionare. Mentre le prestazioni sono considerate un fattore secondario. I due middleware più usati sono la SUN Java Platform, Enterprise Edition (Java EE) e Microsoft.NET Java EE Questo middleware prova a creare uno standard che consenta alle applicazioni basate sul Web di essere portabili tra server, ossia attua i paradigmi Write Once, Run Everywhere. Fornisce al server il controllo del ciclo di vita delle componenti e delle altre risorse in termini di scalabilità, concorrenza, gestione flessibile delle transazioni, gestione delle sessioni utente, sicurezza. La sua architettura è basata su componenti per il progetto, lo sviluppo e l assemblaggio di applicazioni enterprise e mission critical basate su Web. Il modello di sviluppo permette di creare applicazioni distribuite multi-livello (architetture n-tier) indipendenti dalla piattaforma. Per applicazione software enterprise si intende quell applicativo che facilita la gestione delle attività dell impresa, interagendo con i clienti e partner via Internet, facilitando l interazione tra le varie parti di un impresa (eventualmente distribuite geograficamente), gestendo il business. Java EE non è un linguaggio, ma un insieme di diverse tecnologie software. Componenti Java EE Un componente Java EE è un componente software auto-contenuto (building block), riusabile e che viene assemblato con altri componenti per formare un applicazione enterprise distribuita. Un componente viene eseguito all interno di un contesto, detto container. I componenti fondamentali nelle specifiche Java EE sono le Applet Java e applicazioni client, Tecnologie Java Servlet e JSP ed Enterprise Java Beans (EJB). EJB L EJB server è un application server che permette di usare i componenti da parte di client remoti. Fornisce ai container servizi di gestione delle risorse del sistema, mantenimento dello stato, gestione/attivazione di processi/thread, sicurezza. Gestisce le politiche di ottimizzazione delle risorse. Esempi di EJB server sono JBoss, IBM WebSphere Application Server, BEA Web Logic Server. Gli EJB container ospitano ed eseguono i singoli EJB. Un Enterprise Bean rappresenta il componente vero e proprio. È una classe Java specializzata in cui risiede la logica di business dell applicazione. Deve essere installato sull application server tramite la tecnica di deployment dell applicazione stessa. Usa servizi offerti dall ambiente di esecuzione EJB come transazioni, sicurezza e persistenza. Esistono due tipologie principali di EJB: Session Bean (stateful o stateless): oggetto distribuito con o senza stato della conversazione, implementa la logica di business. Message Driven Bean: oggetto distribuito per la gestione di messaggi asincroni. Tramite la tecnologia EJB si ottiene una notevole semplificazione del processo di sviluppo. Il codice è riusabile, modulare, robusto. Gestisce automaticamente transazioni (commit, rollback e recovery), è scalabile e sicuro. Garantisce alte prestazioni tramite un bilanciamento dinamico dei carichi di lavoro e il caching delle connessioni al database. 68

76 8 Sistemi Web distribuiti localmente e geograficamente Siti Web popolari sono soggetti a milioni di HIT al giorno, con l evoluzione dei servizi basati sul Web questi siti sono diventati più complessi e richiedono la generazione di contenuti dinamici e sicuri. Gli utenti, avendo a disposizione collegamenti Internet a banda larga, vogliono che una pagina web sia caricata in breve tempo. Le componenti che generano ritardi nelle connessioni a pagine Web sono soggetti ai ritardi del client, di rete, del server, di DNS. Da recenti statistiche è il server Web a generare i ritardi più grandi. Le ottimizzazioni apportate lato server si chiamano scale-up e scale-out. Le ottimizzazioni di tipo scale-up consistono in interventi sia a livello di SO, come evitare copie multiple dello stesso oggetto o politiche di scheduling diverse da round-robin; che modifiche del software del server Web. Mentre le ottimizzazioni di tipo scale-out, a livello di content/service provider i server possono essere distribuiti localmente, globalmente o integrati con meccanismi di caching; a livello di intermediari può essere usato il caching cooperativo o una Content Delivery Network. 8.1 Sistemi Web Distribuiti I sistemi Web possono essere distribuiti localmente (web cluster) o globalmente (web multi-server e web multi-cluster). Sono sistemi Web scalabili basati su piattaforme con server multipli. Questi sistemi possiedono un meccanismo di routing per indirizzare le richieste client al nodo migliore. Tale meccanismo possiede un algoritmo di distribuzione (dispatching) per individuare il nodo migliore. Esiste quindi un componente del sistema Web che esegue tale algoritmo usando il meccanismo di routing. I web cluster hanno un dispatching a livello 4 della pila OSI. I Web multi-server, invece, possiedono un dispatching di due fasi: DNS + Web Switch. I Web multi-cluster possiedono un dispatching di tre fasi: DNS + Web Switch + Server. Questi ultimi due tipi di sistemi Web distribuiti lavorano a livello 7 della pila OSI Web Cluster Un Web Cluster implementa un architettura parallela o distribuita localmente per reggere il sito Web. Il sito Web possiede un solo hostname ed un solo indirizzo IP (virtual IP address o VIP). Il Web server possiede degli indirizzi IP mascherati all esterno, quindi si utilizza un Web switch di 4 livello come dispatcher, il cui indirizzo IP è l indirizzo IP del sito Web (VIP). Il Web Switch è la componente di rete con il ruolo di dispatcher. Esso possiede il mapping da VIP all indirizzo IP di un server. I pacchetti entranti con indirizzo VIP sono indirizzati dal Web Switch. Questa componente può essere un dispositivo hardware special-purpose, un modulo software eseguito a livello kernel (SO special-purpose) o un modulo software eseguito a livello applicativo (SO generalpurpose). I Web Switch possono essere di livello 4 (Content Information Blind) dove detiene informazioni riguardante sorgente e destinazione: indirizzi IP, numeri di porta TCP, i bit SYN/FIN nell header TCP. Oppure possono essere di livello 7 (Content Information Aware) dove detiene informazioni riguardanti URL, cookie, altri header HTTP, SSL id. Entrambe questi due tipi di Web Switch possono essere specializzati in Web Switch One-Way o Two-Way. La classificazione delle architetture è basata: il livello della pila OSI a cui opera il Web Switch, il percorso seguito dai pacchetti dove i pacchetti in ingresso (inbound) passano sempre dallo switch, mentre i pacchetti in uscita (outbound) per architetture Two-Way passano anche dallo switch o per architetture One-Way passano attraverso un altra connessione. il meccanismo di routing utilizzato dal Web Switch per reindirizzare i pacchetti inbound verso i server, come il packet rewriting o il TCP handoff. 69

77 Packet Packet Packet TCP gateway TCP splicing TCP handoff TCP conn. hop rewriting forwarding tunneling Web Switch di livello 4 Questo tipo di Web Switch opera a livello TCP/IP. La connessione TCP avviene usando pacchetti appartenenti alla stessa connessione TCP devono essere assegnati allo stesso server. Il Web Switch utilizza una binding table per la gestione delle connessioni TCP attive. Il Web Switch esamina l header di ogni pacchetto, se riceve una nuova connessione (SYN) viene assegnato un server, mentre se la connessione è esistente allora viene eseguita una ricerca sulla binding table. Se questo Web Switch è di tipo Two-Way ogni server ha il suo indirizzo IP privato. I pacchetti in uscita devono riattraversare il Web Switch, il quale modifica dinamicamente sia i pacchetti entranti sia quelli uscenti: l indirizzo IP destinazione dei pacchetti entranti viene tradotto da VIP a IP del server; l indirizzo IP sorgente dei pacchetti uscenti viene tradotto da IP del server a VIP; si ricalcola il checksum IP e TCP. Questa tecnica è basata sul meccanismo di NAT (Network Address Translation). In alternativa, se il Web Switch è di tipo One-Way può utilizzare le seguenti tecniche: packet single-rewriting, dove lo switch modifica solo i pacchetti IP entranti, il server modifica i pacchetti IP in uscita, ossia dall IP del server al VIP; packet forwarding, dove non avviene la modifica dei pacchetti IP entranti ed uscenti: i pacchetti sono inoltrati a livello MAC, ossia avviene un reindirizzamento del frame MAC. Questa tecnica garantisce meno overhead sullo switch per ogni pacchetto ricevuto, ma il Web Switch ed il server Web devono trovarsi sulla stessa sottorete fisica. packet tunnelling: il pacchetto IP originale viene incapsulato dallo switch in un altro pacchetto IP, il cui header contiene: VIP come indirizzo IP sorgente e indririzzo server come indirizzo IP destinatario. Web Switch di livello 7 Questo tipo di Web Switch opera a livello applicativo. Stabilisce la connessione TCP con il client ed attende la richiesta HTTP. Ispeziona il contenuto della richiesta HTTP per decidere a quale server inoltrarla, ossia avviene un parsing della linea di richiesta e degli header HTTP. Infine gestisce i pacchetti in ingresso con degli ACK. Le principali caratteristiche del Content Based Routing: consente il partizionamento dei contenuti/servizi del sito Web tra diversi server (eventualmente specializzati); favorisce l utilizzo di meccanismi di caching; supporta il dispatching a granularità fine delle richieste HTTP effettuate tramite connessioni persistenti. Questo Web Switch può essere di tipo Two-Way, utilizzando queste due tecniche: 1. TCP gateway: il Web Switch è realizzato mediante in proxy, il forwarding dei dati è realizzato a livello applicativo. Questa tecnica ha un overhead elevato dato che ogni richiesta attraversa sullo switch tutto lo stack di protocolli. 2. TCP splicing: è un ottimizzazione del TCP gateway poiché il forwarding dei pacchetti è realizzato a livello TCP. Il primo pacchetto determina la scelta del server e l instaurazione della connessione persistente fra il Web Switch ed il server scelto. I pacchetti successivi vengono trasmessi dal Web Switch a livello TCP. Questa tecnica richiede modifiche del kernel del SO del Web Switch. 70

78 Oppure può essere di tipo One-Way, il quale utilizza la tecnica nota come TCP handoff: la connessione TCP viene stabilita con il Web Switch; il Web Switch passa (handoff) la connessione al server, che gestisce il servizio ed invia direttamente la risposta al client. Questa tecnica richiede modifiche del kernel dei SO del Web Switch e dei server. Algoritmi di distribuzione Gli algoritmi di distribuzione possono essere di tipo statico (stateless) oppure di tipo dinamico (state aware). Gli algoritmi dinamici possono gestire informazioni sui client (client info aware), informazioni sullo stato dei server (server state aware) oppure informazioni sui client e sullo stato del server (client info & server state aware). Gli algoritmi di distribuzione statici sono di facile implementazione e generano sul Web Switch un overhead trascurabile, ma è possibile che ci siano possibili situazioni di sbilanciamento del carico. Mentre l implementazione degli algoritmi dinamici è più complessa, l overhead di comunicazione (tra switch e server) e di computazione (sullo switch) non è trascurabile, ma permettono un miglior bilanciamento del carico a parità di politica adottata. Per Web Switch di livello 4 abbiamo algoritmi sia statici che dinamici di tipo content blind. In questo caso non servono algoritmi particolarmente sofisticati. Gli algoritmi statici forniscono prestazioni confrontabili a quelle di algoritmi dinamici nel caso di richieste/servizi Web che rientrano in intervalli temporali di 2 ordini di grandezza, oltre questa soglia è opportuno usare algoritmi dinamici (client o preferibilmente server state aware). Gli algoritmi statici possono essere Random, Round Robin, mentre gli algoritmi dinamici server state aware attuano una distribuzione delle richieste in base allo stato di carico dei server, ad esempio Least loaded, Weighted Round Robin. Mentre per Web Switch di livello 7, dato che usano una connessione applicativa (HTTP), usano algoritmi di distribuzione dinamici di tipo content aware 10. Gli algoritmi di distribuzione client info aware possono essere: identificatore di sessione, dove associa richieste HTTP con stesso SSL id o stesso cookie allo stesso server; partizionamento del contenuto tra i server, dove in base al tipo di risorsa si utilizza un server specializzato per quel determinato tipo di risorsa. In base alla dimensione della risorsa si prova ad aumentare la condivisione del carico, pertanto le soglie di partizionamento vengono determinate staticamente o dinamicamente. In tal caso la dimensione è nota solo per risorse statiche, ma è da stimare per le risorse dinamiche. In base ad una funzione hash applicata sul path della risorsa ha come obiettivo di aumentare il cache hit rate nei server Web. CAP (Content Aware Policy) sfrutta le informazioni relative alla tipologia della richiesta da assegnare. Richiede un meccanismo di classificazione dinamica delle richieste effettuabile in base al tipo di servizio (individuabile dall URL) come CPU-bound, Disk-bound e Network-bound. Queste richieste vengono poi ripartite tra tutti i server in modo da condividere il carico tramite assegnamento round-robin in base al tipo di servizio. Mentre un algoritmo di distribuzione client info & server state aware è LARD (Locality Aware Request Distribution). Questo algoritmo considera sia il tipo di richiesta/servizio, sia lo stato di carico dei server Web. Inoltre ha l obiettivo di aumentare il cache hit rate dei server Web. 8.2 Linux Virtual Server LVS è un software open-source per realizzare Web cluster con Web switch operante a livello 4 usando il modulo ipvs. Per lo switching di livello 7, ci sono i sottoprogetti sperimentali come KTCPVS (Kernel TCP Virtual Server), TCPHA (TCP HAndoff) o TCPSP (TCP SPlicing). 10 Devono essere almeno client info aware poiché occorre usare l informazione contenuta nella richiesta del client 71

79 Questo software garantisce scalabilità dato che è possibile aggiungere e rimuovere dinamicamente i nodi dal cluster, elevata disponibilità dato che fornisce meccanismi per la riconfigurazione dinamica del sistema e per il riconoscimento di failure dei nodi. Permette inoltre di configurare LAN e WAN nei seguenti modi: LVS/NAT è un meccanismo di routing di livello 4 two-way di tipo double packet rewriting. LVS/IP si tratta del meccanismo di routing di livello 4 one-way di tipo IP tunnelling. LVS/DR è un meccanismo di routing di livello 4 one-way di tipo packet forwarding. Altri prodotti per Web cluster sono Citrix NetScaler, Cisco Application Control Engine, F5 Networks BIG-IP Local Traffic Manager, Fondry Networks ServerIron, Nortel Application Switches, Radware AppDirector, Resonate Central Dispatch, Zeus Extensible Traffic Manager. 8.3 Oltre il front-end tier Fino ad ora abbiamo analizzato la replicazione orizzontale nel livello front-end (Web Server). La replicazione orizzontale può essere attuata anche nei livelli più interni come il middle tier composto dagli application server, oppure il back-end tier composto da database server. L obiettivo del dispatching per il middle tier consiste nel scegliere l application server da usare. La granularità del dispatching consiste nell intera richiesta. Il dispatching viene attuato da un entità centralizzata interposta tra front-end e middle tier, oppure in modo distribuito da ciascun server WEb. Il dispatching implementato in molti prodotti commerciali usa semplici politiche di distribuzione come varianti di round-robin, weighted round-robin, least loader. Ad esempio per Apache e Tomcat abbiamo il connettore JK ed il dispatching viene effettuato tramite mod proxy di Apache. Per quanto riguarda il back-end tier, il DB, o più in generale l applicazione di back-end, può consentire di essere eseguito su più nodi e quindi bisogna replicare, completamente o parzialmente, il DB su più repliche identiche, mentre la distribuzione è di solito trasparente al middle tier, ossia non vi è dispatching esplicito. Per aggiornare le repliche abbiamo due tecniche: 1. Master singolo: viene effettuata la lettura su tutti i DB server, la scrittura avviene solo sul master singolo e poi viene replicato l aggiornamento su tutti i nodi slave. 2. Master multipli: la lettura avviene su tutti i DB server, mentre la scrittura avviene su un master. La replicazione dell aggiornamento avviene sugli altri DB server. Per mantenere la consistenza dei dati viene usata: una replicazione eager (sincrona o pessimistica), ossia immediata, tutte le repliche sono aggiornate prima del commit della transazione. Con questo non si ottengono anomalie di concorrenza, ma le prestazioni sono ridotte per le operazioni di scrittura e quindi è necessario un maggiore traffico per propagare gli aggiornamenti. una replicazione lazy (asincrona o ottimistica), ossia ritardata, dopo il commit della transizione. Con questo tipo di replicazione è possibile generare inconsistenza dei dati, pertanto sono state generate strategie di riconciliazione per gestire la possibile inconsistenza tra le repliche. Per incrementare ulteriormente le prestazioni del cluster, è possibile integrare la replicazione del backend tier con meccanismi di caching dei risultati delle query, ad esempio con Oracle Database Cache. In alternativa alla replicazione del DB, si può usare un middleware per gestire un RAIDb (Redunant Array of Inexpensive Databases), ad esempio sequoia. 8.4 Delivery su scala geografica Le architetture alternative a livello front-end come Web Switch di livello 4 o di livello 7 di tipo One- Way o Two-Way offrono un controllo a granularità fine sull assegnamento delle richieste ed un elevata affidabilità, disponibilità e sicurezza. Ma c è la presenza di un single point of failure, il Web Switch. 72

80 Inoltre, la scalabilità è limitata dal Web Switch e dalla banda di accesso ad Internet. Per questo motivo si usa la replicazione su scala geografica, anche detta global scale-out. Il content/service provider ha due possibilità per distribuire i propri contenuti/servizi su scala geografica in modo efficiente e scalabile: il provider possiede e gestisce l intera piattaforma (Sistemi Web distribuiti geograficamente) oppure il provider gestisce solo i contenuti/servizi ma delega ad una terza parte il servizio di delivery dei contenuti/servizi agli utenti finali (Content Delivery Network) Sistemi Web Distribuiti Geograficamente I problemi di rete per sistemi Web distribuiti localmente consistono nella scalabilità limitata dalla banda di accesso ad Internet, l incapacità di evitare i link di rete congestionati, l affidabilità della rete. Il global scale-out genera una maggiore complessità dell architettura tramite meccanismi di routing ed algoritmi di distribuzione e la difficoltà nella gestione dell infrastruttura. Inoltre c è bisogno di una metrica per la selezione del cluster migliore con una conseguente localizzazione e posizionamento dei cluster Web Multi-Cluster I Web Multi-Cluster permettono di avere un sito Web implementato su di un architettura di Web cluster distribuiti geograficamente tra diverse regioni Internet. Il meccanismo di routing delle richieste è basato sul DNS. Gli indirizzi del sito Web possiedono un unico hostname al quale corrispondono molteplici indirizzi IP, tanti quanti sono i Web cluster. L indirizzo IP fornito dal DNS autoritativo corrisponde al VIP dello switch del cluster selezionato. Il DNS autoritativo del sito Web seleziona un cluster nella fase di address lookup mediante un algoritmo di tipo: Round-Robing, prossimità geografica, carico dei cluster, prossimità geografica e carico dei cluster, ecc... Dispatching di primo livello Il primo livello di distribuzione geografica avviene nella fase di risoluzione dell indirizzo (address lookup) dove il client richiede l indirizzo IP del cluster corrispondente all hostname indicato nell URL, se l hostname è valido, il client riceve la coppia indirizzo IP e TTL dalla cache di qualche Name Server locale o intermedio oppure dal DNS autoritativo del sito, opportunamente modificato (integrato o meno da altro componente). Questo può applicare diverse politiche di dispatching per selezionare il Web cluster migliore con algoritmi statici, randomici o di prossimità. Prossimità in Internet La prossimità geografica tra client e server non implica la prossimità Internet (round trip latency). La valutazione statica della prossimità consiste nel prendere l indirizzo IP del client per determinare la zona Internet (simile alla distnza geografica) e contare il numero di hop (informazione stabile più che statica ) tramite traceroute o query delle tabelle di routing. Questa valutazione però non garantisce la selezione del cluster migliore. La valutazione dinamica della prossimità si basa invece sul round trip time, banda disponibile, latenza delle richieste HTTP. Ma richiede tempo aggiuntivo e costi di traffico per effettuare la valutazione. Problemi del dispatching geografico I tipici problemi del dispatching Web sono i picchi di carico in alcune ore/giorni. Quindi il traffico dipende: dai fusi orari; la distribuzione dei client non è uniforme tra le regioni Internet; la prossimità Internet tra client e Web cluster; il caching dell hostname e dell indirizzo IP nei Name Server intermedi per l intervallo del Time-To- Live. 73

81 Nel caso di siti Web molto popolari, il DNS autoritativo controlla solo il 5% del traffico in arrivo al sito a causa del caching hostname/indirizzo IP nei Name Server locali ed intermedi. A differenza del Web Switch, che controlla il 100% del traffico in arrivo al sito, il DNS autoritativo deve utilizzare algoritmi sofisticati come TTL-adattivi. Infatti, non sono stati ancora trovati algoritmi di dispatching DNS in grado di evitare episodi di sovraccarico per tutte le classi di workload. Per risolvere i problemi di dispatching del DNS, nella fase di lookup viene integrato il dispatching attuato dal DNS autoritativo con quello effettuato da un altra entità; mentre nella fase di richiesta viene integrato il dispatching centralizzato attuato dal DNS autoritativo con dispatching distribuito da parte dei server usando meccanismi di re-routing delle richieste come Redirezione HTTP, IP tunnelling, URL rewriting. Dispatching per Web Multi-Cluster Gli inridizzi del sito Web visibili possiedono un unico hostname ed un indirizzo IP per ogni Web cluster. Abbiamo la necessità di usare livelli multipli di routing e dispatching. Nel dispatching inter-cluster c è il DNS autoritativo che seleziona il Web Cluster migliore. Il dispatching intra-cluster è fatto dal Web Switch del cluster che seleziona Web Server migliore. Ciascun Web Server (o Web Switch di livello 7) può redirigere le richieste verso un altro Web Clustwe, ad esempio per risolvere situazioni temporanee di sovraccarico, in questo caso si parla di dispatching inter-cluster. I Web Multi-Cluster con due livelli di dispatching soffrono di un controllo elevato sul carico che raggiunge il Web Cluster per avere un buon bilanciamento intra-cluster. Inoltre reagisce lentamente nel caso in cui si ha un cluster sovraccarico, ossia si ha un cattivo bilanciamento inter-cluster. Con i Web Multi-Cluster a tre livelli di dispatching abbiamo una reazione immediata per spostare il carico da un Web Cluster sovraccarico dato che si ha una migliore redirezione HTTP di IP tunnelling. Redirezione HTTP Il meccanismo di redirezione fa parte del protocollo HTTP ed è supportato dagli attuali browser. Il DNS e Web Switch usano politiche di dispatching centralizzate. La redirezione, al contrario, usa politiche di dispatching distribuite, in cui tutti i server Web possono partecipare al (ri)assegnamento delle richieste. La redirezione è completamente trasparente per l utente, ma non per il client. Si può impostare una redirezione ad un indirizzo IP oppure una redirezione ad un hostname. Questa tecnica è compatibile con tutti i client e server Web, dato che è implementata a livello applicativo. È affidabile poiché è un meccanismo distribuito che non introduce single point of failure. La distribuzione delle richieste è content-aware. Ma la redirezione è limitata solo a richieste HTTP, quindi meccanismi di redirezione più generali come IP tunnelling non sono supportati. Inoltre aumenta il traffico di rete in quanto ogni richiesta rediretta richiede una nuova connessione TCP, anche se la redirezione riduce il tempo di risposta quando le latenze del server sono superiori alle latenze di rete. 74

82 9 Web Caching e sistemi per Web Content Delivery Il caching può essere di vari tipi: caching lato client (content consumer), il browser memorizza sul disco le risorde per accessi futuri; caching lato proxy (terze parti), il proxy server memorizza le risorse più richieste e viene gestito da istituzioni o da ISP; caching lato server (content provider), riduce il carico sulla piattaforma server; distribuzione dei contenuti e servizi (terze parti), gli edge server rappresentano dei nodi in una rete di livello applicativo per la distribuzione e consegna dei contenuti e servizi Web. Questi edge server vengono gestiti da società specializzate. 9.1 Le politiche di rimpiazzamento della cache Quando una risorsa viene richiesta dal proxy ad un server (origin server o altro proxy server) dovrebbe essere memorizzata in cache, per sfruttare il principio di località. Se la cache è piena, occorre rimpiazzare una o più risorse presenti in cache per fare spazio alla nuova risorsa. Quindi occorre definire una politica di rimpiazzamento della cache (cache replacement). Questo tipo di cache possiedono politiche di rimpiazzamento diverse da quelle utilizzate per fare caching della memoria virtuale di un calcolatore. Infatti, nel calcolatore viene effettuato il caching di parti a dimensione fissa di risorse, mentre nel Web il caching riguarda intere risorse, caratterizzate da dimensioni variabili. Una distribuzione heavy-tailed rappresenta una coda più pesante rispetto a quella della distribuzione esponenziale dato che grandissimi valori sono possibili con probabilità non nulla. Data una variabile casuale X con F (x) = P [X x] è distribuita heavy-tailed con indice tail α se: 1 F (x) = Prob[X > x] c x α con α compreso tra 0 e 2. Le principali classi di politiche di rimpiazzamento della cache in ambito Web sono derivate dal caching tradizionale: LRU e LFU. Inoltre, si basano su una chiave primaria (tipicamente la dimensione dell oggetto) rimpiazzando l oggetto più grande all interno della cache come varianti di LRU applicate ad un sottoinsieme di oggetti (LRU-threshold), basate su più chiavi oppure basate sul costo come Greedy-Dual-Size e le sue varianti. Last Recently Used Questa politica rimpiazza l oggetto usato meno recentemente. Viene inserito in testa l oggetto richiesto. Least Frequently Used Rimpiazza l oggetto usato meno frequentemente. Greedy-Dual-Size Associa un costo ad ogni oggetto. Rimpiazza l oggetto con il rapporto minimo tra costo e dimensione. Una variante chiamata Greedy-Dual* sfrutta il fatto che oggetti appartenenti alla stessa pagina hanno maggiore probabilità di essere richiesti insieme. Se un qualunque oggetto nella pagina genera un cache HIT, tutti gli oggetti della pagina sono mantenuti in cache. Metriche di prestazione Per misurare l efficacia del caching, sia delle politiche di rimpiazzamento della cache sia del caching cooperativo che non, si usano le seguenti metriche: tasso di HIT (percentuale di richieste servite della cache), tasso di byte HIT (percentuale di byte serviti dalla cache), banda risparmiata (quantifica la diminuzione di byte richiesti agli origin server), riduzione della latenza. 75

83 9.2 Web Caching cooperativo Diverse cache possono cooperare per aumentare il cache HIT rate, migliorare le prestazioni cercando di distribuire meglio il carico tra più server o cercando di aumentare la scalabilità, oppure aumentare l affidabilità. Possono però generarsi problemi di overhead dovuti proprio alla cooperazione Modalità di cooperazione Esistono due modalità di cooperazione: 1. modalità gerarchica: relazione genitori-figli verticale, ossia se un cache server figlio ha un cache MISS chiede al cache server genitore. 2. modalità flat: relazione paritetica orizzontale, ossia se un cache server ha un cache MISS chiede a tutti o ad un sotto-insieme di cache fratelli (peer). Le informazioni vengono demandate tramite uno schema query-based dove effettua una query per ogni cache miss, oppure tramite uno schema informed-based dove scambia periodicamente informazioni tra i cache server indipendentemente dalla richiesta. La cooperazione tra pari livello (sibling) viene usata per aumentare le prestazioni. Internet Cache Protocol Lo schema di tipo query-based è basato su probe di tipo multicast tra sibling. Questo utilizza il protocollo ICP. Si contatta il proxy che risponde per primo con un HIT per ottenere la risorsa oppure si attende di ricevere tutte le risposte di MISS o la scadenza di un timeout. I messaggi ICP viaggiano su UDP mentre la risorsa viene recuperata da HTTP. Cache Digests Lo schema di tipo informed-based è basato sullo scambio tra i cache server di un digest del contenuto delle varie cache. Ogni cache server replica la directory (summary) di ciascuno dei suoi peer. Un peer viene contattato solo se il summary locale indica un HIT per la risorsa cercata. Il summary è basato sui filtri di Bloom per ridurre l overhead di memorizzazione. Ogni summary è di circa tre ordini di grandezza più piccola della cache corrispondente. Usando i filtri di Bloom possono esserci falsi HIT ma non falsi MISS. Gerarchia con Summary e ICP L uso di summary ad ogni livello della gerarchia permette di ridurre il numero di query tra sibling risultanti in MISS fino al 95%. Scambio di informazioni Usando lo schema query-based si ottiene un minor rischio di informazioni incongruenti (false HIT e false MISS), maggiore latenza dato che bisogna aspettare la risposta ed un minor overhead di comunicazione poiché si scambiano le informazioni solo quando serve. Usando invece lo schema informed-based si ottiene un maggior richio di informazioni incongruenti, minima latenza dato che si conosce già dove andare ed un maggiore overhead di comunicazione poiché si scambiano informazioni periodicamente. Quindi bisogna ottenere il giusto tradeoff tra overhead ed incongruenza. Cache Array Routing Protocol Usando questo protocollo si risolve la richiesta con hop singolo, non è presente un caching ridondante, consente implementazioni client-side ed è possibile riconfigurarlo. Ma lo schema è prettamente locale, ci sono difficoltà di bilanciamento e ci sono MISS penalty. 76

84 9.3 Squid È il server proxy più usato. È progettato per sistemi Unix-like sotto licenza open-source. Tra le sue caratteristiche abbiamo efficienza, stabilità ed affidabilità. Supporta i protocolli applicativi HTTP, FTP, Gopher, WAIS, ecc...; tunnelling tra client e server dei protocolli SSL, HTTPS, TLS; architetture distribuite e gerarchiche di proxy cooperativi; protocolli di cooperazione ICP (di default), CARP, Cache Digest; meccanismi sofisticati per il controllo degli accessi (ACL), caching trasparente, funzionalità di HTTP server accelerator; caching delle risoluzioni DNS. Per decidere quali client sono abilitati ad usarlo come proxy, Squid utilizza una Access Control List (ACL) nella quale sono presenti la lista degli indirizzi IP a livello di dominio per filtrare richieste in ingresso. Può essere anche configurato per disabilitare l accesso a determinati server Web. La validazione delle risorse in cache attuata da Squid, inizialmente utilizzava un meccanismo sul time to live (TTL) degli oggetti di tipo esplicito o implicito (fisso o adattivo), mentre nelle versioni recenti utilizzano un modello basato sul tasso di refresh. Squid gestisce il refreshment della cache. La politica LRU è quella di default per il rimpiazzamento della cache. È un proxy a singolo processo con I/O non bloccante. Utilizza le callback function in C con puntatori a funzioni, mentre per la risoluzione dei nomi in indirizzi IP non usa la funzione gethostbyname() ma il processo esterno dnsserver. 9.4 Collid di bottiglia del delivery In un contesto di gestioni autonome, standard da rispettare e funzionamento best effort First mile: linea di collegamento tra il server Web ed il resto di internet Peering point: punto di connessione tra diversi sistemi autonomi Backbone Last mile: collegamento del client al proprio ISP 9.5 Content Delivery Il content delivery è una strategia per distribuire il contenuto mediante rete IP su base di pay-perdelivery. Una rete basata su questa strategia viene detta CDN, ossia Content Delivery Network o anche detta Content Distribution Network. Il content delivery ha molte probabilità di dar luogo a profitti. Il problma del content delivery è che nessuna rete gestisce più del 5% del traffico, ci sono più di 9000 Autonomous System (AS) e la grande maggioranza di questi gestisce meno dell 1% del traffico. Per risolvere questo problema bisogna distribuire i server in molti (quasi tutti) AS Content Delivery Network Una Content Delivery Network è formata da un infrastruttura di server (detti content server, delivery server oppure edge server) che lavorano insieme. Gli edge server della CDN sono distribuiti su una vasta area geografica per permettere la fornitura dei contenuti e servizi Web da locazioni più vicine all utente. Il content provider delega il servizio del contenuto del proprio sito Web (fornito dall origin server) ad una compagnia che gestisce una CDN (content outsourcing). Gli edge server della CDN forniscono il contenuto dei siti Web gestiti dalla CDN. Lo scopo di usare una CDN è analogo al Web caching, ossia consiste nell avvicinare il contenuto del sito Web all utente per ridurre il tempo di latenza ed il carico sull origin server; i Web cluster possono essere lontani dall utente; la congestione di Internet può far fallire le migliori architetture di server Web; i picchi di traffico (flash crowd) possono provocare un crash dei server Web; i sistemi Web distribuiti geograficamente richiedono che il content provider possegga l infrastruttura di servizio. Web Caching e CDN Il Web Caching e CDN hanno in comune un architettura costituita da un infrastruttura di cache server che operano in modo cooperativo. I cache server sono distribuiti su una vasta area geografica per 77

85 permettere la distribuzione del contenuto Web da locazioni più vicine all utente. Per il resto differiscono stostanzialmente dato che: il web caching opera in modo indipendente dai siti Web, il costo è affidato all ISP (risparmio di banda), non per il content provider, usando il caching generalizzato viene memorizzato il contenuto di tutti i siti Web. La cache dei server proxy vengono riempite in seguito alle richieste degli utenti. Esiste dell overhead sul sito Web derivante dall offloading ripetuto per ogni proxy server che accede al sito. Il content provider non ha il controllo del contenuto nella cache dei proxy server (elevati problemi di consistenza). Il content provider perde molte informazioni sulla distribuzione del proprio contentuto. il content provider delega la distribuzione del contenuto alla CDN, il costo è affidato al content provider e non all ISP, usando il caching selettivo viene memorizzato solo il contenuto dei siti Web gestiti dalla CDN. Il contenuto viene memorizzato in anticipo (prefetching). L overhead sul sito Web è derivato dall unico offloading. Il content provider ha il controllo diretto per contenuto degli edge server. Una CDN può fornire al content provider statistiche accurate sui pattern di accesso al sito Web. Architettura di una CDN L aggiornamento del contenuto può essere statico, dinamico, volatile, multimediale o live streaming ed avviene interrogando l origin server. All interno della CDN abbiamo l interior cache server, mentre il content server è il cache server di backend. A livello applicativo abbiamo contenuti on-demand, streaming on-demand e streaming live. A livello middleware abbiamo data placement, gestione consistenza, monitoring & billing. A livello network abbiamo la localizzazione dei servizi e meccanismi di routing. I dispositivi fisici sono il Web server, Media server, cache static e secure streaming. Contenuti serviti da CDN Una CDN è generalmente utilizzata per fornire i seguenti servizi di delivery: Distribuzione di contenuti non streming: contenuto statico delle pagine Web, in particolare immagini; contenuto dinamico, per lo più contenuto volatile (contenuto statico che cambia frequentemente); contenuto di autenticazione, autenticazione prevista dal protocollo HTTP; Distribuzione contenuti streaming on-demand: il contenuto è digitalizzato e memorizzato come media file su media server; ad esempio video-on-demand, clip musicali; Distribuzione contenuti streaming live: il contenuto è distribuito quasi istantaneamente come media file; ad esempio eventi sportivi e musicali. Gestione della consistenza Per garantire che le copie delle risorse sugli edge server siano consistenti con la risorsa sull origin server possono essere usati differenti approcci: aggiornamento periodico, propagazione dell aggiornamento, aggiornamento on-demand, invalidazione. Meccanismi di routing per CDN Una questione fondamentale riguarda il meccanismo di routing con cui redirigere una richiesta di un client per un oggetto servito dalla CDN verso un edge-server. Le principali tecniche adottate sono: DNS redirection, dove il DNS autoritativo del sito Web delega la risoluzione dell hostname nell URL in un indirizzo IP ad un DNS controllato dalla CDN. Nel mapping da hostname ad indirizzo IP si può effettuare la scelta di un determinato content server. 78

86 Il valore del Time-To-Live (TTL) assegnato dal DNS autoritativo è prossimo a 0, in modo tale che la CDN possa modificare spesso il mapping tra hostname ed indirizzo IP per facilitare il bilanciamento del carico tra gli edge server e limitare il caching delle risoluzioni di indirizzi. Esistono due tipi di CDN che adottano la tecnica del routing mediante DNS: 1. full-content delivery (o first HIT at CDN), dove l origin server è nascosto a tutti, eccetto che alla CDN; il DNS autoritativo dell origin server è modificato in modo da delegare le richieste di risoluzione di indirizzo al DNS server autoritativo della CDN (DNS outsourcing); tutte le richieste inridizzate all origin server arrivano agli edge server. 2. partial-content delivery (o first HIT at origin), dove l origin server modifica nella pagina HTML le URL degli oggetti incorporati nella pagina (sopratutto immagini), in modo tale che siano risolti dal name server autoritativo della CDN. URL rewriting, dove l origin server riscrive dinamicamente le URL presenti nella pagina HTML base in modo da redirigere le richieste dei client per gli embedded object verso un determinato edge server della CDN. Abbiamo due alternative di rewriting: 1. URL riscritta con indirizzo IP: è la soluzione meno usata e riguarda l indirizzamento dell edge server. 2. URL riscritta con hostname: nella fase di risoluzione del nuovo hostname, il DNS server della CDN selezionerà l edge opportuno, indirizzando indirettamente l edge server. Oppure usare una redirezione mediante protocollo HTTP/RTSP, anycast, o IP tunnelling. Le società CDN usano nomi altisonanti, come ad esempio Global Traffic Management System, ma in realtà i principali dispositivi per il routing sono costituiti da DNS autenticativi arricchiti con funzionalità di controllo sullo stato della rete e degli edge server. Partial Content Delivery in Akamai L origin server che vuole delegare il servizio in parte delle proprie risorse all infrastruttura di edge server gestita da Akamai deve rinominare le URL ad esse relative usando un prefisso specifico che include un hostname. La risoluzione dell hostname nell indirizzo IP di un edge server di Akamai è gestita dai server DNS di Akamai. L edge server prescelto è vicino al name server locale del client. Akamai utilizza un doppio livello di name server per la risoluzione dei nomi in indirizzi IP degli edge server, ad esempio dopo la prima risoluzione del DNS. Ad esempio, il client effettua una richiesta per la pagina Web con contiene la risorsa image.gif. L URL della risorsa viene riscritta come: Il prefisso a799.g.akamai.net individua un edge server di Akamai che possiede la risorsa. Tramite il DNS, il client risolve a799.g.akamai.net in un indirizzo IP, ad esempio che dista solo 5 HOP dal client anziché 17 come l origin server. Se l edge server possiede la risorsa image.gif, la invia al client. Altrimenti richiede la risorsa tramite un protocollo interno ad un altro edge server di Akamai oppure all origin server e la memorizza nella propria cache. Un altro client appartenente ad un altra rete che richiede la stessa pagina avrà un link uguale per l immagine ma l indirizzo IP corrispondente potrebbe essere diverso. Content Peering Il content peering consiste nella cooperazione ed interoperabilità tra più CDN allo scopo di migliorare la scalabilità, la tolleranza ai guasti e le prestazioni. Le soluzioni proposte per il content peering adottano il routing tra CDN effettuato a livello del Domain Name System. Il server DNS (potenziato) deve essere in grado di fornire l indirizzo di una CDN che server una regione geografica. L intera CDN agisce come un content server, recuperando il contenuto e tariffando il costo dell operazione alla CDN richiedente. 79

87 9.5.2 Caching di contenuti Web dinamici Ci sono state numerose proposte per risolvere il problema del caching dei contenuti Web dinamici, come l active cache (applet Java eseguite sui cache server), soluzioni integrate con tecnologie di database (Oracle 10g), frammentazione della pagina. Il problema consiste nel mantenere la consistenza delle copie. La soluzione basata su frammentazione della pagina consiste che i frammenti di cui è composta la pagina possono essere statici o dinamici, avere diverse caratteristiche di personalizzazione e di durata. Tecnologia ESI La tecnologia standard Edge Side Includes (ESI) permette la frammentazione della pagina, in questo modo le funzionalità di consegna del contenuto vengono separate dalla generazione del contenuto in modo da incrementare la scalabilità e ridurre i costi. Questa tecnologia utilizza un linguaggio di markup basato su XML per descrivere componenti di una pagina Web in termini di frammenti che sono cacheable e non-cacheable. Difatti sono stati specificati appositi tag XML per specificare i frammenti ESI in una pagina Web. Per ciascun frammento ESI si può specificare un diverso requisito per il caching. Il TTL permette, infatti, di specificare fino a quando è valida la copia del frammento in cache. 80

88 10 Sistemi Peer-to-Peer Il termine peer-to-peer si riferisce ad una classe di sistemi ed applicazioni che utilizzano risorse distribuite per eseguire una funzionalità (critica) in modo decentralizzato. Ogni entità con capacità simili ad altre entità nel sistema viene definito peer. Questi sistemi permettono di offrire ed ottenere risorse dalla comunità di peer. Tutti i nodi (peer) hanno la stessa importanza, sono indipendenti e localizzati ai bordi di Internet senza avere alcun controllo centralizzato. Ogni peer ha funzioni di client e server e condivide delle risorse, per questo motivo viene anche detto servent. In realtà, possono essere presenti server centralizzati o nodi con funzionalità diverse rispetto agli altri, questi vengono detti supernodi ed in questo caso si parla di peer organizzati in una gerarchia. Questo sistema è altamente distribuito poiché il numero di nodi può essere dell ordine delle centinaia di migliaia ed ogni nodo può entrare o uscire dalla rete P2P in ogni momento garantendo anche la ridondanza delle informazioni. I sistemi P2P permettono di distribuire e memorizzare contenuti, condividere risorse di calcolo, collaborare e comunicare, usati per la telefonia, per CDN o piattaforme come JXTA. Il P2P è diventato famoso poiché permette la condivisione dei file. A tal proposito abbiamo bisogno di funzionalità di publishing, searching e retrieval. Ad esempio, Alice esegue un applicazione client P2P sul suo portatile. In modo intermittente si connette ad Internet, ottiene un nuovo indirizzo IP per ogni connessione. Registra il suo contenuto nel sistema P2P. Cerca Hey Jude. L applicazione visualizza altri peer che hanno una copia di Hey Jude. Alice sceglie uno dei peer, Bob. Il file viene copiato dal computer di Bob al portatile di Alice. Mentre Alice esegue il download, altri utenti eseguono un upload da Alice. L applicazione consente ad Alice di registrare una directory del proprio filesystem per la condivisione, pertanto chiunque può ottenere un file dalla directory registrata, proprio come un Web server. Consente ad Alice di copiare dalle directory condivise di altri utenti e di individuare quali peer possiedono un determinato file tramite una ricerca basata su keyword. Gli obiettivi di un sistema P2P consistono nel condividere/ridurre i costi usando risorse non utilizzate, migliorare la scalabilità, aumentare la persistenza e la disponibilità delle risorse, consentire l aggregazione di risorse e l interoperabilità, aumentare l autonomia, aumentare l anonimato/privacy, nascondendo le operazioni svolte dagli utenti, consentire dinamismo e comunicazioni ad-hoc. Mentre i problemi riguardano la sicurezza (garantire l integrità ed autenticità delle risorse), disuguaglianza tra i nodi, un elevato tasso con cui i nodi entrano/escono dal sistema P2P (che porta ad un instabilità del sistema che ne influenza negativamente le prestazioni). Architetture P2P Abbiamo tre tipologie di architetture P2P: 1. decentralizzate pure: tutti i nodi sono peer senza alcun coordinatore centralizzato, ogni peer può funzionare come router, client o server. 2. parzialmente centralizzate: alcuni nodi (supernodi) facilitano l interconnessione tra i peer, quindi prima avviene la comunicazione con un supernodo e poi qualla tra i peer. 3. decentralizzate ibride: è presente un server centralizzato che facilita l interazione tra i peer (servizio di localizzazione) Overlay Network L Overlay Network è la rete virtuale che interconnette i peer ed è basata su una rete fisica sottostante. Questo permette l instradamento delle informazioni su un infrastruttura di rete già esistente, pertanto le nuove applicazioni sono facili da sviluppare ed i tempi di sviluppo sono ridotti. Per quanto riguarda l instradamento applicativo abbiamo il routing content-aware ed il routing application semantic-aware. 81

89 Overlay Routing L idea di base consiste nel riuscire a trovare la risorsa da parte del sistema. Rispetto al routing tradizionale, la risorsa non è esattamente un indirizzo di un nodo della rete, ma può essere un file, una CPU disponibile, dello spazio libero sul disco, ecc... Il recupero di una risorsa avviene con un interazione diretta tra peer, usando protocolli come HTTP Reti non strutturate Nelle reti non strutturate, i nodi sono organizzati come un grafo random, pertanto l organizzazione della rete segue principi molto semplici. Non esistono vincoli sul posizionamento delle risorse rispetto alla topologia del grafo, quindi la localizzazione delle risorse è resa difficoltosa dalla mancanza di organizzazione della rete. L aggiunta o la rimozione di nodi è un operazione semplice e poco costosa. Ma bisogna gestire i nodi che hanno tassi di join/leave molto elevati. La ricerca su questo tipo di reti può essere: per sistemi decentralizzati ibridi una directory centralizzata. Ossia un nodo centralizzato (directory server) possiede il mapping risorse-peer (index), fornendo un servizio di discovery dei peer e di lookup delle risorse. Questo sistema però possiede una gestione costosa della directory centralizzata. Inoltre il suo collo di bottiglia nonché single point of failure è proprio il nodo centrale. per sistemi decentralizzati puri una ricerca flood-based. È un approccio completamente distribuito per localizzare le risorse. Ogni peer propaga (flood) la richiesta ai peer vicini, che a loro volta inviano la richiesta ai loro vicini (escludendo il vicino da cui hanno ricevuto la richiesta). La ricerca continua fin quando la richiesta è risolta oppure viene raggiunto un massino numero di passi. L eventuale risposta viene inviata al peer dal quale è stata ricevuta la richiesta. Con questa tecnica, bisogna evitare la propagazione all infinito decrementando il TTL ad ogni inoltro, e che nel grafo non si generino cicli. A tal proposito ogni richiesta possiede un proprio ID per evitare che venga nuovamente elaborata dai nodi da cui è stata già ricevuta. Il flooding genera una crescita esponenziale del numero di messaggi, infatti è possibile che l intero sistema sia soggetto ad attacchi di tipo DoS, dove abbiamo nodi black-hole in caso di congestione e non è realistico esplorare tutta la rete. Il costo della ricerca è esoso e non è detto che si ottengono risposte in tempi ragionevoli. Manca una relazione tra topologia virtuale e topologia reale. Non è garantito che vengano interrogati tutti i nodi che posseggono la risorsa. Il traffico di query rimane alto dato che i messaggi che non producono risultati occupano comunque bada. nei sistemi parzialmente centralizzati, non tutti i peer sono uguali. I nodi meglio connessi e con buona capacità computazionale possono avere funzioni speciali e vengono chiamati supernodi o super-peer o broker. Tali supernodi vengono identificati dinamicamente. I supernodi agiscono da rappresentanti dei loro sottoposti e quindi l intera architettura è organizzata in modo gerarchico. Questi hanno funzione di directory semi-centralizzata, dove indicizzano le risorse disponibili nei peer che gestiscono. Il flooding riguarda, quindi, solo i supernodi. Rispetto ai sistemi decentralizzati puri, si riduce il tempo di discovery e si sfrutta l eterogeneità dei nodi presenti in una rete P2P Case Study: Gnutella 0.4 Gnutella versione 0.4 implementa un architettura decentralizzata pura. Per accedere alla rete occorre conoscere l indirizzo di un nodo (problema del bootstrap), a tal proposito esistono servizi di host-caching che forniscono liste di nodi della rete Gnutella. Ottenuta la lista, il nodo prova a connettersi con alcuni dei nodi noti. A seconda della velocità di connessione, il nodo prova a mantenere da 3 ad 8 connessioni. Se una connessione viene persa, il nodo 82

90 cerca di connettersi ad un altro nodo della lista, che viene continuamente aggiornata. Un servent può rifiutare una richiesta di connessione, ad esempio perché ha raggiunto il numero massimo di connessioni ammesse. Per cercare le risorse, Gnutella utilizza un flooding con esplorazione breadth-first (BFS). Comunicazione fra servent I messaggi tramessi tra i servent possiedono: DescriptorID: è un identificatore univoco del messaggio all interno della rete. Viene usato per associare le risposte alle richieste e per evitare di propagare le stesse richieste più di una volta. PayloadDescriptor: identifica il tipo di messaggio. all i-esimo passo: TTL(i) = TTL(0) Hops(i). PayloadLength: è la lunghezza dei dati associati al messaggio. I servent comunicano fra loro con messaggi, detti descriptor; sono supportati 5 tipi di messaggi: 1. Ping: usato per il discovery dei nodi vicini nella rete P2P. Il messaggio di Ping viene inviato periodicamente per sondare la rete alla ricerca di altri nodi. Il messaggio di Ping viene inoltrato ai vicini fino a che il TTL non si annulla. 2. Pong: messaggio di risposta ad un Ping. Un nodo che riceve un Ping risponde con uno o più Pong. Il messaggio Pong contiene l IP e la porta su cui sono accettate connessioni, il numero di file condivisi ed il numero di KByte totali condivisi. Possono essere inviati più messaggi di Pong per comunicare il contenuto della propria host-cache. 3. Query: messaggio di richiesta per localizzare una risorsa. Specifica, quindi, il criterio di ricerca e la velocità di trasferimento minima richiesta ai servent. 4. QueryHit: messaggio di risposta a una query. Riguarda tutti i risultati trovati su un dato servent che soddisfano il criterio di ricerca. Le informazioni contenute in questo messaggio sono: Port: porta su cui il servent accetta connessioni in entrata; ResultSet: contiene gli identificatori delle risorse che soddisfano la query; ServentID: identifica univocamente il nodo della rete. 5. Push: permette il download da servent dietro un firewall tramite il protocollo HTTP. Un nodo richiede direttamente il trasferimento di un file ad un peer che ha risposto alla query. Il protocollo utilizzato per il trasferimento è HTTP Case study: KaZaA Utilizza come rete di file sharing FastTrack e KaZaA è il client più popolare. Permette il download parallelo di parti di file. Se il server utilizzato diventa indisponibile allora le richieste vengono automaticamente redirette ad un altro server. Permette di stimare il tempo di download ed è possibile configurare il numero massimo di upload/download simultanei. Permette di gestire la coda al server/client dando differenti priorità agli utenti che accedono al server, inoltre chi collabora alla condivisione dei contenuti viene privilegiato nello scaricare file. La ricerca è basata su keyword. KaZaA è un software proprietario, i file ed i dati di controllo sono crittografati. Ogni informazione viene trasmessa come richiesta o risposta HTTP. Il download include adware e spyware. L architettura è parzialmente centralizzata con organizzazione gerarchica dei peer. Ogni peer è un supernodo oppure viene assegnato ad un supernodo. La lista dei potenziali supernodi viene inclusa all atto del download del software. 83

91 Case study: BitTorrent È il più popolare protocollo P2P per la distribuzione di contenuti e rappresenta circa il 15% del traffico su Internet. L idea di base consiste nel dividere un fule in pezzi da 256KB e far ridistribuire ad ogni peer i dati ricevuti, fornendoli a nuovi destinatari. Con questo metodo si riduce il carico di ogni sorgente, si riduce la dispendenza dal distributore originale e si fornisce ridondanza. Per condividere un file, un peer crea un file.torrent, che mantiene metadati sul file condiviso e sul tracker (il nodo che coordina la distribuzione del file). Per scaricare un file, un peer ottiene un file.torrent per quel file. Si connette al tracker individuato all interno del.torrent, il quale indicherà da quali peer scaricare i vari pezzi del file e si connette ai peer individuati per scaricare i vari pezzi. Un gruppo di peer interconnessi per condividere un torrente viene chiamato swarm. Se lo swarm contiene solo il seeder iniziale, il client si connette direttamente al seeder. Man mano che i peer entrano nello swarm, iniziano a negoziare tra loro pezzi di file, anziché scaricarli dal seeder. Alcune tecniche usate dai client BitTorrent consistono nello scaricare i primi 4 pezzi selezionabili in modo casuale per aumentare la possibilità di trading con gli altri peer. I pezzi rimanenti vengono selezionati in base alla strategia rarest first, in questo modo si riceve prima il pezzo più raro così da migliorare la selezione casuale dei pezzi. Inoltre vengono inviati i dati solo ai peer che inviano i dati (tit-for-tat) Reti strutturate Nelle reti strutturate abbiamo vincoli sul grafo e sul posizionamento delle risorse sui nodi del grafo, quindi l organizzazione della rete segue principi rigidi. L aggiunta o la rimozione dei nodi è un operazione costosa, pertanto tocca migliorare la localizzazione delle risorse. In queste reti, il routing viene effettuato tramite delle Distributed Hash Table (DHT), anche noti come sistemi con documento rooting. Dove ad ogni peer viene assegnato un ID ed ogni peer conosce un certo numero di peer. Per ogni risorsa condivisa pubblicata viene assegnato un ID, basato su una funzione hash applicata al contenuto della risorsa ed al suo nome. Il routing della risorsa pubblicata avviene tramite il peer che ha l ID più simile a quello della risorsa. La richiesta per la risorsa specifica sarà instradata verso il peer che possiede l ID più simile a quello della risorsa. Ciò può creare una possibile copia locale della risorsa ad ogni peer attraversato Distributed Hash Table Le DHT offrono un astrazione distribuita della struttura dati hash table. Le risorse vengono rappresentate da una coppia chiave-valore (K,V), dove K identifica l oggetto che è contenuto in V. put(k,v) memorizza V in tutti i nodi responsabili per l oggetto identificato da K. remove(k,v) cancella tutti i riferimenti a K ed all associato V. V = get(k) recupera V associato a K da uno dei nodi responsabili. Ogni chiave viene mappata su almeno un nodo della rete. Ogni risorsa è identificata solo mediante il valore della chiave, per cercare una risorsa occorre conoscere il valore esatto della chiave. Le DHT operano in maniera distribuita con molti nodi garantendo alta scalabilità. Esistono diverse soluzioni che specificano anche la modalità di routing delle ricerce e della memorizzazione, infatti esistono più di 20 protocolli e prototipi per reti P2P strutturate, tra cui: Chord (MIT). I nodi e le chiavi sono mappati in uno spazio circolare mediante funzioni hash. Ogni nodo è responsabile delle chiavi poste tra sé ed il nodo precedente nel cerchio. La tabella di routing posseduta da ogni nodo viene chiamata Finger Table. Il nodo in posizione x conosce i nodi competenti per le posizioni x+1, x+2, x+4, x+8, ecc... Nella Finger Table, ogni nodo conosce bene le posizioni vicine e possiede un idea approssimata delle posizioni più lontane. L algoritmo di routing per effettuare una ricerca consiste di questi passi: il nodo in posizione x vuole instradare una richiesta per la posizione z; se z è nella zona di competenza del nodo, la ricerca termina; altrimenti, cerca nella finger table un nodo y avente come valore il massimo dei suoi valori inferiori a z ed invia la richiesta a quel nodo. Questo algoritmo 84

92 raggiunge velocemente le vicinanze del punto cercato, per procedere poi con salti via via più piccoli. Il costo di lookup è O (logn), essendo N il numero dei nodi. Purtroppo questo meccanismo è fragile e non fornisce un buon supporto per le ricerche senza matching esatto. Tapestry (Berkeley). Il prefix routing è basato sull algoritmo di Plaxton. La struttura mesh di Plaxton contiene puntatori ai nodi il cui ID corrisponde agli elementi di una struttura ad albero di prefissi di ID. CAN (Berkeley). CAN sta per Content Addressable Network. I nodi e le risorse sono disposti in uno spazio cartesiano di d dimensioni. Ogni nodo è responsabile di un sottospazio, mentre ogni risorsa viene individuata da d coordinate. Pastry(Rice Univ, Microsoft). Il routing è basato sull algoritmo di Paxton. Kademlia (NY Univ). Come in Pastry e Tapestry anche qui il routing è basato su Plaxton. SypNet/SkipGraph, Viceroy, Z-Ring, Chimera (UCSB). 85

93 11 Un introduzione ai Web service Un Web service è un sistema software progettato per supportare l interazione tra macchina a macchina sulla rete in modo inoperabile. Possiede un interfaccia descritta in un formato capibile dalla macchina (WSDL). Altri sistemi interagiscono con il Web service in una maniera prescritta dalla sua descrizione usando messaggi SOAP, tipicamente viene usato HTTP con una serializzazione XML in congiunzione con altri relativi standard Web. I Web service sono un nuovo breed di un applicazione Web. Possiedono contenuti, sono descrittivi, sono applicazioni modulari che possono essere pubblicate, trovate ed invocate sul Web. I Web service effettuano funzioni che possono essere qualsiasi cosa, da semplici richieste a complicati processi business. Un esempio di Web service può fornire le quote di stock o un processo per le transazioni delle carte di credito. Quando un Web service viene deployato, altre applicazioni o altri Web service possono cercarlo ed ivoncare i servizi deployed. Caratteristiche dei Web Service Rappresentano una soluzione per permettere l interazione e l interoperabilità tra applicazioni in ambito Web. Si basano sull idea di fornire un linguaggio ed una piattaforma interoperabile, comune a sistemi differenti. Sono una combinazione di diversi standard tecnologici, di tipo aperto (XML, HTTP, SOAP,...) che permettono a chiunque di utilizzarli. Sono accessibili mediante un interfaccia standard. Permettono a sistemi eterogenei di lavorare insieme per realizzare il Service Oriented Computing (SOC), dove la programmazione avviene con componenti distribuite sul Web Web Service Le componenti software indipendenti dalla piattaforma e dall implementazione possono essere descritte usando un linguaggio di descrizione del servizio, pubblicate in un registro di servizi, scoperte mediante un meccanismo standard (a runtime o a tempo di progetto), invocatemediante un API (solitamente tramite la rete), composte con altri servizi. Un client non può dire quale linguaggio, sistema operativo o tipo di computer è stato usato. Possono essere inviati o ricevuti dati binari (ma ci sono eccezioni). Deve poter trovare il Web service in un registro di servizi. Un Web service deve descrivere se stesso, ossia descrive quali tipi di richieste può soddisfare, quali sono gli argomenti e qual è il trasporto. Gli argomenti e i tipi di dato restituiti devono essere noti (API nota). Questo deve indicare ad un registro di servizi dove è localizzato. Il servizio può a sua volta essere un client di un altro servizio Service Oriented Architecture L architettura di riferimento dei Web service si chiama Service Oriented Architecture (SOA). È composta da un service requestor che richiede l esecuzione di un Web service, un service provider che implementa il servizio e lo rende disponibile sul Web ed un service registry che offre un servizio di pubblicazione e ricerca dei servizi disponibili. Il servizio viene pubblicato in una directory di servizi. Il requestor cerca i dettagli sul servizio in una directory di servizi. Il requestor si collega al provider ed invoca il servizio interagendo con esso. Il protocollo di trasporto consiste nell invio e dicezione di richieste e risposte tra requestor e provider. Il protocollo di comunicazione è basato sullo scambio di messaggi di tipo XML. La descrizione del servizio rappresenta l interfaccia funzionale del servizio. Il business process è la composizione dei servizi. Mentre la definizione dei service registry è data dalla scoperta dei servizi Standard per i Web service Il service provider costruisce e definisce il servizio usando Web Services Description Language (WSDL). Questo inoltre registra il servizio mediante Universal Description Discovery and Integration (UDDI). Il service requestor trova il servizio cercando in un registro UDDI. Si collega al Web service fornito dal service provider ed invoca le sue operazioni mediante Simple Object Access Protocol (SOAP). 86

94 XML e Web Service I Web service si basano sul linguaggio XML perché è indipendente da linguaggi, applicazioni e piattaforme specifiche. XML garantisce ricchezza espressiva, estendibilità, portabilità e facilità di comprensione. Inoltre gli schemi XML possono essere validati da entrambe le parti che comunicano Simple Object Access Protocol È il protocollo specifico di comunicazione tra Web service basato su XML per scambiare dati ed invocare metodi su oggetti remoti usando un protocollo applicativo sottostante, come HTTP, SMTP, FTP,... Questo protocollo è leggero, robusto e flessibile poiché indipendente dal sistema operativo e dal linguaggio di programmazione. XML permette di scambiare strutture dati anche complesse nel payload del messaggio SOAP. Inoltre è possibile serializzare i dati in XML. Motivazioni per SOAP Molte applicazioni distribuite, come DCOM e CORBA, comunicano usando chiamate a procedura remota (RPC) tra oggetti distribuiti. Ma HTTP non è progettato per questi oggetti, le chiamate RPC non possono essere facilmente adattate ad Internet, inoltre esistono problemi di sicurezza per RPC dato che la maggiorparte dei firewall e dei proxy server vengono impostati per bloccare questo tipo di traffico. Il protocollo HTTP è l unico protocollo firewall-friendly ed è supportato da tutti i Web browser e server. Obiettivi di SOAP SOAP permette di aumentare l interoperabilità rispetto a soluzioni proprietarie grazie all uso di XML e HTTP. Permette una facile manutenibilità ed aggiornamento dato che il formato del payload in XML può essere esteso facilmente. Inoltre elimina le limitazioni dovute alle politiche di sicurezza poiché l uso di HTTP e messaggi testuali permette di utilizzare proxy Web senza contare che è possibile controllare gli header HTTP da parte di firewall. Limitazioni di SOAP SOAP non considera le problematiche proprie di un sistema di oggetti distribuiti. Non è ottimale a livello di prestazioni poiché i dati sono serializzati in XML e la deserializzazione richiede di usare un parser XML per estrarre i dati dal payload. È un protocollo senza stato, come HTTP, e non è in grado di mantenere nativamente informazioni di stato fra una connessione e l altra. Non gestisce la sicurezza, infatti si è costretti ad usare SOAP su HTTPS. Cosa definisce SOAP Definisce il modo di incapsulare i dati da scambiare fra host. In caso di errore definisce il formato del messaggio di fault. Definisce la codifica con cui sono scambiati i dati. Vengono utilizzate le definizioni di XML schema. Definisce come specificare il nome della procedura da chiamare, passare i parametri e ricevere la risposta (valore di ritorno). Struttura generale del messaggio SOAP Il SOAP envelope identifica il documento XML come messaggio SOAP. Il SOAP header è opzionale e contiene tutte le informazioni aggiuntive per il processamento del messaggio. Il SOAP body contiene il vero e proprio messaggio, nonché le chimate o risposte. Il SOAP fault contiene le informazioni su eventuali errori occorsi durante il processamento. 87

95 11.3 Descrizione dei servizi Una volta che il Web service è attivo, SOAP non basta per conoscere il tipo di messaggio da inviare per la richesta poiché definisce solo il formato di una envelope. Per garantire l interoperabilità fra sistemi eterogenei è necessario un meccanismo che permetta a requestor e provider di capire l esatta struttura ed il tipo di dati dei messaggi, quindi occorre dire al requestor quale tipo di messaggio XML può inserire nel body del messaggio SOAP. Il Web Services Description Language (WSDL) è un dialetto XML che consente di descrivere un servizio in modo strutturato. Un documento WSDL fornisce la descrizione funzionale del servizio, specificando il formato dei messaggi di richiesta e di risposta Web Service Description Language Un file WSDL è un tipo di documento XML contenente informazioni sul servizio riguardanti la semantica delle interfacce ed i dettagli amministrativi per la chiamata ad un Web service. Quando qualcuno vuole usare un Web service individua il servizio; richiede il file WSDL; lo analizza per determinare la locazione del servizio, le chiamate dei metodi ed i parametri e come accedere ai metodi; crea una richiesta SOAP, invia tale richiesta al servizio. Tutto ciò può essere automatizzato. Cosa descrive WSDL Descrive le operazioni (o metodi) forniti dal servizio, i dettagli sui formati dei dati e sui protocolli necessari per accedere al servizio (tramite l XML schema), i dettagli sulla locazione del servizio che possono variare a seconda del protocollo di trasmissione usato. Architettura di WSDL WSDL descrive i Web service, iniziando con i messaggi che possono essere scambiati tra requestor e provider. I messaggi sono descritti prima in modo astratto; in seguito vengono aggiunte informazioni pratiche sui protocolli di rete ed i formati dei messaggi. Ogni messaggio è composto da una collezione di elementi tipati. Lo scambio di messaggi viene definito operation. Una collezione di operation è definita porttype. Un service contiene una collezione di port. Ogni port è l implementazione di un porttype ed include tutti i dettagli concreti necessari al verificarsi della comunicazione Descrizione di un servizio Un documento WSDL è formato da 7 elementi, corrispondenti a parti dell applicazione: message, operation, porttype, type, binding, port e service. È composto da 2 sezioni: 1. descrizione astratta: specifica l insieme dei messaggi di scambio per interagire con il servizio. Questa è generalizzabile, flessibile e facilmente estendibile. Per definire i tipi di dato usati all interno del documento viene usato l elemento type. Viene definito usando XML schema come type system. La definizione astratta e tipata dei dati scambiati tra requestor e provider, contenente i parametri di richiesta e di risposta sono messi all interno dell elemento message. E può essere un messaggio di input, output o di fault. Tramite l elemento porttype vengono combinati i messaggi con lo scopo di definire l interazione. Solitamente ne esiste uno per documento WSDL. Corrisponde al servizio stesso e contiene un insieme di elementi operation. L elemento operation specifica i nomi delle operazioni di input e output. Quindi vengono specificati i messaggi scambiati durante l operazione. 2. descrizione concreta: contiene i dettagli dell interazione tra requestor e provider. Tali dettagli sono dipendenti dal protocollo di accesso al servizio. 88

96 L elemento binding fornisce i dettagli per l implementazione delle operazioni contenute in un porttype, ossia il protocollo di trasporto e la codifica dei dati (HTTP, SOAP,...). Per specificare l indirizzo di rete del servizio con cui effettuare la connessione utilizziamo l elemento port. Una collezione di port correlati viene definita nell elemento service. Permette di raggruppare tutti i porttype, in modo che sia immediatamente leggibile e comprensibile per un utente quali sono i port supportati da un determinato servizio. Ad esempio tutti i port associati ad una transazione che richiede più passi Universal Description, Discovery, Integration Universal Description, Discovery, Integration (UDDI) è un servizio di directory basato su XML che permette agli utenti dei Web service di localizzarli. Senza UDDI due applicazioni possono comunicare solo se già si conoscono, conoscono i servizi offerti e la loro localizzazione. È quindi necessario un archivio per permettere ai Web service di rendere pubblica la loro presenza e per renderli raggiungibili dagli utenti. Panoramica di UDDI UDDI viene utilizzato da due classi di utenti: il provider che offre un Web service ed un requestor che ricerca un Web service. È simile al DNS, solo che il DNS lavora ad un livello più basso perché risolve indirizzi IP, mentre UDDI lavora a livello più alto perché risolve servizi. UDDI è un servizio globale condiviso tra server differenti sparsi in tutto il mondo, anche se non organizzati secondo una struttura gerarchica. I diversi server possono condividere i dati mediante un protocollo di replicazione. UDDI si basa su SOAP per la trasmissione dei messaggi. Interazione con UDDI Le due azioni principali di UDDI sono la registrazione e la scoperta. La sicurezza è un aspetto fondamentale dato che un concorrente potrebbe cancellare il servizio di un altro publisher. Per risolvere questo problema viene utilizzata una procedura di autenticazione dei publisher dove ogni server mantiene traccia dei publisher e di cosa hanno pubblicato. Solo chi ha pubblicato un servizio è autorizzato a modificarlo o cancellarlo. Informazioni in UDDI Le informazioni in UDDI sono divise in tre categorie principali: pagine bianche: informazioni sui contenuti e gli indirizzi dei service provider; pagine gialle: informazioni sui diversi servizi disponibili organizzati per categorie di business, per tipo di servizi,... pagine verdi: informazioni tecniche sul servizio stesso (eventualmente anche il documento WSDL del servizio) Composizione di servizi Si usa la combinazione di più servizi per realizzare attività complesse che coinvolgono diversi partner azientali, in questo modo si crea un nuovo servizio a valore aggiunto. I due approcci per la composizione dei servizi sono: orchestrazione e coreografia. Orchestrazione Esiste un coordinatore centralizzato (broker) che controlla i Web service coinvolti e coordina l esecuzione delle differenti operazioni. I singoli servizi non sanno di prendere parte ad un business process a livello di astrazione più elevato. Solo il coordinatore conosce gli obiettivi della composizione e gestisce l ordine e la logica delle invocazioni dei servizi, nonché il relativo passaggio dei dati. Il linguaggio standard utilizzato è Business Process Execution Language. 89

97 Coreografia La collaborazione avviene tra entità di pari livello. Ogni servizio coinvolto nella composizione conosce quando eseguire le operazioni e con quali servizi interagire. Tutti i partecipanti alla coreografia sono consapevoli della logica del business process, delle operazioni da eseguire e dei messaggi da scambiare Piattaforme per Web Service I Web service sono basati su tecnologie aperte in modo da avere garanzia di interoperabilità. Esistono molti framework per sviluppare applicazioni che utilizzano la tecnologia dei Web service e questi condividono lo stesso insieme di tecnologie. 90

98 12 Architetture Le architetture software dei sistemi distribuiti definiscono l organizzazione e l interazione dei vari componenti software che costituiscono il sistema. Esistono diverse scelte possibili nella realizzazione di un sistema distribuito. L architettura di un sistema distribuito consiste nell instanziazione finale di un architettura software, i cui componenti sono posizionati sulle diverse macchine che lo costituiscono Stili architetturali Gli stili architetturali sono espressi in termini di definizione ed usano: componenti: unità modulari dotate di interfacce ben definite e completamente sostituibili nell ambiente in cui operano; connettori: meccanismi che consentono la comunicazione, coordinamento o cooperazione tra componenti. Architettura a livelli I componenti di questa architettura sono organizzati a livelli (o layer). Un componente a livello i può invocare un componente del livello sottostante i 1. Le richieste scendono lungo la gerarchia, mentre le risposte risalgono. Quindi queste sono largamente adottate dalla comunità della rete, ad esempio lo stack iso/osi. Architettura basata su oggetti Ogni componente è rappresentato da un oggetto. Gli oggetti sono connessi attraverso un meccanismo di chiamata a procedura remota (rpc) o invocazione di metodo remota (rmi). 91

99 Evoluzione degli stili architetturali Il disaccoppiamento dei componenti nello spazio ( anonimi ) ed anche nel tempo ( asincroni ) ha determinato stili architetturali alternativi. Questi pongono dei vincoli sui componenti che possono interagire e possono introdurre la necessità di conoscenze che a volte non sono necessarie. Il disaccoppiamento diventa un fattore importante per creare maggiore flessibilità in un sistema, ovvero poter definire stili architetturali che possono sfruttare al meglio la distribuzione e la scalabilità dei componenti. Le proprietà del disaccoppiamento sono: Disaccoppiamento spaziale: i componenti non devono conoscersi (reciprocamente o meno); temporale: i componenti interagenti non devono essere compresenti, ossia presenti insieme nello stesso istante, quando la comunicazione ha luogo; sincrono: i componenti interagenti non devono aspettarsi a vicenda e non sono soggetti a blocchi reciproci. Architettura basata su eventi Ogni componente comunica tramite la propagazione di eventi. Architettura basata su dati I componenti comunicano tramite un repository comune (passivo o attivo), come ad esempio un filesystem distribuito. Publish/Subscribe I produttori generano eventi (publish) e si disinteressano della loro consegna. I consumatori si sono registrati come interessati ad un evento (subscribe) e sono avvisati (notify) della sua occorrenza. Vi è quindi un disaccoppiamento tra entità interagenti di tipo spaziale, temporale e sincrono Architetture di sistema Le tipologie di architetture di sistema possono essere: centralizzate, decentralizzate e ibride. 92

100 Architetture Centralizzate Il modello che le rappresenta è quello client/server. L interazione tra client e server implica un comportamento request-reply, ossia è il fattore che crea problemi prestazionali nel Web. Solo alcune richieste sono idempotenti, ovvero possono essere ripetute più volte senza causare danni o problemi; questa proprietà è importante in caso di comunicazioni inaffidabili poiché rende logicamente affidabile una interconnessione fisicamente inaffidabile ad un costo molto elevato. Questo modello è asimmetrico, il client conosce il server ed interagisce con esso in modo sincrono e bloccante, pertanto c è un forte accoppiamento tra le parti interagenti. Stratificazione Il tradizionale stile architetturale a livelli (layer) viene applicato alle architetture centralizzate, difatti abbiamo uno o due livelli per l interfaccia utente (client e server rispettivamente), un livello applicativo ed uno per i dati. La stratificazione è presente anche in molti sistemi informativi distribuiti che implementano il livello dei dati tramite basi di dati relazionali, ad oggetti oppure object-relational. Architetture Multi-Livello In questo caso vi è un mapping tra livelli logici (layer) e livelli fisici (tier). L architettura ad un singolo livello (single-tier) è una configurazione monolitica avente un mainframe ed un thin client (non è client/server). L architettura a due livelli (two-tier) ha due livelli fisici ossia macchina client ed un singolo server. L architettura a tre livelli (three-tier) possiede ciascun livello su una macchina separata. A partire dal two-tier abbiamo la libertà di usare diverse configurazioni architetturali, come si vede nella seguente figura. Le architetture multi-livello passano da un solo livello ad N livelli. Per ogni livello introdotto l architettura guadagna in flessibilità, funzionalità e possibilità di distribuzione. Ma ciò introduce un problema di prestazioni, ovvero aumenta l overhead di comunicazione e si ha una maggiore complessità in termini di gestione ed ottimizzazione. È possibile ottenere diversi tipi di architetture client-server in base all organizzazione del servizio e dei suoi dati. Queste possono essere strutturate sotto la distribuzione: verticale: i componenti logicamente divesi dello stesso servizio possono essere assegnati a macchine distinte, sia su lato server che sul lato client; ogni servizio è fornito tramite cooperazione di componenti distribuiti gerarchicamente. orizzontale: server e client possono essere partizionati ma ogni loro componente può operare da solo, condividere il carico tramite un dispatcher e fornire uno specifico servizio richiesto. 93

101 Architetture decentralizzate e ibride Le architetture decentralizzate pure sono ad esempio i sistemi p2p. Il termine p2p (peer-to-peer) si riferisce ad una classe di sistemi ed applicazioni che utilizzano risorse distribuite per eseguire una funzionalità (anche critica) in modo decentralizzato. Tutti i peer del sistema sono indipendenti e localizzati ai bordi di Internet. Funzionano sia come client che come server e condividono risorse e servizi. Mentre le architetture decentralizzate ibride sono ad esempio gli edge server nelle cdn oppure i sistemi distribuiti collaborativi come quelli basati sul protocollo BitTorrent Architetture e Middleware In molti casi i sistemi middleware seguono un semplice stile architetturale 11 che è poco flessibile ed adattabile. A tal proposito quello che si vuole ottenere è un middleware che si possa adattare all applicazione specifica ed all ambiente in modo dinamico, reattivo e radicale. L interceptor è un costrutto software che interrompe il normale flusso di esecuzione e consente di eseguire altro codice modificando, di conseguenza, la richiesta prima che sia inviata al componente. Ovvero serve ad implementare un middleware adattivo. Interceptor Vi sono tre tecniche di base per ottenere un software adattivo: separazione degli ambiti: cercare di separare le funzionalità dalle extra-funzionalità (affidabilità, prestazioni, sicurezza,...) e successivamente integrarle in una singola applicazione. Questo tipo di middleware può anche essere orientato agli aspetti, ma fino ad oggi sono disponibili solo esempi giocattolo. riflessione computazionale: rappresenta capacità di un programma di controllare se stesso e, se necessario, di adattare il proprio comportamento durante l esecuzione. Le politiche di azione sono definite nel middleware e si possono cambiare a seconda del comportamento del sistema. È una tecnica integrata in alcuni linguaggi di programmazione, come Java, ma non viene usato su sistemi distribuiti su larga scala. progettazione per componenti: permette di organizzare un applicazione distribuita in componenti che possono essere cambiati dinamicamente quando occorre. Questo tipo di middleware supporta il binding tardivo (late composition), ovvero il sistema può essere configurato a run-time e non solo staticamente in fase di progettazione. Tale progettazione è complessa ed anche le interdipendenze tra i componenti. 11 Come mom (message oriented middleware) oppure doc (distributed object computing). 94

102 12.4 Sistemi distribuiti autonomici L autonomic computing è stato introdotto come paradigma architetturale e tecnologico al fine di gestire la crescente complessità ed eterogeneità dei sistemi it mediante adattamenti automatici. Per spiegare questo concetto solitamente ci si riferisce al sistema nervoso centrale, il quale è in grado di controllare alcune funzioni vitali, come ad esempio il battito cardiaco o la temperatura corporea, in modo autonomo. Un sistema autonomico dovrebbe essere in grado di gestire le proprie funzionalità autonomamente, ovvero senza o con limitato intervento umano. Pertanto deve essere in grado di adattarsi con comportamenti reattivi a dinamiche interne ed esterne, più in generale ai cambiamenti dell ambiente che lo circonda. Questo, a fronte di determinate policy impostate dall amministratore, dovrebbe possedere le caratteristiche di autogestione, auto-configurazione, auto-guarizione (garantire la sopravvivenza ai guasti), auto-ottimizzazione delle prestazioni, auto-protezione da attacchi esterni,... Complessivamente deve essere un sistema auto-* (self-*). Un esempio di sistema autonomico è Globule, una cdn collaborativa che analizza le tracce per decidere su quali edge server collocare le repliche dei contenuti Web. Tali decisioni sono guidate da un modello di costo generale: Globule cost = n w i m i i=1 dove w i rappresenta il peso della risorsa e m i rappresenta il costo della risorsa. L origin server raccoglie le tracce ed effettua un analisi di tipo what-if controllando cosa sarebbe successo se la risorsa R fosse stata replicata nell edge server S. Una volta valutate le diverse strategie di replica viene scelta la migliore. 95

103 13 Processi e Virtualizzazione Un sistema software distribuito è composto da un insieme di processi in esecuzione su più nodi. Un algoritmo distribuito può essere visto come un insieme di processi {P 1,P 2,...,P n }. I processi possono comunicare, sincronizzarsi e cooperare con le stesse modalità sia se sono in esecuzione su nodi remoti, sia sullo stesso nodo Processori, Processi e Thread Ogni processore fornisce un insieme di istruzioni con la capacità di eseguirle automaticamente. Il contesto del processore consiste in un insieme di valori memorizzati nei registri del processore, i quali vengono usati per l esecuzione di una serie di istruzioni come ad esempio i registri interni, lo stack pointer, il program counter, ecc... Dunque, usando sistemi multi-processor si possono parallelizzare le istruzioni da eseguire. Sui sistemi distribuiti nasce la necessità di costruire processori virtuali a livello software, ossia che risiedono al di sopra dei processori fisici. Un thread è un processore software minimale. Il contesto di un thread consiste in un insieme minimale di valori memorizzati nei registri e in memoria. Questi dati vengono usati per l esecuzione di una serie di istruzioni come ad esempio lo status del thread oppure il contesto del processore. Il salvataggio del contesto di un thread implica l interruzione del thread attualmente in esecuzione ed il relativo salvataggio di tutti i dati necessari a riprendere l esecuzione. I thread condividono lo spazio di indirizzamento ed il cambio di contesto può essere fatto in modo completamente indipendente dal so. Con queste caratteristiche si ottiene un ottimizzazione nelle fasi di allocazione ed esecuzione dei programmi. Inoltre offrono concorrenza con un minor grado di trasparenza, ovvero generano applicazioni con prestazioni migliori, ma ciò rende difficile la loro codifica e debug. Tramite l uso di applicazioni multi-thread, l esecuzione del programma non viene fermata da una chiamata di sistema bloccante. Un processo è un processore software. Come nel caso dei thread, il contesto di un processo è composto da un insieme di valori memorizzati nei registri ed in memoria, ma questi vengono usati per eseguire uno o più thread. Pertanto eseguire un thread significa usare una serie di istruzioni appartenenti al contesto dello specifico processo. Il cambio di contesto dei processi è generalmente più costoso in quanto coinvolge il so. I processi offrono trasparenza alla concorrenza, ma ad un costo relativamente alto in termini di prestazioni. Tramite l utilizzo di processori, processi e thread la comunicazione è più economica rispetto alle tecniche tradizionali come ipc (inter-process communication). Quindi sono adatti per applicazioni di grandi dimensioni. Processore Virtuale Thread Processo Implementazione dei Thread Ci sono diverse modalità per fornire il supporto di thread, e sono: l implementazione a livello utente, l implementazione a livello kernel, lwp (lightweight process), attivazioni dello scheduler. 96

104 I thread implementati nello spazio utente sono anche detti schema N:1. Tutte le operazioni su thread sono completamente gestite all interno di un singolo processo, quindi non è richiesto l intervento del so per creare, terminare o schedulare un thread (in esecuzione, sospeso o risvegliato). Tale implementazione può essere molto efficiente dato che vi è un tempo di context switching molto basso. Inoltre viene scelta quando ci sono molti eventi esterni di i/o da gestire, ovvero ogni thread viene bloccato in base ad un evento. Poiché tutti i servizi forniti dal kernel sono per conto del processo in cui il thread risiede, se il kernel decide di bloccare il processo allora viene bloccato il thread che appartiene a tale processo. Di conseguenza il kernel non riesce a distinguere i thread dai processi e pertanto si vengono a creare problemi nella segnalazione degli eventi. Inoltre vi è una mancanza di parallelizzazione su multi-processore. I thread possono essere implementati a livello kernel e sono detti schema 1:1. In questo caso il kernel implementa i thread e tutte le operazioni sui thread sono rappresentate da chiamate di sistema. Le operazioni che bloccano un thread non sono più un problema dato che il kernel schedula un altro thread disponibile all interno dello stesso processo. La gestione degli eventi esterni risulta semplice siccome il kernel, che ha il compito di catturare tutti gli eventi, schedula il thread associato con l evento. Il problema principale di questa implementazione è la perdita di efficienza dovuta al fatto che ogni operazione del thread richiede una trap del kernel. Una soluzione possibile a queste due implementazioni consiste proprio nel combinare i concetti dei thread a livello utente ed a livello kernel, ossia ottenere uno schema N:M con N M. Ad esempio i thread di Solaris, o anche detti processi leggeri, possono eseguire thread a livello utente. Possiamo avere differenti scenari: User-Space Thread Kernel-Space Thread LightWeight Process Quando un thread di livello utente fa una chiamata di sistema, il lwp che sta eseguendo il thread si blocca dato che un singolo thread è legato all lwp. Il kernel può, quindi, schedulare un altro lwp che ha un altro thread eseguibile. Quando un thread chiama un operazione di livello utente bloccante, viene eseguito un cambio di contesto ad un thread eseguibile nello stesso lwp. Quando non ci sono thread da schedulare, un lwp può rimanere idle oppure essere rimosso dal kernel. Thread e Sistemi Distribuiti L obiettivo principale dei client multi-thread consiste nel nascondere la latenza delle comunicazioni distribuite. Ad esempio il browser o chiamate rpc. Gli obiettivi principali dei server multi-thread sono una maggiore efficienza prestazionale e maggiore modularità architetturale (specializzazione, semplicità). Per gestire una richiesta in ingresso è molto più economico avviare un thread piuttosto che un processo. Un server single-thread impedisce la scalabilità del server verso un sistema con architettura multi-processore. Inoltre come nei client multi-thread, viene nascosta la latenza di rete reagendo alla richiesta successiva mentre si risponde alla precedente. Ciò permette di ottenere una struttura migliore dato che molti tipi di server hanno una richiesta di i/o elevata, quindi l uso di chiamate di sistema bloccanti semplifica la struttura complessiva. I programmi multi-thread tendono, inoltre, ad essere di dimensioni minori e più semplici da capire per il controllo del flusso semplificato. Lato Client Lato Server 13.2 Virtualizzazione La virtualizzazione è un alto livello di astrazione che nasconde i dettagli dell implementazione sottostante, ovvero è un astrazione di risorse computazionali che presentano all utilizzatore una visione diversa da 97

105 quella reale. Tramite questa tecnica si può nascondere sia la piattaforma utilizzata che le risorse del sistema. Le tecnologie di virtualizzazione comprendono una varietà di meccanismi e tecniche usate per risolvere problemi di prestazioni, sicurezza ed affidabilità. Infatti, queste tecnologie disaccoppiano l architettura ed il comportamento delle risorse hardware e software percepiti dall utente rispetto alla loro realizzazione fisica Macchina Virtuale Il concetto di macchina virtuale (vm, virtual machine) è un idea vecchia, essendo stato definito negli anni 60 in un contesto centralizzato basato su mainframe. Una vm permette di rappresentare le risorse hardware diversamente dai loro limiti fisici. Pertanto una singola macchina fisica può essere rappresentata ed usata come differenti ambienti di elaborazione. La virtualizzazione è ritornata in auge alla fine degli anni 90 e sta assumendo sempre maggiore importanza dato che l hardware cambia più velocemente del software. Ciò permette una maggiore portabilità e migrazione del codice delle applicazioni e vi è un isolamento delle componenti che sono malfunzionanti o soggette ad attacchi. Architetture delle macchine virtuali La virtualizzazione può avere luogo in livelli diversi di un architettura. Ciò dipende fortemente dalle interfacce offerte dai vari componenti del sistema. Sono presenti differenti interfacce tra hardware e software: istruzioni macchina invocabili da ogni programma, o anche detta user isa (vedi punto 4 dell immagine), istruzioni macchina invocabili da programmi privilegiati, o anche detta system isa (vedi punto 3 dell immagine), chiamate di sistema o le abi (application binary interface) (vedi punto 2 dell immagine), chiamate di libreria o anche dette api (vedi punto 1 dell immagine). L obiettivo della virtualizzazione consiste nell imitare il comportamento di queste interfacce. 98

106 Tipi di virtualizzazione Vi sono due tipologie di virtualizzazione: macchina virtuale di processo e monitor della macchina virtuale (vmm, virtual machine monitor), anche detto hypervisor. La macchina virtuale di processo consiste nel compilare un programma in un codice intermedio (portabile), che viene successivamente eseguito nel sistema a run-time. La vm di processo è una piattaforma virtuale che esegue un singolo processo. Essa fornisce un ambiente abi o api virtuale per le applicazioni utente come ad esempio fà Java vm. VM di processo Il monitor della macchina virtuale è uno strato software separato che scherma completamente l hardware sottostante ed imita l insieme di istruzioni fornite dall hardware. Sul vmm possono essere eseguiti indipendentemente e simultaneamente sistemi operativi differenti, ad esempio VMware, Microsoft Virtual PC, VirtualBox, Xen. Hypervisor Il livello architetturale di una vmm può essere implementato direttamente sull hardware (vm classica o di sistema come VMware ESX o XEN) oppure su un sistema operativo esistente (vm ospitata come VMware Server o Virtual PC). 99

107 Modalità di dialogo di una VMM Le modalità di dialogo di una vmm possono essere o la virtualizzazione completa oppure la paravirtualizzazione. Con la virtualizzazione completa il vmm espone ad ogni macchina virtuale un interfaccia hardware simulata funzionalmente identica a quella della sottostante macchina fisica. Con la paravirtualizzazione il vmm espone ad ogni macchina virtuale un interfaccia hardware simulata funzionalmente simile (ma non identica) a quella sottostante fisica. xen è l esempio più noto di paravirtualizzazione, infatti, il vmm offre al so guest un interfaccia virtuale (hypercall api) alla quale il so guest deve riferirsi per avere accesso alle risorse hardware. Pertanto occorre rendere compatibile con xen il kernel ed i driver del so ospite, mentre le applicazioni rimangono invariate. Ciò preclude molti sistemi operativi commerciali a meno che non si abbia il supporto da parte del processore della virtualizzazione nativa (intel-vt e amd-v). Tale tecnica permette, però, di ottenere un overhead molto basso ed è in grado di fornire prestazioni molto simili a quelle dell esecuzione non virtualizzata. Pertanto questa modalità possiede un approccio simile a quello della virtualizzazione completa, ma al posto di emulare l hardware viene creato uno strato minimale di software per gestire le singole istanze delle macchine virtuali e garantire il loro isolamento. Paravirtualizzazione PlanetLab PlanetLab è un sistema distribuito collaborativo caratterizzato da virtualizzazione distribuita. È costituito da un insieme di macchine sparse su Internet. Viene usato come testbed per sperimentare sistemi ed applicazioni distribuite su scala planetaria in un ambiente reale, come ad esempio reti overlay, multicast di livello applicativo, cdn, dht, virtualizzazione ed isolamento, allocazione di risorse, ecc... L organizzazione di base di un nodo consiste nell ospitare una o più macchine virtuali usando una vmm basata su Linux. Il virtual server costituisce un ambiente separato nel quale può essere eseguito un gruppo di processi, pertanto vi è una completa indipendenza, concorrenza ed isolamento tra i processi di virtual server diversi. Un applicazione in PlanetLab viene eseguita in una slice della piattaforma, ossia viene selezionato un insieme di virtual server sui quali l applicazione riceve una frazione delle risorse. I programmi appartenenti a slice diverse, ma in esecuzione sullo stesso nodo, non interferiscono gli uni con gli altri, pertanto possono essere eseguiti su PlanetLab più esperimenti contemporaneamente. Ciò rende PlanetLab un cluster di server virtuali. Quindi la virtualizzazione distribuita consiste in un insieme distribuito di vm che sono trattate dal sistema come un entità singola. 100

108 Problematiche nei sistemi distribuiti Per ottenere la trasparenza in un sistema distribuito, il client deve avere: Trasparenza di accesso: viene generalmente gestita attraverso la generazione di un client stub. Lato Client Trasparenza dell ubicazione, alla migrazione ed al riposizionamento: sono tracciate dal client salvando la locazione effettiva. Trasparenza alla replica: consiste in molteplici invocazioni gestite dal client stub. Trasparenza ai guasti: viene spesso gestita solo dal client, cercando di mascherare malfunzionamenti del server o della comunicazione. Per potersi connettere al server bisogna conoscere la porta a cui connettersi. A tal proposito possiamo avere che la porta: Lato Server è preassegnata e nota. viene assegnata dinamicamente tramite l interazione con un daemon. Il daemon è in ascolto su una porta preassegnata delle richieste per alcuni servizi. Le richieste per lo stesso servizio vengono girate su una porta assegnata dinamicamente di cui viene informato il server corrispondente. Come ad esempio usando daemon per i protocolli sip o ftp. viene attivata dinamicamente dal server tramite l interazione di un superserver. Il superserver ascolta le porte corrispondenti e per ogni richiesta in arrivo risveglia (o crea dinamicamente) il server corrispondente. Come ad esempio il daemon xinetd. È possibile interrompere un servizio su di un server tramite le seguenti tecniche: viene interrotta la connessione e di conseguenza anche il servizio. viene usata una porta di controllo separata dove il server possiede un thread (o processo) in attesa di messaggi urgenti. Quando arriva un messaggio urgente, la richiesta di servizio associata viene sospesa. si usano meccanismi di comunicazione out-of-band forniti a livello di trasporto, ad esempio tcp permette di trasmettere dati urgenti sulla stessa connessione. I server possono avere o meno uno status. Un server senza stato, o anche detto stateless, non mantiene informazioni accurate sullo stato del client e non deve informarlo di eventuali cambi di stato lato server. Quindi il client ed il server sono completamente indipendenti, riducendo le inconsistenze di stato dovuto a crash del client o del server. Però vi è una possibile perdita di prestazioni, ad esempio il server non può anticipare il comportamento del client. Un server con stato, o anche detto stateful, mantiene le informazioni persistenti sullo stato del client, ad esempio registra che un file è stato aperto, in modo da poterne fare il prefetching, oppure conosce il contenuto della cache di un client e consente ad un client di mantenere una copia locale di dati condivisi. Server Stateless Server Stateful 101

109 Migrazione del codice Nei sistemi distribuiti la comunicazione può non essere limitata al passaggio dei dati, ma riguarda anche lo scambio di programmi che sono in esecuzione. La migrazione viene usata per bilanciare o condividere il carico di lavoro, permettendo di risparmiare le risorse di rete e ridurre il tempo di risposta processando i dati vicino a dove risiedono. Ciò aiuta a sfruttare il parallelismo senza avere le difficoltà della programmazione parallela, senza contare che il sistema distribuito viene configurato dinamicamente. Il processo di migrazione del codice è composto da tre segmenti: segmento del codice: rappresentano le istruzioni del programma in esecuzione; segmento delle risorse: sono i riferimenti alle risorse esterne di cui il processo ha bisogno; segmento dell esecuzione: è il contesto di esecuzione del processo (stack, program counter, dati privati). La migrazione può avere mobilità leggera oppure forte. Nella mobilità leggera viene trasferito solo il segmento del codice, mentre nella mobilità forte viene trasferito anche il segmento dell esecuzione. La migrazione può essere iniziata dal mittente o dal destinatario e può essere creato un processo per eseguire il codice migrato oppure il processo viene clonato. Il collegamento tra processo e risorsa viene effettuato tramite: collegamento per identificatore: ad esempio il processo è collegato ad un socket; collegamento per valore: occorre solo il valore di una risorsa come ad esempio una libreria standard; collegamento per tipo: occorre solo una risorsa di tipo specifico. Il collegamento tra risorsa e macchina sono di tipo: risorsa unattached, che è facile da spostare; risorsa fastened, che è più costosa da spostare, come ad esempio una base dati; risorsa fissa. Nei sistemi distribuiti eterogenei, la macchina target può non essere in grado di eseguire il codice. Infatti la definizione del contesto di processo, thread e processore è fortemente dipendente dall hardware, dal so e dal sistema run-time. Quindi la soluzione migliore consiste nell usare una macchina virtuale di processo oppure un monitor di macchina virtuale. 102

110 14 Comunicazione La comunicazione nei sistemi distribuiti si basa sullo scambio di messaggi a basso livello. Per permettere lo scambio di messaggi, le parti devono accordarsi su diversi aspetti, ad esempio quanto voltaggio erogare per segnalare un bit 0 e quanto per un bit 1, oppure come identificare l ultimo bit del messaggio, eccetera. La soluzione a questi problemi consiste nel suddividere il problema in livelli, dove ciascun livello in un sistema comunica con lo stesso in un altro sistema. Se si considera il modello iso/osi, bisogna unire i livelli di sessione e presentazione nel livello middleware. Il livello middleware fornisce servizi comuni e protocolli general-purpose di alto livello, ovvero possono essere usati da differenti applicazioni. I protocolli che usa variano da quelli di naming, che permettono la condivisione di risorse tra differenti applicazioni, ai protocolli di sicurezza, che gestiscono la comunicazione. Inoltre, usa tecniche come il (un)marshaling dei dati per i sistemi integrati oppure meccanismi di scaling come il supporto per la replicazione ed il caching. Livello Middleware 14.1 Tipi di comunicazione La comunicazione tra sistemi distribuiti si distingue in base alla sua: Persistenza: quando una connessione è persistente il messaggio viene immagazzinato nel middleware per tutto il tempo necessario alla consegna. Pertanto non occorre che il processo mittente ferma la sua esecuzione dopo l invio del messaggio e non è necessario che il processo destinatario sia in esecuzione quando il messaggio è stato ricevuto. Se la connessione è transiente il messaggio viene memorizzato dal middleware fin quando i processi mittente e destinatario sono in esecuzione. Se la consegna non è possibile, il messaggio viene cancellato, come avviene nei router a livello di trasporto. Sincronicità: quando vi è una comunicazione sincrona, una volta sottomesso il messaggio, il processo mittente si blocca finché l operazione non viene completata. L invio e la ricezione sono operazioni bloccanti, infatti il mittente si blocca quando: il middleware non prende il controllo della trasmissione; il messaggio non viene consegnato al destinatario; oppure il messaggio non viene elaborato dal destinatario. Al contrario se la comunicazione è asincrona, una volta sottomesso il messaggio, il processo mittente continua l elaborazione. Il messaggio viene memorizzato temporaneamente dal middleware fino ad avvenuta trasmissione. L invio è un operazione non bloccante, mentre la ricezione del messaggio può essere bloccante o meno. 103

111 Dipendenza dal tempo: quando avviene una comunicazione discreta ogni messaggio costituisce un unità di informazione completa. Invece se la comunicazione è di tipo stream allora vengono inviati molti messaggi in relazione temporale tra loro o in base all ordine di invio. In questo modo il destinatario è in grado di ricostruire un unità di informazione completa. Le possibili combinazioni reali delle tipologie di connessione precedentemente elencate sono le seguenti: comunicazione persistente e asincrona: posta elettronica; comunicazione persistente e sincrona: il mittente rimane bloccato fino alla copia garantita del messaggio presso il destinatario; comunicazione transitoria e asincrona: il mittente non attende ma se il destinatario non è raggiungibile il messaggio viene perso; comunicazione transitoria e sincrona: possono esserci i seguenti scenari: mittente è bloccato fino alla copia del messaggio nel destinatario; mittente viene bloccato fino alla copia non garantita del messaggio nello spazio del destinatario (rpc asincrona); mittente attende fino alla ricezione di un messaggio di risposta dal destinatario (rpc sincrona e rmi) Semantica della comunicazione Il messaggio di richiesta e/o risposta si può perdere, in questo caso bisogna prevedere una semantica di comunicazione in presenza di errori. Questa dipende da tre meccanismi di base: 1. request retry (rr 1 ): il client continua a provare fino ad ottenere la risposta oppure la certezza del guasto del destinatario. 2. duplicate filtering (df): il server scarta gli eventuali duplicati di richieste provenienti dallo stesso client. 3. result retrasmit (rr 2 ): il server conserva le risposte per poterle ritrasmettere senza ricalcolarle. Ciò è fondamentale dove l operazione non è idempotente 12. In base alla presenza o meno di questi tre meccanismi abbiamo le seguenti tipologie di semantica: may-be: il messaggio può arrivare o meno, nessun meccanismo viene utilizzato e non vengono fatte azioni per garantire affidabilità sul messaggio. Ad esempio connessioni di tipo udp. at-least-once: il messaggio, se arriva, arriva almeno una volta. In caso di insuccesso non viene trasferita nessuna informazione. In questo caso il client usa rr 1, ma il server non si accorge delle ritrasmissioni. All arrivo di una risposta, il client non sa quante volte sia stata calcolata dal server; quindi non conosce per certo lo stato del server. 12 Operazione idempotente: l esecuzione ripetuta di un operazione produce lo stesso risultato della singola esecuzione della stessa operazione. 104

112 at-most-once: il messaggio, se arriva, arriva al più una volta. In questo caso, il client sa che la risposta è stata calcolata una sola volta. Se la risposta del server non arriva allora abbiamo guasti permanenti al server. Quindi vengono usati tutti i meccanismi, pertanto è adatta a qualunque operazione, anche non idempotente. Tale semantica non mette vincoli sulle azioni conseguenti, pertanto manca un coordinamento tra client e server. Ciò può generare inconsistenza sull accordo tra mittente e ricevente. exactly-once: il messaggio arriva una volta sola oppure entrambi conoscono lo stato finale dell altro. Tale semantica specifica un accordo completo sull interazione, ovvero si è sicuri che se il messaggio non è arrivato o non è stato considerato da entrambi. Questa semantica ammette la conoscenza concorde dello stato delle parti. Dato che non c è una durata massima del protocollo nasce la necessità di avere altri meccanismi per tollerare i guasti lato server Programmazione di applicazioni di rete Nella programmazione di rete esplicita le chiamate vengono effettuate tramite api socket e vi è uno scambio esplicito di messaggi. Questa viene usata nella maggior parte delle applicazioni di rete note, come ad esempio browser o Web server. La distribuzione delle risorse, però, non è trasparente e la maggior parte dell applicazione deve essere scritta dal programmatore. Per risolvere questi problemi abbiamo bisogno di un ulteriore livello di astrazione, ossia uno strato tra il sistema operativo e le applicazioni: il middleware. Questo verrà usato per nascondere la complessità degli strati sottostanti, liberare il programmatore da compiti automatizzabili e migliorare la qualità del software mediante il riuso di soluzioni consolidate, corrette ed efficienti. Nella programmazione di rete implicita le chiamate vengono effettuate tramite rpc, ovvero l applicazione distribuita è realizzata usando le chiamate di procedura, dove il processo chiamante (client) ed il processo contenente la procedura chiamata (server) possono trovarsi su macchine diverse. Questo tipo di programmazione può anche essere attuata tramite rmi, ovvero l applicazione distribuita è realizzata invocando i metodi degli oggetti di un applicazione Java in esecuzione su una macchina remota. Tramite la programmazione implicita abbiamo la trasparenza della distribuzione delle risorse. Programmazione Esplicita Programmazione Implicita 14.4 Chiamate a procedura remota (RPC) Le remote procedure call (rpc) utilizzano il modello client/server con la stessa semantica di una chiamata di procedura, ossia: 105

113 un processo sulla macchina A invoca una procedura sulla macchina B; il processo chiamante sulla macchina A viene sospeso; a questo punto vi è l esecuzione della procedura chiamata sulla macchina B; l input e l output della procedura sono veicolati da paramentri, pertanto lo sviluppatore non vede nessuno scambio di messaggi; il processo chiamante riceve il messaggio di risposta dalla macchina B e si sblocca. A differenza di una chiamata di procedura remota, una locale inserisce sullo stack i parametri e l indirizzo di ritorno. La procedura chiamata mette sullo stack il valore di ritorno e restituisce il valore della procedura chiamante. In programmazione abbiamo due tipologie di passaggio di parametri: per valore: i dati vengono copiati nello stack, il chiamato agisce su tali dati e le modifiche non influenzano il chiamante. per copia/ripristino: la variabile viene copiata nello stack chiamante e ricopiata dopo la chiamata sovrascrivendo il valore originale del chiamante. Ma questa tecnica viene implementata raramente nei linguaggi Architettura delle RPC L architettura rpc è costituita da differenti attori: client, client stub, server stub e server. Il client invoca un client stub che si incarica di tutto: dal recupero del server al trattamento dei parametri, dal trasporto della richiesta al supporto run-time. Il server riceve la richiesta dal relativo server stub che si incarica del trattamento dei parametri dopo che ha ricevuto la richiesta pervenuta dallo strato di trasporto. Al completamento del servizio, il server rimanda il risultato al client. Questo modello prevede la massima trasparenza dato che gli stub sono generati automaticamente, mentre lo sviluppatore progetta e si occupa solo delle parti applicative e logiche di interesse. Quindi i passi effettuati da una rpc sono:. 1. Lato client viene usata una normale chiamata di procedura locale che invoca il client stub. 2. Il client stub costruisce un messaggio ed usa le api del sistema operativo locale. A questo punto viene fatto il marshaling dei parametri, ossia l operazione di impacchettare i parametri in un messaggio. 3. Il sistema operativo del client invia il messaggio al sistema operativo remoto. 4. Il sistema operativo remoto passa il messaggio al server stub. 5. Il server stub spacchetta il messaggio, estrae i parametri e richiama il server, ossia viene effettuato l unmarshaling dei parametri. 6. Il server esegue il lavoro e restituisce il risultato al server stub. 7. Il server stub impacchetta in un messaggio la risposta ed usa le api del sistema operativo. 8. Il sistema operativo del server invia il messaggio al sistema operativo del client. 9. Il sistema operativo del client passa il messaggio al client stub. 10. Il client stub spacchetta il messaggio e restituisce il risultato al client. 106

114 Problemi di RPC Il middleware deve essere in grado di: scambiare messaggi per consentire l identificazione dei messaggi di chiamata e di risposta e l identificazione univoca della procedura remota. gestire l eterogeneità dei dati scambiati come il marshaling/unmarshaling dei parametri oppure la loro serializzazione. gestire alcuni errori dovuti alla distribuzione, come l implementazione errata, oppure errori dell utente o il roll-over. Problemi per la rappresentazione dei dati Per realizzare la semantica della chiamata a procedura in modo trasparente, ovvero senza conoscere dove si trova tale procedura, può esserci una differente rappresentazione dei dati tra le macchine coinvolte. Di conseguenza il client ed il server potrebbero usare una diversa codifica (encoding) dei dati, ad esempio la codifica dei caratteri, la rappresentazione di numeri interi e in virgola mobile, l ordinamento dei byte (little endian vs big endian), strutture dati. Bisogna interpretare i messaggi in modo non ambiguo, pertanto è possibile inserire nel messaggio informazioni sul formato di rappresentazione usato, oppure si può optare sull imposizione di forme canoniche concordate. Inoltre, i client e server devono coordinarsi anche sul protocollo di trasporto usato per lo scambio di messaggi. L interface definition language (idl) è il linguaggio per la definizione delle interfacce. Questo linguaggio permette di identificare univocamente una procedura, ovvero specifica la firma del servizio, utilizzando la coppia [servizio, versione]. Di conseguenza è possibile utilizzare più versioni dello stesso servizio. Esso descrive le operazioni remote, ovvero definisce un astrazione dei dati da trasmettere in input ed output. Infine è in grado di generare automaticamente gli stub dell interfaccia specificata dall utente. IDL Tecnica per il passaggio di parametri Se la procedura chiamante e quella chiamata sono in esecuzione su macchine diverse, e cioè hanno uno spazio di indirizzi differente, si deve definire una tecnica per il passaggio dei parametri. Un riferimento è un indirizzo di memoria valido solo nel contesto in cui viene usato. La soluzione consiste nell usare il meccanismo di copia-ripristino. Ovvero il client stub copia i dati puntati nel messaggio e lo invia al server stub. Il server stub effettua le operazioni con la copia, usando lo spazio di indirizzi della macchina ricevente. Se occorre effettuare una modifica sulla copia, questa sarà poi riportata dal client stub sul dato originale. 107

115 È possibile ottimizzare questa soluzione se il client stub sa che il riferimento è un parametro di input, pertanto non serve che venga ricopiato il suo valore restituito. Se, invece, si tratta di un parametro di output, non serve effettuare la copia di partenza. Presenza di malfunzionamenti Per gestire la presenza di malfunzionamenti abbiamo bisogno di definire una corretta semantica. Nel caso di procedura locale verrà usata la semantica exactly-once, mentre per le procedure remote verrà usata la semantica at-least-once oppure at-most-once. Problema per la localizzazione delle macchine Un altro problema consiste nel creare una tecnica per la localizzazione della macchina che eseguirà la procedura chiamata. Il binding stabilisce come ottenere l aggancio corretto tra il client ed il server in grado di fornire l operazione. Esistono due tipologie di binding: Binding 1. statico: l indirizzo del server è cablato all interno del client. Questo tipo di binding è semplice e dal costo limitato ma non fornisce la trasparenza e la flessibilità. 2. dinamico: ritarda la decisione al momento della necessità, è trasparente e flessibile ma richiede costi maggiori. Di conseguenza il costo deve essere comunque limitato ed accettabile. Nel binding dinamico si distinguono due fasi nella relazione client/server: naming: fase statica, ovvero prima dell esecuzione. Il client specifica a chi vuole essere connesso, tramite un nome univoco che identifica il servizio. In questa fase si associano dei nomi univoci alle operazioni o alle interfacce astratte e si attua il binding con l interfaccia specifica del servizio. Binding Dinamico addressing: fase dinamica, ovvero durante l esecuzione. Il client deve essere realmente collegato al server che fornisce il servizio al momento dell invocazione. In questa fase si cercano gli eventuali server pronti per il servizio. L indirizzamento può essere: esplicito: il client manda un messaggio di multicast o broadcast ed attende solo la prima risposta. Il supporto runtime delle rpc di ogni macchina risponde se il servizio richiesto è fornito da un suo server in esecuzione. implicito: viene usato un name server (o binder o directory service) che registra tutti i server ed agisce su opportune tabelle di binding. Inoltre prevede funzioni di ricerca di nomi, registrazione, aggiornamento ed eliminazione. Ogni chiamata richiede un collegamento dinamico, spesso per questioni di costo dopo un primo legame si usa lo stesso binding ottenuto come se fosse statico. Infatti il binding può avvenire meno frequentemente delle chiamate stesse. In genere, si usa lo stesso binding per molte chiamate allo stesso server RPC asincrona In modalità sincrona, una chiamata rpc provoca il blocco del client, ma se il server non deve restituire nessun risultato, il blocco del client non è necessario. 108

116 Alcuni sistemi rpc forniscono la possibilità di effettuare rpc asincrone, ossia il client, una volta effettuata la chiamata e ricevuto dal server un acknowledge che la chiamata di procedura è stata ricevuta ed avviata, può dedicarsi ad altri task. Se la rpc produce un risultato, si può spezzare l operazione in due, ovvero si ottiene una rpc sincrona differita. C è una prima rpc asincrona dal client al server per avviare l operazione, mentre la seconda rpc asincrona dal server al client per restituire il risultato. Se il client riprende l esecuzione senza aspettare l acknowledge, la rpc asincrona è detta a senso unico Implementazione del supporto RPC A questo punto analizziamo l implementazione ed il supporto rpc di Sun Microsystems: open network computing (onc), anche noto come sunrpc. onc è una suite di prodotti che include: external data representation (xdr): rappresentazione e conversione dei dati; remote procedure call generator (rpcgen): generazione del client stub e server stub; port mapper: risoluzione indirizzo del server; network file system (nfs): file system distribuito di Sun Microsystem; e molte altre implementazioni, tra cui distributed computing environment (dce) rpc. 109

117 Definizione del programma RPC Un programma rpc è composto da due parti descrittive: 1. le definizioni xdr: definizioni dei tipi di dati dei parametri presenti solo se il tipo di dato non è noto in xdr. 2. la definizione di procedure rpc: specifiche del protocollo rpc per le procedure (servizi) offerte, ovvero l identificazione delle procedure ed il tipo di parametri. Tali definizioni sono raggruppate in un unico file con estensione.x. La prima parte del file è composta dalle definizioni xdr, delle costanti e dei tipi di dati dei parametri in ingresso ed uscita per tutti i tipi di dato in cui non esiste una corrispondente funzione xdr built-in. La seconda parte del file è composta dalle definizioni xdr delle procedure. Per le specifiche del protocollo rpc il numero di procedura 0 è riservato a NULLPROC. Ogni definizione di procedura ha un solo parametro di ingresso e di uscita. Gli identificatori di programma, versione e procedura usano tutte lettere maiuscole ed il seguente spazio di nomi: NOMEPROGRAMMAPROG per il nome del programma; NOMEPROGRAMMAVERS per la versione del programma; NOMEPROCEDURA per i nomi delle procedure. 1 struct square_in { 2 long arg1; 3 }; 4 struct square_out { 5 long res1; 6 }; 7 program SQUARE_PROG { 8 version SQUARE_VERS { 9 square_out SQUAREPROC( square_in) = 1; 10 } = 1; 11 } = 0x ; Nel precedente listato è definita la procedura remota SQUAREPROC e rappresenta la procedura numero 1 della versione 1 del programma numero 0x Implementazione del programma RPC Il programmatore ha il compito di sviluppare: il programma client: l implementazione del main() e della logica necessaria per il reperimento ed il binding del servizio/i remoto/i. il programma server: l implementazione di tutte le procedure (servizi). Ma lo sviluppatore non implementa la funzione main() del programma server. 1 # include <stdio.h> 2 #include <rpc/rpc.h> 3 #include "square.h" 4 square_out *squareproc_1_svc(square_in *inp, struct svc_req *rqstp) { 5 static square_out out; 6 out.res1 = inp->arg1 * inp->arg1; 7 return (&out); 8 } Nel precedente listato si vede l implementazione lato server della procedura remota, dove i parametri di ingresso e uscita sono passati per riferimento. Il risultato punta ad una variabile statica (allocazione globale) in modo da essere presente anche quando si esce dalla procedura chiamata. Inoltre il nome della procedura cambia leggermente (si aggiunge il carattere underscore seguito dal numero di versione, tutto in caratteri minuscoli). 110

118 1 # include <stdio.h> 2 #include <rpc/rpc.h> 3 #include "square.h" 4 int main(int argc, char **argv) { 5 CLIENT * cl; 6 char *server; 7 square_in in; 8 square_out * outp; 9 if (argc!= 3) { 10 printf("usage: client <hostname> <integer-value>\n"); 11 exit(1); 12 } 13 server = argv[1]; 14 cl = clnt_create(server, SQUARE_PROG, SQUARE_VERS, "tcp"); 15 if (cl == NULL) { 16 clnt_pcreateerror( server); 17 exit(1); 18 } 19 in.arg1 = atol(argv[2]); 20 if ((outp = squareproc_1(&in, cl)) == NULL) { 21 printf("%s", clnt_sperror(cl, argv[1])); 22 exit(1); 23 } 24 printf("result: %ld\n", outp->res1); 25 exit(0); 26 } In questo listato, viene implementato il lato client dell applicazione. Questa accetta due parametri di ingresso: il nome dell host remoto ed il valore intero da calcolare. Come si vede, il gestore di trasporto (cl) gestisce la comunicazione con il server e viene usata connessione di tipo tcp. Il client deve conoscere l host remoto dove è in esecuzione il servizio ed alcune informazioni per invocare il servizio stesso (programma, versione e nome della procedura). Per la chiamata della procedura remota viene aggiunto al nome della procedura remota un underscore seguito dal numero di versione (in caratteri minuscoli). I parametri di ingresso della procedura chiamata sono due: il parametro di ingresso vero e proprio ed il gestore di trasporto del client. Il client gestisce gli errori che si possono presentare durante l invocazione remota usando le funzioni di stampa degli errori: clnt pcreateerror() e clnt perror(). Ricapitolando, i passi di base per lo sviluppo sunrpc sono i seguenti: 1. definire, se necessario, servizi e tipi di dati. 2. generare in modo automatico gli stub del client e del server e, se necessario, le funzioni di conversione xdr tramite l uso del programma rpcgen ( i file xxx.h, xxx clnt.c, xxx svc.c e xxx xdr.c dove xxx rappresenta il nome definito nel file di definizione con estensione x). 3. realizzare i programmi client e server, compilare tutti i file sorgente (programmi client e server, stub e file per la conversione dei dati) e fare il linking dei file oggetto. 4. pubblicare i servizi lato server tramite portmap (vedi paragrafo ) e registrare i servizi attivando tale daemon. 5. reperire lato client l end-point del server tramite il port mapper e creared il gestore di trasporto per l interazione col server. A questo punto l interazione fra client e server può procedere RPC e socket Le rpc sfruttano i servizi dei socket: tcp per i servizi con connessione, udp per servizi senza connessione (di default su sunrpc). 111

119 14.5 Caratteristiche di SUNRPC Un programma tipicamente contiene più procedure remote. Per ogni procedura remota sono previste anche più versioni e si passa un unico argomento in ingresso ed in uscita per ogni invocazione. La mutua esclusione è garantita dal programma server. Ovvero, viene costruito un server sequenziale e viene eseguita una sola invocazione per volta 13 e di conseguenza non si prevede alcuna concorrenza. Il programma client lavora in modo sincrono e bloccante fin quando non percepisce la risposta dal server. Mentre la comunicazione avviene con semantica at-least-once, dove vengono fatte un numero di ritrasmissioni dopo un intervallo di time-out Identificazione di messaggi RPC Per l identificazione globale di un messaggio di richiesta rpc bisogna avere: numero di programma: rappresentato da interi a 32 bit e possiede i seguenti range: da 0x a 0x1fffffff sono predefiniti da Sun e servono per applicazioni di interesse comune; da 0x a 0x3fffffff sono definibili dall utente; da 0x a 0x5fffffff sono transitori e riservati alle applicazioni; da 0x6fffffff a 0xffffffff sono riservati alle estensioni. numero di versione; numero di porta: rappresentato da un numero a 16 bit su cui il server risponde; numero di procedura. 13 Il sistema operativo Solaris è in grado di generare server multi-thread dato che le sono presenti chiamate di sistema thread-safe. 112

120 Livelli di SUNRPC Abbiamo tre tipi di livelli: 1. alto: si utilizzano servizi rpc standard come rnusers(), rusers() o rstat(). 2. intermedio: si definiscono ed utilizzano nuovi servizi rpc tramite l uso delle primitive: callrpc() e registerrpc(). La primitiva callrpc() viene usata lato client e rappresenta la chiamata esplicita al meccanismo rpc, ovvero invoca l esecuzione della procedura remota. La primitiva registerrpc() viene usata lato server e provvede alla registrazione della procedura remota, associando un identificatore univoco alla procedura implementata nell applicazione. 3. basso: vi è la gestione avanzata del protocollo rpc. Gestisce la chiamata remota tramite funzioni avanzate per ottenere la massima capacità espressiva. Per comunicare tra host eterogenei ci sono due soluzioni possibili: 1. dotare ogni nodo di tutte le funzioni di conversione possibili per ogni rappresentazione dei dati. Questa soluzione fa ottenere alte prestazioni, ma vi è anche la presenza di un alto numero di funzioni di conversione pari a N (N 1) con N numero di host. 2. oppure concordare un formato comune di rappresentazione dei dati, dove ogni host possiede le funzioni di conversione da/per questo formato. Questa soluzione fornisce basse prestazioni, ma ha un numero minore di funzioni di conversione pari a 2N. La soluzione adottata da xdr è la seconda, dove ogni host fornisce solamente le proprie funzionalità di trasformazione, ovvero dal formato locale a quello standard e viceversa. 113

121 external Data Rapresentation Il protocollo external data rapresentation (xdr) svolge le funzioni di marshaling dei dati. Le funzioni built-in di conversione relative a tipi atomici predefiniti sono: xdr bool(), xdr char(), xdr int(),... Mentre quelle per tipi standard (costruttori riconosciuti) sono: xdr array(), xdr string(),... XDR Server: registrazione di una procedura remota Ad una procedura registrata, che può essere invocata, viene associato un identificatore strutturato secondo il protocollo rpc. Il client deve conoscere il numero di porta su cui il server risponde. Mentre il server deve registrare il programma rpc nella tabella dinamica dei servizi dell host su cui il server viene eseguito. Tale tabella, detta port map, contiene una registrazione di una tripla (numero programma, numero versione, protocollo di trasporto) ed il numero di porta. Dato che tutte le procedure rpc all interno di un programma condividono lo stesso gestore di trasporto manca il numero di procedura. Per ogni host che possiede un server rpc esiste un unico processo, chiamato port mapper, che gestisce questa tabella dinamica. 114

122 Port Mapper Il port mapper viene lanciato come daemon (portmap) in ascolto sulla porta 111 (sia in udp che in tcp 14 ). Questo identifica il numero di porta associata ad un programma rpc (servizio) registrato, e l allocazione di ogni servizio risulta dinamica tra i vari nodi. Il port mapper registra i servizi sul nodo ed offre le seguenti procedure: inserimento di un servizio; eliminazione di un servizio; corrispondenza, associazione astratta e porta; l intera lista di corrispondenza; supporto all esecuzione remota. Il limite di questa tecnica è che ogni richiesta del client è preceduta da un interrogazione al port mapper. Per avere una lista di tutti i programmi rpc invocabili su di un host è sufficiente usare il comando: rpcinfo -p nomehost. Mentre la directory /etc/rpc contiene l elenco dei programmi rpc disponibili Processo di sviluppo Data una specifica di partenza, ossia un file di linguaggio xdr, rpcgen produce in automatico: header file, file stub del client e del server, file di routine xdr. Lo sviluppatore deve realizzare il programma client e quello server. 14 Per default si usa solo il trasporto di tipo udp. 115

123 14.6 Java RMI Il paradigma rpc può essere facilmente esteso al modello ad oggetti distribuiti. Java rmi (remote method invocation) è un insieme di strumenti, politiche e meccanismi che permettono l invocazione remota di metodi su oggetti che risiedono su diverse jvm. Viene creato localmente solo il riferimento ad un oggetto remoto che è attivo su una macchina remota. Pertanto il programma client invoca i metodi remoti attraverso questo riferimento locale. In Java rmi, la definizione del comportamento (interfaccia) e l implementazione di quel comportamento (classe) sono concetti separati. La separazione logica tra interfaccia ed oggetto permette anche la loro separazione fisica. L interfaccia remota specifica quali metodi dell oggetto possono essere invocati da remoto, ma lo stato interno di un oggetto non viene distribuito. Quando un client effettua il binding con un oggetto remoto, una copia dell interfaccia del server (stub) viene caricata nello spazio del client. La richiesta in arrivo all oggetto remoto viene trattata da un agente locale al server (skeleton). Usando Java rmi vi è un unico ambiente di lavoro dato che si utilizza il linguaggio Java, ma grazie all uso di questo linguaggio può essere usato su diversi sistemi operativi Stub e Skeleton In Java rmi viene adottato il pattern proxy. I due proxy, stub dalla parte client e skeleton dalla parte server, nascondono al livello applicativo la natura distribuita dell applicazione: stub: proxy locale sul nodo client dove vengono fatte le invocazioni destinate all oggetto remoto; skeleton: proxy remoto sul nodo server che riceve le invocazioni fatte dallo stub e realizza le corrispondenti chiamate sull oggetto server. Stub e skeleton rendono possibile la chiamata di un servizio remoto come se fosse locale, agendo da proxy e sono generati dal compilatore rmi. Inoltre, non è necessario l un/marshaling dei dati, ma 116

124 questi vengono serializzati utilizzando le funzionalità offerte direttamente a livello di linguaggio. Per serializzazione si intende la trasformazione di oggetti complessi in sequenze di bytes (writeobject()). Mentre per deserializzazione si intende la decodifica di una sequenza di byte e procede con la costruzione di una copia dell oggetto originale (readobject()). Quindi la comunicazione avviene nel modo seguente: 1. Il client ottiene un istanza dello stub. 2. Il client invoca metodi sullo stub. 3. Lo stub effettua la serializzazione delle informazioni per l invocazione (id e argomenti) di un metodo ed invia un messaggio allo skeleton contenente tali informazioni. 4. Lo skeleton effettua la deserializzazioe dei dati ricevuti, invoca la chiamata sull oggetto che implementa il server (dispatching), effettua la serializzazione del valore di ritorno e lo invia allo stub in un messaggio. 5. Lo stub effettua la deserializzazione del valore di ritorno e restituisce il risultato al client. Interazione tra componenti in Java RMI Il rmi Registry è il binder per Java rmi. Offre un servizio di nomi che consente al server di pubblicare un servizio e al client di recuperarne il proxy. Quindi funziona come punto di indirezione Architettura Java RMI L architettura di Java rmi è fatta a strati e sono presenti solo iterazioni sincrone e bloccanti. Il rrl (remote reference layer) è responsabile della gestione del riferimento all oggetto remoto, ovvero gestisce lo scambio di dati con il livello di trasporto. Il tl (transport layer) è responsabile della gestione delle connessioni fra i diversi address space, ovvero jvm diverse. Inoltre gestisce il ciclo di vita delle connessioni ed il parallelismo. In questo livello viene usato un protocollo proprietario ma è possibile utilizzare protocolli applicativi diversi, purché siano orientati alla connessione, come ad esempio http. 117

125 Passi per l utilizzo di Java RMI Per realizzare componenti remote bisogna definire il comportamento, ossia un interfaccia che: estende java.rmi.remote; e propaga java.rmi.remoteexception. ed implementarne il comportamento stesso tramite una classe che: implementa l interfaccia precedentemente definita; ed estende java.rmi.unicastremoteobject. Pertanto è necessario compiere i seguenti passi: 1. definire interfacce ed implementazioni dei componenti utilizzabili in remoto; 2. compilare le classi e generare stub e skeleton delle classi utilizzabili in remoto; 3. pubblicare il servizio attivando il rmi Registry e registrando il servizio; 4. ottenere il riferimento all oggetto remoto tramite il name service, facendo un lookup sul Registry. A questo punto l interazione tra il client ed il server può procedere Confronto tra SUNRPC e Java RMI Usando sunrpc si attua una visione a processi e non vi è una completa trasparenza all accesso. Ciò genera operazioni richieste all host del server. Le entità che si possono richiedere sono solo operazioni o funzioni. La semantica di comunicazione utilizzata è at-least-once. La comunicazione può essere sincrona ed asincrona con una durata massima (timeout). Il server possiede un proprio port mapper e la presentazione dei dati avviene usando il linguaggio idl ad hoc (xdr) e gli stub vengono generati con rpcgen. Il passaggio dei parametri avviene per valore, dove le strutture complesse e definite dall utente sono linearizzate e ricostruite dal server per poi essere distrutte al termine dell operazione. Supporta varie estensioni quali i messaggi broadcast e sicurezza. Usando, invece, Java rmi si attua una visione per sistemi ad oggetti passivi con trasparenza all accesso, con scelte che non privilegiano l efficienza. Le entità che si possono usare sono i metodi degli oggetti presenti nelle interfacce. La semantica di comunicazione è di tipo at-most-once. L unico modo di comunicazione permesso è quello di tipo sincrono. La durata massima e le eccezioni gestiscono solo i casi di errore. La ricerca del server viene attuata tramite l utilizzo di un registry con broker unico centrale. La presentazione dei dati avviene tramite lo stub e lo skeleton. Il passaggio dei parametri per default avviene per valore, mentre avviene per riferimento per i soli oggetti con interfacce remotizzabili Comunicazione orientata ai messaggi rpc e rmi migliorano la trasparenza all accesso ma non sono sempre meccanismi adatti a supportare la comunicazione in un sistema distribuito. Ad esempio quando non si può essere certi che il destinatario sia in esecuzione. La natura essenzialmente sincrona delle rpc comporta una perdita di efficacia della comunicazione. Pertanto è possibile usare, in alternativa alle precedenti tecniche, una comunicazione orientata ai messaggi. Questa può essere di tipo: transiente, tramite socket Bekeley e message passing interface (mpi). persistente, tramite sistemi a code di messaggi o message oriented middleware (mom). 118

126 Message Passing Interface (MPI) È una libreria standard de-facto per lo scambio di messaggi tra diversi processi. Rappresenta soltanto la specifica di un interfaccia e non una sua implementazione. Infatti esistono numerose implementazioni di mpi, tra cui Openmpi e mpich. L utilizzo principale di mpi è nel campo della computazione parallela e distribuita. Prevede nelle proprie specifiche la definizione di una serie di primitive per la comunicazione tra processi: quelle per la comunicazione punto-punto e quelle per la comunicazione collettiva. Comunicazione punto-punto in MPI Per l invio e la ricezione di un messaggio tra due processi diversi, con una comunicazione di tipo puntopunto, possiamo usare le seguenti api: MPI Send() e MPI Recv(). MPI Send() lavora in modalità sincrona o bufferizzata a seconda dell implementazione. Ad esempio MPI Bsend() invia i dati in modo bloccante e bufferizzato, MPI Ssend() invia i dati in modo sincrono e bloccante, mentre MPI Isend() e MPI Irecv() scambia i dati in modo non bloccante. 1 # include <stdio.h> 2 #include <string.h> 3 # include <mpi.h> 4 int main (int argc, char **argv) { 5 int myrank; 6 char message[20]; 7 MPI_Status status; 8 MPI_Init(&argc, &argv); 9 MPI_Comm_rank( MPI_COMM_WORLD, & myrank); 10 printf("il mio rank e : %d\n", myrank); 11 if (myrank == 0) { 12 /* Invia un messaggio al processo 1 */ 13 strcpy(message, "PROVA"); 14 MPI_Send(message, strlen(message)+1, MPI_CHAR, 1, 99, MPI_COMM_WORLD); 15 printf("%d) Ho inviato: %s \n", myrank, message); 16 } else if(myrank==1) { 17 /* Riceve il messaggio dal processo 0 */ 18 MPI_Recv(message, 20, MPI_CHAR, 0, 99, MPI_COMM_WORLD, &status); 19 printf("%d) Ho ricevuto: %s \n", myrank, message); 20 } 21 MPI_Finalize(); 22 return 0; 23 } Sistemi a code di messaggi L idea base di un sistema a code di messaggi consiste in applicazioni distribuite che comunicano inserendo messaggi in apposite code. Di solito ogni applicazione ha la sua coda privata, anche se esistono code condivise da più applicazioni. Questa tipologia di scambio di messaggi fornisce un eccellente supporto per comunicazioni persistenti ed asincrone. Il mittente ha solo la garanzia che il suo messaggio venga inserito nella coda del destinatario, ma non vi è alcuna garanzia che il destinatario prelevi il messaggio dalla sua coda. Inoltre è presente un disaccoppiamento temporale tra mittente e destinatario. Un messaggio può contenere qualunque dato e deve essere indirizzato opportunamente utilizzando un nome univoco a livello del sistema. 119

127 Le principali primitive offerte dall api di un sistema a code di messaggi sono: put: invio non bloccante di un messaggio che viene aggiunto ad una coda. Tale operazione è asincrona. get: ricezione bloccante di un messagggio fin quando la coda contiene almeno un messaggio da prelevare oppure attende un messaggio specifico. Tale operazione è sincrona rispetto alla presenza di messaggi in coda. poll: ricezione non bloccante di un messaggio dove viene installato un handler che avvisa il destinatario quando viene inserito un messaggio in una coda. Il meccanismo di callback separa la coda dall attivazione del destinatario Architettura di sistema a code di messaggi I messaggi possono essere inseriti/letti solo in/da code locali al mittente/destinatario: inserimento in coda sorgente (o di invio); lettura da coda di destinazione (o di ricezione); la coda appare locale sia al mittente che al destinatario (trasparenza della distribuzione); è il sistema ad occuparsi del trasferimento dei messaggi. Il mom (message-oriented middleware) deve mantenere la corrispondenza tra ogni coda e la sua posizione sulla rete (servizio di naming). L architettura generale di un mom scalabile richiede un insieme di gestori (router o relay) specializzati nel servizio di routing dei messaggi da un gestore all altro. Il mom realizza un overlay network con topologia propria e distinta. Una sottorete connessa di router conosce la topologia della rete logica (statica) e si occupa di far pervenire il messaggio dalla coda del mittente a quella del destinatario. Topologie complesse e variabili (scalabili) richiedono la gestione dinamica delle corrispondenze coda-posizione di rete (analogia con routing in reti ip). L area di applicazione più importante dei mom è l integrazione di applicazioni in ambito aziendale (enterprise application integration, eai). Tali applicazioni devono essere in grado di interpretare il formato dei messaggi (struttura e rappresentazione dei dati). Le soluzioni possibili per gestire l eterogeneità dei messaggi sono: 120

128 ogni destinatario è in grado di interpretare ogni formato; il formato dei messaggi deve essere comune; il gateway di livello applicativo, broker, è in grado di effettuare le conversioni tra formati diversi. La soluzione generalmente adottata nei mom si basa sulla presenza di message broker. Questo è un componente, centralizzato, che si prende cura dell eterogeneità delle applicazioni, ossia: Message Brocker trasforma i messaggi in ingresso nel formato adatto all applicazione destinataria, fornendo trasparenza di accesso ai messaggi; gestisce un repository delle regole e dei programmi che consentono la conversione dei messaggi. Tale architettura viene anche detta hub-and-spoke. Esempi di mom con broker sono: ibm WebSphere mq, microsoft message queueing (msmq), sun java message service (jms). IBM WebSphere-MQ È un mom molto diffuso e supportato. I messaggi vengono gestiti dal queue manager (qm), dove le applicazioni possono inserire/estrarre messaggi solo nelle/dalle code locali o attraverso un meccanismo rpc. Il trasferimento dei messaggi da una coda all altra di processi diversi avviene tramite canali unidirezionali ed affidabili gestiti dal message channel agent (mca) che si occupano di tutti i dettagli. Ovvero stabilisce quali canali usare tramite gli strumenti messi a disposizione dai protocolli di rete, specifica il tipo di messaggi oppure imposta l invio o la ricezione dei pacchetti. Ogni canale possiede una coda di invio. Il trasferimento sul canale può avvenire solo se entrambi gli mca sono attivi, quindi mq fornisce i meccanismi per avviare automaticamente un mca. Il routing è sotto il controllo di una gestione di sistema sempre statica e non flessibile, dove l amministratore stabilisce le opportune interconnessioni tra mq con tabelle di routing all atto della configurazione. L indirizzo del destinatario consiste nel nome del gestore di destinazione seguito dal nome della coda di destinazione. Un elemento della tabella di routing è formato dalla coppia: [destmq,sendq], dove destmq è il nome del gestore di destinazione e sendq è il nome della coda d invio locale. Per favorire l integrazione delle applicazioni, il mq broker può intervenire sui messaggi: trasformato formati; organizzando routing in base al contenuto; lavorando su informazioni di applicazione, per specificare sequenze di azioni Comunicazione orientata agli stream I tipi di comunicazione analizzati finora sono basati su uno scambio di unità di informazioni discreto, ovvero indipendente dal tempo. Quindi le relazioni temporali tra i dati non sono fondamentali per l interpretazione dei dati. Nelle applicazioni multimediali vengono utilizzati i data stream, ovvero sequenze (flussi) di unità di dati dipendenti dal tempo. I requisiti temporali vengono espressi come requisiti di qualità del servizio 121

129 (quality of service qos). L invio di data stream è facilitato dal mezzo trasmissivo a rappresentazione continua, ma non ne è dipendente. Ad esempio le pipe unix e le connessioni tcp/ip forniscono un mezzo trasmissivo a rappresentazione discreta orientata a gruppi di byte. Le modalità trasmissiva dei dati sono: asincrone: preserva l ordinamento dei dati, non la distanza temporale tra unità dati in uno stream; sincrone: preserva l ordinamento tra unità di dati e garantisce un tempo massimo di trasmissione per ogni unità dati; isocrona: aggiuge rispetto alla modalità sincrona la garanzia di un tempo minimo di trasmissione, ovvero bounded (delay) jitter Stream Uno stream consiste in un flusso di dati continui che usano la trasmissione isocrona. Gli stream sono unidirezionali e generalmente c è un unica sorgente ed una o più destinazioni. Questi possono essere: semplici: ovvero una semplice sequenza di dati; composti: internamente strutturati e composti da un insieme di stream semplici (substream), con requisiti temporali tra i substream che li compongono. In questo caso possono nascere problemi di sincronizzazione. Ad esempio uno stream composto di un film può essere fatto da un substream video e due substream audio per effetto stereo. In uno stream è essenziale che siano preservate le relazioni temporali. Le metriche che possono essere usate per specificare la qos a livello applicativo sono: il bit rate a cui devono essere trasportati i dati; il tempo massimo di setup di una sessione, ossia quando un applicazione può iniziare ad inviare dati; il tempo massimo di trasporto, ovvero quanto impiega un unità di dati ad arrivare al destinatario; la varianza massima del tempo di trasmissione, anche detto jitter; il tempo massimo di round-trip. Per garantire la qos, considerando che lo stack di protocolli Internet si basa su un servizio a datagram e di tipo best-effort, esistono vari meccanismi a livello di rete come i servizi differenziati mediante i quali alcuni pacchetti possono essere trattati con diversa priorità. A livello di sistemi distribuiti esistono diverse tecniche tra cui l utilizzo di buffer per ridurre lo jitter. Per ridurre gli effetti della perdita dei pacchetti, ossia quando più frame sono nello stesso pacchetto, si usa il frame interleaving. 122

130 Dato uno stream composito, per sincronizzare i diversi stream semplici l applicazione usa l algoritmo token bucket, il quale viene utilizzato per il filtraggio del traffico. In alternativa viene effettuato il multiplexing di tutti i substream in un singolo stream e il demultiplexing al lato ricevente, ad esempio mpeg (motion picture expert group). mpeg è un insieme di algoritmi standard per la compressione video ed audio. Per combinare in un singolo stream un insieme illimitato di stream distinti sia discreti che continui bisogna che: ciascun stream originario viene trasformato in un flusso di unità dati, detti frame, la cui sequenza è determinata un etichetta temporale, detta timestamp, generata da un orologio unico con caratteristiche fissate a 90 mhz. i pacchetti di ogni stream vengono combinati mediante multiplexing in una sequenza composita di pacchetti a lunghezza variabile ma con propria etichetta temporale. a destinazione si ricompongono gli stream originari usando l etichetta temporale per riprodurre la sincronizzazione tra ciascuna unità dati al suo interno Comunicazione multicast La comunicazione multicast è uno schema di comunicazione in cui i dati sono inviati a molteplici destinatari. La comunicazione broadcast è un caso particolare della multicast, in cui i dati sono spediti a tutti i destinatari connessi in rete. Esempi di applicazioni multicast one-to-many sono la distribuzione di risorse audio/video oppure quella di file. Mentre esempi di applicazioni multicast many-to-many sono servizi di conferenza, giochi multiplayer o simulazioni distribuite interattive. Vi sono diverse tipologie di multicast: multicast a livello di protocolli di rete: è a livello ip ed è basato sui gruppi, ovvero host interessati alla stessa applicazione multicast, l indirizzo ip associato ad ogni host è di classe d 15. La replicazione dei pacchetti ed il routing sono gestiti dai router. Questo tipo di multicast viene però usato poco nelle applicazioni data la mancanza di uno sviluppo su larga scala e per il problema di tener traccia dell appartenenza ad un gruppo. multicast a livello applicativo: la replicazione dei pacchetti ed il routing sono gestiti dagli endpoint. Può essere strutturato, ovvero vengono creati percorsi di comunicazione espliciti, oppure non strutturato, ovvero basato su gossip Multicast Strutturato L idea di base del multicasting strutturato o applicativo consiste nell organizzare ed usare i nodi in una rete di overlay. Per costruire una rete overlay possiamo organizzare i nodi in un una struttura ad albero, ovvero un unico percorso tra ogni coppia di nodi, oppure organizzati in una rete a maglia, ovvero molti percorsi tra ogni coppia di nodi. Ad esempio, per costruire un albero per il multicast applicativo in un sistema p2p basato su Chord dobbiamo: 1. il nodo che inizia la sessione multicast genera un identificatore multicast groupid; 2. cerca il nodo responsabile per la chiave groupid; 3. tale nodo diventa la radice dell albero di multicast; 4. se p vuole unirsi all albero, invia una richiesta di join verso la radice; 5. quando la richiesta arriva a q: se q non ha mai visto prima una richiesta di join allora diventa un forwarder dove p diventa figlio di q e q continua ad inoltrare la richiesta verso la radice; 15 La classe d è riservata agli indirizzi multicast: [ ].x.x.x. Questi indirizzi cominciano con la sequenza Non hanno maschera di rete, essendo tutti e 32 i bit dell indirizzo utilizzati per indicare un gruppo, non un singolo host. 123

131 altrimenti se q è già un forwarder per groupid allora p diventa figlio di q, ma non occorre inviare la richiesta di join alla radice. Il multicasting applicativo ha però dei costi. Vi è uno stress sui collegamenti dato che un messaggio di multicast applicativo attraversa lo stesso collegamento fisico per due volte. Inoltre il rapporto tra il tempo di trasferimento nella rete overlay e quello nella rete sottostante, anche detto stretch, è superiore a Multicast Non Strutturato Il multicasting applicativo non strutturato è basato su protocolli di tipo probabilistico, detti anche epidemici o basati su gossip. Questi protocolli permettono la rapida diffusione delle informazioni attraverso la scelta casuale dei successivi destinatari tra quelli noti al mittente. Ogni nodo per inviare un messaggio, recapita il messaggio ad un sottoinsieme, scelto casualmente, dei nodi nel sistema. Ogni nodo che lo riceve ne rinvierà una copia ad un altro sottoinsieme, anch esso scelto casualmente, e così via. Questa tecnica permette che ogni nodo invia soltanto un numero limitato di messaggi, indipendentemente dalla dimensione complessiva del sistema e tali messaggi sono ridondanti. L idea che sta alla base dei protocolli epidemici è nata nel 1987 per garantire la consistenza dei database replicati. Assumendo che non vi siano conflitti di scrittura, le operazioni di aggiornamento sono eseguite inizialmente su una o alcune repliche. Una replica passa il suo stato aggiornato ad un numero limitato di vicini. La propagazione di questo aggiornamento è lazy (ritardata). Alla fine di questa procedura, ogni aggiornamento dovrebbe raggiungere tutte le repliche. I proticolli epidemici sono usati nei sistemi distribuiti per: la diffusione dell informazione; il peer sampling, ossia per la creazione di un gruppo di peer; monitoring dei nodi e risorse in sistemi distribuiti a larga scala; computazioni di valori aggregati, ad esempio la media, o il calcolo del massimo o del minimo. Anti-Entropia L anti-entropia è un modello di propagazione in cui ciascuna replica sceglie a caso un altra replica e si scambiano gli aggiornamenti giungendo al termine ad uno stato identico su entrambe. In questo modello, un nodo p sceglie casualmente un altro nodo q nel sistema. Ci sono tre strategie di aggiornamento: 1. push: p invia soltanto i suoi aggiornamenti a q. 2. pull: p si prende soltanto i nuovi aggiornamenti da q. 3. push-pull: p e q si scambiano reciprocamente gli aggiornamenti, dopodiché possiedono le stesse informazioni. La strategia push-pull impiega solo O(log(N)) round per propagare un singolo aggiornamento a tutti gli N nodi nel sistema. Ogni round rappresenta l intervallo di tempo in cui ogni nodo ha preso almeno una volta l iniziativa di scambiare aggiornamenti. 124

132 Gossiping Il gossiping è un modello di propagazione in cui una replica che è stata appena aggiornata (infettata) contatta un certo numero di repliche scelte casualmente inviandogli il proprio aggiornamento, ovvero infettandole a loro volta. Il modello di base consiste nei seguenti passi: un nodo p che è stato appena aggiornato contatta un certo numero di nodi scelti a caso. se un nodo contattato ha già ricevuto l aggiornamento, ossia è già infetto, p perde interesse nel propagare l aggiornamento con probabilità 1 k di contattare altri nodi. se s è la frazione di nodi che non sono a conoscenza dell aggiornamento si dimostra che: s = e (k+1)(1 s) Se si vuole garantire che tutti i nodi sono aggiornati, il gossiping puro non è sufficiente. 125

133 15 Sistemi di Naming I nomi in un sistema distribuito sono usati per condividere risorse, servizi o applicazioni, per identificare univocamente entità e per far riferimento alla loro posizione (localizzarle). Il naming nei sistemi distribuiti si differenzia da quello usato nei sistemi non distribuiti per l implementazione. Le entità di un sistema distribuito possiedono un nome che solitamente è rappresentato da una stringa. Un punto d accesso è un tipo speciale di entità che permette di operare su un entità, dove il nome del punto d accesso è rappresentato da un indirizzo. Un entità può avere più punti di accesso. Questi possono essere simultanei o diversi nel tempo, ovvero un entità può cambiare punto di accesso ed il punto di accesso può essere riassegnato. Ad esempio, una persona è un entità del sistema paragonabile ad un servizio, il suo telefono è il punto di accesso al servizio e il numero di telefono è il nome del punto di accesso di quel servizio. Quindi bisogna decomporre la corrispondenza entità-indirizzo in due relazioni legate ma distinte. Il nome dell entità rappresenta i nomi degli attributi dell entità, tra cui il punto di accesso. Il punto di accesso rappresenta l indirizzo dell entità. In un sistema distribuito vogliamo che i nomi di entità siano indipendenti dai loro indirizzi. Un nome indipendente dalla posizione dell entità E è a sua volta indipendente dagli indirizzi dei punti di accesso offerti da E. Quando i nomi specificano un entità in modo univoco allora vengono chiamati identificatori. Non tutti i nomi sono identificatori e alcuni devono essere trattabili (ad esclusione di indirizzi ed identificatori). Un identificatore è un nome con le seguenti proprietà: Entità Identificatori 1. denota al più una singola entità; 2. una stessa entità non può avere più di un identificatore; 3. non è riusabile. Ad esempio un numero di telefono fisso non è un vero identificatore dell entità persona perché può essere riassegnato, mentre l indirizzo mac è un identificatore della scheda di rete. Nomi diversi per una stessa entità vengono detti alias. Un sistema di naming permette di localizzare le entità dei sistemi distribuiti mantenendo un collegamento nome-indirizzo tra entità in modo indipendente dalla posizione dell entità. Spesso nei sistemi distribuiti troviamo molti sistemi di naming che hanno numerose proprietà e sono anche molto diversi tra loro. Inoltre data la presenza di entità eterogenee bisogna attuare livelli diversi di nomi, ovvero in un sistema distribuito vengono usati più sistemi di naming e più livelli di nomi. Bisogna avere, infine, sistemi di naming con diversi contesti di visibilità e pertanto più funzioni di trasformazione (mapping) tra livelli di nomi. Le proprietà di base dei sistemi di naming sono: Alias Generalità: varietà dei nomi disponibili trattati. Alias: definizioni multiple della stessa entità, ovvero una varietà di nomi per la stessa entità con mapping capace di traslare tra questi. Distribuibilità: un uso di directory partizionate e/o replicate. User Friendliness: nomi facili per l utente. Mentre ci sono tre tipologie di naming: 1. Semplici o Flat: non strutturati, stringhe di bit casuali. 2. Strutturati: composti da nomi semplici e leggibili dall uomo. 3. Basati sugli attributi: entità descritte da un inseme di coppie [attributo,valore] Naming semplice Il naming semplice utilizza varie tecniche. 126

134 Approccio semplice Un approccio semplice consiste nell usare tecniche come broadcasting/multicasting o i puntatori forwarding. Nel caso del broadcasting viene mandato un messaggio di broadcast contenente l identificatore dell entità, richiedendo all entità di restituire il suo indirizzo. Viene usato, ad esempio, nel protocollo arp quando effettua il mapping tra l indirizzo ip a 32 bit e l indirizzo mac a 48 bit. Tale tecnica manca di scabilità e quindi è adatta solo per reti locali. Inoltre richiede a tutti i processi di ascoltare richieste di localizzazione, i quali non sono in grado di rispondere. A tal proposito viene usata come soluzione parziale il multicasting dove solo un numero ristretto di macchine riceve il messaggio. L approccio dei puntatori forwarding viene spesso usato per localizzare entità mobili. Ogni volta che un entità si sposta, lascia un puntatore forwarding alla sua nuova posizione, quindi un client può rintracciare l entità seguendo la catena di puntatori forwarding. Tale tecnica soffre però di problemi di scalabilità geografica proprio perché vengono a formarsi catene di forwarding molto lunghe e vi è un certo overhead per la deferenziazione dei puntatori. Inoltre, ogni punto intermedio della catena è costretto a mantenere le informazioni di forwarding e quindi il sistema è vulnerabile all interruzione della catena. Per mantenere corta la catena di forwarding, si può aggiornare il riferimento sul client non appena viene localizzata la posizione. I puntatori forwarding sono implementati come una coppia [client stub, server stub]. La redirezione di un puntatore forwarding consiste nel memorizzare il collegamento in un client stub. Broadcasing Puntatori Forwarding Approcci home-based Nello schema a singolo livello, gli approcci home-based possiedono una home che tiene traccia della posizione attuale dell entità e un home address dell entità registrato in un servizio di naming. La home registra l indirizzo attuale dell entità, mentre i client contattano sempre prima la home e poi continuano usando la posizione attuale dell entità. Questo approccio è stato adottato in mobile ip, dove l home agent fornisce l home address mentre il Care-of address è l indirizzo attuale. Nello schema a due livelli bisogna tenere traccia delle entità visitate, controllare prima un registro locale e se la ricerca locale fallisce bisogna contattare la home. Lo svantaggio principale degli approcci home-based verte sull indirizzo della home, infatti per funzionare bisogna che tale indirizzo sia garantito fin quando esiste almeno un entità. Tale problema peggiora quando l entità è si sposta nella rete e l indirizzo della home rimane fisso. Di conseguenza vi è una scarsa scalabilità geografica. Prendiamo ad esempio il meccanismo già esaminato come il sistema Chord (vedi pagina 84). Per tener conto della vicinanza sulla rete in un sistema basato su dht si possono usare differenti tecniche: Esempio Assegnare degli identificatori dei nodi basati sulla topologia, detta proximity id selection. Quando viene assegnato un id ai nodi bisogna fare in modo che i nodi vicini nello spazio degli id siano anche vicini in rete, ma ciò può essere molto difficile da realizzare. Utilizzare la proximity route selection dove ogni nodo mantiene una lista di successori alternativi e sceglie il più vicino tra quelli possibili quando effettua l instradamento 16. Utilizzare la proximity neighbor selection dove si ottimizzano le tabelle di routing in modo da scegliere come neighbor il nodo più vicino, ma non è applicabile in Chord. 16 In realtà sarebbe meglio chiamare questa tecnica proximity next-hop selection. 127

135 Approcci gerarchici L idea che sta alla base degli approcci gerarchici consiste nel costruire un albero di ricerca gerarchico. La rete è suddivisa in domini non sovrapposti. Questi domini possono essere raggruppati in domini di livello più alto. Esiste pertanto un unico dominio top-level che copre l intera rete. Ogni dominio ad ogni livello ha un nodo directory associato. L indirizzo di un entità viene memorizzato nei nodi directory foglia o in un nodo intermedio all interno di un location record. Se un entità si trova nel dominio D, il nodo directory del dominio di livello più alto avrà un puntatore a D. Ovviamente la radice conosce tutte le entità. L operazione di ricerca inizia dal nodo foglia locale. Se il nodo conosce l entità segue il puntatore, altrimenti viene inoltrata la richiesta al padre. La ricerca in su termina sempre al nodo radice. 128

136 15.2 Naming strutturato Il naming strutturato è fatto, ad esempio, da nomi di file o da nomi degli host in Internet. Lo spazio dei nomi è un grafo orientato ed etichettato dove: un nodo foglia rappresenta una data entità; un nodo directory è un entità che fa riferimento ad altri nodi; gli archi sono etichettati con un nome; il nodo con solo archi in uscita è detto nodo radice e possono esserci più nodi radice in un grafo dei nomi; un nodo directory contiene una tabella (directory) di coppie [etichetta arco, identif icatore nodo]. I grafi dei nomi sono spesso aciclici, ma non tutti. Ogni cammino N nello spazio dei nomi è denotato dalla sequenza delle etichette degli archi corrispondenti al cammino, detta anche pathname: N : {label 1,label 2,...,label n }. I cammini possono essere assoluti o relativi. La risoluzione dei nomi è il processo di attraversamento del grafo dei nomi cercando, uno per volta, i componenti del pathname. Per risolvere un nome viene utilizzato il meccanismo di chiusura, che permette la selezione del contesto implicito dal quale iniziare la risoluzione del nome. Ad esempio inizia da un name server dns. Nel grafo dei nomi di un filesystem in unix TM e gnu/linux il nodo radice è sempre il primo inode della partizione che rappresenta il filesystem. Gli hard link rappresentano più cammini assoluti denotano lo stesso nodo di un grafo dei nomi. Mentre i symbolic link rappresentano il nodo che contiene un attributo con il cammino assoluto. Lo spazio dei nomi è il cuore di un naming service. Un naming service consente agli utenti di inserire, eliminare e cercare nomi. Questo viene implementato attraverso uno o più name server. Pertanto bisogna distribuire il processo di risoluzione dei nomi e la gestione dello spazio dei nomi su molteplici macchine, distribuendo i nodi del grafo. La distribuzione dello spazio dei nomi può essere suddivisa in tre livelli logici: Naming Service 1. Livello Globale Sono presenti un nodo radice e nodi directory di alto livello. Viene gestito congiuntamente da diverse amministrazioni, pertanto l informazione è generalmente stabile. A livello globale i name server sono altamente disponibili e sono presenti meccanismi di caching e replicazione per migliorare le prestazioni. 2. Livello Amministrativo Sono presenti nodi directory di livello intermedio che rappresentano entità appartenenti alla stessa unità amministrativa o azienda. A livello amministrativo abbiamo requisiti simili ai name server di livello globale. Possiede quindi un buon livello di disponibilità con caching e replicazione per le prestazioni. 129

137 3. Livello Gestionale I nodi directory sono di livello più basso all interno di una singola amministrazione e possono cambiare frequentemente. A livello gestionale possiamo avere indisponibilità temporanee del name server ed il caching è meno efficiente a causa dei continui aggiornamenti Risoluzione dei nomi Elemento Globale Amministrativo Gestionale Numero nodi Pochi Molti Moltissimi Tempi di risposta Secondi Millisecondi Immediato alle ricerche Propagazione Lenta Immediata Immediata aggiornamenti Numero repliche Molte Nessuna o poche Nessuna Applicazione Si Si A volte caching lato client La risoluzione dei nomi iterativa consiste nei seguenti passi: 1. resolve(dir 0, [name 1,..., name k ]) è inviato a NameServer 0 responsabile per dir 0. Risoluzione iterativa 2. NameServer 0 risolve resolve(dir 0, name 1) con dir 1, restituendo l identificatore, ovvero l indirizzo, di NameServer 1, che memorizza dir Il client o il name resolver locale invia resolve(dir 1, [name 2,..., name k ]) a NameServer 1 e il procedimento ricomincia dal primo punto. La risoluzione dei nomi ricorsiva consiste nei seguenti passi: 1. resolve(dir 0, [name 1,..., name k ]) è inviato al NameServer 0 responsabile per dir 0. Risoluzione ricorsiva 130

138 2. NameServer 0 risolve resolve(dir 0, name 1) con dir 1 ed invia resolve(dir 1, [name 2,..., name k ]) a NameServer 1, che memorizza dir NameServer 0 attende il risultato della risoluzione da NameServer 1 e lo restituisce al client. Un attributo importante di molti nodi è l indirizzo a cui può essere contattata l entità rappresentata. La replicazione dei nodi rende i meccanismi dei name server tradizionali inadatti per localizzare le entità mobili. Quindi distribuendo i nodi su server che potrebbero, in linea di principio, essere localizzati ovunque, si introduce implicitamente nello schema di naming una dipendenza dalla posizione. Per la scalabilità geografica occorre assicurare che il processo di risoluzione scali su grandi distanze geografiche. Pertanto la risoluzione ricorsiva è più scalabile di quella iterativa. Domain Name System basato su DHT Il domain name system (dns) è uno dei più diffusi servizi di naming distribuiti. È stata proposta un implementazione completamente decentralizzata del dns basata su dht. L idea di base consiste nel prendere un nome dns completo, trasformarlo tramite l uso di una funzione hash in una chiave k ed usare un sistema basato su dht per effettuare la ricerca della chiave. In questo modo si ottiene una maggiore scalabilità ma non si possono ricercare tutti i nodi in un sottodominio Naming basato su attributi Il naming semplice o strutturato consente di far riferimento alle entità in modo indipendente dalla loro localizzazione. Esiste anche la possibilità di usare descrizioni ancora più dettagliate delle entità per localizzarle. Il Directory Service realizza un sistema di naming basato su attributi piuttosto che su nomi strutturati, come fa invece il dns. Questo tratta spazi di nomi come insiemi di coppie [attributo, valore], dove alle entità vengono associate informazioni dettagliate che consentono di descrivere le risorse e tali informazioni vengono usate per effettuare la ricerca dell esatta localizzazione della risorsa stessa. A tal proposito viene usato rdf (resource description framework) per descrivere le risorse in modo unificato LDAP Le operazioni di ricerca in un directory service distribuito possono essere molto costose. Infatti, richiedono il matching con i valori degli attributi richiesti rispetto ai valori reali degli attributi, ovvero viene attuata una ricerca esaustiva. La soluzione a questo problema consiste nell implementare un directory service essenziale, come una base di dati, e combinarlo con un tradizionale sistema di naming strutturato. Da tale combinazione ha origine ldap (lightweight directory access protocol), che deriva da osi x.500 con miglioramenti prestazionali. L architettura complessiva ldap risulta molto simile a quella del dns, ma è anche più sofisticata e potente. È un protocollo che permette l accesso di informazioni condivise in rete. Pertanto vi è un accesso di tipo client-server ad una collezione di informazioni. Non va però considerato come una base di dati, poiché lo scopo di una base di dati consiste nel ricercare informazioni e non la gestione delle stesse. Le informazioni mantenute in un server ldap vengono sopratutto lette e soltanto occasionalmente vengono scritte o aggiornate, quindi non sono presenti meccanismi di roll-back e sincronizzazione. Mentre l organizzazione delle informazioni è di tipo descrittivo e non relazionale. 131

139 Sia il server che il gateway ldap implementano il protocollo e richiedono informazioni, ma il server contiene le informazioni, mentre il gateway interroga i server per reperirle. Sono disponibili alcune implementazioni di ldap come OpenLDAP, SUN Directory Service e Microsoft Active Directory. ldap è la soluzione più diffusa per l implementazione di directory general purpose. Fra le possibili applicazioni di ldap troviamo: White Pages (elenco telefonico e indirizzario). Ad esempio i principali client di posta elettronica prevedono la connessione ad un server ldap per le funzioni di rubrica. Autenticazione ed autorizzazione. Routing dei messaggi di posta elettronica. Distribuzione di certificati x.509 e crl. Presenza di oggetti e classi Java tramite jndi. Backend per altri servizi di directory. Memorizzazione di profili utenti. Il Directory Service ldap consiste di elementi della directory, paragonabili al resource record nel dns. Ogni elemento è costituito da un insieme di coppie [attributo, valore] in cui ogni attributo ha un tipo associato, come si vede in tabella: Attributo Abbreviazione Valore Country C NL Locality L Amsterdam Organization O Vrije Universiteit OrganizationUnit OU Comp. Sc. CommonName CN Main server Mail Servers , , FTP Server WWW Server L insieme di tutti gli elementi di un directory service ldap si chiama dib (directory information base). Ogni elemento del dib ha un nome univoco globale. Mentre le informazioni sono organizzate con una struttura gerarchica ad albero chiamata dit (directory information tree). LDAP: /C=NL/O=Vrije Universiteit/OU=Comp. Sc. -> DNS: nl.vu.cs answer = search("&(c = NL) (O = Vrije Universiteit) (OU = *) (CN = Main server)") In caso di directory di larga scala, il dit è suddiviso e distribuito su più server, detti directory service agent (dsa), come al name server nel dns. Un server ldap è in grado di propagare le proprie directory ad altri server ldap, fornendo accesso globale all informazione. Quando un client ldap si connette ad un server ldap può cercare o modificare le informazioni presenti in una directory. In caso di ricerca, il server ldap risponde oppure delega il flusso dell interrogazione ad un altro server. In caso di modifica, il sever ldap verifica che l utente abbia il permesso di attuare la modifica, poi inserisce, aggiorna o cancella l informazione. 132

140 16 Sincronizzazione In un sistema distribuito i processi girano su macchine diverse connesse in rete. Questi processi cooperano per portare a termine un operazione. La loro comunicazione avviene esclusivamente attraverso lo scambio di messaggi. Ma è possibile che i nodi di un sistema distribuito devono eseguire operazioni sincrone nello stesso tempo assoluto, pertanto bisogna definire degli algoritmi che richiedono meccanismi di sincronizzazione o in alternativa questi algoritmi necessitano di eventi ordinati, come i timestamp che specificano l ordine di esecuzione dei messaggi. Di conseguenza, nei sistemi distribuiti il tempo è un fattore critico. In un sistema centralizzato è possibile stabilire l ordine in cui gli eventi si sono verificati e quindi esiste un unica memoria condivisa ed un unico clock. In un sistema distribuito, invece, è impossibile avere un unico clock fisico comune a tutti i processi eppure se si considera il tempo in cui sono stati generati gli eventi la computazione globale può essere vista come un ordine totale. Risalire a questo tempo è di vitale importanza per risolvere molti problemi, o comunque è importante stabilire quale evento è stato generato prima di un altro. Pertanto sono possibili due soluzioni: 1. sincronizzazione degli orologi fisici, dove il middleware di ogni nodo di un sistema distribuito aggiusta il valore del suo clock fisico in modo coerente con quello degli altri. 2. sincronizzazione degli orologi logici, dove viene garantito l ordinamento degli eventi senza usare necessariamente gli orologi fisici Modello della computazione Un sistema distribuito è composto da N processi e canali di comunicazione. Ogni processo P genera una sequenza di eventi che possono essere interni, tramite il cambiamento dello stato del processo, oppure esterni, come il send() o il receive() di messaggi 17. Quindi l evoluzione di una computazione può essere visualizzata con un diagramma spazio-tempo. Denotiamo con i la relazione di ordinamento su un processo P i tra due eventi: e 2 i e 1 se e solo se e 2 è accaduto prima di e 1 in P i. La storia locale è la sequenza di eventi generati da un singolo processo, ad esempio history(p 1) = h 1 = <e 1 1, e 2 1, e 3 1, e 4 1, e 5 1, e 6 1>. La storia locale parziale è il prefisso della storia locale, ad esempio h m 1 = e e m 1. Infine la storia globale è un insieme di storie locali, ad esempio H = n i=1 h i. La tecnica del timestamping viene usata quando ogni processo etichetta, con un timestamp, gli eventi. In questo modo è possibile realizzare una storia globale del sistema. Per attuare questa tecnica ogni processo etichetta gli eventi con il proprio clock fisico. Ma in questo modo si può ricostruire l ordinamento degli eventi di un singolo processo, mentre in un sistema distribuito è impossibile avere un unico clock fisico condiviso tra tutti i processi. Una prima soluzione consiste nel tentare di sincronizzare con una certa approssimazione i clock fisici locali ad ogni processo attraverso opportuni algoritmi. Il processo può etichettare gli eventi con il valore del suo clock fisico che risulta sincronizzato con gli altri con una certa approssimazione. La tecnica di timestamping è quindi basata sulla nozione di tempo fisico (o clock fisico). Però non è possibile mantenere l approssimazione dei clock limitata in un sistema distribuito asincrono, in cui non ci sono vincoli sulla velocità di esecuzione dei processi, sul ritardo di trasmissione dei Storico Computazionale Timestamping 17 Denotiamo con e k i il k-esimo evento generato dal processo i-esimo. 133

141 messaggi e sul tasso di scostamento dei clock (clock drift rate). In un modello asincrono il timestamping non si può basare sul concetto di tempo fisico. Si introduce quindi la nozione di clock basata su un tempo logico (o clock logico). All istante di tempo reale t, il sistema operativo legge il tempo dal clock hardware H i (t) del computer. Quindi produce il clock software C i (t) = ah i (t) + b che approssimativamente misura l istante di tempo fisico t per il processo P i. Ad esempio C i (t) è un numero a 64 bit che fornisce i nano secondi trascorsi all istante t da un istante di riferimento. In generale il clock non è completamente accurato e può essere diverso da t. Se C i si comporta abbastanza bene, può essere usato come timestamp per eventi che occorrono in P i. Quindi per poter distinguere due eventi, la risoluzione del clock, ovvero il periodo che intercorre tra gli aggiornamenti del valore del clock, è pari a T risoluzione < T. Si definisce skew la differenza istantanea tra il valore di due diversi clock. Mentre drift è un fenomeno dovuto a variazioni fisiche dell orologio, ovvero i clock contano il tempo con differenti frequenze, quindi divergono nel tempo rispetto al tempo reale. Il drift rate consiste nella differenza per unità di tempo di un clock rispetto ad un orologio ideale. Ad esempio il drift rate di 2 µsec/sec significa che per ogni secondo il clock incrementa il suo valore di 1 secondo + 2µsec. I normali orologi al quarzo deviano di circa 1 sec in giorni (10 6 sec/sec). Gli orologi al quarzo ad alta precisione hanno un drift rate di circa 10 7 o 10 8 sec/sec. Anche se differenti clock di un sistema distribuito vengono sincronizzati in un certo istante, a causa del drift rate, dopo un certo intervallo di tempo, saranno di nuovo disallineati, pertanto occorre eseguire una sincronizzazione periodica per riallineare i clock. coordinated universal time (utc) è uno standard internazionale per mantenere il tempo. È basato sul tempo atomico ma occasionalmente aggiustato utilizzando il tempo astronomico, ovvero il numero di transazioni al secondo dell atomo di cesio-133. I clock fisici che usano oscillatori atomici sono i più accurati dato che possiedono un drift rate pari a sec/sec. L output dell orologio atomico è inviato in broadcast da stazioni radio su terra o da satelliti, ad esempio come fa il gps. I computer con ricevitori possono sincronizzare i loro clock con questi segnali. I segnali da stazioni radio su terra hanno un accuratezza di circa msec, mentre da gps hanno un accuratezza di circa 1 µsec. Per sincronizzare i clock fisici si può utilizzare la sincronizzazione esterna oppure quella interna. Nella sincronizzazione esterna i clock C i, per i = 1,2,...,N, sono sincronizzati con una sorgente di tempo S in utc, in modo tale che, dato un intervallo I di tempo reale: S (t) C i (t) < D per i = 1,2,...,N e per tutti gli istanti in I. I clock C i sono accurati se rientrano nel bound D. Mentre nella sincronizzazione interna i clock di due computer sono sincronizzati tra loro se: per i = 1,2,...,N nell intervallo I è valida la relazione C i (t) C j (t) < D. I clock C i e C j si accordano all interno del bound D. I clock sincronizzati internamente non sono necessariamente sincronizzati anche esternamente. Infatti tutti i clock possono deviare collettivamente da una sorgente esterna sebbene rimangano tra loro sincronizzati entro il bound D. Mentre se l insieme dei processi è sincronizzato esternamente entro un bound D, allora è anche sincronizzato internamente entro un bound 2D. Un clock hardware H è corretto se il suo drift rate si mantiene all interno di un bound ρ > 0 limitato, quindi il drift rate di un clock corretto è compreso tra ρ e +ρ. Se il clock H è corretto allora l errore che si commette nel misurare un intervallo di istanti reali [t, t], con t > t, è limitato: Clock Fisico Skew, Drift e Drift Rate UTC Sincronizzazione Esterna Sincronizzazione Interna Correttezza clock fisici di (1 ρ) ( t t) H ( t) H (t) (1 + ρ) ( t t) In questo modo si evitano quindi salti del valore del clock. Per quanto riguarda il clock software C, spesso basta una condizione di monotonicità, ovvero t > t C ( t) > C (t). 134

142 La sincronizzazione interna in un sistema distribuito sincrono avviene impostando il tempo di esecuzione in un determinato intervallo, ovvero tra un lower bound ed un upper bound. Ciascun messaggio trasmesso su un canale viene quindi ricevuto in un tempo limitato. Quindi ciascun processo ha un clock locale con un drift rate dal clock reale conosciuto e limitato. L algoritmo di sincronizzazione consiste nei seguenti passi: 1. Un processo P 1 manda il suo tempo locale t ad un processo P 2 tramite un messaggio m. Algoritmo di sincronizzazione 2. P 2 imposta il suo clock a t+t trasmissione dove T trasmissione è il tempo di trasmissione del messaggio m. T trasmissione non è noto ma, essendo il sistema distribuito sincrono allora min T trasmissione max dove u = (max min) rappresenta l incertezza sul tempo di trasmissione. 3. Se P 2 imposta il suo clock a t + (max + min) /2 è possibile mantenere lo skew tra clock al più pari a u 2. In generale, con N clock di bound ottimo sullo skew si ottiene u ( 1 1 N ). Mentre in un sistema distribuito asincrono T trasmissione = min + x con x 0 e non è noto. Un time service può fornire l ora con precisione grazie all uso di un ricevitore utc o di un clock accurato. Il gruppo di processi che deve sincronizzarsi usa un time service che solitamente viene implementato in modo centralizzato da un solo processo oppure in modo decentralizzato da più processi. I time service centralizzati possono essere request-driven, usano il Cristian algorithm, oppure broadcastbased, usano il Berkeley Unix algorithm. Il più famoso time service distribuito è il network time protocol (ntp). Time Service Algoritmo di Cristian L algoritmo di Cristian usa un time server S passivo che riceve il segnale da una sorgente utc tramite sincronizzazione esterna. Un processo P richiede periodicamente il tempo tramite un messaggio m r e riceve t tramite il messaggio m t inviato dal server S. Questo processo, all istante t, controlla il clock ed imposta il suo clock a t + T round 2, dove T round è il round trip time registrato da P. Questo algoritmo permette la sincronizzazione solo se il T round è abbastanza breve. Per determinare l accuratezza dell algoritmo di Cristian consideriamo due casi: Caso 1 Il server S non può mettere t in m t prima che sia trascorso il tempo minimo di trasmissione tra P ed S dall istante in cui il processo P ha inviato m r. Caso 2 Il server S non può mettere t in m t dopo il momento in cui m t arriva al processo P meno il tempo minimo di trasmissione tra i due. Il tempo del server S quando m t arriva è compreso tra [t + min,t + T round min]. l ampiezza di tale intervallo è pari a T round 2min. Quindi 135

143 L accuratezza di questo algoritmo è ± ( T round 2 min ). Con questo algoritmo possiamo avere limiti sull affidabilità e sulle prestazioni, pertanto è necessario utilizzare un insieme di server sincronizzati ed usare messaggi di multicast con i client. Inoltre possono essere presenti errori di trasmissione o errori intenzionali, quindi tale approccio necessita di tecniche di autenticazione Algoritmo di Berkeley L algoritmo per la sincronizzazione interna di un gruppo di macchine è quello di Berkeley. Il master, ovvero il time server attivo, richiede attraverso dei messaggi di broadcast il valore dei clock delle macchine slave. Usa il rtt per stimare i valori dei clock degli slave in modo da calcolarne la media dei vari clock ricevuti. Di conseguenza invia un valore correttivo opportuno agli slave. Se il valore correttivo prevede un salto indietro nel tempo, lo slave non imposta il nuovo valore ma rallenta, data la monotonicità, il tempo. Infatti non è possibile imporre un valore di tempo passato agli slave che si trovano con un valore di clock superiore a quello calcolato come clock sincrono, ciò provocherebbe un problema di ordinamento causa/effetto di eventi e verrebbe violata la condizione di monotonicità del tempo. La soluzione consiste nel mascherare una serie di interrupt che fanno avanzare il clock locale in modo da rallentare l avanzata del clock stesso, pertanto il numero di interrupt mascherati è pari al tempo di slowdown diviso il periodo di interrupt del processore. L accuratezza dipende da un rtt nominale massimo, infatti, il master non considera valori di clock associati a rtt superiori al massimo. La tolleranza ai guasti viene gestita eleggendo un nuovo master al posto del precedente che è deceduto. Quindi nel periodo che intercorre l elezione di un nuovo master non è garantito il controllo della sincronizzazione. Questa tecnica è tollerante a comportamenti arbitrari, dove gli slave possono inviare valori errati di clock. Il master prende un certo numero di valori di clock, da un sottoinsieme di slave, in modo tale che questi valori non differiscono tra loro per una quantità specificata NTP ntp è il time service per Internet ed è una sincronizzazione esterna accurata rispetto ad utc. Possiede un architettura di service time disponibile e scalabile, pertanto ci sono server e path ridondanti. Ovvero i server primari sono connessi alle sorgenti utc (livello 0), i server secondari sono sincronizzati da server primari ed infine le macchine degli utenti sono i server foglia, chiamata sottorete di sincronizzazione. 136

144 La sottorete di sincronizzazione si riconfigura in caso di guasti, ad esempio se il server primario perde la connessione alla sorgente utc diventa server secondario, oppure il server secondario che perde la connessione al suo primario può usare un altro server primario. Per sincronizzarsi si può utilizzare il multicast, ovvero il server all interno della lan ad alta velocità invia in multicast il suo tempo agli altri che impostano il tempo ricevuto assumendo un certo ritardo, ma questa soluzione non è molto accurata. Con le procedure call, un server accetta richieste da altri computer, come l algoritmo di Cristian, così da avere alta accuratezza ed è molto utile se non è disponibile il multicast supportato in hardware. Infine, possiamo utilizzare la modalità simmetrica dove coppie di server scambiano messaggi che contengono informazioni sul timing. Ciò avviene per alti livelli della gerarchia, e quindi si ottiene il massimo dell accuratezza. Tutte queste modalità usano udp poiché le connessioni di tipo tcp impiegano troppo tempo nell instaurare la comunicazione. Ogni messaggio porta timestamp di eventi recenti, quindi vi è la presenza dei tempi locali di send() e receive() del messaggio precedente m ed il tempo locale di send() del messaggio corrente m. Il ricevente segna il tempo locale T i in cui riceve il messaggio m. Nel modo simmetrico il ritardo tra l arrivo di un messaggio e l invio del successivo può essere non trascurabile. Per ogni coppia di messaggi m ed m scambiati tra due server, ntp stima un offset o i tra i due clock ed un ritardo d i che rappresenta il tempo di trasmissione totale per m ed m. Supponendo che il vero offset del clock di B rispetto ad A sia o e che i tempi di trasmissione di m ed m siano rispettivamente t e t allora: Accuratezza di NTP { Ti 2 = T i 3 + t + o T i = T i 1 + t o Il tempo totale di trasmissione dei messaggi è pari a: d i = t + t = T i T i 1 + T i 2 T i 3. Sottraendo le equazioni si ottiene che: { o = o i = o i + ( t t) (Ti 2 Ti 3+Ti 1 Ti) 2 2 Considerando che t e t > 0, si può dimostrare che o i di 2 o o i + di 2. Quindi, o i è una stima dell offset e d i è una misura dell accuratezza della stima. I server ntp filtrano le coppie [o i,d i ], stimano l affidabilità dei dati dalla differenza con la stima e selezionano il peer che usano per sincronizzarsi scegliendo o i corrispondente al minimo d i. L accuratezza è di 10 ms su Internet e di 1 ms su lan Tempo in un sistema asincrono Gli algoritmi per la sincronizzazione dei clock fisici si basano sulla stima dei tempi di trasmissione. 137

145 Nei sistemi reali non sempre i tempi di trasmissione sono predicibili, difatti viene a mancare l accuratezza della sincronizzazione. Di conseguenza l assenza di sincronizzazione implica l impossibilità di ordinare gli eventi appartenenti a processi diversi usando il tempo. Quindi il tempo di due eventi che accadono in processi diversi non può essere generalmente utilizzato per decidere quando un evento precede l altro Tempo logico Consideriamo il caso in cui un processo P i osserva una serie di eventi ordinati e quando P i manda un messaggio a P j, l evento send() precede l evento receive(). Lamport introduce il concetto di relazione di happened-before, anche detta relazione di precedenza o di ordinamento casuale, dove i rappresenta la relazione di ordinamento su un processo P i, mentre è la relazione happened-before tra due eventi qualsiasi. Due eventi e ed ē sono in relazione di happened-before se: Happenedbefore Relationship 1. P i e i ē Ovvero se gli eventi e e ē si manifestano sullo stesso processo P i, allora e i ē (l occorrenza dell evento e è preceduta dall occorrenza dell evento ē). 2. m,send(m) receive(m) Ovvero se un evento e sta mandando un messaggio m ed un evento ē rappresenta la ricezione del messaggio mandato nell evento e allora e ē. 3. Questa relazione è anche transitiva, ovvero: dati tre eventi e, ē e ē, se e ē e ē ē allora e ē. Applicando le tre regole è possibile costruire una sequenza di eventi e 1,e 2,...,e n casualmente ordinati. La relazione happened-before rappresenta un ordinamento parziale, ovvero gode delle proprietà riflessiva, anti-simmetrica e transitiva. Pertanto non è detto che la sequenza e 1,e 2,...,e n sia unica. Inoltre data una coppia di eventi, questa non è sempre legata da una relazione happened-before; in questo caso si dice che gli eventi sono concorrenti e viene indicato con il simbolo. Consideriamo la seguente figura: Siano e 1 2 ed e 1 3 due eventi concorrenti, allora possiamo avere le seguenti sequenze: Clock Logico Scalare { s1 = e 1 1,e 1 2,e 2 2,e 2 3,,e 3 3,e 3 1,e 4 1,e 5 1,e 4 2 s 2 = e 1 3,e 2 1,e 3 1,e 4 1,e 5 3 Un clock logico rappresenta un meccanismo per catturare relazioni cronologiche e casuali in un sistema distribuito. Il clock logico scalare, o anche detto timestamp di Lamport, è un contatore software monotonicamente crescente, il cui valore non possiede alcuna relazione con il clock fisico. Ogni processo P i ha il proprio clock logico L i usato per applicare i timestamp agli eventi. L i (e) identifica il timestamp, basato sul clock logico, ed è applicato dal processo P i all evento e. Da ciò si deduce che se e ē allora L(e) < L(ē). È importante notare che questa relazione è univoca, ovvero se un evento avviene prima di un altro, allora il clock logico dell evento avviene prima dell altro. Quindi usando i clock logici scalari, solo un 138

146 ordinamento parziale casuale può essere dedotto dal clock. Ad esempio, se si conosce che L(e) < L(ē) allora non si può dire che e ē, può darsi che gli eventi e e ē siano avvenuti nello stesso istante. In altre parole, tramite questo tipo di clock non è detto che guardando i timestamp di due eventi si riesca ad ottenere un ordinamento parziale. Per implementare un clock logico scalare, ogni processo P i inizializza il proprio clock logico L i a 0( i = 1,...,N). L i viene incrementato di 1 prima che il processo P i esegua l evento, sia che l evento sia di tipo interno oppure esterno e sia con send() che receive(); quindi L i = L i + 1. Praticamente tale contatore viene incrementato ad ogni tick del sistema. Quando P i invia il messaggio m a P j viene incrementato il valore L i, si allega al messaggio m il timestamp t = L i ed esegue l evento send(m). Alla ricezione del messaggio m da parte di P j, questo aggiorna il proprio clock logico con L j = max(t,l j ), incrementa il valore di L j ed esegue l evento receive(m). Ad esempio consideriamo la seguente figura: Implementazione Esempio: Clock Logico Scalare Si nota che e 1 1 e 1 2 ed i relativi timestamp riflettono questa proprietà (L 1 = 1 e L 2 = 2). e 1 1 e 1 3 ed i relativi timestamp sono uguali (L 1 = 1 e L 3 = 1). e 1 2 e 1 3 ed i relativi timestamp sono diversi (L 2 = 2 e L 3 = 1). Per garantire che aggiornamenti concorrenti su un database replicato siano visti nello stesso ordine da ogni replica consideriamo il seguente esempio. P 1 aggiunge $100 ad un conto con valore iniziale $1000. P 2 incrementa il conto dell 1%. Ci sono due repliche e se non vi fosse un opportuna sincronizzazione le due operazioni non sarebbero eseguite sulle due repliche nello stesso ordine. Infatti sulla prima replica si otterrà $1111, ovvero prima P 1 e poi P 2. Mentre sulla seconda si avrà $1110, cioé prima P 2 e poi P 1. Esempio: Multicasting totalmente ordinato A questo esempio applichiamo gli orologi logici di Lamport tramite l operazione di multicast totalmente ordinata. Tale operazione consegna tutti i messaggi nello stesso ordine ad ogni destinatario. Quindi P i invia, tramite una connessione affidabile e fifo-ordered, il messaggio m i con timestamp a tutti gli altri, includendo sé stesso. Ogni messaggio in arrivo a P j viene accodato nella coda locale q j, in base al suo timestamp, e P j invia in multicast un messaggio di conferma agli altri. P j consegna il messaggio m i alla sua applicazione se: 1. m i è in testa alla coda q j ; 2. per ogni processo P k, c è un messaggio m k in q j con un timestamp maggiore. Ricapitolando il clock logico scalare ha la seguente proprietà: se e ē allora L(e) < L(ē). Però non è possibile assicurare la seguente relazione: se L(e) < L(ē) allora e ē. Di conseguenza non è possibile stabilire, solo guardando i clock logici scalari, se due eventi sono concorrenti o meno. 139

147 Clock Logico Vettoriale Mattern e Fridge propongono di ovviare al problema considerando anche l identificativo del processo in cui l evento occorre, introducendo i clock logici vettoriali. Il clock logico vettoriale per un sistema di N processi è dato da un vettore di N interi. Ciascun processo P i mantiene il proprio clock vettoriale V i. Per il processo P i, V i [i] rappresenta il clock logico locale. Ciascun processo usa il suo clock vettoriale per assegnare il timestamp agli eventi. Analogamente al clock scalare di Lamport, il clock vettoriale viene allegato al messaggio m ed il timestamp diviene vettoriale. Quindi con i clock vettoriali si cattura completamente la caratteristica della relazione happened-before. Ogni processo P i inizializza il proprio clock vettoriale: V i [j] = 0, j = 1,2,...,N. Prima di eseguire un evento interno od esterno, P i incrementa la sua componente del clock vettoriale V i [i] = V i [i] + 1. Quando P i invia il messaggio m a P j incrementa il valore V i per la sua componente, allega al messaggio m il timestamp t = V i ed esegue l evento send(m). Mentre quando P j riceve il messaggio m con il timestamp t aggiorna il proprio clock logico V j [j] = max(t [j],v j [j]), j = 1,2,...,N, incrementa il valore di V j per la sua componente ed esegue l evento receive(m). Dato un clock vettoriale V i, V i [i] è il numero di eventi generati da P i. Mentre V i [j] con i j è il numero di eventi occorsi a P j di cui potrebbe avere conoscenza. Distinguiamo tre possibili casi: Implementazione 1. V = V se e solo se V [j] = V [j], j = 1,2,...,N; 2. V V se e solo se V [j] V [j], j = 1,2,...,N; 3. V < V e quindi l evento associato a V precede quello associato a V se e solo se V V V V, i = 1,2,...,N tale che V [i] V [i] e i {1,2,...,N} tale che V [i] > V [i]. Esaminando i timestamp si riesce a capire se due eventi sono concorrenti o se sono in relazione happenedbefore. Infatti se V (e) = [1,2,0] e V (e) = [1,2,2] allora V (e) < V (e) e quindi e ē. Invece se abbiamo V (e) = [1,2,0] e V (e) = [1,0,2] allora V (e) V (e) e quindi e ē. Passando dal clock logico scalare al clock logico vettoriale aumenta la quantità di conoscenza che i processi si scambiano. Ogni volta che un processo riceve un messaggio aumenta il proprio livello di conoscenza integrando le sue informazioni locali con quelle ricevute da un altro processo. Quindi con l introduzione dei clock logici matriciali ogni processo mantiene informazioni sulla conoscenza che ogni altro processo ha dello stato globale del sistema. Clock logico matriciale 16.4 Sistemi Concorrenti e Mutua Esclusione La mutua esclusione nasce nei sistemi concorrenti dove ogni processo vuole acquisire la risorsa ed utilizzarla in modo esclusivo senza avere interferenze con gli altri processi. Ogni algoritmo di mutua esclusione comprende una sequenza di istruzioni chiamata sezione critica (cs). L esecuzione della sezione critica consiste nell accesso alla risorsa condivisa. La sequenza di istruzioni che precedono la sezione critica è chiamata trying protocol (tp), mentre la sequenza di istruzioni che seguono la sezione critica è chiamata exit protocol (ep). 140

148 Uno scheduler sceglie di volta in volta a quale processo consentire l esecuzione della prossima istruzione. La sequenza decisa dallo scheduler, che comprende tutte le istruzioni dei diversi processi, è chiamata schedule. Gli algoritmi di scheduling devono possedere tre proprietà: 1. mutua esclusione (me) o safety: al più un processo per volta è nella sezione critica. 2. no deadlock (nd): se un processo rimane bloccato nella sua trying section, esistono uno o più processi che riescono ad entrare ed uscire dalla sezione critica. 3. no starvation (ns): nessun processo può rimanere bloccato per sempre nella trying section. Si nota che la proprietà ns implica nd e la proprietà di liveness, che specifica un comportamento paritetico dei vari processi (fairness). Vi sono molte soluzioni basate su variabili condivise per realizzare la mutua esclusione tra N processi, le due più famose sono: l algoritmo di Dijkstra, ideato per sistemi a singolo processore e l algoritmo del panificio di Lamport, ideato per sistemi multi-processore Algoritmo di Dijkstra In questo algoritmo, i processi comunicano leggendo e scrivendo variabili condivise. La lettura e scrittura di una variabile è un azione atomica e non ci sono assunzioni sul tempo che ogni processo impiega ad eseguire un azione atomica. Le variabili condivise sono rappresentate da array booleani inizializzati a true aventi dimensione N. Mentre le variabili locali sono di tipo intero. Ogni processo P i esegue il seguente algoritmo: L i0 : b[i] = false //P i si prenota L i1 : while (k!= i) //la sentinella k seleziona uno dei processi prenotati L i2 : c[i] = true // P i cancella una sua eventuale occupazione L i3 : if(b[k] = true) then k = i L i4 : c[i] = false //P i occupa L i5 : for j = 1 to N do // P i controlla se ci sono conflitti, // ovvero controlla se è presente un altro processo nella sezione critica L i6 : if( j!= i and c[j] = false) then goto L i1 /* sezione critica */ L i7 : c[i] = true L i8 : b[i] = true /* altre istruzioni */ L i9 : goto L i0 Ad esempio consideriamo un sistema mono-processore con 4 processi che tentano l accesso alla sezione critica: 141

149 P 1 P 2 P 3 b[1] = false while (k!= 1) c[1] = true if (b[4] = true) k = 1 b[2] = false while (k!= 2) c[2] = true if (b[4] = true) k = 2 b[3] = false while (k!= 3) c[3] = true if (b[4] = true) k = 3 Si nota che al processo P 4 lo scheduler non concede l esecuzione di nessuna istruzione. Alla fine di questa parte di schedulazione la situazione è la seguente: La prossima istruzione da eseguire per tutti i processi (tranne P 4 ) è L i1. k = 3. A questo punto il processo P 3 occupa (istruzione L i4 ), gli altri non occupano la sezione critica e vedono che P 3 è comunque prenotato, quindi lasciano inalterato il valore di k. Il processo P 3 entra nella sezione critica perché non rileva conflitti, ovvero altri processi che occupano la variabile c. P 1 P 2 P 3 c[3] = false for j = 1 to N do if (j!= 3 and c[j] = false) then goto L i1 cs b[3] = true c[3] = true Quando P 3 esce dalla sezione critica, rilascia sia le variabili c e b. Quindi P 1 e P 2 rimasti nella trying section possono provare a passare il ciclo della sentinella. Da questo schedule sembra che il primo processo che passa il ciclo della sentinella è anche il primo ad entrare in sezione critica. Ciò accade solo se non ci sono conflitti. In caso di conflitto l ordine di entrata in sezione critica lo decide lo scheduler. Consideriamo il seguente caso: 142

150 P 1 P 2 P 3 while (k!= 1) c[1] = true if (b[3] = true) // P 3 uscito da cs then k = 1 while (k!= 1) c[1] = false // occupa if (j!= i and c[j] = false) then goto L i1 while (k!= 2) c[2] = true if (b[3] = true) // P 3 uscito da cs then k = 2 while (k!= 2) c[2] = false // occupa if (j!= i and c[j] = false) then goto L i1 Questo è un caso di conflitto, dove si torna indietro e si cancellano le occupazioni, ma solo uno dei processi la mantiene, in particolare quello che per ultimo aveva posto il suo nome su k (nel precedente esempio P 2 ). L algoritmo di Dijkstra soddisfa le proprietà di mutua esclusione e no deadlock. Ma non soddisfa la proprietà di starvation Algoritmo del panificio di Lamport È una soluzione semplice ispirata ad una situazione reale e cioè: l attesa di essere serviti in un panificio. Il modello di Lamport possiede le seguenti proprietà: I processi comunicano leggendo/scrivendo variabili condivise. La lettura e la scrittura di una variabile non è un azione atomica, infatti un processo potrebbe scrivere mentre un altro processo sta leggendo. Ogni variabile condivisa è di proprietà di un processo, quindi tutti possono leggere la sua variabile e solo il processo può scrivere la sua variabile. Nessun processo può eseguire due scritture contemporaneamente. La velocità di esecuzione dei processi sono non correlate. Le variabili condivise sono rappresentate da un array di interi inizializzati a 0 (num[1,...,n]) e da un array di booleani inizializzati a false (choosing[1,...,n]). Le variabili locali sono rappresentate da un numero intero compreso in [1,...,N]. Il ciclo viene ripetuto all infinito dal processo i, ovvero: Entra nella sezione non critica. Prende un biglietto usando un algoritmo per la selezione del biglietto, chiamato doorway. Attende che il numero sia chiamato confrontando il suo biglietto con quello degli altri. Attende finché il valore del biglietto non è il più basso, ovvero un meccanismo che favorisce il processo con identificativo più piccolo. Entra nella sezione critica, esegue le operazioni contenute in essa ed esce. Nella doorway, ogni processo P i che entra lo segnala agli altri tramite choosing[i]. Prende un numero di biglietto pari al massimo dei numeri scelti dagli altri più 1. Mentre gli altri processi possono accedere concorrentemente alla doorway. 143

151 Nella bakery, ogni processo deve controllare che tra i processi in attesa lui sia il prossimo ad avere accesso alla sezione critica. Il primo ciclo permette a tutti i processi della doorway di terminare la loro scelta del biglietto. Il secondo ciclo lascia un processo P i in attesa finché il suo numero di biglietto non diventa il più piccolo. Tutti i processi che hanno scelto un numero di biglietto uguale al suo non hanno identificativo maggiore, ma sono risolti basandosi sull identificativo del processo. La proprietà supportate dall algoritmo di Lamport sono: mutua esclusione: se P i è nella doorway e P j è nel bakery allora {num[j],j} < {num[i],i}. Proprietà no starvation: nessun processo attende sempre poiché prima o poi avrà il numero di biglietto più piccolo. fcfs: se P i entra nel bakery prima che P j entri nella doorway allora P i entrerà nella sezione critica prima di P j. Supponiamo che abbiamo due processi P 1 e P 2 e non abbiamo l array di booleani choosing[i]. Entrambe questi processi, nello stesso istante, scelgono il numero di biglietto. Assumiamo che entrambe leggono num[1] e num[2] prima che vengano scritti. Se P 1 prende il biglietto assegnandoci il valore 1 a num[1] e P 2 non ha ancora terminato l assegnamento, allora P 1 entra nella sezione critica mentre P 2 riesce a prendere il biglietto assegnandoci il valore 1 a num[2]. A questo punto anche P 2 si ritrova nella sezione critica e quindi si viene a violare la mutua esclusione. Quindi la ragione per cui viene usata l array choosing[i] è quella di prevenire questo genere di situazioni. Qui di seguito è presente lo pseudo-codice dell algoritmo del panificio di Lamport: Choosing var choosing: shared array[0..n-1] of boolean; number: shared array[0..n-1] of integer;... repeat /* Inizio Doorway */ choosing[i] := true; number[i] := max(number[0],number[1],...,number[n-1]) + 1; choosing[i] := false; /* Fine Doorway */ /* Inizio Bakery */ for j := 0 to n-1 do begin while choosing[j] do (* nothing *); while number[j] <> 0 and (number[j], j) < (number[i],i) do (* nothing *); end; /* Fine Bakery */ 144

152 (* critical section *) number[i] := 0; (* remainder section *) until false; 16.5 Mutua esclusione nei sistemi distribuiti Proviamo ad adattare ai sistemi distribuiti l algoritmo del panificio di Lamport ideato per sistemi concorrenti. Ovvero: ogni processo P i si comporta da server rispetto alle proprie variabili num[i] e choosing[i]. la comunicazione non è più basata sulla condivisione della memoria ma sullo scambio di messaggi, quindi ogni processo legge i valori locali degli altri processi tramite messaggi di richiesta-risposta. Ma questa non è una buona soluzione poiché: per ogni lettura bisogna scambiare 2N messaggi, per accedere alla cs servono 3 scambi di messaggi (1 per la doorway e 2 per la bakery) che costano in totale 6N messaggi, la latenza per un singolo scambio di messaggi è uguale al tempo max {T trasmissione,t processo }. Quindi è presente una scarsa efficienza e scalabilità Panoramica sugli algoritmi per la mutua esclusione distribuita Gli algoritmi che garantiscono una mutua esclusione distribuita possono essere basati su: autorizzazione: un processo che vuole accedere ad una risorsa condivisa chiede l autorizzazione. Possono essere su autorizzazione gestita in modo centralizzato, ovvero un unico coordinatore, o in modo completamente distribuito, come l algoritmo di Lamport distribuito, algoritmo di Ricart e Agrawala. token: tra i processi circola un messaggio speciale, detto token. Il token è unico in ogni istante di tempo e solo chi detiene il token può accedere alla risorsa condivisa. Di questa tipologia di algoritmi esistono quelli centralizzati e quelli decentralizzati. quorum (o votazione): si richiede il permesso di accedere ad una risorsa condivisa solo ad un sottoinsieme di processi. Un algoritmo che usa questa tecnica è quello di Maekawa. elezione: un processo si trasforma in coordinatore. In questa tipologia di algoritmi troviamo l algoritmo bully, quello ad anello e del super-peer Algoritmi basati su autorizzazione Algoritmo Centralizzato La richiesta di accesso (enter) ad una risorsa in mutua esclusione viene inviata ad un coordinatore centrale. Se la risorsa è libera il coordinatore informa il mittente che l accesso è consentito (granted). Altrimenti il coordinatore accoda la richiesta con politica fifo ed informa il mittente che l accesso non è consentito (denied), oppure non risponde nel caso di sistema sincrono. Il processo che rilascia la risorsa ne informa il coordinatore (released). Infine, il coordinatore preleva la prima richiesta in attesa ed invia granted al suo mittente. Tramite questo algoritmo si garantisce la mutua esclusione, si evitano meccanismi di starvation ed è facile da implementare. Ma il coordinatore diventa il single point of failure dell algoritmo, oltre ad essere un eventuale collo di bottiglia per le prestazioni. 145

153 Algoritmo di Lamport distribuito Ogni processo mantiene un clock logico scalare ed una coda, per memorizzare le richieste di accesso alla sezione critica. P i manda un messaggio di richiesta con timestamp a tutti gli altri processi e aggiunge la coppia [richiesta,timestamp] alla sua coda locale. La richiesta mandata viene memorizzata nella coda locale al destinatario ed un messaggio di ack viene inviato al mittente. L accesso di P i alla sezione critica avviene se e solo se P i ha una richiesta in coda con timestamp t, t è il timestamp più piccolo tra quelli presenti in coda e P i ha già ricevuto da ogni altro processo un messaggio di ack con timestamp più grande di t. Per rilasciare la sezione critica, P i manda un messaggio release a tutti gli altri processi ed elimina la richiesta dalla coda. Quando gli altri processi ricevono il messaggio release eliminano di conseguenza la richiesta associata. Tale algoritmo rispetta le proprietà di mutua esclusione. Tuttavia richiede 3(N 1) messaggi per accedere alla sezione critica, in particolare ci saranno N 1 messaggi di richiesta, N 1 messaggi di ack e N 1 messaggi di rilascio. Algoritmo di Ricart e Agrawala Questo algoritmo richiede che ci sia un ordinamento totale di tutti gli eventi del sistema ed è basato su clock logici. Il processo che vuole accedere ad una sezione critica manda un messaggio di richiesta a tutti, incluso se stesso, contenente: il nome della sezione critica, il proprio identificatore ed il timestamp locale. Si pone in attesa della risposta da tutti. Ottenuti tutti gli ok entra nella sezione critica ed all uscita da essa manda ok a tutti i processi in coda. Il processo che riceve può non essere nella sezione critica e non vuole accedervi, pertanto manda ok al mittente; oppure si trova nella sezione critica e non risponde mettendo in coda locale il messaggio. Per poter accedere alla sezione critica confronta il timestamp e vince se possiede quello più basso. Quindi se il processo che possiede il timestamp più basso è un altro allora invia ok, altrimenti non risponde e pone il messaggio in coda. Tale algoritmo è completamente distribuito dato che non c è alcun elemento centrale. Rispetto a quello di Lamport si risparmiano messaggi, infatti il messaggio di reply viene inviato solo quando tutte le richieste che precedono quella corrente sono state servite. Quindi Ricart-Agrawala permette di usare solo 2(N 1) messaggi per accedere alla sezione critica. Però se un processo fallisce nessun altro potrà entrare nella sezione critica, ovvero il single point of failure può essere rappresentato da ogni singolo processo dato che ognuno di essi partecipa ad ogni decisione Algoritmi basati su token Viene usata una risorsa ausiliaria, chiamata token. L uso dei token non è utile solo per la mutua esclusione, difatti molti problemi distribuiti vengono risolti con questa tecnica. L algoritmo deve definire come vengono fatte le richieste per il token, e come gestirle. Quindi in un algoritmo basato su token in ogni istante esiste un solo possessore del token, garantendo la safety della mutua esclusione. Gli algoritmi basati su token possono essere: 146

154 centralizzati (o approccio token-asking), dove esiste un unico processo coordinatore che è il responsabile per la gestione del token. decentralizzati (detto approccio perpetuum mobile), dove il token si muove nel sistema e porta con sé tutte le strutture dati richieste dal coordinatore nel caso centralizzato. Algoritmo basato su token centralizzato Esiste un processo che svolge il ruolo di coordinatore e che gestisce il token. Tale coordinatore tiene traccia delle richieste sia quelle effettuate ma non ancora servite che di quelle già servite. Ogni processo P i mantiene un suo clock vettoriale in cui sono riportati i timestamp delle richieste. Quando P i vuole entrare nella sezione critica invia una richiesta al coordinatore contenente il proprio clock vettoriale. Il coordinatore ne prende nota e lo inserisce nella lista delle richieste pendenti. Quando la richiesta di P i diventa eleggibile, il coordinatore invia il token a P i che entrerà nella sezione critica. Uscito dalla sezione critica P i restituisce il token al coordinatore. Tale algoritmo è efficiente in termini di numero di messaggi scambiati per accedere alla sezione critica e viene garantita la fairness. Ma il coordinatore rappresenta il single point of failure. Algoritmo basato su token decentralizzato Il token viaggia da un processo all altro. I processi vengono organizzati in un anello logico, pertanto il token viene passato dal processo P k al processo P (k+1)modn. Il processo che ha il token è abilitato all accesso alla sezione critica. Se un processo riceve il token ma non ha necessità di accedere alla sezione critica, passa il token lungo l anello. Se l anello è unidirezionale, viene garantita la fainess e rispetto all algoritmo centralizzato, viene migliorato il bilanciamento del carico. Però il token può essere perso per malfunzionamento sia hardware che software; in questo caso occorre rigenerarlo. Inoltre può essere soggetto al crash dei singoli processi. Se un processo fallisce occorre riconfigurare l anello logico, mentre se fallisce il processo che possiede il token occorre eleggere il prossimo processo che avrà il token. È probabile che siano presenti guasti temporanei che possono creare token multipli. Inoltre l algoritmo usa sempre banda per trasmettere il token anche quando nessuno chiede l accesso alla sezione critica. 147

155 Algoritmi basati su quorum Per entrare in una sezione critica occorre sincronizzarsi solo con il sottoinsieme dei processi interessati. Usando algoritmi di votazione all interno del sottoinsieme, i processi votano per stabilire chi è autorizzato ad entrare nella sezione critica. In questo caso è presente un insieme di votazione V i, il quale è associato ad ogni processo P i e tale insieme ha intersezione non nulla: (V i V j ) i,j. Un processo P i per accedere alla sezione critica invia una richiesta a tutti gli altri membri di V i. Un processo P j in V i che riceve la richiesta, se è nella sezione critica oppure ha già risposto dopo aver ricevuto l ultimo release, allora non risponde ed accoda la richiesta. Altrimenti risponde subito con un reply. Dopo aver ricevuto tutte le risposte usa la sezione critica. Al rilascio della sezione critica invia un release a tutti gli altri membri di V i. Infine un processo che riceve un messaggio release estrae una richiesta dalla coda ed invia un messaggio reply. Algoritmo di Maekawa Ogni processo P i esegue il seguente algoritmo: Inizializza le variabili state = released e voted = false. Protocollo di ingresso nella sezione critica per P i imposta state = wanted, viene mandata una richiesta di multicast a tutti i processi in V i {P i }, attende finché non riceve tutte le risposte (K 1), e cambia lo stato in held. Dove V i = N i, cioè i sottoinsiemi di votazione possiedono la stessa dimensione. Alla ricezione di una richiesta da P j con j i, se state = held oppure voted = true allora accoda la richiesta da P j senza rispondere. Altrimenti invia la risposta a P j ed imposta voted = true. Protocollo di uscita dalla sezione critica per P i : state = released e viene mandato un messaggio di rilascio in multicast a tutti i processi V i {P i }. Se la coda di richieste non è vuota allora viene estratta la prima richiesta in coda dal processo P k, invia la risposta a P k ed imposta voted = true. Altrimenti voted = false. Alla ricezione di un messaggio di rilascio da P j con i j, se la coda di richieste non è vuota allora viene estratta la prima richiesta in coda dal processo P k, invia la risposta a P k e voted = true. Altrimenti voted = false. Questo algoritmo garantisce la safety ma non l assenza di deadlock. A tal proposito si può rendere l algoritmo deadlock-free con messaggi aggiuntivi. Per scegliere quali sono i processi a cui inviare la richiesta si costruiscono dei sottoinsiemi di processi (V i ) in modo tale che tra di loro si abbia un intersezione non vuota. Se un quorum permette l accesso nella sezione critica ad un processo, nessun altro quorum, anche diverso, potrà accordare lo stesso permesso. In questo modo per accedere alla sezione critica occorrono un numero di messaggi pari a 3 N, ovvero 2 N per l ingresso nella sezione critica e N per l uscita dalla sezione critica. Confronto tra algoritmi Algoritmo Messaggi per Ritardo prima Problemi ingresso/uscita dell ingresso dalla sezione critica (in messaggi) Autorizzazione o 3 2 Crash del coordinatore token centralizzato Ricart-Agrawala 2(N 1) 2(N 1) Crash di un qualunque processo Token decentralizzato 1 a 0 a N 1 Perdita del token Crash di un qualunque processo Maekawa 3 N 2 N Possibile deadlock 148

156 Algoritmi di elezione Molti algoritmi distribuiti richiedono che un processo agisca da coordinatore o che abbia un ruolo speciale. Per eleggere un coordinatore è richiesto un accordo distribuito. L obiettivo dell algoritmo di elezione è assicurare la terminazione con l accordo di tutti i partecipanti. Se i processi sono identici, basta uno qualsiasi. Ad esempio, se ogni processo ha un id univoco allora bisogna eleggere il processo con maggior id. Gli algoritmi di elezione tradizionali sono: algoritmo bully (del prepotente), algoritmo ad anello ed algoritmi di elezione di superpeer. Algoritmo bully Il processo P che rileva l assenza del coordinatore promuove una nuova elezione, ovvero: P invia un messaggio elezione a tutti i processi di identificatore maggiore. Se nessuno risponde, P si autoproclama vincitore e diventa coordinatore. Un processo che riceve il messaggio elezione da un processo di identificatore minore risponde con il messaggio ok e rileva la gestione dell elezione. Se P riceve un messaggio ok ha finito il suo lavoro. Un processo appena creato non conosce il coordinatore e dunque promuove una elezione. L algoritmo designa sempre come coordinatore il processo in vita con identificatore maggiore. Il vincente informa tutti i processi del sistema che hanno un nuovo coordinatore. Nel punto a della precendente figura il nodo 4 organizza l elezione, successivamente i nodi 5 e 6 fermano il nodo 4. A loro volta i nodi 5 e 6 organizzano un elezione ed il nodo 6 ferma il nodo 5. Il nodo 6 diventa il nuovo coordinatore e lo comunica a tutti. Algoritmo ad anello I processi sono ordinati logicamente in un anello, dove ogni processo conosce il suo successore. Il processo P che rileva l assenza del coordinatore promuove una nuova elezione, ovvero: P invia un messaggio di tipo elezione al suo successore. Se il successore non è attivo, il mittente lo salta e passa al successivo lungo l anello, finché non trova un processo attivo. Ad ogni passo, il mittente aggiunge il suo numero di processo nel messaggio. 149

157 Alla fine, il messaggio ritorna al processo P, che identifica il processo con il numero più alto ed invia sull anello un messaggio di tipo coordinatore per informare tutti su chi sia il nuovo coordinatore. Algoritmi di elezione di super-peer Per selezionare un super-peer devono essere soddisfatte le seguenti condizioni: accesso: i peer normali devono avere un basso tempo di latenza verso il super-peer. distribuzione: i super-peer devono essere distribuiti uniformemente nella rete overlay. proporzione: ci deve essere una quantità predefinita di super-peer in base al numero totale di peer nella rete overlay. bilanciamento del carico: ogni super-peer non deve servire più di un certo numero di peer. In base a tali criteri si possono applicare algoritmi derivanti dalla teoria dei grafi e dagli algoritmi di elezione tradizionali, come ad esempio il problema dell insieme dominante, la dominanza della distanza ed il problema del p-center. 150

158 17 Consistenza e Replicazione La replicazione dei dati consente di aumentare l affidabilità del sistema rendendolo tollerante ai guasti e migliora le prestazioni del sistema aumentandone la scalabilità (rispetto alla dimensione o geografica). Per applicare tale tecnica è necessario duplicare i dati sui vari server e di conseguenza possono essere presenti delle inconsistenze. Infatti se si effettuano delle modifiche su una replica bisogna attuare tale modifica anche su tutte le altre repliche Consistenza dei dati Per mantenere le repliche consistenti, occorre garantire in generale che tutte le operazioni conflittuali siano eseguite nello stesso ordine su tutte le repliche. Le operazioni conflittuali possono essere di tipo: read-write: un operazione di lettura ed un operazione di scrittura concorrenti sullo stesso dato; Operazioni Conflittuali write-write: due operazioni di scrittura concorrenti sullo stesso dato. Una possibile soluzione è quella di fare un ordinamento globale delle operazioni, ma ciò risulta troppo onerosa compromettendo di conseguenza la scalabilità del sistema. Pertanto bisogna allentare i vincoli di un ordinamento globale in modo da ottenere un sistema efficiente. A tal proposito non c è una soluzione generica al problema ma esistono diverse soluzioni per differenti requisiti di consistenza. Un archivio di dati distribuito è un insieme di spazi di memorizzazione, fisicamente distribuiti e replicati su molteplici processi. Tale archivio può essere ad esempio rappresentato da una base dati distribuita o un filesystem distribuito. Archivio di dati distribuito Un modello di consistenza rappresenta un insieme di regole da applicare ai singoli processi che accedono ad un archivio di dati distribuito in modo da assicurare un corretto funzionamento dell archivio stesso. In questo modo, i processi concorrenti possono aggiornare simultaneamente un archivio di dati. Abbiamo due tipologie di modelli di consistenza: data-centrici e client-centrici. Modello di consistenza Modelli di consistenza data-centrici Un modello di consistenza data-centrico genera un archivio di dati consistente a livello di sistema. La consistenza di questo modello può essere: stretta, continua o basata sull ordinamento. Per consistenza stretta si intende un ordinamento temporale assoluto di tutti gli accessi all archivio di dati. In mancanza di un orologio globale è difficile stabilire esattamente quale sia l ultima operazione di scrittura, pertanto nasce il problema su come indebolire tale vincolo di consistenza. I modelli di consistenza basati sull ordinamento si suddividono in due tipologie: sequenziale e casuale. Metriche per misurare la consistenza La metrica per misurare la consistenza tra repliche può essere basata per valore, ovvero è presente una deviazione nei valori numerici delle repliche, oppure basata sul tempo, ovvero una deviazione nell età delle repliche (differenza tra il tempo di aggiornamento di due repliche), oppure quella basata sull ordinamento, ovvero una deviazione relativa all ordine di esecuzione delle operazioni di scrittura. Nel modello di consistenza continua si impongono dei limiti a tutti e tre i tipi di deviazione. Se la deviazione è nulla per tutte e tre le metriche allora abbiamo un modello di consistenza stretta. Un applicazione 151

159 può specificare il livello desiderato di consistenza continua tramite conit. Una conit specifica l unità di dati su cui misurare la consistenza. Quindi per misurare il livello di consistenza bisogna scegliere il giusto trade-off per la granularità opportuna della conit. Infatti se la conit è troppo fine vi è un overhead aggiuntivo per gestirla; al contrario vi è una falsa condivisione. Conit Ad esempio consideriamo due repliche A e B che operano su una conit contenente le variabili x e y che sono inizializzate a 0: B invia ad A l operazione x = x + 2 ed A rende questa operazione permanente. A ha tre operazioni provvisorie e quindi vi è una deviazione nell ordinamento pari a 3. A non ha visto la prima operazione da B che determina una deviazione massima tra i valori pari a 5. Per ordinare le operazioni in modo consistente si possono usare modelli di consistenza stretta, sequenziale o casuale. Le repliche devono pertanto accordarsi su quale sia l ordinamento globale degli aggiornamenti prima di renderli permanenti. Consistenza Stretta Qualsiasi lettura su un dato x ritorna un valore corrispondente al risultato della più recente scrittura su x. Pertanto i comportamenti di un processo P i che operano sullo stesso dato in un modello di consistenza stretta sono: W i (x) a: operazione di scrittura da parte del processo P i sul dato x con valore a. R i (x) b: operazione di lettura da parte del processo P i sul dato x con valore restituito b. Questo tipo di consistenza richiede un orologio globale, in cui le write sono viste instantaneamente da tutti i processi. Tale modello è quello con consistenza più forte. 152

request reply richiesta client processo di servizio processo server principale From - Valeria Cardellini, Corso Sist. Distr. A.A.

request reply richiesta client processo di servizio processo server principale From - Valeria Cardellini, Corso Sist. Distr. A.A. Applicazioni di rete Forniscono i servizi di alto livello utilizzati dagli utenti Determinano la percezione di qualità del servizio (QoS) che gli utenti hanno della rete sottostante Programmazione di applicazioni

Dettagli

Programmazione di applicazioni di rete

Programmazione di applicazioni di rete Programmazione di applicazioni di rete Valeria Cardellini Università di Roma Tor Vergata Applicazioni di rete Applicazioni di rete - forniscono i servizi di alto livello utilizzati dagli utenti - determinano

Dettagli

Sviluppo di Applicazioni su Rete. Introduzione all API socket di Berkeley. Interazione tra Processi. Modello Client-Server

Sviluppo di Applicazioni su Rete. Introduzione all API socket di Berkeley. Interazione tra Processi. Modello Client-Server a.a. 2003/04 Introduzione all API socket di Berkeley Prof. Vincenzo Auletta auletta@dia.unisa.it http://www.dia.unisa.it/professori/auletta/ Università degli studi di Salerno Laurea e Diploma in Informatica

Dettagli

Socket. Nei sistemi operativi moderni i servizi disponibili in rete si basano principalmente sul modello client/server.

Socket. Nei sistemi operativi moderni i servizi disponibili in rete si basano principalmente sul modello client/server. Socket Nei sistemi operativi moderni i servizi disponibili in rete si basano principalmente sul modello client/server. Tale architettura consente ai sistemi di condividere risorse e cooperare per il raggiungimento

Dettagli

COMUNICAZIONE TRA PROCESSI REMOTI IN UNIX

COMUNICAZIONE TRA PROCESSI REMOTI IN UNIX A cura del prof. Gino Tombolini 1 COMUNICAZIONE TRA PROCESSI REMOTI IN UNIX Il sistema UNIX TCP/IP fornisce un meccanismo di comunicazione tra processi residenti su nodi distinti di una rete, compatibili

Dettagli

Laboratorio di Reti di Calcolatori

Laboratorio di Reti di Calcolatori Laboratorio di Reti di Calcolatori Funzioni utili, server ricorsivi, echo client ed echo server. Paolo D Arco Abstract Scopo della lezione è presentare alcune funzioni di utilità generale (e.g., funzioni

Dettagli

Socket. Nei sistemi operativi moderni i servizi disponibili in rete si basano principalmente sul modello client/server.

Socket. Nei sistemi operativi moderni i servizi disponibili in rete si basano principalmente sul modello client/server. Socket Nei sistemi operativi moderni i servizi disponibili in rete si basano principalmente sul modello client/server. Tale architettura consente ai sistemi di condividere risorse e cooperare per il raggiungimento

Dettagli

Interazione (TCP) Client-Server con le socket

Interazione (TCP) Client-Server con le socket Interazione (TCP) Client-Server con le socket D. Gendarmi Interazione TCP Client/Server Server 2. Assegnare un local address alla socket 3. Settare la socket all ascolto 4. Iterativamente: a. Accettare

Dettagli

Laboratorio di Sistemi Operativi 29-01-2009. Cognome Nome Mat.

Laboratorio di Sistemi Operativi 29-01-2009. Cognome Nome Mat. Il compito è costituito da domande chiuse, domande aperte ed esercizi. Non è consentito l uso di libri, manuali, appunti., etc. Tempo massimo 2 ore. Domande chiuse: ogni domanda corrisponde ad un punteggio

Dettagli

IPC Inter Process Communication

IPC Inter Process Communication Il protocollo TCP controlla che la trasmissione tra due end points avvenga correttamente. Non stabilisce alcun criterio su chi deve iniziare la comunicazione. Questo compito è svolto dalle applicazioni

Dettagli

Esempio 1: stampa locale di file remoto

Esempio 1: stampa locale di file remoto Alcuni esempi di uso di Socket Esempio 1: stampa locale di file remoto Visualizzazione locale del contenuto di un file remoto. Il client deve richiedere la creazione della connessione e successivamente

Dettagli

Interazione con il DNS Conversioni di Nomi ed Indirizzi

Interazione con il DNS Conversioni di Nomi ed Indirizzi a.a. 2003/04 Interazione con il DNS Conversioni di Nomi ed Indirizzi Prof. Vincenzo Auletta auletta@dia.unisa.it http://www.dia.unisa.it/professori/auletta/ Università degli studi di Salerno Laurea in

Dettagli

Una semplice applicazione client/server 1

Una semplice applicazione client/server 1 Una semplice applicazione client/server 1 Il nostro obiettivo In questa parte del corso implementeremo un applicazione client/server che usa i socket Internet disponibili nei sistemi Unix/Linux. Nello

Dettagli

Socket TCP. seconda parte

Socket TCP. seconda parte Socket TCP seconda parte Schema della connessione Computer 1 127.43.18.1 indirizzo I1 indirizzo I2 Computer 2 143.225.5.3 porta 45000 socket porta 5200 socket processo client processo server socket(...)

Dettagli

Socket TCP. prima parte

Socket TCP. prima parte Socket TCP prima parte Cosa cambia: socket int fd = socket(pf_inet, SOCK_STREAM, 0); if (fd

Dettagli

I/O su Socket TCP: read()

I/O su Socket TCP: read() I/O su Socket TCP: read() I socket TCP, una volta che la connessione TCP sia stata instaurata, sono accedibili come se fossero dei file, mediante un descrittore di file (un intero) ottenuto tramite una

Dettagli

I Socket. Laboratorio Software 2008-2009 M. Grotto R. Farina

I Socket. Laboratorio Software 2008-2009 M. Grotto R. Farina M. Grotto R. Farina Sommario 1. Applicazioni Distribuite 2. I Socket Introduzione Interfacce e protocolli Descrizione Stile di comunicazione Namespace e protocollo Include e system call Creazione e chiusura

Dettagli

Esercitazione [6] Client/Server con Socket

Esercitazione [6] Client/Server con Socket Esercitazione [6] Client/Server con Socket Leonardo Aniello - aniello@dis.uniroma1.it Daniele Cono D'Elia - delia@dis.uniroma1.it Sistemi di Calcolo - Secondo modulo (SC2) Programmazione dei Sistemi di

Dettagli

Progettazione di Applicazioni Robuste. Applicazione Echo. Schema Generale di un Server TCP Ricorsivo 1. Applicazione echo

Progettazione di Applicazioni Robuste. Applicazione Echo. Schema Generale di un Server TCP Ricorsivo 1. Applicazione echo a.a. 2003/04 Applicazione Echo Prof. Vincenzo Auletta auletta@dia.unisa.it http://www.dia.unisa.it/professori/auletta/ Progettazione di Applicazioni Robuste nel progettare applicazioni su rete robuste

Dettagli

Paradigma client-server

Paradigma client-server Interazione Client Server (socket) Vittorio Maniezzo Università di Bologna Vittorio Maniezzo Università di Bologna 15 CliSer - 1/31 Paradigma client-server Le applicazioni utente devono interagire con

Dettagli

Socket per TCP: Fondamenti

Socket per TCP: Fondamenti Socket per TCP: Fondamenti Network Applications Molte applicazioni di rete sono formate da due programmi distinti (che lavorano su due diversi host) uno detto server ed uno detto client. Il server si mette

Dettagli

Laboratorio di Reti di Calcolatori

Laboratorio di Reti di Calcolatori Laboratorio di Reti di Calcolatori Socket UDP. Paolo D Arco Abstract Scopo della lezione è descrivere le funzioni che l interfaccia dei socket offre per far interagire client e server attraverso il protocollo

Dettagli

Program m azione di Sistem a 6

Program m azione di Sistem a 6 Program m azione di Sistem a 6 Lucidi per il corso di Laboratorio di Sistemi Operativi tenuto da Paolo Baldan presso l'università Ca' Foscari di Venezia, anno accademico 2004/ 2005. Parte di questo materiale

Dettagli

rsystem Maximiliano Marchesi maximiliano.marchesi@studenti.unipr.it

rsystem Maximiliano Marchesi maximiliano.marchesi@studenti.unipr.it Maximiliano Marchesi 28 Settembre 2005 Diario delle Revisioni Revisione 1.2 28 Settembre 2005 maximiliano.marchesi@studenti.unipr.it Sommario Introduzione..................................................................................

Dettagli

Cenni di programmazione distribuita in C++ Mauro Piccolo piccolo@di.unito.it

Cenni di programmazione distribuita in C++ Mauro Piccolo piccolo@di.unito.it Cenni di programmazione distribuita in C++ Mauro Piccolo piccolo@di.unito.it Socket Nei sistemi operativi moderni i servizi disponibili in rete si basano principalmente sul modello client/server. Tale

Dettagli

Esercitazione Laboratorio di Sistemi Operativi 20-01-2014. Cognome Nome Mat.

Esercitazione Laboratorio di Sistemi Operativi 20-01-2014. Cognome Nome Mat. Il compito è costituito da domande chiuse e domande aperte. Non è consentito l uso di libri, manuali, appunti., etc. Tempo massimo 2 ore. Domande chiuse: ogni domanda corrisponde ad un punteggio di 1 punto

Dettagli

T.A.R.I. Socket (ICT, AL)

T.A.R.I. Socket (ICT, AL) Internet Applications (Client-Server Concept, Use of Protocol Ports, Socket API, DNS, E-mail, TELNET, FTP) Funzionalità Livello di trasporto e livelli sottostanti Comunicazione base Disponibilità Livello

Dettagli

Guida all' uso dei sockets nella programmazione in C

Guida all' uso dei sockets nella programmazione in C Guida all' uso dei sockets nella programmazione in C ( pseudo-traduzione personalizzata di "Beej's Guide to Network Programming" ) (Prima parte) INTRODUZIONE Finalmente ho trovato una guida chiara e semplice

Dettagli

(VHUFLWD]LRQLGLEDVHVXOOH6RFNHWLQ&

(VHUFLWD]LRQLGLEDVHVXOOH6RFNHWLQ& (VHUFLWD]LRQLGLEDVHVXOOH6RFNHWLQ& 3ULPRHVHUFL]LR6RFNHWVWUHDPFRQULGLUH]LRQH Si progetti un applicazione distribuita Client/Server per una rete di workstation UNIX (BSD oppure System V). In particolare,

Dettagli

Opzioni del Socket. Socket Options. Opzioni di Livello Socket. Livello delle Opzioni

Opzioni del Socket. Socket Options. Opzioni di Livello Socket. Livello delle Opzioni a.a. 2003/04 Opzioni del Socket Socket Options Prof. Vincenzo Auletta auletta@dia.unisa.it http://www.dia.unisa.it/professori/auletta/ Università degli studi di Salerno Laurea in Informatica 1 Ogni socket

Dettagli

Laboratorio di Reti di Calcolatori

Laboratorio di Reti di Calcolatori Laboratorio di Reti di Calcolatori Comunicazione tra processi in una interrete, Socket API. Paolo D Arco Abstract Scopo della lezione è spiegare concisamente come possono comunicare due processi in esecuzione

Dettagli

Basi di network programming sotto Unix/Linux (draft version) Claudio Piciarelli

Basi di network programming sotto Unix/Linux (draft version) Claudio Piciarelli Basi di network programming sotto Unix/Linux (draft version) Claudio Piciarelli 20 dicembre 2004 ii Indice 1 Introduzione 1 1.1 Notazioni e terminologia..................................... 1 2 Un po di

Dettagli

INTERNET DOMAIN SOCKETS (Cap.59)

INTERNET DOMAIN SOCKETS (Cap.59) INTERNET DOMAIN SOCKETS (Cap.59) Internet Domain Stream Socket TCP Internet Domain Datagram Socket UDP A differenza degli UDDS I datagrams possono essere persi duplicati o arrivare in un ordine diverso

Dettagli

programmazione distribuita Introduzione Introduzione alla programmazione distribuita

programmazione distribuita Introduzione Introduzione alla programmazione distribuita Reti Informatiche Introduzione alla programmazione distribuita Introduzione Richiami di Programmazione C Differenze principali C/C++ 2 1 Definizioni di variabili Le variabili possono essere definite solo

Dettagli

Esercitazione di Lab. di Sistemi Operativi 1 a.a. 2011/2012. - Comunicazione Tra Processi (IPC) - - 2 Parte -

Esercitazione di Lab. di Sistemi Operativi 1 a.a. 2011/2012. - Comunicazione Tra Processi (IPC) - - 2 Parte - Esercitazione di Lab. di Sistemi Operativi 1 a.a. 2011/2012 - Comunicazione Tra Processi (IPC) - - 2 Parte - 1 Sommario Comunicazione tra processi su macchine diverse in rete: Socket TCP o Socket Stream

Dettagli

Creare una applicazione Winsock di base

Creare una applicazione Winsock di base Creare una applicazione Winsock di base Usiamo le API Winsock incluse in Creare un progetto per una Socket Windows (in Dev C++) Selezionare la file New Projects Selezionare Empty Project Salvare

Dettagli

SC per Inter Process Comminication. Comunicazione fra macchine diverse: socket

SC per Inter Process Comminication. Comunicazione fra macchine diverse: socket SC per Inter Process Comminication Comunicazione fra macchine diverse: socket 1 Sockets File speciali utilizzati per connettere due o più processi con un canale di comunicazione i processi possono risiedere

Dettagli

Sistemi Operativi (modulo di Informatica II)

Sistemi Operativi (modulo di Informatica II) Sistemi Operativi (modulo di Informatica II) La comunicazione tra processi Patrizia Scandurra Università degli Studi di Bergamo a.a. 2008-09 Sommario Processi cooperanti La comunicazione tra processi Necessità

Dettagli

Inter-process communication: socket

Inter-process communication: socket Le Socket Inter-process communication: socket Abbiamo visti alcune tipologie di Inter-process communication: Anonymous pipes FIFOs o named pipes Le socket di comunicazione si pongono nell'ipc per realizzare:

Dettagli

Introduzione alle applicazioni di rete

Introduzione alle applicazioni di rete Introduzione alle applicazioni di rete Definizioni base Modelli client-server e peer-to-peer Socket API Scelta del tipo di servizio Indirizzamento dei processi Identificazione di un servizio Concorrenza

Dettagli

Una socket è un punto estremo di un canale di comunicazione accessibile mediante un file descriptor. Alcuni tipi predefiniti di socket

Una socket è un punto estremo di un canale di comunicazione accessibile mediante un file descriptor. Alcuni tipi predefiniti di socket Una socket è un punto estremo di un canale di comunicazione accessibile mediante un file descriptor Le socket costituiscono un fondamentale strumento di comunicazione, basato sullo scambio di messaggi,

Dettagli

Esercitazione [5] Input/Output su Socket

Esercitazione [5] Input/Output su Socket Esercitazione [5] Input/Output su Socket Leonardo Aniello - aniello@dis.uniroma1.it Daniele Cono D'Elia - delia@dis.uniroma1.it Sistemi di Calcolo - Secondo modulo (SC2) Programmazione dei Sistemi di Calcolo

Dettagli

Acknowledgment: Prof Vincenzo Auletta, Università di Salerno. Approfondimento alla programmazione distribuita

Acknowledgment: Prof Vincenzo Auletta, Università di Salerno. Approfondimento alla programmazione distribuita Reti Informatiche Approfondimento alla programmazione distribuita Acknowledgment: Prof Vincenzo Auletta, Università di Salerno Introduzione API send e receive bloccanti e non API select Socket UDP Esempio

Dettagli

Università di Roma Tor Vergata Corso di Laurea triennale in Informatica Sistemi operativi e reti A.A. 2013-14. Pietro Frasca.

Università di Roma Tor Vergata Corso di Laurea triennale in Informatica Sistemi operativi e reti A.A. 2013-14. Pietro Frasca. Università di Roma Tor Vergata Corso di Laurea triennale in Informatica Sistemi operativi e reti A.A. 2013-14 Pietro Frasca Lezione 22 Martedì 7-1-2014 1 System Call per l'uso dei segnali Un processo che

Dettagli

DATAGRAM SOCKET. Angelastro Sergio Diomede Antonio Viterbo Tommaso

DATAGRAM SOCKET. Angelastro Sergio Diomede Antonio Viterbo Tommaso DATAGRAM SOCKET Angelastro Sergio Diomede Antonio Viterbo Tommaso Definizione supporta i datagram privo di connessione messaggi inaffidabili di una lunghezza massima prefissata il protocollo UDP supporta

Dettagli

Architettura e servizi Internet

Architettura e servizi Internet Architettura e servizi Internet Laboratorio di Sistemi Operativi Corso di Laurea in Informatica Università degli Studi dell'aquila A.A. 2011/2012 Romina Eramo materiale tratto da: Fazio Vincenzo e-mail:

Dettagli

unsigned long inet_addr(cp) char *cp;

unsigned long inet_addr(cp) char *cp; /* bcopystru.c #include struct point int x; char *y; ; struct point a, b; struct pint *pta, *ptb; a.x = 5; a.y = pippo ; b = a; printf i valori del secondo point sono: %d %s\n,b.x,b.y); pta=

Dettagli

Le Opzioni per i Socket

Le Opzioni per i Socket Advanced TCP Socket Le Opzioni per i Socket Le opzioni per i socket sono controllate mediante tre tipi di primitive: 1) le funzioni getsockopt() e setsockopt(), che permettono di configurare alcune caratteristiche

Dettagli

Programmazione dei socket di rete in GNU/Linux

Programmazione dei socket di rete in GNU/Linux Programmazione dei socket di rete in GNU/Linux Fulvio Ferroni fulvioferroni@teletu.it 2006.09.21 Copyright Fulvio Ferroni fulvioferroni@teletu.it Via Longarone, 6-31030 - Casier (TV) Le informazioni contenute

Dettagli

Laboratorio di Programmazione in Rete

Laboratorio di Programmazione in Rete Laboratorio di Programmazione in Rete a.a. 2005/2006 http://www.di.uniba.it/~lisi/courses/prog-rete/prog-rete0506.htm dott.ssa Francesca A. Lisi lisi@di.uniba.it Orario di ricevimento: mercoledì ore 10-12

Dettagli

Uso di sniffer ed intercettazione del traffico IP

Uso di sniffer ed intercettazione del traffico IP Uso di sniffer ed intercettazione del traffico IP Massimo Bernaschi Istituto per le Applicazioni del Calcolo Mauro Picone Consiglio Nazionale delle Ricerche Viale del Policlinico, 137-00161 Rome - Italy

Dettagli

CORSO DI SISTEMI OPERATIVI A - ESERCITAZIONE 6

CORSO DI SISTEMI OPERATIVI A - ESERCITAZIONE 6 UNIVERSITÀ DEGLI STUDI DI PARMA Facoltà di Ingegneria Corso di Laurea in Ingegneria Informatica, Elettronica e delle Telecomunicazioni a.a. 2005-2006 CORSO DI SISTEMI OPERATIVI A - ESERCITAZIONE 6 1 Socket

Dettagli

Reti di Calcolatori - Laboratorio. Lezione 7. Gennaro Oliva

Reti di Calcolatori - Laboratorio. Lezione 7. Gennaro Oliva Reti di Calcolatori - Laboratorio Lezione 7 Gennaro Oliva Opzioni di socket Ogni socket aperto ha un insieme di opzioni associate che ne determinano il comportamento Distinguiamo due tipi: opzioni binarie

Dettagli

Inter Process Communication. Laboratorio Software 2008-2009 C. Brandolese

Inter Process Communication. Laboratorio Software 2008-2009 C. Brandolese Inter Process Communication Laboratorio Software 2008-2009 C. Brandolese Introduzione Più processi o thread Concorrono alla relaizzazione di una funzione applicativa Devono poter realizzare Sincronizzazione

Dettagli

Sicurezza delle reti. Monga. Ricognizione. Scanning Breve ripasso socket Network mapping Port Scanning NMAP. Le tecniche di scanning

Sicurezza delle reti. Monga. Ricognizione. Scanning Breve ripasso socket Network mapping Port Scanning NMAP. Le tecniche di scanning Sicurezza dei sistemi e delle 1 Mattia Dip. di Informatica Università degli Studi di Milano, Italia mattia.monga@unimi.it Port Lezione V: Scansioni Port a.a. 2015/16 1 cba 2011 15 M.. Creative Commons

Dettagli

Socket per TCP: Fondamenti

Socket per TCP: Fondamenti Socket per TCP: Fondamenti Network Applications Molte applicazioni di rete sono formate da due programmi distinti (che lavorano su due diversi host) uno detto server ed uno detto client. Il server si mette

Dettagli

UDP. Livello di Trasporto. Demultiplexing dei Messaggi. Esempio di Demultiplexing

UDP. Livello di Trasporto. Demultiplexing dei Messaggi. Esempio di Demultiplexing a.a. 2002/03 Livello di Trasporto UDP Descrive la comunicazione tra due dispositivi Fornisce un meccanismo per il trasferimento di dati tra sistemi terminali (end user) Prof. Vincenzo Auletta auletta@dia.unisa.it

Dettagli

SISTEMI OPERATIVI. Sincronizzazione dei processi. Domande di verifica. Luca Orrù Centro Multimediale Montiferru 30/05/2007

SISTEMI OPERATIVI. Sincronizzazione dei processi. Domande di verifica. Luca Orrù Centro Multimediale Montiferru 30/05/2007 2007 SISTEMI OPERATIVI Sincronizzazione dei processi Domande di verifica Luca Orrù Centro Multimediale Montiferru 30/05/2007 Sincronizzazione dei processi 1. Si descrivano i tipi di interazione tra processi?

Dettagli

Socket API per il Multicast

Socket API per il Multicast Socket API per il Multicast Massimo Bernaschi Istituto per le Applicazioni del Calcolo Mauro Picone Consiglio Nazionale delle Ricerche Viale del Policlinico, 137-00161 Rome - Italy http://www.iac.cnr.it/

Dettagli

Il livello di Trasporto del TCP/IP

Il livello di Trasporto del TCP/IP Il livello di Trasporto del TCP/IP Il compito del livello transport (livello 4) è di fornire un trasporto efficace dall'host di origine a quello di destinazione, indipendentemente dalla rete utilizzata.

Dettagli

Opzioni per le Socket

Opzioni per le Socket Opzioni per le Socket A.A. 2005/06 Opzioni per le Socket Ogni socket aperto ha delle proprietà che ne determinano alcuni comportamenti Le opzioni del socket consentono di modificare tali proprietà Ogni

Dettagli

Introduzione. Livello applicativo Principi delle applicazioni di rete. Stack protocollare Gerarchia di protocolli Servizi e primitive di servizio 2-1

Introduzione. Livello applicativo Principi delle applicazioni di rete. Stack protocollare Gerarchia di protocolli Servizi e primitive di servizio 2-1 Introduzione Stack protocollare Gerarchia di protocolli Servizi e primitive di servizio Livello applicativo Principi delle applicazioni di rete 2-1 Pila di protocolli Internet Software applicazione: di

Dettagli

I protocolli UDP e TCP

I protocolli UDP e TCP I protocolli UDP e TCP A.A. 2005/2006 Walter Cerroni Il livello di trasporto in Internet APP. APP. TCP UDP IP collegamento logico tra diversi processi applicativi collegamento logico tra diversi host IP

Dettagli

Lab. di Sistemi Operativi - Esercitazione n 9- -Thread-

Lab. di Sistemi Operativi - Esercitazione n 9- -Thread- Lab. di Sistemi Operativi - Esercitazione n 9- -Thread- 1 Sommario Esercizi su: Comunicazione tra processi: la funzione pipe() Condivisione dati e codice tra due o più processi: concetto di Thread 2 -

Dettagli

Creare un'elementare backdoor in C in ambiente UNIX

Creare un'elementare backdoor in C in ambiente UNIX Creare un'elementare backdoor in C in ambiente UNIX DISCLAIMER: Questo tutorial è a solo scopo didattico. L'autore NON si prende alcuna responsabilità circa usi errati o non legali delle informazioni qui

Dettagli

Esercitazione di Lab. di Sistemi Operativi 1 a.a. 2011/2012. - Comunicazione Tra Processi (IPC)- - 1 Parte -

Esercitazione di Lab. di Sistemi Operativi 1 a.a. 2011/2012. - Comunicazione Tra Processi (IPC)- - 1 Parte - Esercitazione di Lab. di Sistemi Operativi 1 a.a. 2011/2012 - Comunicazione Tra Processi (IPC)- - 1 Parte - 1 Sommario Comunicazione tra processi sulla stessa macchina: fifo (qualunque insieme di processi)

Dettagli

Digressione: man 2...

Digressione: man 2... Syscall File I/O Digressione: man 2... Le funzioni della libreria standard UNIX associate alle system call di Linux sono documentate nella sezione 2 di man e.g.: man 2 open Ogni manpage di system call

Dettagli

Sommario. G. Piscitelli

Sommario. G. Piscitelli Sommario Interprocess Communication Processi (e thread) cooperanti Il paradigma produttore-consumatore Shared Memory e Inter Process Communication (IPC) facility Proprietà caratteristiche della comunicazione

Dettagli

Processi e Sincronizzazione. Laboratorio Software 2008-2009 C. Brandolese M. Grotto

Processi e Sincronizzazione. Laboratorio Software 2008-2009 C. Brandolese M. Grotto Processi e Sincronizzazione C. Brandolese M. Grotto Sommario 1. Processi Concetti fondamentali Stati in GNU/Linux 2. Creazione Descrizione Creazione con system() Creazione con fork() Effetto di fork()

Dettagli

Guida di Beej alla Programmazione di Rete

Guida di Beej alla Programmazione di Rete Guida di Beej alla Programmazione di Rete Usando Socket Internet Brian "Beej Jorgensen" Hall beej@beej.us Versione 2.4.5 5 Agosto 2007 Copyright 2007 Brian "Beej Jorgensen" Hall Traduzione di Fabrizio

Dettagli

Applicazione distribuita

Applicazione distribuita La programmazione di applicazioni distribuite in C Il concetto di applicazione distribuita L architettura di una applicazione distribuita Il paradigma a scambio di messaggi Il paradigma client-server Il

Dettagli

I Socket in PHP. Introduzione. Gestione degli errori nei socket. Funzioni socket di PHP. (manuale PHP)

I Socket in PHP. Introduzione. Gestione degli errori nei socket. Funzioni socket di PHP. (manuale PHP) I Socket in PHP (manuale PHP) Introduzione Questa estensione implementa una interfaccia a basso livello verso i socket, fornendo la possibilità di agire sia come server sia come client. Per l'utilizzo

Dettagli

POSIX - Gestione dei Segnali. E.Mumolo, DEEI mumolo@units.it

POSIX - Gestione dei Segnali. E.Mumolo, DEEI mumolo@units.it POSIX - Gestione dei Segnali E.Mumolo, DEEI mumolo@units.it Segnali in POSIX Segnali in Posix Modalità di notifiche relative a vari eventi asincroni I signal interrompono un processo e possono o meno essere

Dettagli

HTTP adaptation layer per generico protocollo di scambio dati

HTTP adaptation layer per generico protocollo di scambio dati HTTP adaptation layer per generico protocollo di scambio dati Sandro Cavalieri Foschini 101786 Emanuele Richiardone 101790 Programmazione in Ambienti Distribuiti I - 01FQT prof. Antonio Lioy A.A. 2002-2003

Dettagli

Elementi di programmazione con interfaccia Socket

Elementi di programmazione con interfaccia Socket Struttura generale per stream sockets Socket() Well-Known Port Bind() Elementi di programmazione con interfaccia Socket Cenni di programmazione secondo la nuova interfaccia Socket definita nella RFC 2553

Dettagli

Processi e thread. Dipartimento di Informatica Università di Verona, Italy. Sommario

Processi e thread. Dipartimento di Informatica Università di Verona, Italy. Sommario Processi e thread Dipartimento di Informatica Università di Verona, Italy Sommario Concetto di processo Stati di un processo Operazioni e relazioni tra processi Concetto di thread Gestione dei processi

Dettagli

TECNOLOGIE E PROGETTAZIONE DI SISTEMI INFORMATICI E DI TELECOMUNICAZIONI

TECNOLOGIE E PROGETTAZIONE DI SISTEMI INFORMATICI E DI TELECOMUNICAZIONI TECNOLOGIE E PROGETTAZIONE DI SISTEMI INFORMATICI E DI TELECOMUNICAZIONI Confronto tra ISO-OSI e TCP/IP, con approfondimento di quest ultimo e del livello di trasporto in cui agiscono i SOCKET. TCP/IP

Dettagli

Applicazione Client-Server con Server Concorrente Specifiche

Applicazione Client-Server con Server Concorrente Specifiche Applicazione Client-Server con Server Concorrente Specifiche Il progetto consiste nello sviluppo di un'applicazione client/server. Client e server devono comunicare tramite socket TCP.. Il server deve

Dettagli

Tesina esame Programmazione di Sistemi Mobile realizzata da Roberto Giuliani matricola 633688. Sockets e DatagramSocket

Tesina esame Programmazione di Sistemi Mobile realizzata da Roberto Giuliani matricola 633688. Sockets e DatagramSocket Tesina esame Programmazione di Sistemi Mobile realizzata da Roberto Giuliani matricola 633688 Sockets e DatagramSocket Windows Phone prevede un interfaccia di programmazione per garantire agli sviluppatori

Dettagli

Transmission Control Protocol

Transmission Control Protocol Transmission Control Protocol Franco Callegati Franco Callegati IC3N 2000 N. 1 Transmission Control Protocol - RFC 793 Protocollo di tipo connection-oriented Ha lo scopo di realizzare una comunicazione

Dettagli

CAPITOLO 27 SCAMBIO DI MESSAGGI

CAPITOLO 27 SCAMBIO DI MESSAGGI CAPITOLO 27 SCAMBIO DI MESSAGGI SCAMBIO DI MESSAGGI Sia che si guardi al microkernel, sia a SMP, sia ai sistemi distribuiti, Quando i processi interagiscono fra loro, devono soddisfare due requisiti fondamentali:

Dettagli

AXO. Operativo. Architetture dei Calcolatori e Sistema. programmazione di sistema

AXO. Operativo. Architetture dei Calcolatori e Sistema. programmazione di sistema AXO Architetture dei Calcolatori e Sistema Operativo programmazione di sistema Il sistema operativo Il Sistema Operativo è un insieme di programmi (moduli software) che svolgono funzioni di servizio nel

Dettagli

Capitolo 2 - parte 3. Corso Reti ed Applicazioni Mauro Campanella

Capitolo 2 - parte 3. Corso Reti ed Applicazioni Mauro Campanella Capitolo 2 - parte 3 Corso Reti ed Applicazioni Mauro Campanella Agenda - Domain Name Sytem (DNS) - Le socket BSD per Internet M. Campanella Corso Reti ed Applicazioni - Como 2005 Cap 2-3 pag. 2 DNS: Domain

Dettagli

Sistemi Distribuiti. Informatica B. Informatica B

Sistemi Distribuiti. Informatica B. Informatica B Sistemi Distribuiti Introduzione Che cos è un sistema distribuito? Un sistema distribuito è una collezione di computer indipendenti che appare all utente come un solo sistema coerente Da notare: le macchine

Dettagli

CORSO DI RETI SSIS. Lezione n.3 9 novembre 2005 Laura Ricci

CORSO DI RETI SSIS. Lezione n.3 9 novembre 2005 Laura Ricci CORSO DI RETI SSIS Lezione n.3 9 novembre 2005 Laura Ricci IL LIVELLO TRASPORTO realizza un supporto per la comunicazione logica tra processi distribuiti comunicazione logica = astrazione che consente

Dettagli

Introduzione al Linguaggio C

Introduzione al Linguaggio C Introduzione al Linguaggio C File I/O Daniele Pighin April 2009 Daniele Pighin Introduzione al Linguaggio C 1/15 Outline File e dati Accesso ai file File I/O Daniele Pighin Introduzione al Linguaggio C

Dettagli

La sincronizzazione è legata alla implementazione delle pipe: int pipe(int fd[2]);

La sincronizzazione è legata alla implementazione delle pipe: int pipe(int fd[2]); int pipe(int fd[2]); Le pipe sono canali di comunicazione unidirezionali che costituiscono un primo strumento di comunicazione (con diverse limitazioni), basato sullo scambio di messaggi, tra processi

Dettagli

CORSO DI SISTEMI OPERATIVI A - ESERCITAZIONE 3. 1 strace : visualizzazione delle system call invocate da un processo

CORSO DI SISTEMI OPERATIVI A - ESERCITAZIONE 3. 1 strace : visualizzazione delle system call invocate da un processo UNIVERSITÀ DEGLI STUDI DI PARMA Facoltà di Ingegneria Corso di Laurea in Ingegneria Informatica a.a. 2005-2006 CORSO DI SISTEMI OPERATIVI A - ESERCITAZIONE 3 1 strace : visualizzazione delle system call

Dettagli

Processi UNIX. I Processi nel SO UNIX. Gerarchie di processi UNIX. Modello di processo in UNIX

Processi UNIX. I Processi nel SO UNIX. Gerarchie di processi UNIX. Modello di processo in UNIX Processi UNIX I Processi nel SO UNIX UNIX è un sistema operativo multiprogrammato a divisione di tempo: unità di computazione è il processo Caratteristiche del processo UNIX: processo pesante con codice

Dettagli

IL LIVELLO TRASPORTO Protocolli TCP e UDP

IL LIVELLO TRASPORTO Protocolli TCP e UDP Reti di Calcolatori ed Internet IL LIVELLO TRASPORTO Protocolli TCP e UDP 5-1 Il Livello Trasporto I servizi del livello Trasporto Le primitive di Trasporto Indirizzamento Protocolli di Trasporto Livello

Dettagli

Laboratorio del corso Progettazione di Servizi Web e Reti di Calcolatori Politecnico di Torino AA 2014-15 Prof. Antonio Lioy

Laboratorio del corso Progettazione di Servizi Web e Reti di Calcolatori Politecnico di Torino AA 2014-15 Prof. Antonio Lioy Laboratorio del corso Progettazione di Servizi Web e Reti di Calcolatori Politecnico di Torino AA 2014-15 Prof. Antonio Lioy Soluzioni dell esercitazione n. 2 a cura di Giacomo Costantini 19 marzo 2014

Dettagli

Protocolli per il Web. Impianti Informatici. Protocolli applicativi

Protocolli per il Web. Impianti Informatici. Protocolli applicativi Protocolli per il Web Protocolli applicativi I protocolli applicativi 2 Applicazioni Socket interface HTTP (WEB) SMTP (E-MAIL) FTP... NFS RPC DNS... Trasporto TCP UDP Rete ICMP RIP OSPF IP ARP RARP Non

Dettagli

Laboratorio di Sistemi Operativi

Laboratorio di Sistemi Operativi Laboratorio di Sistemi Operativi Segnali a.a. 2011/2012 Francesco Fontanella Segnali - Introduzione I segnali sono interrupt software a livello di processo comunicano al processo il verificarsi di un evento

Dettagli

CREAZIONE PROCESSI IN UNIX 20

CREAZIONE PROCESSI IN UNIX 20 CREAZIONE PROCESSI IN UNIX 20 STRUTTURE DATI PER PROCESSI Un processo puo' essere in escuzione in 2 modi: kernel e utente. Un processo ha almeno 3 regioni: codice, dati e stack Lo stack è allocato dinamicamente.

Dettagli

SISTEMI OPERATIVI DISTRIBUITI

SISTEMI OPERATIVI DISTRIBUITI SISTEMI OPERATIVI DISTRIBUITI E FILE SYSTEM DISTRIBUITI 12.1 Sistemi Distribuiti Sistemi operativi di rete Sistemi operativi distribuiti Robustezza File system distribuiti Naming e Trasparenza Caching

Dettagli

LIBRERIA SOCKET EFUNZIONI DI NETWORKING. Versione 1.0

LIBRERIA SOCKET EFUNZIONI DI NETWORKING. Versione 1.0 LIBRERIA SOCKET EFUNZIONI DI NETWORKING Versione 1.0 Vito Asta, febbraio 2000 N.B. La presente dispensa accenna solo brevemente agli argomenti trattati e alle funzioni viste nel corso delle esercitazioni;

Dettagli

Corso di Laboratorio di Sistemi Operativi

Corso di Laboratorio di Sistemi Operativi Corso di Laboratorio di Sistemi Operativi Lezione 6 Alessandro Dal Palù email: alessandro.dalpalu@unipr.it web: www.unipr.it/~dalpalu Interazione tra Processi I processi concorrenti possono interagire

Dettagli

TCP/IP. Principali caratteristiche

TCP/IP. Principali caratteristiche TCP/IP Principali caratteristiche 1 TCP/IP Caratteristiche del modello TCP/IP Struttura generale della rete Internet IL MONDO INTERNET Reti nazionali e internazionali ROUTER Rete Azienade ROUTER ROUTER

Dettagli

Laboratorio di Programmazione in rete

Laboratorio di Programmazione in rete Laboratorio di rogrammazione in rete Introduzione alla programmazione C di socket A.A. 2005/06 Comunicazione tra computer Come far comunicare più computer su una rete? Una collezione di protocolli: TC/I

Dettagli