Traduction 1001-soldes.com ©2017 Tous droits réservés | Texte original : http://johnnie.jerrata.com/winsocktutorial/

Johnnie's
Winsock
Didactiel
By Johnnie Rose, Jr

Si vous êtes arrivé volontairement à mon tutoriel Winsock, vous avez probablement trouvé l'idée de vos propres applications de communiquer via Internet comme une perspective aussi fascinante que j'ai. Ou, peut-être que quelqu'un d'autre a trouvé la perspective tout aussi intéressante et vous avez été chargé de réaliser cette vision dans la réalité. Dans les deux cas, le service réseau Winsock et ce tutoriel vous aideront à atteindre vos objectifs d'entreprise commerciale, en explorant simplement le domaine de la programmation réseau pour un usage personnel ou quelque chose d'intermédiaire.

Voici ce que nous allons couvrir:

Bien que vous soyez impatient d'atteindre ce point impressionnant auquel votre application a réussi à établir sa première connexion, soyez conscient des concepts sous-jacents au code. Essayez d'éviter de simplement manipuler le code donné pour répondre à vos besoins immédiats et à la place d'identifier les exigences de votre application et ensuite seulement mettre en œuvre ce qui semble être la meilleure solution. C'est assez de mon Zen de conseil en développement logiciel pour le moment; Faisons une programmation réseau ...

N'hésitez pas à télécharger la liste complète du code tutoriel . N'oubliez pas que tout code présenté dans ce didacticiel doit être lié à la bibliothèque Winsock , généralement wsock32.lib ou à un nom similaire. En outre, lorsque vous utilisez du code tel que présenté dans le didacticiel de votre propre IDE (Dev-C ++, Microsoft VC ++, C ++ Builder, etc.), choisissez de créer un projet Windows avec WinMain () pour éviter les erreurs.

Créer un socket d'écoute

Les applications de maintenance de machines externes sont appelées serveurs. Les applications serveur écoutent les clients en initialisant une ou plusieurs sockets d'écoute. Lorsqu'un client se connecte à l'un de ces sockets d'écoute, le serveur reçoit une notification de Winsock, accepte la connexion et commence à envoyer et à intercepter les messages depuis et vers le nouveau client. La méthode la plus simple par laquelle les serveurs gèrent plusieurs clients est peut-être de générer un nouveau thread pour chaque connexion client. Ce modèle de serveur utilise le plus souvent des sockets de blocage, qui s'arrêtent temporairement pour attendre les données entrantes, une nouvelle connexion et d'autres événements réseau. Tout d'abord, identifions certaines structures dont nous aurons besoin pour initialiser un socket de blocage:

struct sockaddr_in

{

  short sin_family;         // Protocol type

  u_short sin_port;         // Port number of socket

  struct in_addr sin_addr;  // IP address

  char sin_zero[8];         // Unused

};

Le premier champ est le type de protocole, qui est généralement AF_INET (TCP / IP). Comme une prise d'écoute n'est pas concernée par l'adresse réseau de la machine sur laquelle elle réside, Winsock attribue automatiquement une adresse IP et un numéro de port aux prises d'écoute lors de la création.

Nous allons construire notre premier serveur d'écoute avec les structures ci-dessus et une petite armée de fonctions réseau:

<
#include <windows.h>

#include <winsock.h>

#include <stdio.h>



#define NETWORK_ERROR -1

#define NETWORK_OK     0



void ReportError(int, const char *);





int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmd, int nShow)

