Calcolatori Elettronici Socket Calcolatori Elettronici C.Brandolese
Calcolatori Elettronici Sommario Introduzione Interfaccie e protocolli Il modello Client/Server Indirizzamento Connessione Strutture dati e costanti Funzioni di utilità Gestione dei socket Esempi Calcolatori Elettronici
Calcolatori Elettronici Introduzione Una applicazione è un insieme di programmi coordinati in modo da svolgere una determinata funzione: la funzione applicativa In un ambiente di programmazione usuale una applicazione coincide generalmente con un singolo programma. In un ambiente distribuito si parla di applicazioni distribuite Applicazione distribuita: E’ costituita da un insieme di programmi che vengono generalmente eseguiti su macchine diverse I vari programmi cooperano attraverso una rete di calcolatori Le applicazioni distribuite richiedono la capacità di comunicare attraverso una rete. Calcolatori Elettronici
Calcolatori Elettronici Introduzione I vari programmi che costituiscono una applicazione distribuita devono seguire alcune regole per comunicare in modo corretro L’insieme delle regole per la comunicazione prende il nome di protocollo applicativo Si noti che, come è ovvio, due programmi comunicano tra di loro solo quando sono in esecuzione: a rigore quindi sono i processi a comunicare sulla base delle istruzioni constenute nei programmi Nel seguito si parlerà indifferentemente di processi o di programmi, intendendo quanto appena chiarito Calcolatori Elettronici
Interfaccie e protocolli Per la comunicazione i programmi di una applicazione distribuita si appoggiano a un insieme di servizi tipici forniti: Dal sistema operativo Dal software di rete Il programmatore utilizzare in pratica delle funzioni di libreria L’insieme delle funzioni di base prende il nome di Application Program Interface o API E’ bene notare la differenza tra protocollo applicativo e API: Il protocollo applicativo rappresenta le regole per la comunicazione, ma non è un canale diretto di comunicazione Le API sono le funzioni che realizzano sul canale fisico le regole di comunicazione definite nel protocollo. Calcolatori Elettronici
Interfaccie e protocolli Esempio di applicazione distribuita: APPLICAZIONE DISTRIBUTA PROGRAMMA APPLICATIVO PROGRAMMA APPLICATIVO Protocollo Applicativo API API Sistema Operativo e Software di Rete Sistema Operativo e Software di Rete RETE Calcolatori Elettronici
Interfaccie e protocolli Nel seguito faremo riferimento ai sistemi operativi: UNIX WindowsNT Supporremo inoltre che il software di rete disponibile sia TCP/IP TCP/IP definisce in maniera astratta alcuni servizi che i vari sistemi operativi realizzano e rendono disponibili tramite le API Uno dei servizi forniti dal protocollo TCP/IP è l’interfaccia: SOCKET (UNIX) WINSOKET (WindowsNT) Calcolatori Elettronici
Interfaccie e protocolli La configurazione di riferimento di una applicazione distribuita basata su TCP/IP e soket è il seguente: PROGRAMMA C Protocollo Applicativo PROGRAMMA C UNIX API + SOKET NT API + WINSOKET UNIX + TCP/IP Canale WindowsNT + TCP/IP Calcolatori Elettronici
Il modello Client/Server Il protocollo TCP/IP fornisce un meccanismo di comunicazione tra processi residenti su macchine diverse Il tipo di comunicazione è detto peer-to-peer (da pari a pari) Sta al programmatore definire le regole di comunicazione cioè il protocollo applicativo Molte applicazioni sono basate sul modello Client/Server In questo modello una delle applicazioni agisce da Server mentre le altre agiscono da Client In generale un processo può assumere il ruolo di Client o di Server dinamicamente nel tempo Calcolatori Elettronici
Il modello Client/Server Il processo Server: Fornisce servizi Accetta richieste provenienti dai client Esegue i servizi richiesti Eventualmente ritorna un risultato al richiedente Il processo Client: Richiede servizi ad un server Attende una risposta da parte del server Ad esempio: Server: WEBServer Client: Browser Calcolatori Elettronici
Calcolatori Elettronici Indirizzamento Quando un processo P intende comunicare con un processo Q, in esecuzione su un’altra macchina deve: Identificare la macchina su cui è in esecuzione Q Identificare il processo Q fra tutti quelli in esecuzione sulla macchina Una macchina è individuata dal suo indirizzo IP: Un indirizzo IP è un numero di 32 bit univoco all’interno di una rete Gli indirizzi IP vengono scritti esprimendo il valore decimale dei 4 gruppi di 8 bit che lo compongono, separati da un punto Un processo è individuato attraverso un port: Un port è un numero usato per identificare un processo solo ai fini della comunicazione TCP/IP Il port di un processo non ha nessuna relazione con il suo pid Calcolatori Elettronici
< IP_Address, Port > Indirizzamento Port e pid identificano un processo ma: Il pid di un processo è assegnato dal sistema operativo ed il programmatore non ha alcun controllo sul suo valore Il port viene assegnato dal programmatore Affinché il processo P possa comunicare con il processo Q, i due programmi devono stabilire un port con cui identificarsi I port da 0 a 1023 sono utilizzati da servizi standard e non possono essere usati dal programmatore Un idirizzo TCP completo è quindi costituito dalla coppia: < IP_Address, Port > Calcolatori Elettronici
Calcolatori Elettronici Connessione La comunicazione in TCP si dice orientata alla connesione Questo significa che, prima di poter scambiare dati, due processi devono stabilire una connessione In un modello client/server, per stabilire una connessione tra due processi P e Q è necessario che: Il processo P, che agisce da server, si metta in attesa di richieste da parte del processo Q Il processo Q, che agisce da client, invia richieste al processo P ed attende una risposta Quando il server P accetta una richiesta di un servizio da parte del processo Q si stabilisce la connessione Calcolatori Elettronici
Calcolatori Elettronici Connessione Aspetti generali di una connessione: I due processi che stabiliscono una connessione sono detti punti terminali Ogni processo è identificato dalla coppia <IP_Address, Port> La connessione è identificata dai suoi punti terminali cioè dalle due coppie <IP_Address, Port> relative appunto ai punti terminali Dopo aver stabilito la connessione, i processi dispongono di un canale: Bidirezionale: la comunicazione può avvenire nelle due direzioni Affidabile: un meccanismo di acknowledge garantisce che i dati siano ricevuti Orientato allo stream: i dati vengono trasmessi in modo continuo Si noti che l’identificazione della connessione consente ad un processo di partecipare a diverse connessioni in quanto TCP le distingue in base agli indirizzi dei punti terminali Calcolatori Elettronici
Strutture dati e costanti Gli indirizzi di socket sono memorizzati in una struttura dati C: struct sockaddr_in { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; Il significato dei campi è il seguente sin_family: famiglia degli indirizzi. Nel nostro caso AF_INET. sin_port: numero del port. Compreso tra 0 e 64k sin_addr: indirizzo IP. Si tratta di una union di cui useremo solo il campo s_addr di tipo u_long, cioè intero a 32 bit senza segno sin_zero: non utilizzato Calcolatori Elettronici
Strutture dati e costanti Sono definiti alcuni gruppi di costanti Costanti per la definizione del tipo di indirizzo AF_UNIX: Indirizzi ARPA, locali alle macchine AF_INET: Indirizzi Internet Costanti per la definizione del tipo di comunicazione: SOCK_STREAM: Orientata allo stream SOCK_DGRAM: Datagram SOCK_RAW: Raw data Queste costanti vengono utilizzate nella creazione dei socket e nella definizione degli indirizzi Calcolatori Elettronici
Calcolatori Elettronici Funzioni di utilità Conversione da stringa a indirizzo IP: unsigned long inet_addr(const char *cp); Conversione da formato host a formato network uint32_t htonl(unint32_t hostlong); uint16_t htons(uint16_t hostshort); Conversione da formato network a formato host uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort); Calcolatori Elettronici
Calcolatori Elettronici Gestione dei socket Creazione di un socket: int socket(int domain, int type, int protocol); I parametri hanno il seguente significato: domain: famiglia di protocolli, nel nostro caso AF_INET type: tipo di connessione, nel nostro caso SOCK_STREAM protocol: tipo di protocollo, nel nostro caso esiste solo il protocollo IP quindi questio parametro vale generalmente 0 Crea un socket con le caratteristiche specificate e restituisce un descrittore Il descrittore è simile al descrittore utilizzato per la gestione dei files Calcolatori Elettronici
Calcolatori Elettronici Gestione dei socket Chiusura di un socket: int close(int socket); I parametri hanno il seguente significato: socket: descrittore di socket Chiude il socket individuato dal descrittore specificato Dopo la chiusura, il socket non è più accessibile Calcolatori Elettronici
Calcolatori Elettronici Gestione dei socket Associazione di un socket ad un indirizzo locale: int bind(int s, struct sockaddr *locaddr, int locaddrlen); I parametri hanno il seguente significato: s: descrittore di socket locaddr: indirizzo IP local al quale il socket viene associato locaddrlen: dimensioni della struttura dati utilizzata per specificare l’indirizzo locale. Generalmente si ottiene tramite la funzione standard sizeof() Lega il socket il cui descrittore è s all’indirizzo locale specificato nella struttura dati locaddr. Questa funzione associa al socket uno dei due punti terminali Calcolatori Elettronici
Calcolatori Elettronici Gestione dei socket Dimensione della coda di un socket: int listen(int s, int backlog); I parametri hanno il seguente significato: s: descrittore di socket backlog: dimensione massima della coda di richieste Specifica il numero massimo di richieste che possono essere messe in coda sul socket s Una richiesta che arrivi quando la coda è piena viene rifutata ed il processo richiedente riceve un messaggio di errore Calcolatori Elettronici
Calcolatori Elettronici Gestione dei socket Accettazione di una richiesta di connessione: int accept(int s, struct sockaddr *destaddr, int *destaddrlen); I parametri hanno il seguente significato: s: descrittore di socket destaddr: indirizzo del processo richiedente destaddrlen: dimensione della struttura dati contenete l’indirizzo del processo richiedente Accetta la prima richiesta di connessione in coda, crea un nuovo socket e ne restituisce il descrittore Il socket creato non può più essere utilizzato per accettare altre richieste mentre il socket originale si Calcolatori Elettronici
Calcolatori Elettronici Gestione dei socket Crea una connessione: int connect(int s, struct sockaddr *destaddr, int *destaddrlen); I parametri hanno il seguente significato: s: descrittore di socket destaddr: indirizzo del processo remoto richiedente destaddrlen: dimensione della struttura dati contenete l’indirizzo del processo richiedente Crea una connessione, nel nostro caso TCP/IP, con il richiedente il cui indirizzo è specificato dagli ultimi due parametri Dopo la creazione della connessione i due processi possono iniziare lo scambio di dati Calcolatori Elettronici
Calcolatori Elettronici Gestione dei socket Scrive/legge dati attraverso un socket: int write(int fd, char *buf, int nbyte); int read (int fd, char *buf, int nbyte); I parametri hanno il seguente significato: fd: descrittore di socket (o di file) buf: puntatore all’area dati nbyte: dimensione dei dati Legge o scrive dati attraverso un socket Queste funzioni sono le stesse utlizzate per la lettura scrittura di dai da/verso i files Calcolatori Elettronici
Esempio: Client/Server – Singola connessione Il server: Legge dalla linea di comando il numero di porta crea un socket su quella porta Aspetta chiamate Ogni volta che arriva una chiamata: Legge da socket un comando e lo esegue Scrive il risultato sullo stesso socket Chiude la connessione Si rimette in attesa Il ciclo di attesa non termina mai Per terminare il processo server occorre ucciderlo (kill) esplicitamente Calcolatori Elettronici
Esempio: Client/Server – Singola connessione Il client: Legge dalla linea di comando l'indirizzo IP e il numero di port del server Crea un socket Si connette al server Legge un comando da standard input Invia il comando al server Legge la risposta Trascrive la risposta su standard output Chiude la connessione Termina Ogni volta che il client è invocato comunica un solo comando al server Calcolatori Elettronici
Esempio: Client/Server – Singola connessione Il server è organizzato come segue: Creazione del socket socket() Inizializzazione dell’indirizzo: contenuto nella variabile server_addr il tipo è struct sockaddr_in Binding del socket bind() Definizione del numero massimo di connessioni listen() Ciclo infinito, in cui: Accetta una connessione accept() Legge un comando da socket read() Scrive il risultato dell’esecuzione del comando su socket write() Termina la connessione close() Calcolatori Elettronici
Esempio: Client/Server – Singola connessione #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define MAX_CONN 5 int main(int argc, char* argv[]) { int sock_fd; int new_sock_fd; int client_len; struct sockaddr_in server_addr; struct sockaddr_in client_addr; char command; if( argc != 2 ){ printf(“Numero di parametri errato\n”); exit(1); } sock_fd = socket(AF_INET, SOCK_STREAM, 0); ... Calcolatori Elettronici
Esempio: Client/Server – Singola connessione ... server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons((u_short) atoi(argv[1])); bind( sock_fd, (struct sockaddr *) &server_addr, sizeof(server_addr) ); listen(sock_fd, MAX_CONN); Calcolatori Elettronici
Esempio: Client/Server – Singola connessione ... while(1) { new_sock_fd = accept( sock_fd, (struct sockaddr *) &client_addr, &client_len ); read(new_sock_fd, &command, 1); switch (command) { case 'a': write(new_sock_fd, "SERVICE_A", strlen("SERVICE_A") ); break; case 'b': write(new_sock_fd, "SERVICE_B", strlen("SERVICE_B") ); break; default: write(new_sock_fd, "UNKNOWN", strlen("UNKNOWN") ); break; } close(new_sock_fd); Calcolatori Elettronici
Esempio: Client/Server – Singola connessione Il client è organizzato come segue: Creazione del socket socket() Inizializzazione dell’indirizzo: L’indirizzo IP è in argv[1] Il port del server è in argv[2] Connessione al server connect() Legge un comando da standard input Scrive il comando sul soket write() Legge il risultato dal socket read() Scrive il risultato sullo standard output Chiude il socket e termina close() Calcolatori Elettronici
Esempio: Client/Server – Singola connessione #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> int main(int argc, char* argv[]) { int sock_fd; int err; struct sockaddr_in server_addr; char command; char result; if (argc != 3) { printf(“Numero di parametri errato\n”); exit(1); } sock_fd = socket(AF_INET, SOCK_STREAM, 0); ... CLIENT Calcolatori Elettronici
Esempio: Client/Server – Singola connessione server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr( argv[1] ); server_addr.sin_port = htons( (u_short)atoi(argv[2]) ); err = connect( sock_fd, (struct sockaddr *) &server_addr, sizeof(server_addr) ); if( err < 0 ) exit(1); read(0, &command, 1); write(sock_fd, &command, 1); while( read( sock_fd, &result, 1 ) ) write( 1, &result, 1 ); close( sock_fd ); } CLIENT Calcolatori Elettronici
Esempio: Client/Server – Più connessioni Il server: Legge dalla linea di comando il numero di porta Crea un socket su quella Aspetta chiamate Ogni volta che arriva una chiamata: Genera un processo figlio, che gestirà la connessione Il processo padre chiude la connessione e si rimette in attesa Il processo figlio legge da socket un comando Esegue il comando e scrive il risultato sullo stesso socket Ripete la sequenza di gestione dei comandi finché non riceve il comando di uscita Chiude la connessione e termina Calcolatori Elettronici
Esempio: Client/Server – Più connessioni Il server è organizzato come segue: Creazione del socket socket() Inizializzazione dell’indirizzo: contenuto nella variabile server_addr il tipo è struct sockaddr_in Binding del socket bind() Definizione del numero massimo di connessioni listen() Ciclo infinito, in cui: Accetta una connessione e crea un figlio accept() Il figlio legge un comando da socket read() Il figlio scrive un risultato sul socket write() Il figlio terminala connessione close() Il padre terminala connessione close() Calcolatori Elettronici
Esempio: Client/Server – Più connessioni #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #define MAX_CONN 5 int main(int argc, char* argv[]) { int sock_fd, new_sock_fd; int client_len; int pid; char command; struct sockaddr_in server_addr; struct sockaddr_in client_addr; if (argc != 2) { printf(“Numero di parametri errato\n”); exit(1); } SERVER Calcolatori Elettronici
Esempio: Client/Server – Più connessioni ... sock_fd = socket(AF_INET, SOCK_STREAM, 0); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons((u_short) atoi(argv[1])); bind( sock_fd, (struct sockaddr *) &server_addr, sizeof(server_addr) ); listen(sock_fd, MAX_CONN); ... Calcolatori Elettronici
Esempio: Client/Server – Più connessioni while( 1 ) { new_sock_fd = accept( sock_fd, (struct sockaddr *) &client_addr, &client_len ); pid = fork(); if( pid == 0 ) { do { read( new_sock_fd, &command, 1 ); execute_command( new_sock_fd, command ); } while( command != 'q‘ ); close( new_sock_fd ); exit( 0 ); } else { close( new_sock_fd ); } SERVER child Calcolatori Elettronici
Esempio: Client/Server – Più connessioni Il server si aspetta una sequenza di comandi terminata dal carattere q I client gestiscono una sequenza di comandi Nel codice seguente si mostra solamente ciò che accade dopo l’apertura della connessione La parte di codeice del client qui omessa è molto simile al codice visto per la connessione singola Calcolatori Elettronici
Esempio: Client/Server – Più connessioni #define MAXSTR 4096 char command; int nbytes; char buf[MAXSTR]; ... while( 1 ) { read( 0, &command, 1 ); write( sock_fd, &command, 1 ); if( command == ‘q‘ ) break; nbytes = read( sock_fd, buf, MAXSTR); write( 1, buf, nbytes ); } close( sock_fd ); CLIENT Calcolatori Elettronici