Programmazione di Sistema 5

Documenti analoghi
Programmazione di Sistema 5. Pipe e Fifo. IPC: Inter Process Communication. Pipe anonimi e con nome (FIFO) Paolo Baldan

Program m azione di Sistem a 5

Program m azione di Sistem a 6

Program m azione di Sistem a 6a

Programmazione di Rete

I Socket. Laboratorio Software M. Grotto R. Farina

Reti (già Reti di Calcolatori )

Scrittura dei programmi applicativi di rete

LABORATORIO di Reti di Calcolatori

RETI DI CALCOLATORI. Prof. PIER LUCA MONTESSORO Ing. DAVIDE PIERATTONI. Facoltà di Ingegneria Università degli Studi di Udine

Programmazione socket. Queste slide sono distribuite con licenza Creative Commons Attribuzione-Non commerciale-condividi allo stesso modo 2.

Esercitazione [08] Server multi-process/multi-thread

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

Socket TCP. seconda parte

Scrittura dei programmi applicativi di rete

Comunicazione tra processi: pipe Le pipe sono un meccanismo UNIX di Inter Process Communication (IPC)

Il sistema operativo LINUX Inter Process Communication. Sommario. popen ( ) PIPE. pipe ( ) popen ( ) Sistemi operativi Modulo II

Introduzione ai socket

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

Esercitazione [7] Server multi-process/multi-thread

Esercitazione 4. Gestione dei file in Unix

Laboratorio di Sistemi Operativi Cognome Nome Mat.

Igino Corona

L interfaccia socket

Socket Programming. Socket Programming. Università di Palermo

Esercitazione [7] Client/Server con Socket

Sistemi Operativi Teledidattico

Laboratorio reti AA 2008/2009. Dott. Matteo Roffilli Ricevimento in ufficio dopo la lezione

Interazione (TCP) Client-Server con le socket

SC per Inter Process Comminication. Pipe senza nome e con nome (FIFO)

Socket TCP. prima parte

Interazione (TCP) Client-Server con le socket

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

Laboratorio reti AA 2007/2008. Dott. Matteo Roffilli Ricevimento in ufficio dopo la lezione

Sistemi Operativi (M. Cesati)

LABORATORIO di Reti di Calcolatori

Internetworking with TCP/IP (Douglas E. Comer) Vol. I and Vol III.

Inter-process communication

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

Corso di Sistemi Operativi Esercitazioni

L uso di Socket UDP. TCP vs. UDP UDP

Sistemi Operativi (M. Cesati)

SC per Inter Process Communication. Pipe senza nome e con nome (FIFO)

La Comunicazione tra Processi in Unix

Esempio 1: stampa locale di file remoto

Laboratorio reti AA 2008/2009. Dott. Matteo Roffilli Ricevimento in ufficio dopo la lezione

IPC Inter Process Communication

Domain Name Service. Mapping nomi/indirizzi con Socket API in C

Università degli Studi di Cagliari Corso di Laurea Specialistica in Ingegneria Elettronica. SISTEMI OPERATIVI A.A. 2004/2005 Docente: Giorgio Giacinto

Capitolo 3 -- Stevens

File binari e file di testo

Laboratorio di Sistemi Operativi

Sistemi Operativi (M. Cesati)

Sistemi Operativi (M. Cesati)

LABORATORIO di Reti di Calcolatori

Esercitazione Laboratorio di Sistemi Operativi Cognome Nome Mat.

I.I.S. G.B. PENTASUGLIA MATERA ISTITUTO TECNICO SETTORE TECNOLOGICO LICEO SCIENTIFICO SCIENZE APPLICATE. Classe: 5Ci

COMUNICAZIONE TRA PROCESSI REMOTI IN UNIX

Processi. Comunicazione tra processi Stefano Quer Dipartimento di Automatica e Informatica Politecnico di Torino

Laboratorio di Sistemi Operativi

TECN.PROG.SIST.INF. UDP socket in Windows. Roberta Gerboni

Laboratorio reti AA 2008/2009. Dott. Matteo Roffilli Ricevimento in ufficio dopo la lezione

System call per l accesso a file

Sistemi Operativi (M. Cesati)

TECN.PROG.SIST.INF. I Socket Roberta Gerboni

Simulazione esame Laboratorio di Sistemi Operativi Cognome Nome Mat.

Una semplice applicazione client/server 1

La programmazione di rete

I/O su Socket TCP: read()

Sistemi di Elaborazione. Introduzione alla Programmazione distribuita

La gestione dell'errore

API Socket di Berkeley

Capitolo 5 -- Stevens

Laboratorio di. Reti Informatiche. Corso di Laurea Triennale in Ingegneria Informatica A.A. 2016/2017. Ing. Niccolò Iardella