{

	WORD sockVersion;

	WSADATA wsaData;

	int nret;



	sockVersion = MAKEWORD(1, 1);			// We'd like Winsock version 1.1





	// We begin by initializing Winsock

	WSAStartup(sockVersion, &wsaData);





	// Next, create the listening socket

	SOCKET listeningSocket;



	listeningSocket = socket(AF_INET,		// Go over TCP/IP

			         SOCK_STREAM,   	// This is a stream-oriented socket

				 IPPROTO_TCP);		// Use TCP rather than UDP



	if (listeningSocket == INVALID_SOCKET)

	{

		nret = WSAGetLastError();		// Get a more detailed error

		ReportError(nret, "socket()");		// Report the error with our custom function



		WSACleanup();				// Shutdown Winsock

		return NETWORK_ERROR;			// Return an error value

	}





	// Use a SOCKADDR_IN struct to fill in address information

	SOCKADDR_IN serverInfo;



	serverInfo.sin_family = AF_INET;

	serverInfo.sin_addr.s_addr = INADDR_ANY;	// Since this socket is listening for connections,

							// any local address will do

	serverInfo.sin_port = htons(8888);		// Convert integer 8888 to network-byte order

							// and insert into the port field





	// Bind the socket to our local server address

	nret = bind(listeningSocket, (LPSOCKADDR)&serverInfo, sizeof(struct sockaddr));



	if (nret == SOCKET_ERROR)

	{

		nret = WSAGetLastError();

		ReportError(nret, "bind()");



		WSACleanup();

		return NETWORK_ERROR;

	}





	// Make the socket listen

	nret = listen(listeningSocket, 10);		// Up to 10 connections may wait at any

							// one time to be accept()'ed



	if (nret == SOCKET_ERROR)

	{

		nret = WSAGetLastError();

		ReportError(nret, "listen()");



		WSACleanup();

		return NETWORK_ERROR;

	}





	// Wait for a client

	SOCKET theClient;



	theClient = accept(listeningSocket,

			   NULL,			// Optionally, address of a SOCKADDR_IN struct

			   NULL);			// Optionally, address of variable containing

							// sizeof ( struct SOCKADDR_IN )



	if (theClient == INVALID_SOCKET)

	{

		nret = WSAGetLastError();

		ReportError(nret, "accept()");



		WSACleanup();

		return NETWORK_ERROR;

	}





	// Send and receive from the client, and finally,

	closesocket(theClient);

	closesocket(listeningSocket);





	// Shutdown Winsock

	WSACleanup();

	return NETWORK_OK;

}





void ReportError(int errorCode, const char *whichFunc)

{

   char errorMsg[92];					// Declare a buffer to hold

							// the generated error message

   

   ZeroMemory(errorMsg, 92);				// Automatically NULL-terminate the string



   // The following line copies the phrase, whichFunc string, and integer errorCode into the buffer

   sprintf(errorMsg, "Call to %s returned error %d!", (char *)whichFunc, errorCode);



   MessageBox(NULL, errorMsg, "socketIndication", MB_OK);

}

Une chose que vous pouvez immédiatement remarquer à propos du code est la quantité d'effort mis dans la vérification d'erreur. Chaque fois qu'une erreur se produit, le code obtient un code d'erreur spécifique avec WSAGetLastError () et stocke le résultat dans nret. Le code d'erreur est ensuite envoyé avec une chaîne indiquant le nom de la fonction ayant échoué à une fonction personnalisée nommée ReportError (). Là, un message d'erreur est construit et montré à l'utilisateur avec un appel à MessageBox (), qui fait partie du WinAPI standard. Par exemple, si listen () avait échoué avec un code d'erreur de 10093 (défini comme WSANOTINITIALISED), la chaîne d'erreur terminée serait "Appel à listen () erreur retournée 10093!". Vous, le développeur prudent, recherchez alors le code et découvrez que l'erreur s'est produite car un appel réussi à WSAStartup () n'a pas encore été effectué.

Aleksandar Pavlov a développé ReportError () pour inclure des descriptions d'une douzaine d'erreurs de socket courantes. En utilisant sa version mise à niveau , vous n'aurez plus besoin de rechercher ce que le code signifie, et votre programme devient beaucoup plus convivial avec très peu d'effort de votre part.

Les définitions de NETWORK_ERROR et NETWORK_OK sont également incluses. Ceux-ci peuvent être utiles lors de la vérification de la valeur de retour de vos propres fonctions réseau. Si vos fonctions renvoyaient l'une de ces valeurs, la fonction appelante pourrait effectuer un test d'égalité simple pour révéler toute erreur: if (myNetworkingFunction () == NETWORK_ERROR) {...}. La fonction appelante peut alors obtenir un code spécifique avec WSAGetLastError () et gérer l'erreur en conséquence. En fin de compte, la mise en œuvre d'un bon système de gestion des erreurs vous permettra d'économiser de nombreux jours ou semaines de développement car vous saurez instantanément pourquoi votre programme a échoué.

