A cura del prof. Gino Tombolini 1 COMUNICAZIONE TRA PROCESSI REMOTI IN UNIX Il sistema UNIX TCP/IP fornisce un meccanismo di comunicazione tra processi residenti su nodi distinti di una rete, compatibili con i meccanismi da usarsi nell'ipc locale. Unix fornisce una interfaccia indipendente dall'architettura di rete utilizzata. Incorpora nel sistema un meccanismo adatto a fornire il supporto alla condivisione di risorse in un ambiente distribuito senza dover realizzare un nuovo sistema operativo distribuito. Fornisce un meccanismo di comunicazione tra processi sia locali che remoti che rappresenta una generalizzazione del meccanismo delle pipe insito in Unix. La generalizzazione dipende dal fatto che il sistema deve poter utilizzare diverse architetture di rete, con diversi insiemi di protocolli, diversi hardware, diverse convenzioni relative alla specifica dei nomi, da assegnare a ciascun elemento che può scambiare dati. Occorre definire il dominio di comunicazione che rappresenta la tipologia della comunicazione (locale o remota) e la tecnica di denominazione dei nodi comunicanti. Il dominio di comunicazione deve ovviamente essere parametrico rispetto alla particolare architettura di rete in cui operiamo. Il protocollo deve fornire una nuova astrazione per denotare i punti terminali di un canale di comunicazione bidirezionale. Questa nuova astrazione è data dai socket (Unix BSD). Il socket è il terminale virtuale di un canale di comunicazione bidirezionale, qualcosa di simile alla presa telefonica senza la quale non è possibile alcuna comunicazione. Creazione di un socket. Un socket può essere creato con una apposita primitiva in cui occorre specificare per quale dominio di comunicazione (architettura di rete) è valido il socket (ad esempio se lo creiamo per l'architettura Internet avrà una certa struttura, diversa per architetture diverse). Il controllo della semantica (cioè del tipo di comunicazione da instaurare) è reso disponibile al programma in maniera consistente a tutti i domini di comunicazione. Esempio se siamo in ambiente Internet potremmo voler creare una comunicazione di tipo TCP o UDP. Occorre definire anche il tipo di socket che denota lo stile della comunicazione (es. connection oriented o datagram connection-less). La primitiva per creare un socket : sd = socket(dominio, tipo, protocollo); int sd, dominio, tipo, protocollo; sd = descrittore di socket dominio = denota il particolare dominio di comunicazione tipo = denota il tipo di comunicazione (tipo circuito virtuale o datagram) protocollo = uno dei protocolli supportati dal dominio (0=default), normalmente ce n'è uno solo I domini di comunicazione Unix sono : AF_UNIX = identifica una comunicazione locale AF_INET = identifica una comunicazione remota usando i protocolli TCP/IP I tipi di socket (ad esempio per AF_INET) sono : SOCK_STREAM = trasferimento affidabile di sequenze di byte SOCK_SEQPACKET =trasferimento affidabile di pacchetti SOCK_DGRAM = trasferimento non affidabile di tipo datagram UDP SOCK_RAW = accesso diretto ai protocolli di basso livello
A cura del prof. Gino Tombolini 2 Un esempio di dominio AF_INET OSI UNIIX BSD ESEMPIO LIV. 7 LIV. 6 PROGRAMMA TELNET LIV. 5 SOCKET SOCK_STREAM LIV. 4 PROTOCOLLI TCP LIV. 3 IP LIV. 2 DRIVER DI RETE DRIVER DI RETE LIV. 1 HARDWARE ETHERNET (FTP) MAIL Associazione socket - indirizzo Una volta che il socket è stato creato occorre legare il socket ad un ben preciso indirizzo. Questo si fa con la primitiva bind. Se il socket è simile alla presa telefonica, la bind è simile al numero telefonico con cui individuare le utenze. error=bind(sd, ind, lun); int error, sd, lun; struct sockaddr *ind; sd= descrittore di socket, ind=indirizzo ad esempio Internet attraverso il quale il socket sarà conosciuto lun=lunghezza dell'indirizzo (in tal modo si ha la dovuta generalità per le diverse architetture) l'indirizzo ha poi una struttura che dipende dall'architettura usata (es. in Internet è dato dall'indirizzo porta e indirizzo host, mentre in Unix è un pathname). Il formato degli indirizzi è il seguente : sa_family sa_data 2byte variabile Per AF_UNIX il formato è quello del nome di un file (pathname) e la dimensione max è di 128 byte Per AF_INET il formato è quello Internet (32bit x host + 16bit x porta) struttura sock_addr_in sa_family sin_port sin_addr sin_zero (per compatib.) AF_INET 2 byte 2byte 4 byte 8 byte short u_short struct in_addr char[8] Un tipico esempio tra processi remoti è la comunicazione client-server : CLIENT SERVER send(richesta) receive(risultati) receive(richiesta) elabora send(risultati)
A cura del prof. Gino Tombolini 3 Comunicazione connection oriented Per instaurare una comunicazione di tipo connection oriented tra due processi (client e server) occorre effettuare le seguenti operazioni : 1. Client e server creano ognuno il proprio socket e definiscono l indirizzo (socket e bind); il server deve definire una coda di richieste di connessione e attendere l arrivo delle richieste stesse; 2. deve essere creata una connessione (circuito virtuale) tra i due socke, cioè il client richiede la connessione al server; 3. i processi possono ora comunicare; quando la richiesta viene accettata dal server, la connessione è in atto. Server Client bind() bind() (qui la bind non è indispensabile) listen() connect() accept()... attesa... read() write()... write() read() La primitiva listen() permette di definire una coda con ben precise dimensioni nella quale vengono accodate richieste di connessione provenienti dal client. La primitiva accept() accetta richieste dai client, il server va in attesa fino all arrivo delle richieste (stato di blocco del server); quando arrivano richieste il server legge (primitiva read) le richieste, le elabora ed invia i risultati ai client (primitiva write). error = listen(sd, dim); int error, sd, dim; crea una coda in cui inserisce le richieste di connessione dai client; la coda può essere lunga al massimo dim richieste. nuovo_sd = accept(sd, ind, lun); int nuovo_sd, sd, lun; struct sockaddr *ind; ind può restituire l indirizzo del socket del processo mittente e lun la sua lunghezza; nuovo_sd rappresenta un secondo descrittore del circuito virtuale che si è instaurato quando arriva la richiesta; in pratica viene creato un nuovo socket di lavoro che può servire al processo figlio attivato per la gestione della connessione attuale, mentre il processo padre torna ad attendere nuove richieste di connessione in parallelo al processo figlio. error = connect(sd, ind, lun); int error, sd, lun;
A cura del prof. Gino Tombolini 4 struct sockaddr *ind; sd è il socket locale; ind è l indirizzo del socket del server remoto; lun è la lunghezza di tale indirizzo. Una volta completata la connessione, si possono usare le normali procedure read() e write(). cont=read(sd, buf, lun); oppure cont=write(sd, buf, lun); Per eliminare la trasmissione o la ricezione ad un socket si può utilizzare la primitiva shutdown() (sconnessione senza distruzione del socket. Per eliminare il socket può essere utilizzata la primitiva close(). Esempio SERVER (non crea nessun processo figlio) main() {... s=socket(af_inet, SOCK_STREAM,0); <preparazione nella struttura nome dell indirizzo del server> bind(s, &nome,sizeof(nome)); listen(s, 10); for(;;) { d=accept(s,0,0); m=read(d, buf, sizeof(buf)); close(d); Esempio CLIENT main() {... s=socket(af_inet, SOCK_STREAM,0); <preparazione nella struttura nome dell indirizzo del client> bind(s, &nome,sizeof(nome)); <preparazione nella struttura nomes dell indirizzo del server> connect(s, &nomes, sizeof(nomes));. write(s, buf, sizeof(buf));. read(s, buf, sizeof(buf)); close(s); Comunicazione Datagram.
A cura del prof. Gino Tombolini 5 In questo caso non viene creata una connessione tra client e server. le fasi sono quindi : 1. client e server devono creare il proprio socket e definirne l indirizzo (socket e bind); 2. i processi possono comunicare specificando per ogni messaggio il socket destinatario. Si hanno a disposizione due primitive recvfrom() e sendto() con funzionamento analogo alle read e write, in cui occorre però anche specificare dei parametri aggiuntivi che denotano indirizzo e lunghezza della struttura socket ricevente (sendto) o trasmittente (recvfrom). Server Client bind() bind() recvfrom() sendto().... ricezione ed elab.... sendto() recvfrom()... /* CLIENT.C */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <stdio.h> #include <netdb.h> #define PORTNUM 601 main(argc, argv) int argc; char *argv[]; { int s; struct sockaddr_in sin; struct hostent *hp; char buf[80]; if(argc!=2) { printf("numero di argomenti errato"); if((hp=gethostbyname(argv[1]))==0) { perror("errore: gethostbyname"); bzero(&sin,sizeof(sin)); bcopy(hp->h_addr,&sin.sin_addr,hp->h_length); sin.sin_family=hp->h_addrtype; sin.sin_port=portnum; if((s=socket(af_inet, SOCK_STREAM,0))==-1) { perror("errore: socket"); if (connect(s,&sin,sizeof(sin),0)==-1) { perror("errore: connect"); /*sprintf(buf,"hello world");*/ gets(buf); if(write(s,buf,sizeof(buf))==-1) { perror("errore: write"); close(s); /* SERVER */
A cura del prof. Gino Tombolini 6 #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <stdio.h> #include <netdb.h> #define PORTNUM 601 main(argc, argv) int argc; char *argv[]; { int s, ns, pid; struct sockaddr_in sin; char buf[80]; if((s=socket(af_inet, SOCK_STREAM,0))==-1) { perror("errore: socket"); bzero(&sin,sizeof(sin)); sin.sin_port=portnum; if (bind(s,&sin,sizeof(sin))==-1) { perror("errore: bind"); if (listen(s,5)==-1) { perror("errore: listen"); while(1) { if((ns=accept(s,0,0))==-1) { perror("errore: accept"); if((pid=fork())==-1){ perror("errore: fork"); if(pid==0) { while(1) { if((pid=read(ns,buf,sizeof(buf)))==-1){ perror("errore: read"); if(pid<sizeof(buf)) break; printf("dati dal client : %s",buf); printf("\n Processo figlio terminato \n"); close(ns); exit(0);