Un server di posta (che usa il protocollo SMTP) è identificato dal numero di porta 25.

INTERNET DOMAIN SOCKETS (Cap.59)

Laboratorio reti AA 2007/2008. Dott. Matteo Roffilli Ricevimento in ufficio dopo la lezione

GESTIONE DEI FILE IN C. Docente: Giorgio Giacinto AA 2008/2009

Il linguaggio C. Breve panoramica su stdio.h

Comunicazioni fra processi remoti: i socket nello UNIX di Berkeley

Cenni di programmazione distribuita in C++ Mauro Piccolo

Sistemi Operativi (M. Cesati)

unsigned long inet_addr(cp) char *cp;

Lo strato di applicazione in Internet

Sistemi Operativi (M. Cesati)

Esempio 1: stampa locale di file remoto

IPC: InterProcess Communication

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

Le strutture. Una struttura C è una collezione di variabili di uno o più tipi, raggruppate sotto un nome comune.

rsystem Maximiliano Marchesi

GESTIONE DELLA COMUNICAZIONE LOCALE TRA PROCESSI IN UNIX:

Transcript:

Programmazione di Sistema 5 Paolo Baldan Università Ca Foscari Venezia Corso di Laurea in Informatica 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.

IPC: Inter Process Communication 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) 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). 2

Pipe e Fifo

Pipe anonimi e con nome (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 con nome e anonimi (FIFO) Pipe Anonimi Presenti in tutte le versioni di UNIX utilizzati, ad es., dalle shell per le pipeline. Pipe con nome o FIFO Presenti in UNIX System V (anche in BSD per compatibilità ). 5

Pipe anonimi Un pipe anonimo è un canale di comunicazione che unisce due processi (creato con pipe()) unidirezionale permette la comunicazione solo tra processi con un antenato comune Un pipe presenta due lati di accesso (in/out), ciascuno associato ad un descrittore di file. Il lato di scrittura è acceduto invocando write(). Il lato di lettura è acceduto invocando read(). 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(). 6

Pipe anonimi: pipe() int pipe (int fd[2]) crea un pipe anonimo e restituisce due descrittori di file lato di lettura fd[0] (aperto in lettura) lato di scrittura fd[1] (aperto in scrittura) fd[0] fd[1] Processo pipe Kernel Fallisce restituendo -1 se il kernel non ha più spazio per un nuovo pipe; altrimenti restituisce 0. 7

Pipe anonimi: lettura Se un processo legge da un pipe: se il lato scrittura è stato chiuso, read() restituisce 0 che indica la fine dell'input; se il pipe è vuoto e il lato di scrittura è ancora aperto, si sospende fino a che diventa disponibile qualche input; se 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. 8

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. 9

Pipe anonimi: pipe() La tipica sequenza di eventi è 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

Esempio: scambio di un messaggio: talk.c Il programma talk.c usa un pipe per permettere al padre di leggere un messaggio inviato dal figlio. 11

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"; 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 */ } 12

Esempio: talk.c (scambio di un messaggio) Esempio di esecuzione... $./talk Read 24 bytes: A message for your pipe $ 13

Comunicazione tramite pipe (e non solo) 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

Protocol.c (Semplice protocollo) /* 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 /* The index of the read end of the pipe */ #define WRITE 1 /* 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 */ } 15

Protocol.c (cont.) 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 */ } exit(exit_success); 16

Esempio: ridirezione con pipe (connect.c) 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

Esempio: ridirezione con pipe (connect.c) #include <stdio.h> #define READ 0 #define WRITE 1 /* connect cmd1 cmd2 redirige lo stdout di cmd1 sullo stdin di cmd2 */ 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 */ } } 18

Pipe con nome (FIFO) I pipe con nome o FIFO, sono più flessibili di quelli anonimi ed offrono i seguenti vantaggi: esistono 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 fino a che non sono esplicitamente rimossi; Sono file speciali e possono essere creati: usando l'utility UNIX mknod; usando la system call mknod(). 19

Utility mknod 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. Un comando equivalente è mkfifo [-m mode] name. 20

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 prova $ ls -l > prova & [3] 827 $ more < prova total 22 -rwxr-xr-x 1 baldan users 4230 May 22 2000 background... -rwxr-xr-x 1 baldan users 4591 May 29 2000 writer -rw-r--r-- 1 baldan users 669 May 29 2000 writer.c [1]+ Done ls -l > prova $ 21

System call mknod() int mknod (const char *path, mode_t mode, dev_t dev) 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. 22

System call mknod() Un utente generico può invocare mknod() solo per creare un pipe con nome. In questo caso pathname è 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. dev è un parametro che viene ignorato nel caso dei pipe con nome, e quindi posto a 0. 23