En plus de renvoyer une nouvelle connexion client, accept () permet au serveur d'extraire des informations sur le client plutôt que par des méthodes nécessitant des appels de fonction supplémentaires ou du temps (ce qui peut poser un problème dans les serveurs de jeu critique). Pour tirer parti de cette fonctionnalité, transmettez l'adresse d'un cast struct sockaddr_in à un pointeur sockaddr, c'est-à-dire (LPSOCKADDR) & aSockaddrInStructure. En outre, déclarez une variable entière, définissez la valeur de l'int sur la taille de la structure sockaddr et transmettez l'adresse de l'entier comme troisième paramètre. Si des informations d'adresse doivent être renvoyées après l'appel de fonction, le paramètre de longueur doit être présent.

jdarnold nous avertit de ne pas croire la documentation de MSDN concernant ce troisième paramètre: "Les docs de MSDN impliquent que vous n'avez pas besoin de passer dans addrlen, que c'est juste un paramètre de sortie facultatif, mais ils sont faux Inbound indique combien d'octets dans le tampon sockaddr, et [Winsock] sortant remplit le nombre de [Winsock] utilisé Si vous passez zéro comme len, [Winsock] ne touchera pas le tampon. ""

Ce n'est pas vraiment un serveur car il attend qu'un seul utilisateur se connecte et se déconnecte immédiatement, mais c'est la conception la plus basique. Juste pour clarifier les choses, un appel à WSAStartup () inclut un WORD spécifiant quelle version vous voulez charger (dans ce cas c'est 1.1) et l'adresse d'une structure WSADATA. Ensuite, nous verrons comment se connecter avec d'autres ordinateurs.

Faire vos propres connexions

Créer un socket pour se connecter à quelqu'un d'autre utilise la plupart des mêmes fonctions, à l'exception de la structure HOSTENT:

Alors, allons droit au code:

#include <windows.h>

#include <winsock.h>

#include <stdio.h>



#define NETWORK_ERROR -1

#define NETWORK_OK     0



void ReportError(int, const char *);





int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmd, int nShow)

{

	WORD sockVersion;

	WSADATA wsaData;

	int nret;



	sockVersion = MAKEWORD(1, 1);





	// Initialize Winsock as before

	WSAStartup(sockVersion, &wsaData);





	// Store information about the server

	LPHOSTENT hostEntry;



	hostEntry = gethostbyname("www.yahoo.com");	// Specifying the server by its name;

							// another option: gethostbyaddr()



	if (!hostEntry)

	{

		nret = WSAGetLastError();

		ReportError(nret, "gethostbyname()");	// Report the error as before



		WSACleanup();

		return NETWORK_ERROR;

	}





	// Create the socket

	SOCKET theSocket;



	theSocket = socket(AF_INET,			// Go over TCP/IP

			   SOCK_STREAM,			// This is a stream-oriented socket

			   IPPROTO_TCP);		// Use TCP rather than UDP



	if (theSocket == INVALID_SOCKET)

	{

		nret = WSAGetLastError();

		ReportError(nret, "socket()");



		WSACleanup();

		return NETWORK_ERROR;

	}





	// Fill a SOCKADDR_IN struct with address information

	SOCKADDR_IN serverInfo;



	serverInfo.sin_family = AF_INET;



	// At this point, we've successfully retrieved vital information about the server,

	// including its hostname, aliases, and IP addresses.  Wait; how could a single

	// computer have multiple addresses, and exactly what is the following line doing?

	// See the explanation below.



	serverInfo.sin_addr = *((LPIN_ADDR)*hostEntry->h_addr_list);



	serverInfo.sin_port = htons(80);		// Change to network-byte order and

							// insert into port field





	// Connect to the server

	nret = connect(theSocket,

		       (LPSOCKADDR)&serverInfo,

		       sizeof(struct sockaddr));



	if (nret == SOCKET_ERROR)

	{

		nret = WSAGetLastError();

		ReportError(nret, "connect()");



		WSACleanup();

		return NETWORK_ERROR;

	}





	// Successfully connected!





	// Send/receive, then cleanup:

	closesocket(theSocket);

	WSACleanup();

}





void ReportError(int errorCode, const char *whichFunc)

{

   char errorMsg[92];					// Declare a buffer to hold

							// the generated error message

   

   ZeroMemory(errorMsg, 92);				// Automatically NULL-terminate the string



   // The following line copies the phrase, whichFunc string, and integer errorCode into the buffer

   sprintf(errorMsg, "Call to %s returned error %d!", (char *)whichFunc, errorCode);



   MessageBox(NULL, errorMsg, "socketIndication", MB_OK);

}

