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