You are currently browsing the daily archive for Agosto 15th, 2008.

In questo articolo illustrerò le fondamenta per usare le librerie WinSock2 della Microsoft che permettono di creare applicazioni di rete server e client. Precisamente in questo articolo costruiremo un client semplice e base, da modificare successivamente per le varie esigenze. L’articolo tratta molto la pratica, descrivendo poca teorica, ma necessaria a capire il funzionamento del tutto!

IMPORTANTE: Per usare il codice che sta per seguire bisogna linkare al linker le seguenti lib:

  • ws2_32.lib
  • Mswsock.lib
  • Advapi32.lib

L’header del nostro file .c o .cpp sarà composto dai seguenti header necessari e della dichiarazione di una variabile globale utilizzata per effettuare vari controlli:

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <string.h>

int iResult;

Passiamo ora all’inizio della programmazione pratica. Innanzi tutto bisogna creare e inizilizzare un oggetto WSADATA (per maggiori informazioni sulla struttura WSADATA visitate il supporto MS all’indirizzo http://msdn.microsoft.com/en-us/library/ms741563(VS.85).aspx):

WSADATA wsaData;
//Inizializzo Winsock
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if(iResult != 0)
{
printf(“WSAStartup failed: %d\n”, iResult);
return 1;
}

Il blocco If, come si può capire serve per controllare eventuali errori. La variabile iResult è una variabile globale, di tipo intero. Alla funzione WSAStartup abbiamo pasato come argomenti due parametri: il primo è la versione dei socket che stiamo usando, ovvero 2.2, il secondo parametro invece è l’indirizzo dell’oggetto WSAData precedentemente creato. Andiamo avanti, e vediamo come fare per gestire passo passo il lato client. Iniziamo col definire una porta che useremo per le connessioni e l’inizializzazione di un oggetto addinfo che contiene una struttura sockaddr (per informazioni a queste due struttura vi rimando sempre al sito MSDN DI Microsoft):

#define PORT “8000″
struct addrinfo *result = NULL, *ptr = NULL, hints;

ZeroMemory(&hints, sizeof(hints));

hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;

In questo caso abbiamo riempito il campo della struttura ai_family con AF_UNSPEC che indica che non è definito la versione del protocollo IP, per cui può essere sia IPv4 che IPv6. Se volessimo definire l’uso dell’IPv4 avremmo messo AF_INET, contrariamente per IPv6 avremmo messo AF_INET6. Il secondo campo, ai_socktype, indica il tipo di socket, in questo caso abbiamo scleto SOCK_STREAM che è quello usato per comunicazioni di rete. Il terzo campo, ai_protocol, riguarda il protocollo usato per la trasmissione e il controllo dei dati trasmessi: il suo valore è IPPROTO_TCP, che indica appunto il protocollo TCP. La scelta risulta pressochè obbligatoria, in quanto prima abbiamo specificato il tipo di socket come SOCK_STREAM, se avessimo invece usato SOCK_DGRAM avremmo potuto usare il protocollo UDP, che appunto usa i datagrammi. Queste sono le specificazioni basilari, per maggiori informazioni sulla struttura addrinfo potete visitare il sito di MS http://msdn.microsoft.com/en-us/library/ms737530(VS.85).aspx

Adesso dobbiamo vedere a quale indirizzo IP dobbiamo connetterci, quindi il server di destinazione. In questo caso useremo la funzione getaddrinfo che restiruisce un valore intero, anche se il nostro valore sarà una stringa come ad esempio “192.168.1.2″ o “irc.azzurra.org”. Andiamo a vedere il codice:

// Resolve the server address and port
iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);
if ( iResult != 0 ) {
printf(“getaddrinfo failed: %d\n”, iResult);
WSACleanup();
return 1;
}

La funzione getaddrinfo accetta diversi parametri, precisamente 4.. analizziamoli: il primo è l’host, ovvero il nodo a cui connetterci, esso è rappresentato da un indirizzo IPv4 o 6 in base alle dichiarazioni prima effettuate, ed è passato in formato stringa, tant’è vero che gli abbiamo dato in pasto il parametro argv[1] della funzione main(), quindi il suo valore sarà inserito all’avvio dell’applicazione tramite riga di comando, ovvero: nomeprogramma server_a_cui_connettersi. In seguito abbiamo passato la porta e due indirizzi: il primo è del puntatore a una struttura addrinfo e il secondo a una struttura addrinfo. La funzione restituisce un valore intero, per tale motivo l’abbiamo assegnato alla variabile intera iResult e abbiamo controllato eventuali errori con un iterazione if: se tutto è andato a buon fine, solitamente, riceveremo un valore pari a 0. Altrimenti ci sono diversi tipi di errori, consultabili nella pagina MSDN http://msdn.microsoft.com/en-us/library/ms738520(VS.85).aspx