La ligne la plus compliquée de la liste est la suivante:

serverInfo.sin_addr = *((LPIN_ADDR)*hostEntry->h_addr_list);

parce qu'il effectue plusieurs opérations --- l'une d'elles relativement cachée --- à la fois. Démenons-nous étape par étape:

Le membre h_addr_list de la structure HOSTENT est essentiellement défini comme char ** h_addr_list, qui est un tableau de chaînes, ou char *. gethostbyname () a identifié et copié toutes les adresses connues du serveur dans cette liste. Cependant, le concept d'adresses multiples est-il fondamentalement logique? En fait, c'est le cas. En fait, votre ordinateur dispose d'un éventail d'adresses réseau générales. Votre adresse Internet peut être 205.182.67.96, votre adresse LAN peut être 10.0.0.2, et tous les ordinateurs sur lesquels Windows est installé ont naturellement une adresse "loopback" de 127.0.0.1, utilisée par l'ordinateur pour se référer à lui-même sur le réseau local . Le même concept s'applique dans le domaine des adresses Internet ou IP, ce qui explique pourquoi une liste est nécessaire plutôt qu'un espace de stockage pour une seule adresse. Notez que l'adresse préférée , c'est-à-dire l'adresse la plus accessible, est toujours copiée dans le premier élément de la liste, suivie de la seconde préférence ou d'autres adresses.

Que fait * hostEntry-> h_addr_list? Vous pourriez deviner que l'opérateur de déférence (*) est utilisé pour accéder à une seule adresse dans la liste. Cependant, en omettant de fournir un index spécifique, l'opération de déréférencement révèle automatiquement la première adresse préférée . Cette section particulière est équivalente à * hostEntry-> h_addr_list [0], dont l'existence est garantie puisque le serveur doit avoir au moins une adresse.

Ensuite, le caractère * renvoyé par l'opération de déréférencement est transtypé dans un fichier in_addr * ou LPIN_ADDR. Enfin, une autre opération de déférence est exécutée pour renvoyer la structure in_addr désignée par le pointeur, qui ne peut contenir qu'une seule adresse. La structure in_addr résultante est ensuite affectée à serverInfo.sin_addr. Le suivant connect () prend l'adresse en tant que paramètre lors de la formation d'une connexion au serveur.

Si l'adresse IP du serveur est connue, un HOSTENT valide peut être obtenu en utilisant gethostbyaddr () (par opposition à gethostbyname () utilisé dans la liste précédente):

LPHOSTENT hostEntry;

in_addr iaHost;



iaHost.s_addr = inet_addr("204.52.135.52");



hostEntry = gethostbyaddr((const char *)&iaHost, sizeof(struct in_addr), AF_INET);



if (!hostEntry)

{

	// Handle accordingly

}

La ligne la plus compliquée de la liste est la suivante:

serverInfo.sin_addr = *((LPIN_ADDR)*hostEntry->h_addr_list);

parce qu'il effectue plusieurs opérations --- l'une d'elles relativement cachée --- à la fois. Démenons-nous étape par étape:

Le membre h_addr_list de la structure HOSTENT est essentiellement défini comme char ** h_addr_list, qui est un tableau de chaînes, ou char *. gethostbyname () a identifié et copié toutes les adresses connues du serveur dans cette liste. Cependant, le concept d'adresses multiples est-il fondamentalement logique? En fait, c'est le cas. En fait, votre ordinateur dispose d'un éventail d'adresses réseau générales. Votre adresse Internet peut être 205.182.67.96, votre adresse LAN peut être 10.0.0.2, et tous les ordinateurs sur lesquels Windows est installé ont naturellement une adresse "loopback" de 127.0.0.1, utilisée par l'ordinateur pour se référer à lui-même sur le réseau local . Le même concept s'applique dans le domaine des adresses Internet ou IP, ce qui explique pourquoi une liste est nécessaire plutôt qu'un espace de stockage pour une seule adresse. Notez que l'adresse préférée , c'est-à-dire l'adresse la plus accessible, est toujours copiée dans le premier élément de la liste, suivie de la seconde préférence ou d'autres adresses.

