Concetti Preliminari
L’interfaccia di programmazione chiamata Windows Socket (Winsock) utilizza il paradigma delle socket introdotto dalla libreria BDS del mondo UNIX, anche se con qualche piccolo adattamento per il sistema operativo della Microsoft.
E’ importante capire che Windows Socket non è un protocollo ma un’interfaccia e come tale è utilizzata per sfruttare le capacità di comunicazione di un qualunque numero di sottostanti protocolli di trasporto. Poiché non è un protocollo, in nessun modo modifica lo stato dei bit che scorrono attraverso il mezzo il trasporto fisico e non è necessario che sia utilizzato da entrambi i lati del collegamento.
Le socket
Il componente base della comunicazione nella libreria Winsock è la socket. Una socket rappresenta un’estremità della connessione attraverso la quale avviene la trasmissione dei dati. Una socket esiste all’interno di un dominio di comunicazione. Un dominio di comunicazione è un concetto astratto introdotto per raggruppare le proprietà comuni dei thread che comunicano tra di loro tramite le socket. Normalmente le socket scambiano i dati solo con altre socket dello stesso dominio di comunicazione.
In UNIX qualunque processo di I/O è effettuato attraverso la lettura e la scrittura di un file, per questo motivo anche le socket in tale sistema operativo sono implementate come descrittori di file (piccoli interi non negativi). In Windows, invece, gli handle delle socket non hanno tali restrizioni, per questo motivo è stato definito un nuovo tipo (SOCKET) per rappresentare le socket, che può assumere tutti i valori compresi tra 0 e INVALID_SOCKET – 1.
Tipi di socket
Le socket sono classificate in tipi differenti in accordo alle proprietà visibili all’utente. Si presume che le applicazioni comunichino utilizzando solamente socket dello stesso tipo.
I principali tipi di socket disponibili per lo sviluppatore sono due:
-
stream socket fornisce un flusso di dati bidirezionale, affidabile, sequenziale e senza la duplicazione dei dati,
-
datagram socket fornisce un flusso di dati bidirezionale senza la garanzia della sequenzialità, affidabilità e univocità dei dati.
Un processo che implementa la comunicazione tramite le datagram socket potrebbe ricevere messaggi doppioni ed in un ordine differente da quello secondo il quale sono stati trasmessi. Una caratteristica importante delle datagram socket è che i confini dell’informazione sono mantenuti durante la trasmissione, mentre utilizzando le stream socket è necessario implementare un protocollo per determinare l’inizio e la fine del messaggio scambiato.
Un processo che implementa la comunicazione tramite le datagram socket potrebbe ricevere messaggi doppioni ed in un ordine differente da quello secondo il quale sono stati trasmessi. Una caratteristica importante delle datagram socket è che i confini dell’informazione sono mantenuti durante la trasmissione, mentre utilizzando le stream socket è necessario implementare un protocollo per determinare l’inizio e la fine del messaggio scambiato.
I file di intestazione
Sono supportati un certo numero di file di intestazione standard delle socket di Berkeley. Comunque questi file di intestazione semplicemente includono il file WINSOCK2.H ed è perciò sufficiente (e raccomandato) che i codici sorgenti delle applicazioni che utilizzano le Windows socket includano solamente il file WINSOCK2.H utilizzando la seguente direttiva di precompilazione:
L’ordine dei byte
E’ necessario porre attenzione alle differenze di ordinamento dei byte che esistono tra l’architettura Intel e ciascun protocollo di trasporto utilizzato nella rete. Esistono due tipi di ordinamento:
Network Byte Order (detto anche Big-Endian Byte Order) dove il bit più significativo occupa la prima posizione della rappresentazione del valore
Host Byte Order dove il bit meno significativo occupa la prima posizione della rappresentazione del valore
Ogni riferimento ad un indirizzo o numero di porta passato a o da le funzioni di Winsock deve essere codificato secondo il Network Byte Order. Nel caso del protocollo IP, questo include sia l’indirizzo IP, sia il campo relativo al numero di porta della struttura sockaddr_in (ma non il campo sin_family).
Per semplificare il compito del programmatore la libreria fornisce alcune funzioni utili per la conversione di valori di tipo short e long:
- htons() – Host to Network Short
- htonl() – Host to Network Long
- htohs() - Network to Host Short
- ntohl() - Network to Host Long
Gli indirizzi IP
La libreria Winsock mette a disposizione dello sviluppatore due funzioni per gestire gli indirizzi.
La funzione inet_addr() converte una stringa contenente un indirizzo IP nella forma opportuna per essere memorizzato nella struttura in_addr. Il valore ritornato è già nel formato Network Byte Order e non è necessario richiamare la funzione htonl().
001struct sockaddr_in ina;
002ina.sin_addr.s_addr = inet_addr(“127.0.0.1”);
Nel caso in cui la stringa passata come parametro alla funzione inet_addr() non contenga un indirizzo IP corretto, la funzione restituisce il valore INADDR_NONE.
Un’altra funzione interessante è la inet_ntoa() che converte un indirizzo di rete internet (di tipo in_addr) nel classico formato “puntato” (di tipo stringa).
001printf(“IP address: %s\n”, inet_ntoa(ina.sin_addr.s_addr));
E’ importante notare che la funzione restituisce un puntatore ad una stringa che è allocata staticamente dalla libreria ed ogni chiamata alla funzione sovrascrive il contenuto precedente della stringa.
Ad esempio:
001char *a1, *a2;
002.
003.
004a1 = inet_ntoa(ina1.sin_addr); // this is 192.168.4.14
005a2 = inet_ntoa(ina2.sin_addr); // this is 10.12.110.57
006printf("address 1: %s\n",a1);
007printf("address 2: %s\n",a2);
008visualizzerà:
009address 1: 10.12.110.57
010address 2: 10.12.110.57
Quindi se fosse necessario salvare la stringa contente la notazione puntata dell’indirizzo è necessario copiarne il valore in un array di char tramite la funzione strcpy().
WSAStartup e WSACleanup
Prima di invocare una qualunque funzione delle Winsock, la libreria deve essere inizializzata tramite la chiamata alla WSAStartup(). Un’applicazione od una DLL tramite questa funzione può specificare la versione delle Windows Socket di cui necessita e ricevere informazioni sull’implementazione della libreria messa a disposizione del sistema. Le informazioni sono rilasciate nella struttura WSADATA.
Attraverso questa funzione è possibile verificare la versione dell’implementazione della libreria fornita dal sistema in uso.
001WORD wVersionRequested;
002WSADATA wsaData;
003int err;
004wVersionRequested = MAKEWORD(2, 2);
005err = WSAStartup(wVersionRequested, &wsaData);
006if (err != 0) {
007 /* Tell the user that we could not find a usable */
008 /* WinSock DLL. */
009 return;
010}
011
012/* Confirm that the WinSock DLL supports 2.2. */
013/* Note that if the DLL supports versions greater */
014/* than 2.2 in addition to 2.2, it will still return */
015/* 2.2 in wVersion since that is the version we */
016/* requested. */
017if (LOBYTE(wsaData.wVersion ) != 2 ||
018 HIBYTE( wsaData.wVersion ) != 2){
019 /* Tell the user that we could not find a usable */
020 /* WinSock DLL. */
021 WSACleanup();
022 return;
023}
024/* The WinSock DLL is acceptable. Proceed. */
Come si può notare anche dal codice sorgente precedente un’applicazione deve chiamare la funzione WSACleanup() per ogni chiamata alla funzione WSAStartup() che termina con successo. Questo significa che se un’applicazione chiama tre volte la funzione WSACleanup() dovrà chiamare tre volte la funzione WSACleanup().
La funzione WSACleanup() termina l’utilizzo della libreria Winsock e tutte le socket ancora aperte sono automaticamente chiuse e deallocate come se per ognuna fosse stata chiamata la funzione closesocket().