Adesso, non ci resta che creare ed utilizzare il socket! Creiamo quindi l’oggetto SOCKET con la seguente stringa:

SOCKET ConnectSocket = INVALID_SOCKET;

Quindi inizializziamo il socket, con i valori prima definiti:

ptr=result;

// Socket for connect to server
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
ptr->ai_protocol);

E facile capire, che i parametri passati sono proprio gli elemnti della strutta addrinfo che abbiamo prima riempito. Ma, come proposto dal sito di MSDN Microsoft, è bene cercare sempre eventuali errori, e lo facciamo con un controllo per mezzo dell’istruzione if:

if (ConnectSocket == INVALID_SOCKET) {
printf(“Error at socket(): %ld\n”, WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
}

Quindi, facciamo un pò di chiarezza del codice stampato: se il socket ritorna essere invalido, stampiamo la scritta di errore e richiamiamo la funzione WSAGetLastError() che appunto ci comunica l’ultimo errore verificato. Poi liberiamo le risorse tramite la funzione freeaddrinfo() e usiamo WSACleanup per terminare anche l’uso della libreria WS2_32 DLL. Adesso però, prendendo per buono che tutto è andato a buon fine, dobbiamo connettersi a un socket remoto. Bene, è arrivato il momento di usare la funzione connect(). Vediamo un pò di codice, e poi commentiamo:

// Connect to server.
iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
if (iResult == SOCKET_ERROR) {
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;

freeaddrinfo(result);

if (ConnectSocket == INVALID_SOCKET) {
printf(“Unable to connect to server!\n”);
WSACleanup();
return 1;
}
}

Analizziamo il codice. Abbiamo usato la funzione connect per connetterci al server remoto, ed ecco i parametri che abbiamo passato alla funzione: il primo è il socket usato per la connessione, il secondo e il terzo sono dei puntatori che accedono agli elementi delle relative strutture, precisamente il server a cui provare a connettersi e la porta. Di seguito poi, come potrete capire, avviene il controllo per verificare l’eventuale presenza di errori, e in tal caso comunicarli. Ora però, dobbiamo inviare e ricevere dati, quindi vediamo come farlo, come comunciare col server:

#define DEFAULT_BUFLEN 512
char *sendbuf = “Qui i dati”;

char recvbuf[DEFAULT_BUFLEN];
// Inviamo un buffer iniziale di dati

iResult = send( ConnectSocket, sendbuf, (int)strlen(sendbuf), 0 );

if (iResult == SOCKET_ERROR) {

printf(“send failed: %d\n”, WSAGetLastError());

closesocket(ConnectSocket);

WSACleanup();

return 1;

}

printf(“Bytes Sent: %ld\n”, iResult);

Molto semplice, abbiamo usato la funzione send per inviare dei dati. Essa accetta come primo parametro il socket da noi creato, in successione i dati da inviare sottoforma di char *, e la lunghezza del buffer dati inviati. L’ultimo argomento è solitamente 0, per ulteriori informazioni potete consultare il sito di MSDN, che è una vera encicplopedia di riferimento :-)

Adesso vediamo come mandare un shutdown il socket per usarlo nel secondo momento per ricevere i dati dal server:

if (iResult == SOCKET_ERROR) {

printf(“shutdown failed: %d\n”, WSAGetLastError());

closesocket(ConnectSocket);

WSACleanup();

return 1;

}


Ed ecco infine come ricevere i dati:

// Receive data until the server closes the connection
do {

iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
if ( iResult > 0 )
printf(“Bytes received: %d\n”, iResult);
else if ( iResult == 0 )
printf(“Connection closed\n”);
else
printf(“recv failed: %d\n”, WSAGetLastError());

} while( iResult > 0 );

In questo caso, i parametri sono simili alla funzione di invio dati, l’unica differenza è che il secondo parametro è il buffer dati che riceviamo e non quello che inviamo, il funzionamento è pressochè simile. Infine, non ci resta che disconnettersi dal server:

iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
printf(“shutdown failed: %d\n”, WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}

// Cleanup
closesocket(ConnectSocket);
WSACleanup();

return 0;

PS. Buona parte di questo articolo fa parte di una mia traduzione ai post scritti su MSDN in lingua inglese, spero di aver fatto un buon lavoro per alcuni che non masticano bene l’inglese e di aver trasmessmo come iniziare ad usare le socket in WIndows con il linguaggio C/C++.

Post più letti

Blog Stats

  • 1,458 hits

 

Agosto: 2008
L M M G V S D
« Giu   Nov »
 123
45678910
11121314151617
18192021222324
25262728293031