Que fait * hostEntry-> h_addr_list? Vous pourriez deviner que l'opérateur de déférence (*) est utilisé pour accéder à une seule adresse dans la liste. Cependant, en omettant de fournir un index spécifique, l'opération de déréférencement révèle automatiquement la première adresse préférée . Cette section particulière est équivalente à * hostEntry-> h_addr_list [0], dont l'existence est garantie puisque le serveur doit avoir au moins une adresse.

Ensuite, le caractère * renvoyé par l'opération de déréférencement est transtypé dans un fichier in_addr * ou LPIN_ADDR. Enfin, une autre opération de déférence est exécutée pour renvoyer la structure in_addr désignée par le pointeur, qui ne peut contenir qu'une seule adresse. La structure in_addr résultante est ensuite affectée à serverInfo.sin_addr. Le suivant connect () prend l'adresse en tant que paramètre lors de la formation d'une connexion au serveur.

Si l'adresse IP du serveur est connue, un HOSTENT valide peut être obtenu en utilisant gethostbyaddr () (par opposition à gethostbyname () utilisé dans la liste précédente):

LPHOSTENT hostEntry;

in_addr iaHost;



iaHost.s_addr = inet_addr("204.52.135.52");



hostEntry = gethostbyaddr((const char *)&iaHost, sizeof(struct in_addr), AF_INET);



if (!hostEntry)

{

	// Handle accordingly

}

Dans ce cas, inet_addr () est utilisé pour copier une chaîne indiquant l'adresse IP directement dans une structure in_addr. Ensuite, l'adresse de la structure est transtypée en un const char * comme requis par gethostbyaddr (). Les deux méthodes sont appelées résoudre l'adresse du serveur puisque Winsock renvoie des enregistrements d'adresse complète à partir d'informations partielles.

Quelques notes supplémentaires: le port 80 a été utilisé simplement parce que les transferts de pages Web se produisent sur ce port. Si vous deviez envoyer une chaîne à un serveur Web demandant un fichier spécifique et tenter de récupérer quelque chose, vous auriez un navigateur Web très simple. Bien sûr, cette chaîne doit inclure une commande HTTP complète. C'est génial de pouvoir écouter et se connecter à d'autres ordinateurs, mais la communication implique aussi l'envoi et la réception.

Envoi et réception

L'envoi est géré, de manière pratique, par la fonction send ():

int send(

  SOCKET s,

  const char * FAR buf,

  int len,

  int flags

);

Fondamentalement, vous copieriez ce que vous vouliez dans un tampon et utiliser la fonction send () sur un socket connecté pour que les données passent à l'autre extrémité:

char buffer[256];		// Declaring a buffer on the stack

char *buffer = new char[256];	// or on the heap



ZeroMemory(buffer, 256);

strcpy(buffer, "Pretend this is important data.");



nret = send(theSocket,

	    buffer,

	    strlen(buffer),	// Note that this specifies the length of the string; not

				// the size of the entire buffer

	    0);			// Most often is zero, but see MSDN for other options



delete [] buffer;		// If and only if the heap declaration was used



if (nret == SOCKET_ERROR)

{

	// Get a specific code

	// Handle accordingly

	return NETWORK_ERROR;

} else {

	// nret contains the number of bytes sent

}

Recevoir est le même processus, à l'envers:

char buffer[256];		// On the stack

char *buffer = new char[256];	// or on the heap



nret = recv(theSocket,

	    buffer,

	    256,		// Complete size of buffer

	    0);



delete [] buffer;		// Manipulate buffer, then delete if and only if

				// buffer was allocated on heap



if (nret == SOCKET_ERROR)

{

	// Get a specific code

	// Handle accordingly

	return NETWORK_ERROR;

} else {

	// nret contains the number of bytes received

}

Ce qui est intéressant à noter est qu'il y a un bouton sur la barre d'outils dans Microsoft Outlook intitulé "Envoyer / Recv". Est-ce que "Receive" est abrégé en "Recv" simplement pour s'assurer que le bouton a l'air correct, ou est-ce l'habitude du programmeur de taper recv () tant de fois? Former vos propres théories de conspiration ( encore une fois, bon pour smalltalk lors des fêtes ).