System call mknod() I permessi sono modificati da umask nel solito modo: i permessi effettivi del file creato sono mode & ~ umask. Un funzione di libreria che permette di ottenere lo stesso risultato è int mkfifo (const char *path, mode_t mode) 24

Pipe con nome: open() int open (const char *pathname, int flags) Per i FIFO, i parametri tipici di open() sono: pathname: il nome del pipe che si vuole aprire (se non esiste, si ottiene l'errore E_NOENT); flags: indicano il tipo di accesso al pipe O_RDONLY: sola lettura; O_WRONLY: sola scrittura; O_NONBLOCK: apertura in modalità non bloccante. Restituisce un descrittore di file, se ha successo; -1 altrimenti. 25

Pipe con nome: open() Comportamento Bloccante Normalmente, l'apertura di un FIFO in lettura blocca il processo fino a che il FIFO non viene aperto anche in scrittura, e viceversa. Con 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

Pipe con nome: read() ssize_t read (int fd, void *buf, size_t count) La lettura da un pipe con nome avviene come per i file regolari. Normalmente... read() è bloccante: il processo è sospeso fino a che divengono disponibili i byte richiesti. Se è stato specificato O_NONBLOCK Se non ci sono sufficienti byte da leggere: read() restituisce il numero di caratteri letti segnala l'errore EAGAIN. 27

Pipe con nome: write() ssize_t write (int fd, const void *buf, size_t count) 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. 28

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). int unlink (const char *filename) 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. 29

Pipe con nome: esempio L'esempio si compone di un processo lettore e due processi scrittori. reader.c 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. 30

reader.c #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> /* For S_IFIFO */ #include <fcntl.h> 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

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 */ } 32

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; } 33

Pipe con nome: esempio 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

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 Astrattamente, un socket è un endpoint di comunicazione al quale è possibile associare un nome. server create/bind connect create client accept fork/serve communicate 37

Socket 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. 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

Creazione di un socket: socket() int socket (int domain, int type, int protocol) domain specifica il dominio in cui si vuole utilizzare il socket (protocol family). Useremo solo i valori PF_UNIX PF_INET type specifica il tipo di comunicazione Useremo solo il valore SOCK_STREAM (two-way, connectionbased, reliable, variable length) protocol 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. Restituisce un descrittore di file associato al socket creato, se ha successo; -1, altrimenti. 39

Socket: Attributi principali Dominio: indica dove risiedono server e client. Alcuni domini sono: PF_UNIX: client e server sono sulla stessa macchina; PF_INET: client e server sono ovunque in Internet. Tipo: tipo di comunicazione tra server e client. I tipi principali sono: SOCK_STREAM: affidabile, preserva l'ordine di invio dei dati, basata su stream di byte di lunghezza variabile; 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. SOCK_DGRAM: inaffidabile, senza una connessione fissa, basata su messaggi di lunghezza variabile. 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: Attributi principali Protocollo: specifica il protocollo utilizzato per la comunicazione sul socket. 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. Noi vedremo solo i socket SOCK_STREAM 41

Socket SOCK_STREAM Il server crea un socket anonimo con socket() gli 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() Il client crea un socket anonimo con socket() lo collega al socket del server con connect() opera sul socket con read() e write(). Sia il client che il server, una volta terminate le operazioni col socket, lo chiudono con close(). 42

File di intestazione Un programma che usa i socket deve includere i file header sys/types.h sys/socket.h Inoltre, deve includere sys/un.h, se usa socket del dominio PF_UNIX netinet/in.h, arpa/inet.h e netdb.h, se usa socket del dominio PF_INET. 43

Assegnazione di un indirizzo: bind() 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. Tipo e valore dell'indirizzo dipendono dal dominio del socket. Possono essere nome di un file nel file system locale (per PF_UNIX) indirizzo IP e numero di porta (per PF_INET) addrlen è la la lunghezza della struttura dati che memorizza l'indirizzo. Restituisce 0, se ha successo; -1, altrimenti. 44

Assegnazione di un indirizzo: bind() Il tipo struct sockaddr è struct sockaddr { unsigned short sa_family; /* address family, PF_xxxx */ char sa_data[14]; /* 14 bytes protocol address */ } Le diverse strutture dati indirizzo relative ai diversi domini devono essere convertite in (struct sockaddr *) all'invocazione di bind(). 45

Assegnazione di un indirizzo: PF_UNIX In PF_UNIX gli indirizzi sono nomi di file. 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. La struttura dati che contiene l'indirizzo (definita in sys/un.h) è struct sockaddr_un { unsigned short sun_family; /* PF_UNIX */ char sun_path[108]; /* pathname */ } I campi dovrebbero assumere i seguenti valori: sun_family: PF_UNIX, sun_path: il pathname completo o relativo del socket. 46

