Programmazione di Sistema – 5 Pipe e Fifo
Transcript
Programmazione di Sistema – 5 Pipe e Fifo
IPC: Inter Process Communication Programmazione di Sistema – 5 Meccanismi che permettono a processi distinti di comunicare e scambiare informazioni. I processi che comunicano possono risiedere sulla stessa macchina (segnali, pipe, fifo, socket) su macchine diverse (socket) Paolo Baldan Università Ca’ Foscari Venezia Corso di Laurea in Informatica La comunicazione può essere finalizzata a: cooperazione: i processi scambiano dati per l'ottenimento di un fine comune. sincronizzazione: lo scambio di informazioni permette a processi indipendenti, ma correlati, di schedulare correttamente la propria attività (es. di non accedere contemporaneamente ad una risorsa condivisa). Parte di questo materiale è rielaborato dalle slide del Corso di Laboratorio di Sistemi Operativi tenuto da Rosario Pugliese presso l'Università di Firenze, anno accademico 2001/02. 2 Pipe anonimi e con nome (FIFO) Pipe e Fifo Meccanismo utilizzato dalla shell per connettere l'output di un comando all'input di un altro (pipeline). $ who | sort I due processi connessi da un pipe sono eseguiti concorrentemente. Memorizza automaticamente l'output dello scrittore (who) in un buffer. Se il buffer è pieno, lo scrittore si sospende fino a che alcuni dati non vengono letti. Se il buffer è vuoto, il lettore (sort) si sospende fino a che diventano disponibili dei dati in output. 4 Pipe anonimi Pipe con nome e anonimi (FIFO) Pipe Anonimi Presenti in tutte le versioni di UNIX utilizzati, Un pipe anonimo è un canale di comunicazione che unisce due processi (creato con pipe()) unidirezionale ad es., dalle shell per le pipeline. permette la comunicazione solo tra processi con un antenato comune Pipe con nome o FIFO Presenti in UNIX System V (anche in BSD per compatibilità ). Un pipe presenta due lati di accesso (in/out), ciascuno associato ad un descrittore di file. Il Il 5 Pipe anonimi: pipe() lato 6 Se un processo legge da un pipe: se il lato scrittura è stato chiuso, read() restituisce 0 che indica la fine dell'input; di lettura fd[0] (aperto in lettura) di scrittura fd[1] (aperto in scrittura) se Processo se fd[0] fd[1] pipe Memorizza il suo input in un buffer (massima dimensione (PIPE_BUF) varia, ma è tipicamente intorno ai 4K) Quando un processo ha finito di usare un (lato di un) pipe chiude il descrittore con close(). Pipe anonimi: lettura int pipe (int fd[2]) crea un pipe anonimo e restituisce due descrittori di file lato lato di scrittura è acceduto invocando write(). lato di lettura è acceduto invocando read(). il pipe è vuoto e il lato di scrittura è ancora aperto, si sospende fino a che diventa disponibile qualche input; il processo tenta di leggere più byte di quelli presenti nel buffer associato, i byte disponibili vengono letti e read() restituisce il numero dei byte effettivamente letti. Kernel Fallisce restituendo -1 se il kernel non ha più spazio per un nuovo pipe; altrimenti restituisce 0. 7 8 Pipe anonimi: pipe() Pipe anonimi: scrittura Se un processo scrive su di un pipe: se il lato di lettura è stato chiuso, write() fallisce ed allo scrittore è inviato un segnale SIGPIPE, la cui azione di default è di far terminare il ricevente; se scrive meno byte di quelli che un pipe può contenere, write() viene eseguita in modo atomico (non possono avvenire interleaving dei dati scritti da processi diversi sullo stesso pipe); se scrive più byte di quelli che un pipe può contenere (PIPE_BUF), non c'è garanzia di atomicità. lseek() non ha senso se applicata ad un pipe. Dato che l'accesso ad un pipe anonimo avviene tramite file descriptor, solo il processo creatore ed i suoi discendenti possono accedere al pipe. il processo crea un pipe anonimo (pipe()); il processo crea un figlio (fork()); lo scrittore chiude il suo lato di lettura del pipe ed il lettore chiude il suo lato scrittura (close()); i processi comunicano usando write() e read(); ogni processo chiude (close()) il suo descrittore quando ha finito. Una comunicazione bidirezionale si può realizzare utilizzando due pipe. 10 9 Esempio: scambio di un messaggio: talk.c La tipica sequenza di eventi è Esempio: talk.c (scambio di un messaggio) #include <stdio.h> #define READ 0 /* The index of the read end of the pipe */ #define WRITE 1 /* The index of the write end of the pipe */ char *phrase = "A message for your pipe"; Il programma talk.c usa un pipe per permettere al padre di leggere un messaggio inviato dal figlio. int main (void) { int fd [2], bytesRead; char message [100]; /* Parent process' message buffer */ pipe (fd); /* Create an unnamed pipe */ if (fork () == 0) { /* Child, writer */ close(fd[READ]); /* Close unused end */ write (fd[WRITE], phrase, strlen (phrase) + 1); /* include \0 */ close (fd[WRITE]); /* Close used end */ } else { /* Parent, reader*/ close (fd[WRITE]); /* Close unused end */ bytesRead = read (fd[READ], message, 100); printf ("Read %d bytes: %s\n", bytesRead, message); close (fd[READ]); /* Close used end */ } 11 } 12 Comunicazione tramite pipe (e non solo) Esempio: talk.c (scambio di un messaggio) Esempio di esecuzione ... $ ./talk Read 24 bytes: A message for your pipe $ Quando un processo “scrittore” invia più messaggi di lunghezza variabile tramite un pipe, occorre fissare un protocollo di comunicazione che permetta al processo “lettore” di individuare la fine di ogni singolo messaggio. Alcune possibilità sono: inviare la lunghezza del messaggio (dato di dimensione fissa e nota) prima del messaggio stesso; terminare un messaggio con un carattere speciale come '\0' o un newline. Più in generale, il protocollo stabilisce la sequenza di messaggi attesa delle due parti. 14 13 Protocol.c (Semplice protocollo) Protocol.c (cont.) /* Semplice protocollo di comunicazione tramite pipe anonima Dovendo inviare messaggi di lunghezza variabile ogni messaggio e` preceduto da un intero che da` la sua lunghezza in caratteri */ #define READ 0 #define WRITE 1 else { /* Parent, reader*/ close (fd[WRITE]); /* Close unused end */ while (read(fd[READ], &length, sizeof(int))) { bytesRead = read (fd[READ], buffer, length); if (bytesRead != length) { printf("Error!\n"); /* actually it could be that length bytes were too much to be written atomically ... */ exit(EXIT_FAILURE); } printf ("Father: Read %d bytes: %s\n", bytesRead, buffer); } close (fd[READ]); /* Close used end */ /* The index of the read end of the pipe */ /* The index of the write end of the pipe */ char *msg[3] = { "Primo", "Secondo", "Terzo" }; int main (void) { int fd [2], i, length, bytesRead; char buffer [100]; /* Message buffer */ pipe (fd); /* Create an unnamed pipe */ if (fork () == 0) { /* Child, writer */ close(fd[READ]); /* Close unused end */ for (i = 0; i< 3; i++) { length=strlen (msg[i])+1 ; /* include \0 */ write (fd[WRITE], &length, sizeof(int)); write (fd[WRITE], msg[i], length); } close (fd[WRITE]); /* Close used end */ } } exit(EXIT_SUCCESS); } 15 16 Esempio: ridirezione con pipe (connect.c) Esempio: ridirezione con pipe (connect.c) #include <stdio.h> #define READ 0 #define WRITE 1 Il programma connect.c esegue due programmi e connette l'output di uno all'input dell'altro. I nomi dei programmi sono passati come argomenti e si assume che nessuno dei due programmi sia invocato con argomenti. connect cmd1 cmd2 17 Pipe con nome (FIFO) come file speciali nel file system; il loro uso non è limitato a processi con antenati comuni; un pipe con nome può essere usato da qualunque processo che conosca il nome del file corrispondente. esistono int main (int argc, char *argv []) { int fd [2]; pipe (fd); /* Create an unamed pipe */ if (fork () != 0) { /* Parent, writer */ close (fd[READ]); /* Close unused end */ dup2 (fd[WRITE], 1); /* Duplicate used end to stdout */ close (fd[WRITE]); /* Close original used end */ execlp (argv[1], argv[1], NULL); /* Execute writer program */ perror ("connect"); /* Should never execute */ } else { /* Child, reader */ close (fd[WRITE]); /* Close unused end */ dup2 (fd[READ], 0); /* Duplicate used end to stdin */ close (fd[READ]); /* Close original used end */ execlp (argv[2], argv[2], NULL); /* Execute reader program */ perror ("connect"); /* Should never execute */ } } fino a che non sono esplicitamente rimossi; usando mknod [-m mode] name p Permette al superuser di creare vari tipi di file speciali. La sintassi specificata, consente ad un utente qualsiasi di creare un file speciale di tipo pipe con nome (opzione p) individuato dal nome name. I diritti di accesso ad un pipe sono assegnati contestualmente alla creazione con l'opzione -m, dove mode specifica i diritti in ottale; successivamente alla creazione con il comando chmod, come per i file regolari. Sono file speciali e possono essere creati: usando 18 Utility mknod I pipe con nome o FIFO, sono più flessibili di quelli anonimi ed offrono i seguenti vantaggi: esistono /* connect cmd1 cmd2 redirige lo stdout di cmd1 sullo stdin di cmd2 */ l'utility UNIX mknod; la system call mknod(). 19 Un comando equivalente è mkfifo [-m mode] name. 20 System call mknod() Named pipe (esempio) Un analogo del comando ls | more può essere realizzato creando una pipe, su cui ls scrive e more legge ... Il comando ls appena lanciato si blocca poiche' apre in scrittura una pipe priva di lettori ... $ mknod -m 0660 prova p $ ls -l prov* prw-rw---1 rossi users 0 May 28 10:35 $ ls -l > prova & [3] 827 $ more < prova total 22 -rwxr-xr-x 1 baldan users 4230 May 22 ... -rwxr-xr-x 1 baldan users 4591 May 29 -rw-r--r-1 baldan users 669 May 29 [1]+ Done ls -l > prova $ int mknod (const char *path, mode_t mode, dev_t dev) prova Permette a un superuser di creare un nodo del file system (file regolare, file speciale di dispositivo, pipe con nome). Restituisce 0 se ha successo; -1 altrimenti. 2000 background 2000 writer 2000 writer.c 22 21 System call mknod() System call mknod() Un utente generico può invocare mknod() solo per creare un pipe con nome. In questo caso pathname I permessi sono modificati da umask nel solito modo: i permessi effettivi del file creato sono mode & ~ umask. è il nome di file che identifica il pipe con nome; mode specifica i diritti di accesso ed il tipo di file da creare, combinati tramite “|”. In pratica S_IFIFO | diritti dove il flag S_IFIFO indica che il nodo da creare è un pipe con nome. Un funzione di libreria che permette di ottenere lo stesso risultato è int mkfifo (const char *path, mode_t mode) dev è un parametro che viene ignorato nel caso dei pipe con nome, e quindi posto a 0. 23 24 Pipe con nome: open() Pipe con nome: open() int open (const char *pathname, int flags) Normalmente, l'apertura di un FIFO in lettura blocca il processo fino a che il FIFO non viene aperto anche in scrittura, e viceversa. Per i FIFO, i parametri tipici di open() sono: il nome del pipe che si vuole aprire (se non esiste, si ottiene l'errore E_NOENT); pathname: flags: Con indicano il tipo di accesso al pipe O_RDONLY: sola lettura; O_WRONLY: sola scrittura; O_NONBLOCK: apertura in modalità non bloccante. Comportamento Bloccante Restituisce un descrittore di file, se ha successo; -1 altrimenti. il flag O_NONBLOCK l'apertura in sola lettura avrà successo anche se il FIFO non è stato ancora aperto in scrittura l'apertura in sola scrittura fallirà (con errore ENXIO) a meno che il lato di lettura non sia già stato aperto. I pipe con nome sono file, quindi, per poterli utilizzare, diversamente dai pipe anonimi, vanno prima aperti. 26 25 Pipe con nome: read() Pipe con nome: write() ssize_t read (int fd, void *buf, size_t count) ssize_t write (int fd, const void *buf, size_t count) La lettura da un pipe con nome avviene come per i file regolari. Normalmente ... è bloccante: il processo è sospeso fino a che divengono disponibili i byte richiesti. read() Se è stato specificato O_NONBLOCK Se La scrittura su un pipe con nome avviene come per i file regolari. La scrittura su di un FIFO il cui lato di lettura è chiuso, fallisce ed il processo riceve un segnale SIGPIPE. non ci sono sufficienti byte da leggere: read() restituisce il numero di caratteri letti segnala l'errore EAGAIN. 27 28 Pipe con nome: esempio Pipe con nome: close() e unlink() int close (int fd) Un volta terminate le operazioni su di un pipe con nome, il suo descrittore fd viene chiuso con close() (come per i file regolari). crea un pipe con nome aPipe e lo apre in lettura; quindi legge e stampa sullo schermo delle linee che terminano con 0 fino a che il pipe è chiuso da tutti i processi scrittori. writer.c apre in scrittura il pipe aPipe, vi scrive tre messaggi; quindi chiude il pipe e termina. Se quando writer.c tenta di aprire aPipe il file non esiste, writer.c ci riprova dopo un secondo fino a che non ha successo. Il lettore leggere fino a che ci sono scrittori, legge quello che di volta in volta viene scritto e capisce che non c'è più niente da leggere quando ottiene (read = 0). A questo punto il lettore chiude il suo descrittore e rimuove il FIFO. reader.c int unlink (const char *filename) L'esempio si compone di un processo lettore e due processi scrittori. Come per i file regolari, unlink() rimuove il link hard da filename al file relativo. Se filename è l'ultimo link al file, anche le risorse del file sono deallocate. 30 29 reader.c #include #include #include #include <stdio.h> <sys/types.h> <sys/stat.h> <fcntl.h> reader.c int main (void) { int fd; char str[100]; unlink("aPipe"); /* Remove named pipe if it already exists */ mknod ("aPipe", S_IFIFO, 0); /* Create named pipe */ chmod ("aPipe", 0660); /* Change its permissions */ fd = open ("aPipe", O_RDONLY); /* Open it for reading */ while (readLine (fd, str)) /* Display received messages */ printf ("%s\n", str); close (fd); /* Close pipe */ unlink("aPipe"); /* Remove used pipe */ } /* For S_IFIFO */ int readLine (int fd, char *str) { /* Read a single '\0'-terminated line into str from fd */ /* Return 0 when the end-of-input is reached and 1 otherwise */ int n; do { /* Read characters until '\0' or end-of-input */ n = read (fd, str, 1); /* Read one character */ } while (n > 0 && *str++ != '\0'); return (n > 0); /* Return false if end-of-input */ } 31 32 Pipe con nome: esempio writer.c #include <stdio.h> #include <fcntl.h> int main (void) { int fd, messageLen, i; char message [100]; /* Prepare message */ sprintf (message, "Hello from PID %d", getpid ()); messageLen = strlen (message) + 1; do { /* Keep trying to open the file until successful */ fd = open ("aPipe", O_WRONLY); /* Open named pipe for writing */ } while (fd == -1); for (i = 1; i <= 3; i++) { /* Send three messages */ write (fd, message, messageLen); /* Write message down pipe */ sleep (3); /* Pause a while */ } close (fd); /* Close pipe descriptor */ return 0; } Esempio di esecuzione ... $ reader & writer & writer & [5] 1268 [6] 1269 [7] 1270 $ Hello from PID 1269 Hello from PID 1270 Hello from PID 1269 Hello from PID 1270 Hello from PID 1269 Hello from PID 1270 <return> [5] Done reader [6]- Done writer [7]+ Done writer $ 34 33 Socket Socket Potente meccanismo di comunicazione tra processi in ambiente Unix. Comunicazione bidirezionale tra processi. I processi comunicanti possono risiedere sullo stesso host su host distinti. Utilizza l'interfaccia di accesso ai file. Es: collegamento remoto di un utente (rlogin); trasferimento di file da una macchina all'altra. 36 Socket Socket Astrattamente, un socket è un endpoint di comunicazione al quale è possibile associare un nome. server create/bind connect create La comunicazione tramite i socket si basa sul modello client-server. Un processo, il server, crea un socket il cui nome è noto anche ai processi client. I client possono comunicare col server tramite una connessione al suo socket. client accept fork/serve communicate Il client crea un socket anonimo e quindi chiede che sia connesso al socket del server. Una connessione che ha successo restituisce un descrittore di file al client ed uno al server, che permettono di leggere e scrivere sul canale. Una volta creata la connessione col client, solitamente il server crea un processo figlio che si occupa della gestione della connessione, mentre il processo originario continua ad accettare altre connessioni da client. 38 37 Creazione di un socket: socket() Socket: Attributi principali int socket (int domain, int type, int protocol) specifica il dominio in cui si vuole utilizzare il socket (protocol family). Useremo solo i valori domain PF_UNIX: PF_INET: PF_UNIX PF_INET type specifica il tipo di comunicazione client e server sono sulla stessa macchina; client e server sono ovunque in Internet. Tipo: tipo di comunicazione tra server e client. I tipi principali sono: affidabile, preserva l'ordine di invio dei dati, basata su stream di byte di lunghezza variabile; Useremo solo il valore SOCK_STREAM (two-way, connectionbased, reliable, variable length) SOCK_STREAM: specifica il protocollo da utilizzare per la trasmissione dei dati. Se vale 0 indica il protocollo di default per la coppia domain/type in questione. protocol Dominio: indica dove risiedono server e client. Alcuni domini sono: Un socket viene creato ed esiste durante una sessione in cui coppie di processi comunicano tra loro. Alla fine della sessione il socket viene chiuso e, di fatto, rimosso dal sistema. inaffidabile, senza una connessione fissa, basata su messaggi di lunghezza variabile. SOCK_DGRAM: Restituisce un descrittore di file associato al socket creato, se ha successo; -1, altrimenti. 39 Il socket viene creato per una singola comunicazione tra processi e non esiste nè prima nè dopo. Una comunicazione ulteriore tra gli stessi processi richiede un nuovo socket. 40 Socket SOCK_STREAM Socket: Attributi principali Protocollo: specifica il protocollo utilizzato per la comunicazione sul socket. crea un socket anonimo con socket() assegna un indirizzo con bind() dichiara il numero di richieste di connessioni che possono essere accodate su quel socket con listen() accetta una connessione con accept() opera sul socket con read() e write() gli Ogni socket può adottare protocolli diversi per la comunicazione protocolli stream, come TCP, che assicurano la ricezione dei dati spediti preservando anche l'ordine di spedizione. protocolli datagram, come UDP, che non assicurano l'affidabilità della comunicazione Tipicamente, le system call che si richiedono un parametro che specifichi il protocollo accettano 0 per indicare “il protocollo più opportuno”. un socket anonimo con socket() collega al socket del server con connect() opera sul socket con read() e write(). lo Noi vedremo solo i socket SOCK_STREAM File di intestazione Sia il client che il server, una volta terminate le operazioni col socket, lo chiudono con close(). 42 Assegnazione di un indirizzo: bind() Un programma che usa i socket deve includere i file header int bind (int fd, const struct sockaddr *addr, int addrlen) associa al socket anonimo riferito dal descrittore fd l'indirizzo (locale o di rete, a seconda del dominio) memorizzato nella struttura puntata da addr. sys/types.h sys/socket.h Il client crea 41 Il server Tipo e valore dell'indirizzo dipendono dal dominio del socket. Possono essere Inoltre, deve includere se usa socket del dominio PF_UNIX netinet/in.h, arpa/inet.h e netdb.h, se usa socket del dominio PF_INET. sys/un.h, nome di un file nel file system locale (per PF_UNIX) indirizzo IP e numero di porta (per PF_INET) è la la lunghezza della struttura dati che memorizza l'indirizzo. addrlen 43 Restituisce 0, se ha successo; -1, altrimenti. 44 Assegnazione di un indirizzo: PF_UNIX Assegnazione di un indirizzo: bind() Il tipo struct sockaddr è Qualsiasi file può essere utilizzato, purché si abbiano i diritti di scrittura sulla directory che lo contiene. Per connettersi ad un socket, è sufficente avere diritti di lettura sul file relativo. struct sockaddr { unsigned short sa_family; /* address family, PF_xxxx */ char sa_data[14]; /* 14 bytes protocol address */ } In PF_UNIX gli indirizzi sono nomi di file. Le diverse strutture dati indirizzo relative ai diversi domini devono essere convertite in (struct sockaddr *) all'invocazione di bind(). La struttura dati che contiene l'indirizzo (definita in sys/un.h) è struct sockaddr_un { unsigned short sun_family; char sun_path[108]; } /* PF_UNIX */ /* pathname */ I campi dovrebbero assumere i seguenti valori: sun_family: 45 Assegnazione di un indirizzo: PF_UNIX prima un unlink() di quel nome oppure un nome di file nuovo (tempnam(), ...) generare PF_UNIX, il pathname completo o relativo del socket. 46 Esempio: Creazione e bind in PF_UNIX Se si tenta un bind() con un nome di file esistente si verifica un errore. Quindi è opportuno: invocare sun_path: La lunghezza dell'indirizzo, ovvero il terzo parametro di bind(), è calcolata con sizeof(). 47 #include <stddef.h> #include <stdio.h> #include <errno.h> #include <stdlib.h> #include <sys/socket.h> #include <sys/un.h> /* For PF_UNIX sockets */ int make_named_socket (const char *filename) { struct sockaddr_un name; int sock; sock = socket (PF_UNIX, SOCK_STREAM, 0); if (sock < 0) { perror ("socket"); exit (EXIT_FAILURE); } name.sun_family = PF_UNIX; strcpy (name.sun_path, filename); if (bind (sock, (struct sockaddr *) &name, sizeof(name)) < 0) { perror ("bind"); exit (EXIT_FAILURE); } return sock; } 48 Connessione ad un indirizzo: connect() Connessione ad un indirizzo: connect() int connect (int fd, const struct sockaddr *addr, int addrlen) tenta di connettere un socket anonimo creato da un client, riferito dal descrittore fd, ad un socket il cui indirizzo è nella struttura puntata da addr. Se connect() ha successo, fd può essere usato per comunicare con il socket del server. La struttura a cui punta addr deve seguire le stesse regole viste nel caso di bind(). connect() restituisce valore 0, se ha successo (la connessione è instaurata); altrimenti (es. il socket del server non esiste, la sua coda è piena, ecc.) Dominio, tipo e protocollo del socket del client devono concordare con quelli del socket del server. -1 addrlen contiene la lunghezza della struttura dati che memorizza l'indirizzo. Quando ha successo, connect() si sincronizza con accept(). 50 49 Numero di connessioni: listen() Accettazione di una connessione: accept() int accept (int fd, struct sockaddr *addr, int *adLen) int listen (int fd, int queuelength) Specifica il numero massimo queuelength di richieste di connessioni pendenti che possono essere accodate sul socket corrispondente al descrittore fd. Se un client tenta una connessione ad un socket la cui coda è piena la connessione gli viene negata (connect() fallisce) viene notificato l'errore ECONNREFUSED. gli 51 Estrae la prima richiesta di connessione dalla coda delle connessioni pendenti del socket riferito da fd. Crea un nuovo socket con gli stessi attributi di quello originario e lo “connette” al socket del client. Restituisce un descrittore di file corrispondente, che permette di comunicare con il client. Quindi il socket originario può essere usato per accettare altre connessioni. 52 Operazioni su socket Accettazione di una connessione: accept() Normalmente accept() blocca l'invocante fino alla connessione. La struttura puntata da addr è riempita con l'indirizzo del client (usata con connessioni Internet). adLen punta ad un intero, che, dopo che la connessione è stabilita, darà la lunghezza effettiva, in byte, della struttura puntata da addr. Una volta stabilita una connessione, il socket del client e quello del server possono essere utilizzati in tutto e per tutto come descrittori di file sui quali si opera mediante read() (bloccante) e write(). Vedi anche send() e recv(). Una volta concluse le operazioni su di un socket, il relativo descrittore può essere chiuso tramite una close(). accept() restituisce un descrittore di file se ha successo; -1 altrimenti. 54 53 Esempio di socket PF_UNIX: chef e cook Socket PF_INET Consiste di due programmi: chef.c, il server, cook.c, il client Il server crea un socket recipe e attende la richiesta di una ricetta da parte di un client Il client si connette al socket recipe e invia la richiesta (nella forma di un numero) Il server fornisce la ricetta richiesta (letta da un file) sul socket recipe Il client legge la ricetta fornita dal server (sequenza di stringhe di lunghezza variabile terminate dal carattere '\0') e man mano che la legge la mostra sullo standard output, quindi termina. 55 La principale differenza tra un socket nei domini PF_UNIX e PF_INET è l'indirizzo. PF_UNIX: pathname valido nel filesystem. PF_INET: specificato da due valori: indirizzo IP, che individua un unico host Internet. numero di porta, che specifica una particolare porta dell'host. 56 Network byte order Assegnazione di un indirizzo: PF_INET La struttura che contiene l'indirizzo (nel file netinet/in.h) struct sockaddr_in { short int sin_family; /* Address family */ struct in_addr sin_addr; /* Internet address */ unsigned short int sin_port; /* Port number */ } I suoi campi assumono i seguenti valori: Formato di rappresentazione dei dati indipendente dalle singole piattaforme. Per creare una connessione ad un socket Internet, bisogna che nella struttura struct sockaddr_in che rappresenta l'indirizzo del socket internet PF_INET, sin_addr: l'indirizzo Internet della macchina host, sin_port: il numero di porta del socket. sin_family: port address in sin_addr number in sin_port siano rappresentati in network byte order Stesso discorso vale per dati di tipo intero trasmessi tramite un socket. 58 57 Indirizzi Internet / Hostname Indirizzi Internet: rappresentaz. interna Ogni computer nella rete Internet ha uno o più indirizzi Internet (IP) come numeri (es. 187.128.20.203) che identificano univocamente il computer in tutta la rete. su ogni macchina ci si trovi, l'indirizzo (di loopback) 127.0.0.1 fa riferimento alla macchina stessa. Gli indirizzi Internet sono rappresentati internamente interi (unsigned long int) “impaccati” in una struttura struct in_addr che include un campo s_addr (unsigned long int). Ogni computer ha anche uno o più host name, stringhe (es. turing.dsi.unive.it) che lo identificano. Definizioni utili costante (unsigned long int), uguale al valore 127.0.0.1 (localhost) dell'indirizzo di loopback, che identifica la macchina stessa.. INADDR_LOOPBACK: un indirizzo in questo formato è più semplice da ricordare. Per aprire una connessione, però, l'indirizzo va convertito nel formato numerico. costante (unsigned long int), utilizzata, in genere, quando si invoca una accept() per indicare “qualsiasi indirizzo richiedente”. INADDR_ANY: 59 60 Hostname Manipolare Indirizzi Internet Alcune funzioni utili per manipolare indirizzi Internet (definite in arpa/inet.h). int inet_aton (const char *name, struct in_addr *addr) converte l'indirizzo Internet name dalla notazione standard con numeri e punti in formato binario e lo memorizza nella struttura puntata da addr. Restituisce valore diverso da 0, se l'indirizzo è valido; 0 altrimenti. unsigned dato un indirizzo Internet addr in network byte order lo converte nella notazione standard con numeri e punti. struct 61 Le funzioni e gli altri simboli per manipolare il database sono definite nel file netdb.h. Una entry nel database ha il seguente tipo: } char *h_addr /* /* /* /* /* host official name */ host alternative names */ PF_INET */ length in byte of address */ host addr array, NULL terminated */ /* synonym for h_addr_list[0] */ 62 Hostname: Altre funzioni utili int gethostname (char *name, size_t length) inserisce il nome dell'host locale (seguito dal carattere NULL) nell'array di caratteri name di lunghezza length. void bzero (void *buffer, size_t length) riempie l'array buffer di lunghezza length con il carattere ASCII NULL. Tale funzione è utilizzata per “ripulire” il contenuto di una struct sockaddr prima di assegnare valori significativi ai suoi campi. hostent *gethostbyname (const char *name) restituisce la entry relativa all'host con host name name. Se il nome name non compare in /etc/hosts, restituisce NULL. struct hostent *gethostbyaddr (const char *addr,int length, int format) Si può pensare che esista un database (distribuito, basato su informazioni locali /etc/hosts e su name server) che mantiene le associazioni tra host name ed indirizzi Internet. struct hostent { char *h_name char **h_aliases int h_addrtype; int h_length char **h_addr_list *inet_ntoa (struct in_addr addr) In particolare, *h_addr fornisce l'indirizzo Internet dell'host a cui una data entry corrisponde. In una struttura del tipo struct hostent gli indirizzi Internet di un host sono sempre in formato network byte order. Alcune utili funzioni di ricerca nel database: long int inet_addr (const char *name) Hostname converte l'indirizzo Internet name dalla notazione standard con numeri e punti in network byte order. Restituisce -1 se l'indirizzo non è valido. char fornisce la entry relativa all'host con indirizzo in addr (length è la lunghezza in byte dell'indirizzo e format è PF_INET). 63 bzero() è disponibile nelle versioni UNIX Berkeley; la sua corrispondente in System V è void memset (void *buffer, int value, size_t length) riempie l'array buffer di lunghezza length con value. 64 Numeri di porta Funzioni su stringhe e affini Utili per riempire i campi relativi all'indirizzo di un socket. char *strcpy (char *dst, const char *src) copia la stringa puntata da src (incluso il terminatore NULL) nell'array puntato da dst. Le due stringhe non si devono sovrapporre e dst dev'essere sufficentemente lunga. void I numeri di porta sono unsigned short int, con valori compresi tra 0 e 65.535. Esistono delle porte “riservate” per usi specifici il file /etc/services contiene una lista di associazioni tra servizi e porte (e protocolli). Esempi: char *strncpy (char *dst, const char *src, size_t n) opera in maniera simile a strcpy() ma copia al più n byte (il risultato potrebbe quindi non essere terminato da '0'). Se src è più corta di n, il resto di dst sarà riempita da caratteri '0'. *memcpy (void *dst, const void *src, size_t n) copia n byte dall'area di memoria src all'area dst (le due aree non si devono sovrapporre) e restituisce un puntatore a dst. ftp 21, ssh 22, telnet 23, www 80 ... I numeri di porta utilizzati dall'utente non devono interferire con quelli in /etc/services per i quali esistono dei “gestori” di sistema che implementaro i relativi servizi. 66 65 Numeri di porta Network byte order per numeri di porta Ci sono due macro intere (definite in netinet/in.h) legate ai numeri di porta: i numeri di porta minori di questo valore sono riservati ai superutenti; in_addr_t i numeri di porta non minori di questo valore sono riservati per un uso esplicito e non sono mai allocati automaticamente. in_addr_t IPPORT_RESERVED: IPPORT_USERRESERVED: Funzioni (netinet/in.h) che permettono di effettuare le conversioni necessarie. Quando si usa un socket senza specificarne l'indirizzo (bind con INADDR_ANY, listen o connect su unbound socket), il sistema genera automaticamente un numero di porta per il socket che è compreso tra IPPORT_RESERVED e IPPORT_USERRESERVED. 67 htonl (in_addr_t hostLong) in_port_t htons (in_port_t hostShort) ntohl (in_addr_t networkLong) in_port_t ntohs (in_port_t networkShort) e htons trasformano un intero, long e short ripettivamente, in uno in formato network byte order. ntohl e ntohs effettuano le conversioni opposte. Le funzioni htonl e ntohl sono usate con valori che rappresentano numeri di host (unsigned long int), mentre htons e ntohs sono usate con valori che rappresentano numeri di porta. htonl 68 Esempio: Creazione e bind in PF_INET Assegnazione di un indirizzo: PF_INET #include <stdio.h> #include <errno.h> #include <stdlib.h> #include <sys/socket.h> #include <netinet/in.h> /* For PF_INET sockets */ int make_socket (unsigned short int port) { struct sockaddr_in name; int sock; sock = socket (PF_INET, SOCK_STREAM, 0); if (sock < 0) { perror (“socket”); exit (EXIT_FAILURE); } name.sin_family = PF_INET; name.sin_port = htons (port); name.sin_addr.s_addr = htonl (INADDR_LOOPBACK); if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0) { perror (“bind”); exit (EXIT_FAILURE); } return sock; } #include #include #include #include #include <stdio.h> <stdlib.h> <sys/socket.h> <netinet/in.h> <netdb.h> /* For PF_INET sockets */ void init_sockaddr (struct sockaddr_in *name, const char *hostname, unsigned short int port) { struct hostent *hostinfo; name->sin_family = PF_INET; name->sin_port = htons (port); hostinfo = gethostbyname (hostname); 69 } if (hostinfo == NULL) { fprintf (stderr, ``Unknown host %s\n'', hostname); exit (EXIT_FAILURE); } name->sin_addr = *(struct in_addr *) hostinfo->h_addr; 70