Programmazione socket Queste slide sono distribuite con licenza Creative Commons Attribuzione-Non commerciale-condividi allo stesso modo 2.5 Italia
Applicazioni di rete Realizzare un'applicazione di rete significa implementare un processo comunicante e distribuito. In genere si tratta di software in esecuzione su un host in grado di comunicare con altri host sulla rete per mezzo di un sistema di comunicazione a messaggi e secondo un determinato modello di cooperazione (il più diffuso è il modello client/server, in crescita il modello peer-to-peer). 2
Modello client/server /1 Prevede il dialogo tra applicativi software in esecuzione su due host delle rete. Client Si tratta dell'applicazione che richiede un servizio, iniziando così la comunicazione Server Si tratta dell'applicazione che eroga il servizio; in continua attesa di essere contattato da un client appropriato Un'applicazione è in grado di identificare un'altra applicazione in rete tramite: l'indirizzo IP dell'host su cui risiede l'applicazione con cui intraprendere la comunicazione il numero della porta che permette all'applicativo server di individuare per quale applicativo, tra quelli operativi e in ascolto, è arrivata la richiesta di un servizio 3
Modello client/server /2 Generalmente i server sono in grado di rispondere a richieste da parte di client anche in modalità multitask/multithread; questo significa che più client possono nello stesso momento richiedere lo stesso servizio a un'applicazione server che deve quindi essere in grado di identificare univocamente il client al quale inviare la risposta. Ogni connessione viene quindi identificata in modo univoco tramite l'utilizzo dei seguenti 4 dati: Sorgente: Indirizzo IP del server che eroga il servizio Indirizzo di porta del server Destinazione: Indirizzo IP del client che richiede il servizio Indirizzo di porta del client 4
Programmazione socket /1 Tipicamente gli applicativi client/server utilizzano TCP (comunicazione affidabile orientata alla connessione) o UDP (datagram non affidabile) come protocollo di trasporto. Per poter implementare la comunicazione si avvalgono delle cosiddette API (Application Programming Interface) messe a disposizione dal sistema operativo ospitante, denominati socket. I socket sono quindi un'interfaccia messa a disposizione del sistema operativo che permette l'invio e la ricezione di messaggi da e per un altro host (in alcuni casi l'host sorgente e destinazione possono coincidere). L'approccio all'utilizzo dei socket, implementato in tutti i linguaggi di programmazione, è molto simile a quello utilizzato per la gestione dei file: apertura del canale lettura/scrittura dei messaggi chiusura del canale 5
Programmazione socket /2 Comunicazione orientata alla connessione (TCP) socket () bind () esegue bind su una porta nota listen () TCP SERVER accept () in attesa di connessione da parte di un client read () elaborazione dati connessione instaurata (3-way handshake) invio richiesta dati socket () connect () write () TCP CLIENT write () invio risposta read () read () close () comunicazione chiusura comunicazione close () 6
Programmazione socket /3 Funzione socket: int socket ( int domain, int type, int protocol ); restituisce un intero da gestire come un descrittore di file; restituisce -1 in caso di errore nella creazione del socket. int domain identifica la famiglia del protocollo da utilizzare; in genere si utilizza AF_INET (comunicazioni Ipv4), AP_INET6 (comunicazioni Ipv6) o AF_UNIX (comunicazioni UNIX sullo stesso host) int type definisce il tipo di comunicazione; in genere si utilizza SOCK_STREAM (per comunicazioni orientate alla connessione), SOCK_DGRAM (per comunicazioni senza connessione) o SOCK_RAW (per utilizzo diretto del protocollo IP) int protocol specifica il protocollo da utilizzare; di solito si lascia il valore a 0 perché il protocollo è desunto dai precedenti parametri (AF_INET + SOCK_STREAM = TCP; AF_INET + SOCK_DGRAM = UDP) 7
Programmazione socket /4 Funzione bind (utilizzata dal server): int bind ( int sockfd, const struct sockaddr *my_addr, socklen_t addrlen ); l'applicativo comunica al sistema operativo le modalità di attesa delle connessioni da parte dei client. int sockfd è il descrittore del socket ottenuto dalla funzione socket () 8
Programmazione socket /5 Funzione bind (utilizzata dal server): int bind ( int sockfd, const struct sockaddr *my_addr, socklen_t addrlen ); struct sockaddr *my_addr struct sockaddr { uint8_t sa_len; sa_family_t sa_family; /* address family: AF_xxx value */ char sa_data[14]; /* protocol-specific address */ }; struct sockaddr_in { uint8_t sin_len; /* length of structure (16) */ sa_family_t sin_family; /* AF_INET */ in_port_t sin_port; /* 16-bit TCP or UDP port number */ /* network byte ordered */ struct in_addr sin_addr; /* 32-bit IPv4 address */ /* network byte ordered */ char sin_zero[8]; /* unused */ }; struct in_addr { in_addr_t s_addr; /* 32-bit IPv4 address */ /* network byte ordered */ }; 9
Programmazione socket /6 Funzione bind (utilizzata dal server): int bind ( int sockfd, const struct sockaddr *my_addr, socklen_t addrlen ); socklen_t addrlen è la dimensione in byte delle struttura passata 10
Programmazione socket /7 Funzione listen (utilizzata dal server): int listen ( int sockfd, int backlog ); mette in stato di ascolto il server. int sockfd è il descrittore del socket ottenuto dalla funzione socket () int backlog specifica quante richieste possono essere accettate dal server e messe in coda 11
Programmazione socket /8 Funzione accept (utilizzata dal server): int accept ( int sockfd, struct sockaddr *addr, socklen_t *addrlen); viene chiesto dal server di poter accedere alla prima richiesta in coda; se la coda è vuota il processo viene messo in stato sleep (attesa) int sockfd già visto struct sockaddr *addr già visto socklen_t *addrlen già visto 12
Programmazione socket /9 Funzione connect (utilizzata dal client): int connect ( int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen ); questa funzione viene utilizzata dal client per stabilire una connessione con un server remoto; blocca l'esecuzione dell'applicativo fino a che non si riceve comunicazione dal sistema operativo: 0 in caso di successo, -1 in caso di errore. int sockfd già visto struct sockaddr *serv_addr già visto socklen_t addrlen già visto 13
Programmazione socket /10 Funzioni read e write (utilizzate sia dal client che dal server): int read (int sockfd, void *buf, size_t count); int write (int sockfd, const void *buf, size_t count); queste funzioni vengono utilizzate per leggere e scrivere byte sul socket; viene ritornato il numero di byte effettivamente letti/scritti oppure -1 in caso di errore. int sockfd già visto buf Buffer dati utilizzato per scrivere/leggere i dati size_t count numero di byte da leggere/scrivere 14
Programmazione socket /11 Funzione close (utilizzata sia dal client che dal server): int close ( int sockfd ); con close si chiude una connessione; ritorna 0 in caso di successo, -1 in caso di errore. int sockfd già visto 15
Esempio applicazione client/server /1 File-server & File-client Si vuole realizzare un applicativo server che, utilizzando il protocollo TCP, si metta in attesa di connessioni sulla porta 12345. Si vuole realizzare un applicativo client che, utilizzando il protocollo TCP, instauri una connessione con un server remoto in attesa sulla porta 12345. Lo scopo del server è quello di ricevere un messaggio di richiesta contenente un nome di file, leggere il file richiesto e stamparlo sul socket. Lo scopo del client è di comunicare al server il nome di un file che intende ricevere; una volta ricevuto lo stampa sullo STDOUT. Fonte esercizio: Andrew S. Tanenbaum, Computer Networks 16
Esempio applicazione client/server /2 (server) /* File server - Server * * Based on "Computer Networks" - Andrew S. Tanunbaum * http://authors.phptr.com/tanenbaumcn4/ * * Once the server has been compiled and started, clients anywhere on * the Internet can send commands (file names) to the server. * The server responds by opening and returning the entire file requested. * * Compile it under Linux with: "gcc -o server file-server server.c" */ #include <stdio.h> #include <memory.h> #include <stdlib.h> #include <sys/types.h> #include <sys/fcntl.h> #include <sys/socket.h> #include <netdb.h> #define SERVER_PORT 12345 /* arbitrary, but client and server must agree */ #define BUF_SIZE 4096 /* block transfer size */ #define QUEUE_SIZE 10 17
Esempio applicazione client/server /3 (server) int main ( int argc, char *argv[] ) { int s, b, l, fd, sa, bytes, on = 1; char buf[buf_size]; /* buffer for outgoing file */ struct sockaddr_in channel; /* hold's IP address */ /* Build address structure to bind to socket. */ memset ( &channel, 0, sizeof ( channel ) ); /* zero channel */ channel.sin_family = AF_INET; channel.sin_addr.s_addr = htonl ( INADDR_ANY ); channel.sin_port = htons ( SERVER_PORT ); /* Passive open. Wait for connection. */ s = socket ( AF_INET, SOCK_STREAM, IPPROTO_TCP ); /* create socket */ if ( s < 0 ) fatal ( "socket failed" ); b = bind ( s, (struct sockaddr *) &channel, sizeof ( channel ) ); if ( b < 0 ) fatal ( "bind failed" ); l = listen ( s, QUEUE_SIZE ); /* specify queue size */ if ( l < 0 ) fatal ( "listen failed" ); 18
Esempio applicazione client/server /4 (server) /* Socket is now set up and bound. Wait for connection and process it. */ while ( 1 ) { sa = accept ( s, 0, 0 ); /* block for connection request */ if ( sa < 0 ) fatal ( "accept failed" ); read ( sa, buf, BUF_SIZE ); /* read file name from socket */ /* Get and return the file. */ fd = open ( buf, O_RDONLY ); /* open the file to be sent back */ if ( fd < 0 ) fatal ( "open failed" ); } } while ( 1 ) { bytes = read ( fd, buf, BUF_SIZE ); /* read from file */ if ( bytes <= 0 ) break; /* check for end of file */ write ( sa, buf, bytes ); /* write bytes to socket */ } close ( fd ); /* close file */ close ( sa ); /* close connection */ 19
Esempio applicazione client/server /5 (server) fatal ( char *string ) { printf ( "%s\n", string ); exit ( 1 ); } 20
Esempio applicazione client/server /6 (client) /* File server - Client * * Based on "Computer Networks" - Andrew S. Tanunbaum * http://authors.phptr.com/tanenbaumcn4/ * * Once the server has been compiled and started, clients anywhere on * the Internet can send commands (file names) to the server. * The server responds by opening and returning the entire file requested. * * Compile it under Linux with: "gcc -o client file-server client.c" */ #include <stdio.h> #include <memory.h> #include <stdlib.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #define SERVER_PORT 12345 /* arbitrary, but client and server must agree */ #define BUF_SIZE 4096 /* block transfer size */ 21
Esempio applicazione client/server /7 (client) int main ( int argc, char **argv ) { int c, s, bytes; char buf[buf_size]; /* buffer for incoming file */ struct hostent *h; /* info about server */ struct sockaddr_in channel; /* holds IP address */ if ( argc!= 3 ) fatal ( "Usage: client server-name file-name" ); h = gethostbyname ( argv[1] ); /* look up host's IP address */ if (! h ) fatal ( "gethostbyname failed" ); s = socket ( AF_INET, SOCK_STREAM, IPPROTO_TCP ); if (s < 0) fatal("socket"); memset ( &channel, 0, sizeof ( channel ) ); channel.sin_family = AF_INET; memcpy ( &channel.sin_addr.s_addr, h->h_addr, h->h_length ); channel.sin_port = htons ( SERVER_PORT ); c = connect ( s, ( struct sockaddr * ) &channel, sizeof ( channel ) ); if ( c < 0 ) fatal ( "connect failed" ); 22
Esempio applicazione client/server /8 (client) /* Connection is now established. Send file name including 0 byte at end. */ write ( s, argv[2], strlen ( argv[2] ) + 1 ); } /* Go get the file and write it to standard output. */ while ( 1 ) { bytes = read ( s, buf, BUF_SIZE ); /* read from socket */ if ( bytes <= 0 ) exit ( 0 ); /* check for end of file */ write ( 1, buf, bytes ); /* write to standard output */ } fatal ( char *string ) { printf ( "%s\n", string ); exit ( 1 ); } 23
Esempio applicazione client/server /9 (esecuzione) tode@linux:~/server> tode@linux:~/server>./server tode@linux:~/client>./client localhost /etc/issue Welcome to opensuse 10.2 (i586) - Kernel \r (\l). tode@linux:~/client> tode@linux:~/client> netstat -nat Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State tcp 0 0 0.0.0.0:139 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:2544 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:631 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:12345 0.0.0.0:* LISTEN tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN tcp 0 0 0.0.0.0:445 0.0.0.0:* LISTEN tcp 0 0 :::22 :::* LISTEN tcp 0 0 :::631 :::* LISTEN tcp 0 0 ::1:25 :::* LISTEN 24
Compito (per i laboratori...) Quali altre applicazioni che conosci funzionano nel modo descritto dai programmi precedenti? In vista dei laboratori pensare alla realizzazione di un applicativo client in grado di reperire un feed RSS e di stamparlo su STDOUT 25
Programmazione socket /12 Comunicazione senza connessione (UDP) socket () bind () esegue bind su una porta nota UDP SERVER recvfrom () in attesa di dati spediti da un client elaborazione dati sendto () invio dati invio risposta socket () sendto () recvfrom () UDP CLIENT close () 26
Programmazione socket /13 Funzioni sendto e recvfrom (utilizzate sia dal client che dal server): int sendto ( int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen ); tramite questa funzione si inviano byte a un host remoto; ritorna il numero di byte inviati oppure -1 in caso di errore locale (non c'è infatti verifica dell'effettiva ricezione dei dati da parte dell'host remoto). int recvfrom ( int sockfd, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen ); riceve dati da un host remoto e ne memorizza len nel buffer buf: in caso arrivi un pacchetto di dimensioni maggiori di len i dati in più sono persi; dalla struttura from si possono ricavare indirizzo IP e porta dell'host mittente. 27