Corso di Reti di Calcolatori Interfaccia socket Introduzione • La programmazione di rete è ciò che permette a due o più computer collegati da una rete fisica di comunicare tra loro. • Per poter comunicare con un computer ho bisogno di: 1. Un indirizzo a cui connettermi: ogni computer connesso in una rete dispone di un indirizzo univoco, che consente di localizzarlo a livello globale 2. Una porta aperta: il concetto di porta permette ad un computer di ricevere diverse connessioni, infatti tramite diverse porte è possibile dallo stesso computer fornire diversi servizi (posta, web, telnet, ecc.) 3. Un protocollo: che permetta di far capire al programma in ascolto le mie richieste [2] TCP/IP • TCP/IP non include una definizione di API. • C’è in giro una quantità di API da usare col TCP/IP: – – – – Sockets TLI, XTI Winsock MacTCP [3] Funzioni necessarie: • Specificare gli endpoints, locale e remoto, della comunicazione • Initiare la connessione • Attendere per le connessioni in arrivo • Send e receive dei dati • Terminare la connessione ‘gracefully’ • Gestione degli Errori [4] Comunicazione • La comunicazione tra due pc funziona in questo modo: – un CLIENT si collega ad un SERVER chiedendo un servizio – il SERVER, se dispone del servizio, assolverà la richiesta del CLIENT, inviandogli le risposte. • Questa comunicazione avviene tramite un protocollo di comunicazione (insieme di regole da rispettare). • Al di sopra di questi protocolli esiste un livello di astrazione, chiamato socket, che permette di trasmettere e ricevere informazioni senza doversi preoccupare della correttezza delle regole definite dal protocollo utilizzato. [5] Indirizzamento • Ogni computer della rete è identificato da un hostname e da un indirizzo IP unico a 32 bit es: “www.google.com” 209.85.135.103 • L’indirizzo IP contiene sia l’identificativo della rete che quello della macchina • Il comando nslookup offre la lista di tutti i server alias per un indirizzo. – Esempio >>> nslookup www.google.com Risposta da un server non di fiducia: Nome: www.l.google.com Addresses: 209.85.135.99, 209.85.135.103, 209.85.135.147, 209.85.135.104 Aliases: www.google.com [6] Come identificare i processi ? • Si assegna ad ogni processo un numero di porta • I numeri di porta sono assegnati a servizi – di sistema (porte da 0-1023) – dinamici o privati (1024 - 65535) • I server (daemon) usano di solito porte ben note – es HTTP = 80 , Telnet = 25, FTP = 21 • I client di solito usano porte dinamiche • Questa associazione viene usata dai protocolli di livello transport come TCP o UDP [7] TCP/IP: interfaccia di programmazione [8] Socket • Un socket è un punto finale di un collegamento bidirezionale tra due programmi in esecuzione sulla rete. • Un socket è legato a un numero di porta così che il livello TCP può identificare l’applicazione a cui i dati devono essere inviati. – Usando i socket la comunicazione in rete è molto simile all’ I/O su file. La gestione dei socket viene trattata come la gestione di file. I comandi di lettura e scrittura sui flussi usati per l’I/O su file sono applicabili anche all’I/O su socket. – La comunicazione basata su socket è indipendente dal linguaggio di programmazione. – Un programma socket scritto nel linguaggio Java può comunicare via rete con un programma non Java purché questo usi i socket. [9] Concetto di Socket • Per attivare una connessione fra due processi si utilizza dunque lo schema Host-Port – il server è eseguito su un host e aspetta richieste su una determinata porta – il client viene eseguito su un host e richiede l’uso di una porta per effettuare richieste ad un server • Specificando ora anche il protocollo per la comunicazione riusciamo a descrivere univocamente la connessione con una quintupla detta Association: (proto, ind_locale, proc_locale, ind_remoto, proc_remoto) es: (TCP, 123.23.4.221, 1500, 234.151.124.2, 4000) [10] Concetto di Socket • La quintupla viene in realtà costituita a partire da due elementi simmetrici, un’associazione locale ed una remota: – Protocollo, Ind_locale, proc_locale (TCP, 123.23.4.221,1500) – Protocollo, Ind_remoto, proc_ remoto (TCP ,234.151.124.2,4000) • Ciascuna di queste parti è detta socket – Nota: 1040 per TCP 1040 UDP [11] Concetto di Socket Quindi un socket è 1. un descrittore di risorsa che consente ad una applicazione di effettuare operazioni di lettura e scrittura verso un particolare dispositivo di I/O. 2. Astrazione di un canale di comunicazione tra processi distribuiti in rete 3. Interfaccia per la suite di protocolli TCP/IP 4. Punto di arrivo nella comunicazione in rete [12] Il protocollo TCP e l’interfaccia socket • Nel modello TCP/IP l’interfaccia socket svolge tutte le operazioni necessarie per stabilire una connessione, ovvero: – si occupa di creare i pacchetti TCP – stabilisce una connessione con l’algoritmo del “three way handshake” – gestisce tutte le problematiche della trasmissione dei pacchetti [13] Creazione di un socket • Lato server – Creazione del socket – Bind ad una porta – Listen, predisposizione a ricevere sulla porta – Accept, blocca il server in attesa di una connesione – Lettura - scrittura dei dati – Chiusura • Lato client – Creazione del socket – Richiesta di connessione – Lettura - scrittura dei dati – Chiusura [14] Comunicazione socket (1) • Un server (programma) gira su un computer specifico e ha un socket legato ad una porta specifica. • Un server aspetta e ascolta sul socket finché un client non esegue una richiesta di connessione. [15] Comunicazione socket (2) • Se tutto funziona, il server accetta la connessione e ottiene dal sistema operativo un nuovo socket legato a una porta diversa. – In questo modo un server può continuare ad ascoltare sul socket originale per eventuali altre richieste di connessione, mentre comunica con il client già connesso. [16] Indirizzi, porte e socket • La metafora della posta – L’utente è l’applicazione. – Il suo indirizzo di casa è l’indirizzo IP. – La casella postale è la porta. – L’ufficio postale è la rete. – Il socket è la chiave che dà all’utente l’accesso alla casella postale giusta. • Nota: si presuppone che anche la posta in uscita venga depositata dall’utente nella sua casella postale invece di imbucarla in una buca delle lettere. [17] L'interfaccia dei socket • La socket API (Application Program Interface) è l'interfaccia standard tra i programmi applicativi e i protocolli TCP/IP • La base per l'I/O di rete dell'API è costituita da una generalizzazione del meccanismo UNIX di accesso ai file che invece di un descrittore di file fornisce un terminale di comunicazione. • Quando un programma applicativo crea un socket e stabilisce una connessione TCP dal socket a una destinazione esterna, può usare write per inviare un flusso di dati attraverso la connessione (il programma all'altro lato può usare read per riceverla). • Per rendere possibile usare le primitive come read e write sia con i file sia con i socket, il sistema operativo si assicura che se un intero è stato assegnato come descrittore di file, non sarà assegnato anche come descrittore di socket. [18] Creiamo un socket #include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol); • Ma cosa sono gli argomenti? 1. domain deve essere posto a "PF_INET". 2. type dice al kernel che tipo di socket è: SOCK_STREAM o SOCK_DGRAM. 3. protocol = "0" per avere la socket() • socket() restituisce – un “socket descriptor” o – -1 in caso di errore. [19] La specificazione di un indirizzo locale • Inizialmente i socket vengono creati senza alcuna associazione agli indirizzi locale o di destinazione. • In molti casi i programmi applicativi non si preoccupano dell'indirizzo locale che usano e vogliono consentire al software del protocollo di sceglierne uno per loro conto. • Tuttavia, i server che funzionano su una porta ben nota devono essere in grado di indicare la porta al sistema. • Una volta creato il socket, il server usa la funzione bind per specificare un indirizzo locale. Bind ha la forma seguente: – bind(socket, localaddr, addrlen) • L'argomento socket è il descrittore intero del socket da collegare all'indirizzo, localaddr è una struttura che contiene l'indirizzo locale a cui il software dovrebbe essere [20] collegato e addrlen è la lunghezza di quest’ultima Funzione bind -- What port am I on? • Associa e può riservare (in modo esclusivo) una porta TCP o UDP a uso di un socket. int status = bind(sockid, &addrport, size); – status: = -1, se bind non ha avuto successo; – sockid: descrittore del socket, intero; – addrport: struct sockaddr, l’indirizzo (IP) e la porta della macchina; L’indirizzo di solito è impostato a INADDR_ANY – size: le dimensioni (in byte) della addrport structure. • Può essere evitata per entrambi i tipi di socket. [21] Uso di bind SOCK_DGRAM (UDP) – Se si spedisce solamente, bind non serve. Il sistema operativo trova lui una porta ogni volta che il socket invia un pkt. – Se si riceve, bind è indispensabile. SOCK_STREAM (TCP) – Destinazione determinata durante il setup della connessione (three-way handshake). – Non è necessario conoscere la porta da cui avviene l’invio. Durante il setup della connessione, il lato ricevente viene informato del numero di porta. [22] Chiamata della funzione connect-- Hey, you! int status = connect(sock, &name, &namelen); – status: 0 se connect ha successo, altrimenti -1 ; – sock: intero, il socket che deve essere usato nella connessione; – name: struct sockaddr, indirizzo del partecipante passivo; – namelen: intero, sizeof(name). • “connect” è bloccante. [23] listen()--Will somebody please call me? • Come si comporta un server in attesa di ‘incoming connections’ e come le gestisce ? • Il processo si svolge in due step: first listen(), then accept() int listen(int sockfd, int backlog); – sockfd: è il solito ‘socket file descriptor’ restituito dalla socket() system call. – backlog: è il numero di connessioni contemporanee permesse sulla coda di ingresso. • Che cosa vuole dire ? – Le ‘incoming connections’ restano in una coda di attesa finchè non le si accept(). – Solitamente il limite delle connessioni accodate è tra 5 e 20 connessioni. [24] listen • Come si può facilmente immaginare, c’è bisogno di chiamare bind() prima di chiamare listen() o il kernel sarà in ascolto su una porta random. • Listening per connessioni in arrivo, la sequenza delle system calls è: socket(); bind(); listen(); /* accept() goes here */ [25] accept()--"Thank you for calling port 3490." • Cosa succede ? 1. Qualcuno, da remoto cerca di connect() alla vs. macchina su una porta sulla quale siete in listen()ing. 2. La connessione viene up waiting to be accept()ed. 3. You call accept() and you tell it to get the pending connection. 4. It'll return to you a brand new socket file descriptor to use for this single connection! That's right, suddenly you have two socket file descriptors for the price of one! 5. The original one is still listening on your port and the newly created one is finally ready to send() and recv(). We're there! [26] sendto() e recvfrom() -- Talk to me, DGRAM style • Poichè i datagram sockets non sono connessi ad un host remoto, qual sarà l’informazione necessaria da fornire prima di inviare il pacchetto ? • That's right! l’indirizzo IP di destinazione ! : int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, socklen_t tolen); • Questa call è la stessa del caso send() con l’aggiunta di due informazioni. – to è un pointer ad una struct sockaddr che contiene l’ IP address di destinazione e la porta. – tolen, ponetela a sizeof(struct sockaddr). [27] close() e shutdown() -- Get outta my face! • Whow! Abbiamo fatto send() e recv() di dati per tutto il tempo, adesso siamo stufi. Siamo pronti a chiudere la connessione e a liberare il ns. socket descriptor. Facile: possiamo usare la funzione regolare di Unix per chiudere un file: close(sockfd); • Questo inibirà ogni read/write addizionale sul socket. Chiunque tenterà letture o scritture sul socket del sistema remoto riceverà un errore. [28] Shutdown • Se si vuole un maggiore controllo su come il socket viene chiuso, si può usare la funzione shutdown(). Questa funzione permette di chiudere la comunicazione solo in una direzione, o in entrambe (come close()) • Synopsis: int shutdown(int sockfd, int how); – sockfd è il socket_descriptor che volete chiudere, e how è un valore tra: 0 – la ricezione è disabilitata 1 – la trasmissione è disabilitata 2 – sono disabilitate entrambe (come close()) • Nota: shutdown() non chiude il file_descriptor – ne cambia l’uso. Per liberare il socket_descriptor, usare close(). [29] TCP Server–Client: Interazione [30] UDP Server–Client: Interazione [31] getpeername()--Who are you? • La funzione getpeername() vi dice chi c’è all’altro capo di uno stream socket (connesso). • Synopsis: #include <sys/socket.h> int getpeername(int sockfd, struct sockaddr *addr, int *addrlen); • Una volta recuperato l’indirizzo, si può usare inet_ntoa() o gethostbyaddr() per avere maggiori informazioni. [32] gethostname() -- Who am I? • Ancora più facile di getpeername() è la funzione gethostname(): restituisce il nome del computer su cui sta girando il vs. programma. Il nome può essere usato da gethostbyname() per determinare l’indirizzo IP della macchina locale. • Ecco un esempio: #include <unistd.h> int gethostname(char *hostname, size_t size); [33] DNS -- You say "whitehouse.gov", I say 63.161.169.137 • "DNS sta per "Domain Name Service". In poche parole, voi dite al DNS un nome di un indirizzo (human-readable) di un sito, e lui vi restituisce l’ IP address (lo potete usare con bind(), connect(), sendto(), o per quello che vi serve.) • Pertanto, se uno scrive: – $ telnet whitehouse.gov • telnet scoprirà che ha bisogno di connect()-ersi a "63.161.169.137". • Ma come funziona ? Basta usare la funzione gethostbyname(): #include <netdb.h> struct hostent *gethostbyname(const char *name); [34] send() e recv() -- Talk to me, baby! • Queste due funzioni sono per la comunicazione su uno stream sockets o un datagram sockets di tipo connesso. • Se volete usare i regolari unconnected datagram sockets, riferirsi alle funzioni sendto() e recvfrom(). • La send() call: • int send(int sockfd, const void *msg, int len, int flags); [35] Esempio di Codice char *msg = "Beej was here!"; int len, bytes_sent; . . . len = strlen(msg); bytes_sent = send(sockfd, msg, len, 0); [36] recv • La recv() call è simile: int recv(int sockfd, void *buf, int len, unsigned int flags); – – – – sockfd è il socket_descriptor da cui leggere, buf è il buffer su cui memerizzare le informazioni, len è la massima lunghezza del buffer e flags può essere settato a 0. • recv() restituisce il numero di bytes letti nel buffer, o -1 in caso di errore (errno viene settata di conseguenza.) • recv() può restituire 0. Questo vuole dire una sola cosa: il sito remoto ha chiuso la connessione ! Il valore di 0 è il modo di recv() di informarvi su questo evento. [37] Invio e ricezione di dati (1) • Con una connessione TCP (SOCK_STREAM) int count = send(socket, &buf, len, flags); – – – – count: numero di byte trasmessi (-1 in caso di errore); buf: char [], buffer che devono essere trasmessi; len: intero, lunghezza di buffer (in byte) da trasmettere; flags: intero, opzioni speciali, di solito solo 0. int count = recv(socket, &buf, len, flags); – – – – count: numero di byte ricevuti (-1 in caso di errore); buf: void [], memorizza i byte ricevuti; len: numero di byte ricevuti; flags: intero, opzioni speciali, di solito solo 0. • Le chiamate sono bloccanti: la chiamata ritorna solo dopo che i dati sono stati inviati (al socket buffer) o ricevuti. [38] Invio e ricezione di dati (2) • Senza connessione, via UDP (SOCK_DGRAM) int count = sendto(sock, &buf, len, flags, &addr, addrlen); – count, sock, buf, len, flags: uguale all’invio; – addr: struct sockaddr, indirizzo della destinazione; – addrlen: sizeof(addr). int count = recvfrom (sock, &buf, len, flags, &name, namelen); – count, sock, buf, len, flags: uguale alla ricezione; – name: struct sockaddr, indirizzo della sorgente; – namelen: sizeof(name): parametro valore/risultato. • Le chiamate sono bloccanti: la chiamata ritorna solo dopo che i dati sono stati inviati (dal buffer del socket) o ricevuti. [39] Funzione close • Una volta terminato l’uso di un socket, lo si dovrebbe chiudere. status = close(s) – status: 0 se l’operazione ha avuto successo, -1 in caso di errore; – s: il descrittore di file (il socket che viene chiuso). • La chiusura di un socket: – chiude la connessione TCP (nel caso SOCK_STREAM); – libera la porta usata dal socket. [40] Esercizio inaddr [41] Winsock • Il winsock si basa su queste principali sette funzioni: 1. Socket(): crea una socket che verrà utilizzata per la comunicazione 2. Connect(): collega la socket ad un indirizzo remoto 3. Read(), Write(); Send(), Sendto(), Recv(), Recvfrom(): funzioni di invio e ricezione dati 4. Listen(): specifica le code di socket client 5. Accept(): attende delle connessioni in una specifica porta 6. Bind(): collega la socket ad una porta specifica 7. Close(): chiude la socket [42] Programmare le socket • Otto sono i passi fondamentali per programmare le socket in windows: – 1. Definizione delle librerie da utilizzare (Winsock.h) – 2. Inizializzazione della libreria socket (WSAStartup()) – 3. Creazione della socket – 4. Descrizione delle caratteristiche della socket – 5. Connessione al sistema remoto – 6. Operazioni sulla socket – 7. Chiusura della socket – 8. Rilascio delle risorse della libreria (WSACleanup() ) [43] L’ereditarietà dei socket • UNIX usa le chiamate di sistema fork ed exec per attivare nuovi processi. • È una procedura in due fasi: nella prima si crea una copia del programma in esecuzione al momento, mentre nella seconda la nuova copia è sostituita dal programma applicativo desiderato. • Quando un programma chiama fork, la copia appena creata eredita l'accesso a tutti i socket aperti, così come l'accesso a tutti i file aperti. Quando un programma chiama exec la nuova applicazione mantiene l'accesso a tutti i socket aperti. • I server principali usano l'ereditarietà dei socket quando creano server secondari per gestire una specifica connessione. [44] La struttura sockaddr • Si usa quando si passa un indirizzo TCP/IP all'interfaccia dei socket. • inizia con un campo ADDRESS FAMILY a 16 bit che identifica la famiglia di protocolli a cui appartiene l'indirizzo. È seguita da un indirizzo di 14 ottetti al massimo. • Il valore nel campo ADDRESS FAMILY determina il formato degli ottetti dell'indirizzo rimanente. Ad esempio, il valore 25 nel campo ADDRESS FAMILY significa che gli ottetti degli indirizzi rimanenti contengono un indirizzo TCP/IP. Ogni famiglia di protocolli definisce come userà gli ottetti nel campo degli indirizzi. • Per gli indirizzi TCP/IP l'indirizzo dei socket è noto come sockaddr_in e comprende sia l'indirizzo IP sia il numero di porta [45] La funzione gestsockopt • La funzione gestsockopt consente all'applicazione di richiedere informazioni su un socket. La chiamata ha la forma seguente: – getsockopt(socket, level, optionid, optionval, lenght) L'argomento socket specifica il socket per cui sono necessarie le informazioni, level identifica se l'operazione è applicata al socket stesso o ai protocolli sottostanti e optionid stabilisce l’opzione a cui si applica la richiesta. La coppia di argomenti optionval e lenght specifica due puntatori. Il primo contiene l'indirizzo del buffer in cui il sistema pone il valore richiesto e il secondo l'indirizzo di un intero in cui il sistema pone la lunghezza del valore dell'opzione. [46] La funzione setsockopt • La funzione setsockopt consente al programma applicativo di impostare un'opzione di un socket usando l'insieme di valori ottenuto con getsockopt. Il chiamante specifica il socket e il nuovo valore dell'opzione. La chiamata a setsockpt ha la forma seguente: – setsockpt(socket, level, optionid, optionval, lenght) • Gli argomenti sono come quelli di getsockopt, tranne lenght che contiene la lunghezza dell'opzione che viene passata al sistema [47] Accettazione delle connessioni • Come si è visto, i server usano le funzioni socket, bind e listen per creare un socket, collegarlo a una porta di protocollo ben nota e specificare una lunghezza della coda per le richieste di connessione. • La chiamata a bind associa il socket a una porta di protocollo ben nota, ma il socket non è collegato a una destinazione esterna specifica. La destinazione viene specificata con un carattere jolly consentendo al socket di ricevere richieste di connessione da client arbitrari. • Una volta che il socket è stato attivato, il server deve aspettare una connessione; per eseguire ciò utilizza la funzione accept [48] Un client di esempio • Il seguente programma C di esempio illustra come usare l'API socket da programmare per accedere ai protocolli TCP/IP. È una semplice implementazione di un client e di un server whois. • Come definito nella RFC 954, il servizio whois consente a un client di ottenere informazioni su un utente su un sistema remoto. • In questa implementazione, il client è un programma applicativo che l’utente attiva con due argomenti: il nome di una macchina remota e il nome dell'utente di quella macchina su cui si desiderano le informazioni. [49] Un client d’esempio (II) • Il client chiama gethostbyname per tradurre il nome della macchina remota nell'indirizzo IP e getservbyname per trovare la porta ben nota per il servizio whois. • Una volta che ha risolto i nomi di servizio e di host, il client crea un socket, specificando che il socket userà la consegna affidabile a flusso (cioè, TCP). • Infine, il client collega il socket alla porta del servizio whois sulla macchina di destinazione specificata. [50] Codice del client (1) /* whoisclient.c - main */ #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> /*----------------------------------------------------------* Programma: whoisclient * Scopo: programma applicativo UNIX che diventa un client * per il servizio Internet "whois". * Utilizzo: whois hostname username * Autore: Barry Shein, Università di Boston * Data: Tanto tempo fa in un universo molto lontano *-----------------------------------------------------------*/ [51] Codice del client (2) main(argc, argv) int argc; /* dicharazioni di argomenti UNIX standard */ char *argv[]; { int s; /* descrittore di socket */ int len; /* lunghezza dei dati ricevuti */ struct sockaddr_in sa; /* struttura Internet socket addr.*/ struct hostent *hp; /* risultato della ricerca del nome dell'host */ struct servent *sp; /* risultato della ricerca del servizio */ char buf[BUFSIZ+1]; /* buffer per leggere informazioni whois */ char *myname; /* puntatore al nome di questo programma */ char *host; /* puntatore al nome dell'host remoto */ char *user; /* puntatore al nome dell'utente remoto */ myname = argv[0]; [52] Codice del client (3) /* Controlla che vi siano due argomenti sulla riga di comando */ if(argc != 3) {fprintf(stderr, "Usage: %s host username\n", myname); exit(1);} host = argv[1]; user = argv[2]; /* Cerca il nome di host specificato */ if((hp = gethostbyname(host)) == NULL) { fprintf(stderr,"%s: %s: no such host?\n", myname, host); exit(1);} /* Pone l'indirizzo dell'host e il tipo di indirizzo nella struttura socket */ bcopy((char *)hp->h_addr, (char *)&sa.sin_addr, hp->h_length); sa.sin_family = hp->h_addrtype; /* Cerca il numero di socket per il servizio WHOIS */ if((sp = getservbyname("whois","tcp")) == NULL) { fprintf(stderr,"%s: No whois service on this host\n", myname); exit(1);} /* Pone il numero di socket whois nella struttura socket. */ sa.sin_port = sp->s_port; [53] Codice del client (4) /* Assegna un socket aperto */ if((s = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0) {perror("socket"); exit(1);} /* Si connette al server remoto */ if(connect(s, &sa, sizeof sa) < 0) {perror("connect"); exit(1);} /* Invia la richiesta */ if(write(s, user, strlen(user)) != strlen(user)) { fprintf(stderr, "%s: write error\n", myname); exit(1);} /* Legge la risposta e la mette nell'output dell'utente */ while( (len = read(s, buf, BUFSIZ)) > 0) write(1, buf, len); close(s); exit(0);} [54] Un server di esempio • Il server di esempio è solo leggermente più complicato del client. • Il server ascolta la porta ben nota "whois" e ritorna le informazioni richieste in risposta alle interrogazioni dei client. • Le informazioni vengono prese dal file delle password UNIX sulla macchina del server. [55] Un server di esempio (2) /* whoisserver.c - main */ #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <pwd.h> /*----------------------------------------------------------* Program: whoisserver * Scopo: programma applicativo UNIX che funge da server per * il servizio "whois" sulla macchina locale. Ascolta la * porta ben nota WHOIS (43) e risponde alle interrogazioni * dei client. Questo programma deve essere eseguito con i * privilegi del super-user. * Utilizzo: whois hostname username * Autore: Barry Shein, Università di Boston * Data: Tanto tempo fa in un universo molto lontano *------------------------------------------------------------*/ [56] Un server di esempio (3) #define BACKLOG 5 /* # di richieste che vogliamo accodare */ #define MAXHOSTNAME 32 /* lunghezza massima del nome di host */ main(argc, argv) int argc; /* dichiarazioni di argomenti UNIX standard */ char *argv[]; { int s, t; int i; /* descrittori di socket */ /* intero */ struct sockaddr_in sa, isa; /* struttura socket address */ struct hostent *hp; /*risultato della ricerca del nome dell'host */ char *myname; /* puntatore al nome di questo programma */ struct servent *sp; /* risultato della ricerca del servizio */ char localhost[MAXHOSTNAME+1];/* nome di host locale */ [57] Un server di esempio (4) myname = argv[0]; /*Consulta l'immissione relativa al servizio WHOIS */ if((sp = getservbyname("whois","tcp")) == NULL) {fprintf(stderr, "%s: No whois service on this host\n", myname); exit(1);} /*Prende le informazioni sull'host locale */ gethostname(localhost, MAXHOSTNAME); if((hp = gethostbyname(localhost)) == NULL) {fprintf(stderr, "%s: cannot get local host info?\n", myname); exit(1);} /* Pone il numero di socket WHOIS e le informazioni * sugli indirizzi nella struttura socket */ sa.sin_port = sp->s_port; bcopy((char *)hp->h_addr, (char *)&sa.sin_addr, hp->h_length); sa.sin_family = hp->h_addrtype; [58] Un server di esempio (5) /* Assegna un socket aperto per connessioni in ingresso */ if((s = socket(hp->h_addrtype, SOCK_STREAM, 0)) < 0) {perror("socket"); exit(1);} /* Associa il socket alla porta per rilevare le connessioni in ingresso */ if(bind(s, &sa, sizeof sa) < 0) {perror("bind");exit(1);} /* Imposta il numero massimo di connessioni tralasciate */ listen(s, BACKLOG); /* Entra in un ciclo infinito aspettando nuove connessioni */ while(1) { i = sizeof isa; /* Restiamo in accept() mentre aspettiamo nuovi client */ if((t = accept(s, &isa, &i)) < 0) { perror("accept"); exit(1); } whois(t); /* esegue il servizio WHOIS effettivo */ close(t); } } [59] Un server di esempio (6) /* Riceve la richiesta WHOIS dall'host remoto e invia una risposta */ whois(sock) int sock; { struct passwd *p; char buf[BUFSIZ+1]; int i; /* Riceve una richiesta di una linea */ if((i = read(sock, buf, BUFSIZ)) <= 0) return; buf[i] = '\0'; /* Terminatore null */ /* Cerca l'utente richiesto e formatta la risposta */ if((p = getpwnam(buf)) == NULL) strcpy(buf,"User not found\n"); else sprintf(buf, "%s: %s\n", p->pw_name, p->pw_gecos); /* Restituisce la risposta */ write(sock, buf, strlen(buf)); return; } [60] Conclusioni • Poiché il software del protocollo TCP/IP risiede all'interno del sistema operativo, l'interfaccia esatta tra un programma applicativo e i protocolli TCP/IP dipende dai dettagli del sistema operativo e non è specificata dallo standard TCP/IP. • I socket hanno adottato il paradigma UNIX openread-write-close. • L'API socket, che in origine fu progettata per BSD UNIX, ma è diventata di fatto uno standard utilizzato anche da fornitori come la Microsoft. [61]