C'est là que j'ai rencontré un petit problème lors de l'écriture de mes propres programmes Winsock. Utiliser simplement recv () est génial quand vous savez exactement combien de données vous allez recevoir (comme dans un jeu, où le premier octet peut être une commande et l'octet suivant un paramètre, etc.), mais quand vous ne le faites pas, Ne sais pas, que faites-vous? Si les données que vous recevez sont terminées par un caractère de nouvelle ligne (un problème courant avec les clients Java qui parlent aux serveurs C), vous pouvez écrire une fonction readLine () pour capturer tout ce qui précède ce caractère. Voici ce que j'ai utilisé:

char * readLine()

{

   vector theVector;

   char buffer;

   int bytesReceived;



   while (true)

   {

      bytesReceived = recv(theSocket, &buffer, 1, 0);

      if (bytesReceived <= 0)

         return NULL;



      if (buffer == '\n')

      {

         char *pChar = new char[theVector.size() + 1];

         memset(pChar, 0, theVector.size() + 1);



         for (int f = 0; f < theVector.size(); f++)

            pChar[f] = theVector[f];



         return pChar;

      } else {

         theVector.push_back(buffer);

      }

   }

}

Un vecteur est utilisé à la place d'un tableau parce que son espace de stockage peut être augmenté automatiquement pour s'adapter à la longueur de la ligne. Si recv () renvoie une erreur (indiquée par bytesReceived étant inférieur à zéro), NULL est renvoyé. Comme c'est une possibilité, les fonctions d'appel doivent s'assurer que la chaîne renvoyée par readLine () est valide avant utilisation. À l'intérieur de la boucle, un seul caractère est reçu de la socket et, si ce n'est pas un caractère de retour à la ligne, ajouté au vecteur. S'il s'agit d'un caractère de retour à la ligne, le contenu du vecteur est copié dans une chaîne C et renvoyé. La chaîne est déclarée être un caractère plus grand que le vecteur et memset () est mis à zéro pour que la ligne retournée soit automatiquement terminée par NULL. La fin des chaînes avec NULL empêche les erreurs inhabituelles et constitue généralement une bonne pratique de programmation.

Nor présente cette version intelligemment améliorée avec le soutien pour les backspaces et la possibilité de changer le caractère de nouvelle ligne facilement:

// Code originally written by Nor.  Modified slightly to

// support the MessageBox() API, make logic more readable,

// align spacing, and add comments.  Posted with permission.



#define backKey '\b'					// To disable backspaces, #define backKey NULL

#define newLine '\n'

#define endStr  '\0'



char *readLine(SOCKET s)

{

	vector theVector;

	char buffer;

	char *pChar;

	int bytesReceived;



	while (true)

	{

		bytesReceived = recv(s, &buffer, 1, 0);



		if (bytesReceived <= 0)

		{

			MessageBox(NULL, "recv() returned nothing.", "socketIndication", MB_OK);

			return NULL;

		}



		switch (buffer)

		{

			case backKey:			// Handle backspace

				if (theVector.size() > 0)

					theVector.pop_back();

				break;

			case endStr:			// If end of string char reached,

			case newLine:			// or if end of line char reached,

				pChar = new char[theVector.size() + 1];

				memset(pChar, 0, theVector.size() + 1);



				for (int f = 0; f < theVector.size(); f++)

					pChar[f] = theVector[f];

				return pChar;

				break;

			default:			// Any regular char

				theVector.push_back(buffer);

				break;

		}

	}

}

Sockets non bloquants et asynchrones

Jusqu'à présent, nous avons parlé de bloquer les sockets, où appeler une fonction telle que accept () attend indéfiniment qu'un utilisateur se connecte. Un socket non bloquant revient immédiatement à chaque fois qu'il est demandé de faire quelque chose, soit avec un résultat réussi, soit une erreur, soit rien (indiquant qu'il y aura quelque chose à recevoir plus tard). L'inconvénient de l'utilisation de ce type est que vous devrez interroger manuellement le socket pour voir si un résultat est entré dans chaque fonction que vous appelez. Vous pouvez passer un ensemble de sockets à la fonction select () pour voir lesquels sont prêts pour la lecture, l'écriture ou ont renvoyé des erreurs.

