Il sito dedicato all'informatica ideato da Iasparra Francesco

WinSock: i socket in Windows

  • Cosa sono e come funzionanto i WinSock di Windows

  • Data: 02/01/2003 Autore: Iasparra Francesco 


Attualmente la suite di protocolli di comunicazione piu’ diffuso e’ il TCP/IP (Transmission Control Protocol/Internet Protocol) usato per la comunicazione sulla rete mondiale (Internet Protocol Suite).
Una qualsiasi applicazione sviluppata su piattaforma Windows o Unix che voglia utilizzare la suite TCP/IP per la comunicazione su rete, deve utilizzare le socket.
Gli sviluppatori di applicazioni Internet, basate sulla famiglia di protocolli TCP/IP, utilizzano le socket come una interfaccia standard di programmazione per sviluppare software capace di gestire una qualsiasi implementazione di TCP/IP.
La prima implementazione dell’interfaccia Socket nasce, in origine, all’interno del sistema operativo UNIX, attualmente viene implementata su molteplici sistemi operativi, tra cui Windows.
Le socket in Windows chiamate "WinSock" sono un "open transport-level network API standard", progettato per il sistema operativo Windows dal WinSock Group fondato nel 1991 e formato da produttori e sviluppatori di applicazioni per le reti, e di software per sistemi operativi.
WinSock Group si riunisce molte volte all’anno con lo scopo di raffinare ed estendere le specifiche dell’interfaccia sockets, e per testare nuovi stack di protocolli forniti dai vari produttori.
Per le versioni di Windows 95/NT sono state utilizzate Windows Sockets versione 1.1, pubblicate nel gennaio del 1993, derivate da Berkeley Software Distribuition (BSD) 4.3 per TCP/IP.

Le specifiche di Windows Sockets include le routine dei socket in stile Berkeley. E’ previsto un ulteriore supporto mediante estensioni ai socket di Berkeley sotto forma di funzioni che iniziano con il prefisso WSA (WinSock API per l’appunto).
Alcune di queste funzioni sono ad esempio WSAGetLastError(), WSAsyncSelect(), WSAStartup(). Nei successivi paragrafi vengono discusse le principali funzioni da utilizzare nello sviluppo di applicazioni client/server che utilizzano il protocollo TCP/IP.

La seguente tabella mostra le dipendenze tra la versione delle Winsock che si vuole utilizzare, il file header per sviluppo dell’applicazione in linguaggio C e le DLL a cui agganciarsi.

WinSock Version Platform Header file Dynamic Link Library
16-bit WinSock 1.1 16/32-bit Windows Winsock.h Winsock.dll
32-bit WinSock 1.1 32-bit Windows Winsock.h Wsock32.dll
16-bit WinSock 2.0 32-bit Windows Winsock2.h Ws2_32.dll

L'API delle Windows Sockets è detta Open nel senso che è stata creata, come altri sistemi aperti, nello spirito di collaborazione tra diversi produttori di software di rete ed istituzioni universitarie, ed è disponibile gratuitamente, così come lo sono i file di intestazione sviluppati in linguaggio C (Winsock.h) e le relative librerie (Winsock.dll, Wsock32.dll ed altre) di cui la tabella sopra ne specifica le dipendenze.
Quindi in pratica la specifica Windows Sockets API (WSA) definisce una collezione di chiamate di funzioni, procedure, strutture di dati, e le relative semantiche, che permettono, a qualsiasi applicazione Windows, un accesso standardizzato ai servizi di rete offerti da una suite di protocolli sottostante.

Un socket in Windows e’ individuato mediante il tipo SOCKET, che contrariamente a cio’ che avviene in Unix esso non è di tipo intero non negativo (come file descriptor), ma qualunque valore e’ un socket valido a meno del valore NVALID_SOCKET che verra’ utilizzato per controllare eventuali errori.

Stile BSD :

s = socket(...);
if (s == -1) 
{...}   

Stile WINDOWS:

s = socket(...);
if (s == INVALID_SOCKET) {...}

La realizzazione della funzione select() e’ stata modificata in Windows per gestire i WinSock, in cui la struttura fd_set non contiene piu’ una bitmask ma un array di SOCKET:

typedef struct fd_set {
u_int fd_count;
SOCKET fd_array[FD_SETSIZE];
} fd_set;    