Assegnazione di un indirizzo: PF_UNIX Se si tenta un bind() con un nome di file esistente si verifica un errore. Quindi è opportuno: invocare prima un unlink() di quel nome oppure generare un nome di file nuovo (tempnam(),...) La lunghezza dell'indirizzo, ovvero il terzo parametro di bind(), è calcolata con sizeof(). 47

Esempio: Creazione e bind in PF_UNIX #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() 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. Dominio, tipo e protocollo del socket del client devono concordare con quelli del socket del server. addrlen contiene la lunghezza della struttura dati che memorizza l'indirizzo. 49

Connessione ad un indirizzo: connect() 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); -1 altrimenti (es. il socket del server non esiste, la sua coda è piena, ecc.) Quando ha successo, connect() si sincronizza con accept(). 50

Numero di connessioni: listen() 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) gli viene notificato l'errore ECONNREFUSED. 51

Accettazione di una connessione: accept() int accept (int fd, struct sockaddr *addr, int *adlen) 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

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. accept() restituisce un descrittore di file se ha successo; -1 altrimenti. 53

Operazioni su socket 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(). 54

Esempio di socket PF_UNIX: chef e cook 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

Socket PF_INET 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

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: sin_family: PF_INET, sin_addr: l'indirizzo Internet della macchina host, sin_port: il numero di porta del socket. 57

Network byte order 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 address in sin_addr port number in sin_port siano rappresentati in network byte order Stesso discorso vale per dati di tipo intero trasmessi tramite un socket. 58

Indirizzi Internet / Hostname Ogni computer nella rete Internet ha uno o più indirizzi Internet (IP) 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. Ogni computer ha anche uno o più host name, stringhe (es. turing.dsi.unive.it) che lo identificano. un indirizzo in questo formato è più semplice da ricordare. Per aprire una connessione, però, l'indirizzo va convertito nel formato numerico. 59

Indirizzi Internet: rappresentaz. interna Gli indirizzi Internet sono rappresentati internamente come interi (unsigned long int) impaccati in una struttura struct in_addr che include un campo s_addr (unsigned long int). Definizioni utili INADDR_LOOPBACK: costante (unsigned long int), uguale al valore 127.0.0.1 (localhost) dell'indirizzo di loopback, che identifica la macchina stessa.. INADDR_ANY: costante (unsigned long int), utilizzata, in genere, quando si invoca una accept() per indicare qualsiasi indirizzo richiedente. 60

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 long int inet_addr (const char *name) converte l'indirizzo Internet name dalla notazione standard con numeri e punti in network byte order. Restituisce -1 se l'indirizzo non è valido. char *inet_ntoa (struct in_addr addr) dato un indirizzo Internet addr in network byte order lo converte nella notazione standard con numeri e punti. 61

Hostname 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. Le funzioni e gli altri simboli per manipolare il database sono definite nel file netdb.h. Una entry nel database ha il seguente tipo: struct hostent { char *h_name /* host official name */ char **h_aliases /* host alternative names */ int h_addrtype; /* PF_INET */ int h_length /* length in byte of address */ char **h_addr_list /* host addr array, NULL terminated */ char *h_addr /* synonym for h_addr_list[0] */ } 62

Hostname 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: struct 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) fornisce la entry relativa all'host con indirizzo in addr (length è la lunghezza in byte dell'indirizzo e format è PF_INET). 63

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. 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

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. 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'. void *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. 65

Numeri di porta 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: 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

Numeri di porta Ci sono due macro intere (definite in netinet/in.h) legate ai numeri di porta: IPPORT_RESERVED: i numeri di porta minori di questo valore sono riservati ai superutenti; IPPORT_USERRESERVED: i numeri di porta non minori di questo valore sono riservati per un uso esplicito e non sono mai allocati automaticamente. 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

Network byte order per numeri di porta Funzioni (netinet/in.h) che permettono di effettuare le conversioni necessarie. in_addr_t htonl (in_addr_t hostlong) in_port_t htons (in_port_t hostshort) in_addr_t ntohl (in_addr_t networklong) in_port_t ntohs (in_port_t networkshort) htonl 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. 68

Esempio: Creazione e bind in 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; } 69

Assegnazione di un indirizzo: PF_INET #include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <netinet/in.h> /* For PF_INET sockets */ #include <netdb.h> 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); } if (hostinfo == NULL) { fprintf (stderr, ``Unknown host %s\n'', hostname); exit (EXIT_FAILURE); } name->sin_addr = *(struct in_addr *) hostinfo->h_addr; 70