Les fonctions utilisant des sockets asynchrones sont également renvoyées immédiatement, mais vous pouvez spécifier un message à envoyer à votre procédure de fenêtre lorsqu'un événement spécifié s'est produit. Par exemple, vous pouvez demander au socket d'envoyer un message SOCKET_GOTMSG chaque fois qu'il reçoit quelque chose. Habituellement, il est judicieux de vérifier les erreurs (lourdes, mais nécessaires) lorsque vous recevez un message de socket pour éviter de causer des problèmes inutiles plus tard. Tout d'abord, définissons certaines fonctions que nous utiliserons pour configurer un socket asynchrone:

Alors, configurons un socket asynchrone:

// Nous commençons par créer un drapeau que Windows utilisera pour nous contacter quand quelque chose arrive

#define THERE_WAS_A_SOCKET_EVENT	WM_USER + 100	// WM_USER is a base for custom messages

// Quelque part dans notre code d'initialisation après CreateWindow (), nous appelons WSAAsyncSelect ()

WSAAsyncSelect ( theSocket, hwnd, THERE_WAS_A_SOCKET_EVENT, FD_READ | FD_WRITE | FD_CONNECT | ... );



// This translates: Windows, please contact me using the THERE_WAS_A_SOCKET_EVENT flag that I

// previously defined whenever there's data to read (FD_READ), or when I'm free to send data

// (FD_WRITE), or when I've successfully connected to someone else (FD_CONNECT), or when...etc.

// Dans notre procédure de fenêtre (la fonction qui gère tous les messages que Windows envoie à votre application)

LRESULT WINAPI TheWindowProcedure ( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam )

{



	switch ( msg )

	{

		case THERE_WAS_A_SOCKET_EVENT:

			if ( WSAGETSELECTERROR ( lParam ) )

			{	// If an error occurred,

				closesocket ( theSocket );

				WSACleanup ();				// Shutdown Winsock

				return NETWORK_ERROR;

			}

			switch ( WSAGETSELECTEVENT ( lParam ) )

			{	// What happened, exactly?

				case FD_READ:

					// Receive data

					break;

				case FD_WRITE:

					// Write data

					break;

				case FD_CONNECT:

					// Just connected to server

					break;

				case ...				// Same setup for other flags

					break;

			}

			break;



		// other case statements with logic that handles other Windows messages



	}

}

Notez que vous ne pouvez pas définir un message pour chaque événement, comme SOCKET_GOTMSG pour FD_READ, puis SOCKET_CONNECTED pour FD_CONNECT. Cela est dû au fait que les appels répétés à WSAAsyncSelect () pour configurer chaque indicateur annuleront les effets du dernier appel à WSAAsyncSelect ().

Plus de tutoriels et de liens

J'ai écrit ce tutoriel en décembre 2000, et les sept années qui se sont écoulées depuis lors ont vu un flux constant de visiteurs et d'améliorations. J'espère que vous avez aimé lire autant que j'ai aimé écrire: merci d'utiliser le tutoriel Winsock de Johnnie . Ce qui précède n'est qu'un bref aperçu des possibilités que vous pouvez atteindre grâce à Winsock, et d'autres ont fait un bien meilleur travail que moi en sondant les détails de ce sujet:

(Passez votre souris sur une couverture de livre pour plus d'informations.)

Programmation TCP / IP efficace: 44 conseils pour améliorer vos programmes réseau Sockets TCP / IP en C: Guide pratique pour les programmeurs Fenêtres de programmation, cinquième édition Abonnement d'un an au magazine Maxim

Je suis d'accord avec Thomas Bleeker (MadWizard) que "la programmation réseau semble plus facile que c'est." Je ne peux pas vous faire comprendre l'importance de pratiquer l'utilisation de ces fonctions avec un débogueur afin que vous puissiez voir ce qui se passe. En fin de compte, vous aurez une meilleure compréhension de la façon dont les choses fonctionnent si vous vous trompez, étudiez les raisons pour lesquelles vous vous êtes trompé et expérimentez le plaisir de faire les choses correctement. Faire des erreurs, en d'autres termes, c'est comment nous apprenons.

Comment puis-je améliorer?

Y a-t-il quelque chose à clarifier? Le didacticiel ne couvre-t-il pas un sujet lié à Winsock que vous aimeriez connaître? Le tutoriel a-t-il répondu à vos besoins en tant que développeur de logiciel? Est-ce amusant? intelligemment écrit? trop simpliste? ou juste?