Una socket al momento della sua creazione non contiene informazioni dettagliate circa il modo in cui viene usata tranne le informazioni sulla famiglia di protocolli su cui si lavora ed il tipo di servizio, oltre chiaramente l’handle che la identifica. In particolare la socket non contiene i numeri di porta di protocollo o gli indirizzi IP, ne della macchina locale ne di quella remota, senza i quali, però, essa non può essere utilizzata da un'applicazione.
I protocolli TCP/IP definiscono un terminale di comunicazione endpoint consistente di un indirizzo IP e di un numero di porta. Altre famiglie di protocolli definiscono i propri indirizzi terminali in altri modi. Siccome le socket supportano più famiglie di protocolli, esse non definiscono il modo in cui specificare un indirizzo, né definiscono un particolare formato di protocollo di indirizzo, lasciano invece ad ogni famiglia di protocolli il compito di specificare gli indirizzi nella maniera voluta. E per permettere ciò le socket definiscono una famiglia di indirizzo per ogni tipo di indirizzo usato.
Una famiglia di protocolli può avere una o più famiglie di indirizzo. I protocolli TCP/IP usano tutti la stessa rappresentazione di indirizzo, denotando la propria famiglia di indirizzo con la costante simbolica AF_INET (definita in winsock.h )
Per mantenere inoltre la generalità dei servizi offerti rispetto alle diverse famiglie di protocolli, il sistema delle socket definisce un formato generalizzato che tutti i tipi di indirizzi terminali usano. Tale formato consiste della coppia : famiglia d'indirizzo, indirizzo, terminale nella famiglia.
In pratica nell'implementazione software si hanno delle dichiarazioni di strutture predefinite in C per gli indirizzi terminali. La struttura più generale è nota come struttura sockaddr e contiene un campo identificatore della famiglia di indirizzi formato da due byte e da un array a quattordici byte che contiene l'indirizzo:

struct sockaddr { /* struttura che contiene un indirizzo */
u_short sa_family; /* tipo o famiglia di indirizzo */
char sa_data[14]; /* valore dell'indirizzo */
};    

Ogni famiglie di indirizzo ha una propria lunghezza per indicarne il valore, ad esempio un' indirizzo TCP/IP è composto da otto byte mentre quello dello stack di protocolli IPX/SPX è lungo quattordici byte (nel BSD UNIX può essere ancora più lungo), per tali motivi quindi la struttura appena vista ha un valore di puro riferimento. Quindi per ogni famiglia che ha definito un suo formato di indirizzo ben preciso il software delle socket fornisce la relativa dichiarazione della corrispondente struttura.
Per la famiglia TCP/IP un indirizzo consiste di un campo di due byte che identifica la famiglia AF_INET (si noti che tale campo è comune a tutte le strutture di indirizzo), un campo a due byte che contiene il numero di porta, uno a quattro byte contenente l'indirizzo IP ed un ultimo campo ad otto byte inutilizzato e presente solo per conservare i limiti di ampiezza con la struttura. La struttura così risultante è:

struct sockaddr { /* struttura che contiene un indirizzo */
u_short sa_family; /* tipo o famiglia di indirizzo */
char sa_data[14]; /* valore dell'indirizzo */
}; 

Nell' ambito della programmazione con le Winsock, risulta fondamentale assegnare i ruoli di client e server al fine di semplificare la stesura del codice e non creare situazioni di ambiguità. Il server è quello che si preoccupa di stare in ascolto, in attesa di eventuali richieste di connessione entranti, mentre il client è quello che richiede la connessione e che di solito è il primo a trasmettere dati. Inizialmente sia il client che il server creano una propria socket indipendente. Queste vengono associate successivamente durante la fase di connessione.
Affinché due sockets possano comunicare come client e server, bisogna che queste siano dello stesso tipo, cioè devono essere entrambe di tipo stream (TCP) o datagram (UDP).
Operazioni fondamentali per una connessione TCP

SERVER CLIENT NOTE
WSAStartup WSAStartup Inizializzazione delle risorse
Socket Socket Creazione della socket
Bind   Assegna posta e indirizzo alla socket
Listen   Ascolta le richieste di connessione
  Connect Si connette all’ endpoint remoto
Accept   Accetta la richiesta di connessione
Recv Send Scambio dati
Send Recv Scambio dati
CloseSokect CloseSokect Chiusura dei socket
WSACleanup WSACleanup Rilascio delle risorse

Operazioni fondamentali per una connessione UDP

SERVER CLIENT NOTE
WSAStartup WSAStartup Inizializzazione delle risorse
Socket Socket Creazione della socket
Bind Bind Assegna posta e indirizzo alla socket
Recvform Sendto Sendto Scambio dei dati
Sendto Recvform Scambio dei dati
CloseSokect CloseSokect Chiusura dei socket
WSACleanup WSACleanup Rilascio delle risorse

Sono stati effettuati molti aggiornamenti a questo schema di base di funzionamento; sono stati aggiunti API, protocolli, tipi di trasmissione; Winsock 2 e' un esempio di standard utilizzabile per un vasto numero di protocolli e tipi di connessione.


La prima funzione che deve essere chiamata da qualsiasi programma Windows che voglia utilizzare le API Windows Sockets e’ WSAStartup().
Se questa funzione non e’ chiamata la funzione socket fallisce e restituisce il codice di errore INVALID_SOCKET.
Questa richiede due parametri: una word contenente la versione più alta delle Winsock API supportata dalla nostra applicazione, codificata in modo tale che nel byte meno significativo sia contenuta la versione principale e nell' altro il numero di revisione (ad esempio 0101h per l'API 1.1 e 0002h per la 2.0). Viene allora utilizzata la macro MAKEWORD(int x,int y) la quale crea una word a partire da due interi.
La struttura di tipo WSAData, verrà riempita dalla funzione WSAStartup() con le caratteristiche della Winsock DLL che andremo ad utilizzare:

