1 / 32

Programmazione socket

Programmazione socket. un’interfaccia situata nell’host, creata dall’applicazione e controllata dal SO attraverso la quale un processo applicativo può sia inviare che ricevere messaggi a/da un altro processo applicativo situato in un altro host. socket. Programmazione socket.

rhian
Télécharger la présentation

Programmazione socket

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Programmazione socket

  2. un’interfaccia situata nell’host, creata dall’applicazione e controllata dal SO attraverso la quale un processo applicativo può sia inviare che ricevere messaggi a/da un altro processo applicativo situato in un altro host socket Programmazione socket Obiettivo: imparare a costruire applicazioni client/server che comunicano tramite socket Socket API • introdotte in UNIX BSD4.1, 1981 • create, utilizzate e rilasciate esplicitamente dalle applicazioni • paradigma client/server • due tipi di servizi di trasporto via API socket: • unreliable datagram • reliable, byte stream-oriented

  3. SOCK_STREAM TCP affidabile ordine dati garantito orientato alla connessione bidirezionale SOCK_DGRAM UDP inaffidabile nessuna garanzia su ordine dati nessuna nozione di “connessione” – l’applicazione indica la destinazione di ogni pacchetto può inviare o ricevere socket socket 2 3 2 D1 3 2 1 1 1 Dest. 3 3 2 D2 1 App App D3 I due tipi principali di socket

  4. Creazione di socket in C: socket • int s = socket (domain, type, protocol); • s: socket descriptor, un intero (come un file-handle) • domain: intero, dominio di comunicazione • es., AF_INET (IPv4 protocol) – usato di solito • type: tipo di comunicazione • SOCK_STREAM: affidabile, 2-vie, connection-based • SOCK_DGRAM: inaffidabile, connectionless • altri valori: servono permessi root, usati raramente o obsoleti • protocol: specifica il protocollo, di solito settato a 0 (vedere file /etc/protocols per una lista di opzioni) • NOTA: Una chiamata socket non specifica da dove verranno i dati o dove andranno, crea solamente un’interfaccia!

  5. La funzione bind • associa e riserva un port alla socket • int status = bind (sockid, &addrport, size); • status: error status, = -1 se il bind fallisce • sockid: intero, socket descriptor • addrport: struct sockaddr, l’indirizzo (IP) e il port della macchina • es. indirizzo: INADDR_ANY sceglie l’indirizzo locale • es. port: 0 lascia al SO il compito di stabilire il port • size: la dimensione (in byte) della struttura addrport • usato dal server (opzionalmente dal client)

  6. Quando usare il bind • SOCK_DGRAM: • in trasmissione il bind non è necessario. Il SO trova un port ogni volta che la socket manda un pacchetto • in ricezione il bind è necessario • SOCK_STREAM: • la destinazione è determinata durante il setup di connessione • non occorre conoscere il port attraverso cui vengono inviati i dati (durante il setup di connessione l’estremità ricevente è informata sul port del mittente)

  7. Connection Setup (SOCK_STREAM) • Ricordare: nessun connection setup per il SOCK_DGRAM • I partecipanti alla connessione sono di due tipi: • passivo: aspetta che un partecipante attivo richieda la connessione • attivo: inizia la richiesta di connessione verso il lato passivo • Una volta che la connessione è stabilita, i partecipanti attivi e passivi sono “simili” • entrambi possono mandare e ricevere dati • ognuno può terminare la connessione

  8. Participante passivo (es. server) step 1: listen(per arrivo di richieste) step 3: accept (una richiesta) step 4: trasferimento dati La connessione viene accettata su una nuova socket La vecchia socket continua ad aspettare la connessione di nuovi partecipanti Three way handshaking Participante attivo (es. client) step 2: richiede & stabilisce connection step 4: trasferimento dati l-sock a-sock-1 socket socket a-sock-2 Connection setup (cont.) Passive Participant Active 1 Active 2

  9. Connection setup:listen & accept • Usate dal partecipante passivo (server) • int status = listen (sock, queuelen); • status: 0 se si mette in ascolto, -1 se dà errore • sock: intero, socket descriptor • queuelen: intero, numero di partecipanti attivi che possono “aspettare” per una connessione • listen è non-blocking: ritorna immediatamente • int s = accept (sock, &name, namelen); • s: intero, la nuova socket (usata per il trasferimento dati) • sock: intero, la socket originale, usata come prototipo per s • name: struct sockaddr, indirizzo del partecipante attivo • namelen: sizeof(name): valore/risultato • deve essere settato in maniera appropriata prima della chiamata • aggiustato dal SO quando la funzione ritorna • accept è blocking: aspetta una connessione prima di ritornare

  10. connectcall • Usata dal partecipante attivo (client) • int status = connect (sock, &name, namelen); • status: 0 se connessione OK, -1 altrimenti • sock: intero, socket da essere utilizzata nella connessione • name: struct sockaddr: indirizzo del partecipante passivo • namelen: intero, sizeof(name) • connect è blocking

  11. Sending / Receiving Data • Con connessione (SOCK_STREAM): • int count = send (sock, &buf, len, flags); • count: Numero byte trasmessi (-1 se errore) • buf: char[ ], buffer da trasmettere • len: intero, lunghezza buffer (in byte) da trasmettere • flags: intero, opzioni speciali, di solito settate a 0 • int count = recv (sock, &buf, len, flags); • count: Num. byte ricevuti (-1 se errore) • buf: void[ ], immagazzina i byte ricevuti • len: intero, lunghezza buffer (in byte) • flags: intero, opzioni speciali, di solito settate a 0 • Le chiamate sono blocking [ritornano solo dopo che i dati sono inviati (al socket buf) / ricevuti]

  12. Sending / Receiving Data (cont.) • Senza connessione (SOCK_DGRAM): • int count = sendto (sock, &buf, len, flags, &addr, addrlen); • count, sock, buf, len, flags: stesse di send • addr: struct sockaddr, indirizzo della destinazione • addrlen: sizeof(addr) • int count = recvfrom (sock, &buf, len, flags, &addr, addrlen); • count, sock, buf, len, flags: stesse di recv • name: struct sockaddr, indirizzo della sorgente • namelen: sizeof(name): valore/risultato • Le chiamate sono blocking [ritornano solo dopo che i dati sono inviati (al socket buf) / ricevuti]

  13. close • Quando si finisce di utilizzare una socket, la socket dovrebbe essere chiusa: • status = close (s); • status: 0 se OK, -1 se errore • s: il socket descriptor (della socket da chiudere) • Chiusura di una socket • Chiude una connessione (per SOCK_STREAM) • Libera il port utilizzato dalla socket

  14. crea socket crea socket socket() socket() chiudi socket associa port aspetta richieste accetta richieste manda dati manda dati chiudi socket accept () close () close () listen () send () send () bind () richiedi connessione connect () ricevi dati recv () ricevi dati recv () Client/server socket interaction: TCP Server Client TCP connection setup

  15. crea socket crea socket socket() socket() chiudi socket manda dati manda dati chiudi socket associa port sendto () sendto () close () close () bind () ricevi dati recvfrom () ricevi dati recvfrom () Client/server socket interaction: UDP Server Client

  16. Generica: struct sockaddr { u_short sa_family; char sa_data[14]; }; sa_family specifica quale famiglia di indirizzi deve essere usata determina come i 14 byte rimanenti saranno utilizzati Specifica Internet: struct sockaddr_in { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; }; sin_family = AF_INET sin_port: port # (0-65535) sin_addr: IP address sin_zero: non utilizzato La struct sockaddr • //Structure per ragioni storiche • struct in_addr { • u_long s_addr; //32-bit long • };

  17. 128 128 119 119 40 40 12 12 Byte-ordering di indirizzo e port • Indirizzo e port sono memorizzati come interi • u_short sin_port; (16 bit) • in_addr sin_addr; (32 bit) • Problema: • Macchine/SO usano differenti modalità per memorizzare i dati • little-endian: lower bytes first • big-endian: higher bytes first • Queste macchine devono poter comunicare l’una con l’altra attraverso la rete Big-Endian machine Little-Endian machine 12.40.119.128 SBAGLIATO 128.119.40.12

  18. Soluzione: Network Byte-Ordering • Definizioni: • Host Byte-Ordering: il byte ordering usato dall’host (big o little) • Network Byte-Ordering: il byte ordering usato dalla rete – sempre big-endian • Ogni word inviata attraverso la rete dovrebbe essere convertita in Network Byte-Order prima della trasmissione (e viceversa in Host Byte-Order una volta riceuta) • D: Le socket devono effettuare la conversione automaticamente? • D: Dato che per le macchine big-endian non servono routine di conversione e per le macchine little-endian sì, come si può evitare di scrivere due versioni di codice?

  19. u_long htonl(u_long x); u_short htons(u_short x); u_long ntohl(u_long x); u_short ntohs(u_short x); 12 40 119 128 htonl ntohl 128 128 128 119 119 119 40 40 40 12 12 12 Funzioni di byte-ordering • Sulle macchine big-endian, queste routine non fanno nulla • Sulle macchine little-endian, invertono il byte order • Lo stesso codice funziona indipendentemente dal tipo di “endian” della macchina Big-Endian machine Little-Endian machine 128.119.40.12 128.119.40.12

  20. Altre funzioni utili • atoi (char* s): converte la stringa s in un intero • bcopy (void* s, void* d, int n): copia n byte di s in d • bzero (char* c, int n): pone n byte a 0 a partire dal valore puntato da c • gethostname (char *name, int len): ritorna il nome dell’host sui cui il processo risiede • gethostbyname (char *name): converte l’hostname in una struttura (hostent) contenente l’indirizzo IP (utilizzando il servizio di DNS)

  21. Server : Inizializzazione #include <sys/types.h> […altri include…] #define MAX_CODA 5 /* massimo backlog */ main(int argc, char* argv[]) /* prende in input la porta */ { int sock; /* socket in attesa */ int sockmsg; /* socket servente */ struct sockaddr_in server; if ( argc != 2 ) { printf("uso: %s <numero-della-porta>\n", argv[0]); exit(EXIT_FAILURE); } sock = socket(AF_INET,SOCK_STREAM,0); /* socket prototipo */ if( sock <0 ) { printf("server: errore %s nella creazione del socket\n", strerror(errno)); exit(EXIT_FAILURE); }

  22. Server: Creazione della coda server.sin_family = AF_INET; server.sin_addr.s_addr = INADDR_ANY; server.sin_port = htons(atoi(argv[1])); if( bind(sock, (struct sockaddr *)&server, sizeof(server)) ) { printf("server: bind fallita\n"); exit(EXIT_FAILURE); } printf("server: rispondo sulla porta %d\n", ntohs(server.sin_port)); if( listen(sock, MAX_CODA) <0 ) { printf("server: errore %s nella listen\n", strerror(errno)); exit(EXIT_FAILURE); } struttura per il bind dimensiono la coda di backlog

  23. Server: Gestione delle connessioni int totale=0; char input[256]; sockmsg = accept(sock, 0, 0); if( sockmsg <0 ) { printf("errore %s nella accept\n", strerror(errno)); exit(EXIT_FAILURE); } printf("server: accetto una nuova connessione\n”); close(sock); printf("server: ho chiuso il socket\n"); } /* fine della funzione main */ qui ci va il codice che presta il servizio (segue)

  24. Server: Gestione del client { /* questo e’ il codice di servizio del server */ int len; printf("server %d: iniziato \n", getpid() ); while( len = recv(sockmsg, input, sizeof(input), 0) ) { int numero; char tot[256]; input[len]='\0'; /* termina la stringa*/ numero = atoi(input); /* converti in intero */ printf("server: arrivato il numero: %d\n", numero); totale=totale+numero; /* calcolo totale*/ sprintf(tot, "%d", totale); /* prepara la stringa */ send(sockmsg, tot, sizeof(tot), 0);/* invia la stringa */ } close(sockmsg);/* prima di uscire chiudi il socket */ printf("server: socket chiuso\n”); exit(EXIT_SUCCESS); /* connessione terminata */ }

  25. Client: Inizializzazione #include <stdio.h> […altri include…] main(int argc, char* argv[]) { int sock; /* descrittore del socket */ struct sockaddr_in server; struct hostent *hp; char input[256]; if(argc!=3) { printf("uso: %s <host> <numero-della-porta>\n", argv[0]); exit(1); } sock = socket(AF_INET, SOCK_STREAM, 0); if( sock < 0 ) { printf("client: errore %s nella creazione del socket\n", strerror(errno)); exit(1); }

  26. Client: Connessione col server hp = gethostbyname(argv[1]); if( hp == NULL ){ printf("client: l'host %s non e' raggiungibile.\n", argv[1]); exit(1); } server.sin_family = AF_INET; bcopy(hp->h_addr, &server.sin_addr, hp->h_length); server.sin_port = htons(atoi(argv[2])); if( connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0 ) { printf("client: errore %s durante la connect\n", strerror(errno)); exit(1); } printf("client: connesso a %s, porta %d\n", argv[1], ntohs(server.sin_port));

  27. Client: Gestione messaggi printf("client: num. o ‘quit’? "); scanf("%s",&input); while( strcmp(input,"quit") != 0 ) { char result[256]; if( send (sock, (char *)&input, strlen(input), 0) <0) { printf("errore %s durante la write\n", strerror(errno)); exit(1); } if( recv(sock,(char *)&result, sizeof(result), 0) < 0 ) { printf("errore %s durante la read\n", strerror(errno)); exit(1); } printf("client: ricevo dal server %s\n", result); printf("client: num. o \"quit\"? "); scanf("%s",&input); } close(sock); printf("client: ho chiuso il socket\n"); } /* fine della funzione main */

  28. Gestione del blocco delle funzioni • Molte delle funzioni esaminate si bloccano finchè accade un determinato evento • accept: fino all’arrivo di una connessione • connect: fino a quando la connessione non è stabilita • recv, recvfrom: fino a quando un pacchetto (di dati) non è ricevuto • send, sendto: fino a quando i dati non vengono messi nel buffer della socket • Per semplici programmi il blocco è conveniente • Cosa accade ai programmi più complessi? • connessioni multiple • invio e ricezione contemporaneo • necessità di eseguire in contemporanea codice non legato alla rete

  29. Gestione blocco delle funzioni (cont.) • Opzioni: • creazione di codice multi-process o multi-threaded • “eliminazione” del blocking (es., usando la funzione di controllo del file descriptor fcntl) • uso della funzione select • Cosa fa la select? • si può bloccare permanentemente, per un intervallo limitato o non bloccarsi • input: un set di file-descriptor • output: info sullo stato dei file-descriptor • cioè, può identificare le socket che sono “pronte all’uso”: le funzioni che coinvolgono quelle socket ritornano immediatamente

  30. select function call • int status = select (nfds, &readfds, &writefds, &exceptfds, &timeout); • status: # di oggetti pronti, -1 se errore • nfds: 1 + il numero del più grande file descriptor da controllare • readfds: lista dei descrittori “pronti alla lettura” • writefds: lista dei descrittori “pronti alla scrittura” • exceptfds: lista dei descrittori che registrano un’eccezione • timeout: intervallo dopo il quale la select ritorna, anche se non c’è niente di pronto – può essere tra 0 e  (settare il parametro timeout a NULL per )

  31. Da utilizzare con la select • select utilizza una struct fd_set per le liste dei descrittori • è un vettore di bit • se il bit iè settato in [readfds, writefds, exceptfds], select controllerà che il file descriptor (cioè la socket) i è pronta per [reading, writing, exception] • Prima di chiamare select: • FD_ZERO (&fdvar): azzera la struttura • FD_SET (i, &fdvar): aggiunge il file descriptor i alla lista • FD_CLR (i, &fdvar): rimuove il file descriptor i dalla lista • Dopo aver chiamato select: • int FD_ISSET (i, &fdvar): booleano ritorna TRUE iff i è “pronto”

  32. Rilascio dei port • Qualche volta un’uscita “rude” da un programma (es. ctrl-c) non rilascia il port correttamente • In ogni caso il port dovrebbe essere rilasciato dopo alcuni minuti • Per ridurre la probabilità di questo inconveniente, includere il codice seguente: #include <signal.h> void cleanExit(){exit(0);} • nel codice della socket: signal(SIGTERM, cleanExit); signal(SIGINT, cleanExit);

More Related