struct WSAData {
WORD wVersion;
WORD wHighVersion;
char szDescription[WSADESCRIPTION_LEN+1];
char szSystemStatus[WSASYSSTATUS_LEN+1];
unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR * lpVendorInfo;
};    

La principale funzione di WSAStartup() è quella di inizializzare la Winsock DLL e per questo motivo deve essere la prima funzione Winsock ad essere chiamata da un'applicazione.
Se il valore di ritorno è non nullo, significa che si è verificato un errore fatale e che l'applicazione deve essere chiusa (in quanto questa risulterà impossibilitata a lavorare con le Winsock). In caso contrario (ritorno nullo), analizzando la struttura WSAData, otterremo interessanti infAormazioni circa la DLL utilizzata (ad esempio quale versione delle Winsock dovremo effettivamente usare).
La sintassi e’ la seguente:

int retvalue = WSAStartup(WORD rvers,WSADATA wsimpl);   

La funzione restituisce zero in caso di successo oppure in caso di errore:
- WSASYSNOTREADY: il sistema non e’ pronto per la comunicazione in rete
- WSAVERNOTSUPPORTED: versione richiesta delle WinSock non disponibile
- WSAEINVAL: la DLL non supporta la versione richiesta

Creare un Windows Socket. Per fare questo bisogna passare:
- La famiglia di indirizzi (AF_INET o PF_INET indistintamente per specificare la famiglia di protocolli TCP/IP).
- Il tipo di socket (stream indicando SOCK_STREAM oppure datagram indicando SOCK_DGRAM).
- Il tipo di protocollo (IPPROTO_TCP o IP_PROTOUDP o il valore nullo per non specificarne alcuno).

La sua sintassi e’ la seguente:

SOCKET retvalue = socket (int afam,int type,int protocol);    

Questa funzione ritorna direttamente l'handle della nuova socket, in caso di successo, oppure la costante INVALID_SOCKET in caso di errore. In quest'ultimo caso, il codice dell'errore può essere reperito utilizzando la funzione WSAGetLastError().

 In questo esempio mostreremo come sia possibile creare un server TCP, che aspetta un richiesta di connessione, riceve un carattere dal client e chiude la connessione. Il client viene eseguito mendiante il comando telnet, aprendo una console e al prompt dei comandi digitando:

C:\ telnet 127.0.0.1

Questo comando va lanciato dopo aver avviato il server TCP. A questo punto, viene visualizzato la finestra dell’applicazione Telnet ed e’ possibile digitare un carattere, fatto ciò viene visualizza nella finestra del server il carattere che e’ stato digitato nell’applicazione telnet e immediatamente dopo la connessione viene chiusa dal server.

int main(void){
	SOCKET sock21, asock;
	sockaddr_in sock_in21, asock_in;
	WORD wVersionRequested;
	int err, addrlen;
	char *send_buff="SERVER TELNET \n";
	char recv_buff[256];
	wVersionRequested=MAKEWORD(2,0);
	printf("MINISERVER TCP/IP su porta Telnet\n");
	err=WSAStartup(wVersionRequested,&wsaData);
	if(err!=0){
		printf("Libreria winsock non presente\n");
		exit(1);
	} 
	sock21=socket(PF_INET,SOCK_STREAM,0);
	if(sock21==INVALID_SOCKET){
		printf("Errore socket():%d\n",WSAGetLastError());
		exit(1);
	}
	sock_in21.sin_family=PF_INET;
	sock_in21.sin_port=htons(IPPORT_TELNET);
	sock_in21.sin_addr.s_addr=INADDR_ANY;
	err=bind(sock21,(struct sockaddr *) &sock_in21, sizeof(struct sockaddr_in));
	if(err==SOCKET_ERROR){
		printf("Errore bind():%d\n",WSAGetLastError());
		exit(1);
	}
	err=listen(sock21,1);
	if(err==SOCKET_ERROR){
		printf("Errore listen():%d\n",WSAGetLastError());
		exit(1);
	} else {
		printf("Aspettimo la connessione..\n");
	}
	addrlen=sizeof(struct sockaddr);
	asock=accept(sock21,(struct sockaddr *) &asock_in,(LPINT)&addrlen);
	if(asock==INVALID_SOCKET){
		printf("Errore accept():%d\n",WSAGetLastError());
		exit(1);
	} else {
		printf("Client: %s:%d\n",inet_ntoa(asock_in.sin_addr),ntohs(asock_in.sin_port));
	}
	send(asock,send_buff,strlen(send_buff),0);
	memset(recv_buff,0,sizeof(recv_buff));
	recv(asock,recv_buff,sizeof(recv_buff),0);
	printf("CLIENT>%s\n",recv_buff);
	closesocket(asock);
	closesocket(sock21);
	WSACleanup();
	return 0;
}    

Con questo ho concluso. Avete visto come la sintassi di programmazione delle Winsock sia identica a quella dei socket in Linux, a meno che' non si voglia utilizzare la modalità asincrona.


  • Java

  • Php

  • Mysql

  • Apache ant

  • Eclipse

  • Spring

  • Hibernate

  • Netbeans

  • Debian

  • Linux

  • Maven