Original Article: Johnnie's WinsockTutorial
Author: Johnnie Rose, Jr.

Tutorial do Winsock de Johnnie

Se você propositadamente chegou no meu tutorial do Winsock, provavelmente achou a idéia de seus próprios aplicativos se comunicarem pela Internet como fascinante como um prospecto. Ou, talvez, alguém tenha encontrado a perspectiva igualmente interessante e você foi encarregado de trazer essa visão em realidade. Em ambos os casos, o serviço de rede Winsock e este tutorial irão ajudá-lo a alcançar seus objetivos de empresa comercial, simplesmente explorando o domínio da programação de rede para uso pessoal ou algo intermediário.

Aqui estamos o que vamos cobrir:

  • Criando um soquete de escuta: Dado um pequeno exército de funções de rede, podemos construir um programa que espera pacientemente conexões recebidas? (Sim, nós podemos.)
  • Fazendo suas próprias conexões: Dado mais algumas funções, podemos criar um programa que seja conectado com sucesso a um servidor de escuta? (Sim, nós podemos.)
  • Mais Tutoriais e Links: Quais recursos existem acima e além desse tutorial? I highlight 3 Isso deve mantê-lo ocupado por um tempo (depois de ter digerido meu tutorial, é claro :-).

Embora você esteja ansioso para alcançar esse ponto inspirador no qual sua aplicação tenha feito com sucesso sua primeira conexão, esteja ciente dos conceitos por trás do código. Tente evitar simplesmente manipular o código fornecido de acordo com suas necessidades imediatas e, em vez disso, identificar os requisitos de sua aplicação e somente implementar o que parece ser a melhor solução. Isso é o suficiente da minha Zen of Software Development conselhos para agora; vamos fazer alguma programação de rede ...

Sinta-se livre para baixe toda a listagem do código do tutorial. Lembre-se disso qualquer código apresentado neste tutorial deve ser vinculado à biblioteca Winsock, geralmente wsock32.lib ou algo similarmente chamado. Além disso, ao usar o código exatamente como apresentado no tutorial em seu próprio IDE (Dev-C ++, Microsoft VC ++, C ++ Builder, etc.), Escolha criar um projeto do Windows com um WinMain() para evitar erros.

Criando um soquete de escuta

As aplicações que atendem máquinas externas são chamadas de servidores. As aplicações de servidor escutam os clientes inicializando um ou mais soquetes de escuta. Quando um cliente se conecta a um desses soquetes de escuta, o servidor recebe uma notificação da Winsock, aceita a conexão e começa a despachar e interceptar mensagens de e para o novo cliente. Talvez o método mais simples pelo qual os servidores manipulem múltiplos clientes é gerar um novo tópico para cada conexão de cliente. Este modelo de servidor geralmente utiliza sockets de bloqueio, que pausam temporariamente para aguardar dados recebidos, uma nova conexão e outros eventos de rede. Em primeiro lugar, vamos identificar algumas estruturas que precisaremos para inicializar um soquete de bloqueio:

  • WSADATA: Essa estrutura é usada para consultar o sistema operacional para a versão do Winsock que nosso código requer. Um aplicativo chama WSAStartup () para inicializar a Winsock DLL correta.
  • SOCKET: Um objeto (na verdade, é definido como um u_int, inteiro não assinado, em winsock.h---good to know for smalltalk at parties) usado por aplicativos para armazenar um identificador de soquete.
  • SOCKADDR_IN: Um aplicativo utiliza essa estrutura para especificar como um soquete deve operar. SOCKADDR_IN contém campos para um endereço IP e um número de porta:

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

};

O primeiro campo é o tipo de protocolo, que geralmente é AF_INET (TCP / IP). Como um soquete de escuta não está preocupado com o endereço de rede da máquina em que reside, o Winsock atribui automaticamente um endereço de IP e um número de porta para soquetes de escuta após a criação.

Construiremos nosso primeiro servidor de escuta com as estruturas acima e um pequeno exército de funções de rede:

#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);

}

Uma coisa que você pode notar imediatamente sobre o código é a quantidade de esforço colocada na verificação de erros. Sempre que ocorre um erro, o código obtém um código de erro específico com WSAGetLastError () e armazena o resultado em nret. O código de erro é então enviado juntamente com uma seqüência de caracteres indicando o nome da função falhada para uma função personalizada chamada ReportError (). Lá, uma mensagem de erro é construída e mostrada ao usuário com uma chamada para MessageBox (), que faz parte do WinAPI padrão. Por exemplo, a escutar () falhou com um código de erro de 10093 (definido como WSANOTINITIALISED), a seqüência de erro concluída seria "Ligar para ouvir () retornou o erro 10093!". Você, o desenvolvedor prudente, procuraria o código e descobriria que o erro ocorreu porque uma chamada bem-sucedida para o WSAStartup () ainda não havia sido feita.

Aleksandar Pavlov expandiu este ReportError () para incluir descrições para cerca de uma dúzia de erros de soquete comuns. Usando sua versão atualizada, você não precisará mais pesquisar o que o código significa, e seu programa se torna muito mais fácil de usar com muito pouco esforço de sua parte.

Também estão incluídos para NETWORK_ERROR e NETWORK_OK. Estes podem ser úteis ao verificar o valor de retorno de suas próprias funções de rede. Se suas funções retornassem um desses valores, a função de chamada poderia realizar um teste de igualdade simples para revelar quaisquer erros: se (myNetworkingFunction () == NETWORK_ERROR) {...}. A função de chamada pode então obter um código específico com WSAGetLastError () e lidar com o erro em conformidade. Em última análise, a implementação de um bom esquema de tratamento de erros agora salvará muitos dias ou semanas de tempo de desenvolvimento, pois você saberá instantaneamente por que seu programa falhou.

Além de retornar uma nova conexão de cliente, accept () permite que o servidor extraia informações sobre o cliente e não através de métodos que exigem chamadas de função extra ou tempo (o que pode se tornar um problema nos servidores de jogos, onde a velocidade do loop de aceitação é especialmente crítico). Para tirar proveito desta funcionalidade, passe o endereço de um sockaddr_in struct cast para um sockaddr pointer, ou seja, (LPSOCKADDR) e aSockaddrInStructure. Além disso, declare uma variável inteira, defina o valor do int para o tamanho da estrutura sockaddr e passe o endereço do inteiro como o terceiro parâmetro. Se a informação de endereço for devolvida após a chamada de função, o parâmetro de comprimento deve estar presente.

jdarnold nos adverte para não acreditar na documentação do MSDN a respeito deste terceiro parâmetro: "Os documentos do MSDN implicam que você não precisa passar no addrlen, que é apenas um parâmetro de saída opcional, mas eles estão errados. Inbound diz quantos bytes estão em o buffer sockaddr e o Winsock [Winsock] preenchem o número de [Winsock] usado. Se você passar zero como o len, [Winsock] não tocará o buffer ".

Este não é um servidor, uma vez que espera que apenas um usuário se conecte e depois desconecte imediatamente, mas esse é o design mais básico. Apenas para limpar as coisas, uma chamada para WSAStartup () inclui uma PALAVRA especificando a versão que deseja carregar (neste caso é 1.1) e o endereço de uma estrutura WSADATA. Em seguida, abordaremos como se conectar a outros computadores.

Fazendo suas próprias conexões

Criar um soquete para se conectar a outra pessoa usa a maioria das mesmas funções, com exceção da estrutura HOSTENT:

  • HOSTENT: Uma estrutura usada para informar o soquete a qual computador e porta se conectar. Essas estruturas geralmente aparecem como variáveis LPHOSTENT, que são apenas ponteiros para as estruturas HOSTENT. Como você codifica para o Windows, você geralmente descobrirá que qualquer tipo de dados precedido por LP denota que o tipo é realmente um ponteiro para um tipo "base" (por exemplo, LPCSTR é um ponteiro para uma string C, também conhecido como char * ).

Então, vamos direto ao código:

#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);

}

A linha mais complicada na listagem é a seguinte:

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

porque executa várias operações - uma delas relativamente oculta - de uma só vez. Vamos separá-lo passo a passo:

O membro h_addr_list da estrutura HOSTENT é basicamente definido como char ** h_addr_list, que é uma matriz de strings, ou char * 's. gethostbyname () identificou e copiou todos os endereços conhecidos do servidor para esta lista. No entanto, o conceito de múltiplos endereços é fundamentalmente sensato? Na verdade, sim. O seu computador, de fato, tem uma série de endereços de rede geral. Seu endereço de Internet pode ser 205.182.67.96, seu endereço de LAN pode ser 10.0.0.2 e todos os computadores nos quais o Windows está instalado naturalmente têm um endereço "loopback" de 127.0.0.1, usado pelo computador para se referir a si próprio na rede local . O mesmo conceito se aplica ao domínio dos endereços da Internet ou IP, razão pela qual é necessária uma lista em vez de espaço de armazenamento para um único endereço. Observe que o preferido O endereço, ou seja, o endereço mais acessível, é sempre copiado para o primeiro elemento da lista, seguido do segundo ou outros endereços preferidos.

O que é * hostEntry-> h_addr_list fazendo? Você pode adivinhar que o operador de deferência (*) está sendo usado para acessar um único endereço na lista. No entanto, ao não fornecer um índice específico, a operação de desreferência revela automaticamente o primeiro endereço preferido. Essa seção específica é equivalente a * hostEntry-> h_addr_list [0], que é garantido existir, uma vez que o servidor deve ter pelo menos um endereço.

Em seguida, o char * retornado pela operação de desenergização é lançado em um in_addr * ou LPIN_ADDR. Finalmente, outra operação de deferência é realizada para retornar a estrutura in_addr referida pelo ponteiro, que só pode conter um único endereço. A estrutura resultante in_addr é atribuída a serverInfo.sin_addr. A conexão subseqüente () leva o endereço como um parâmetro ao formar uma conexão com o servidor.

Se o endereço IP do servidor for conhecido, um HOSTANTE válido pode ser obtido através do uso de gethostbyaddr () (em oposição a gethostbyname () usado na lista anterior):

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

}

Nesse caso, inet_addr () é utilizado para copiar uma string que denote o endereço IP diretamente em uma estrutura in_addr. Posteriormente, o endereço da estrutura é convertido em um char const * conforme exigido por gethostbyaddr (). Ambos os métodos são referidos como a resolução do endereço do servidor, uma vez que o Winsock retorna registros de endereços completos a partir de informações parciais.

Algumas notas adicionais: a porta 80 foi usada simplesmente porque as transferências de páginas da Internet ocorrem por essa porta. Se você enviasse uma string para um servidor web solicitando um arquivo específico e tentasse receber algo de volta, você teria um navegador web muito simples. Claro, essa string deve incluir um comando HTTP completo. É ótimo que possamos ouvir e conectar-se a outros computadores, mas a comunicação também envolve o envio e o recebimento.

Envio e recebimento

O envio é manipulado, convenientemente, pela função send ():

int send(

  SOCKET s,

  const char * FAR buf,

  int len,

  int flags

);

Basicamente, você copiaria o que queria em um buffer e usaria a função send () em um soquete conectado para fazer com que os dados fossem para a outra extremidade:

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

}

Receber é o mesmo processo, para trás:

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

}

What's interesting to O interessante é notar que há um botão na barra de ferramentas no Microsoft Outlook com o nome "Enviar / Recv". O "Receber" é abreviado para "Recv" simplesmente para garantir que o botão esteja certo ou o hábito de um programador digitar recv () tantas vezes? Formar suas próprias teorias de conspiração (novamente, bom para smalltalk em festas).

Foi aqui que encontrei um pequeno problema ao escrever meus próprios programas Winsock. O uso de recv () é ótimo quando você sabe exatamente quanto você receberá dados (como em um jogo, onde o primeiro byte pode ser um comando e o próximo byte ser um parâmetro, etc.), mas quando você don Não sei, o que você faz? Se os dados que você está recebendo são encerrados por um caractere de nova linha (um problema comum com clientes Java falando com servidores C), você pode escrever uma função readLine () para capturar tudo até esse personagem. Aqui eu usei:

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);

      }

   }

}

Um vetor é utilizado em vez de uma matriz porque o seu espaço de armazenamento pode ser aumentado automaticamente para se adequar ao comprimento da linha. Se recv () retornar um erro (indicado por bytesReceived sendo menor que zero), NULL é retornado. Uma vez que esta é uma possibilidade, as funções de chamada devem garantir que a string retornada da readLine () seja válida antes de usar. Dentro do loop, um único char é recebido do soquete e, se não um caractere de nova linha, adicionado ao vetor. Se for um caractere de nova linha, o conteúdo do vetor é copiado para uma string C e retornado. A string é declarada como um char maior que o vetor e memset () 'ted to zero para que a linha retornada seja automaticamente encerrada em NULL. Terminar strings com NULL impede erros incomuns e, geralmente, é uma boa prática de programação.

Nor apresenta esta versão inteligentemente aprimorada com suporte para backspaces e a capacidade de mudar o personagem da nova linha facilmente:

// 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;

		}

	}

}

Soquetes não bloqueáveis e assíncronos

Até este ponto, falamos sobre o bloqueio de sockets, onde chamar uma função, como aceitar (), espera indefinidamente para que um usuário se conecte. Um soquete sem bloqueio retorna imediatamente sempre que é dito para fazer algo, seja com um resultado bem-sucedido, um erro ou nada (indicando que haverá algo para receber mais tarde). A desvantagem de usar este tipo é que você precisará consultar manualmente o soquete para ver se um resultado veio em cada função que você chama. Você pode passar um conjunto de soquetes para a função select () para ver quais estão prontos para leitura, escrita ou erros devolvidos.

As funções que utilizam soquetes assíncronos também retornam imediatamente, mas você pode especificar uma mensagem para enviar ao seu procedimento de janela quando ocorreu um evento especificado. Por exemplo, você pode enviar o Soquete a uma mensagem SOCKET_GOTMSG sempre que receber algo. Normalmente, é inteligente verificar erros (pesados, mas necessários) quando você recebe uma mensagem de soquete para evitar problemas desnecessários mais tarde. Primeiro, vamos definir algumas funções que usaremos para configurar um soquete assíncrono:

  • int WSAAsyncSelect ( SOCKET s, HWND hwnd, unsigned int wMsg, long lEvent )
    Esta função é usada para identificar um soquete como assíncrono e associar uma mensagem com ele. s é o soquete com o qual você está trabalhando. hwnd é o identificador para a janela que receberá a mensagem quando o soquete gerar um evento. O wMsg é a mensagem que deseja enviar ao seu procedimento de janela (um exemplo é a mensagem SOCKET_GOTMSG acima). O parâmetro lEvent requer uma ou mais bandeiras que informam o soquete sobre quais eventos enviarem sua mensagem. Algumas dessas bandeiras são:
    • FD_READ: Socket está pronto para receber dados
    • FD_WRITE: Socket está pronto para enviar dados
    • FD_ACCEPT: Usado em servidores, esta mensagem indica que um usuário conectou
    • FD_CONNECT: Usado em aplicativos do cliente, esta mensagem informa que a tomada foi conectada
    • FD_CLOSE: O soquete acabou de ser fechado
  • WSAGETSELECTERROR ( LPARAM lparam )
    Determina se o soquete retornou um erro. Tecnicamente, esta não é uma função, mas uma macro (Você pode realmente gerar smalltalk em festas com esse pequeno fato).
  • WSAGETSELECTEVENT ( LPARAM lparam )
    Outra macro útil definida no winsock2.h é WSAGETSELECTEVENT (), que é usado para ver exatamente o que o soquete fez.

Então, vamos configurar um soquete assíncrono:

// We begin by creating a flag that Windows will use to contact us when something happens

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

// Somewhere in our initialization code after CreateWindow (), we call 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.

// In our window procedure (the function which handles all the messages that Windows sends to your app)

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



	}

}

Observe que você não pode definir uma mensagem para cada evento, como SOCKET_GOTMSG para FD_READ e, em seguida, SOCKET_CONNECTED para FD_CONNECT. Isso ocorre porque as chamadas repetidas para WSAAsyncSelect () para configurar cada sinalizador cancelarão os efeitos da última chamada para WSAAsyncSelect ().

Mais Tutoriais e Links

Eu escrevi este tutorial em dezembro de 2000, e os sete ou mais anos desde então viram um fluxo constante de visitantes e melhorias. Espero que tenha gostado de ler tanto quanto gostei de escrever: obrigado pelo uso do Tutorial Winsock de Johnnie. O acima é apenas uma breve visão geral das possibilidades que você pode alcançar através do Winsock, e outros fizeram um trabalho muito melhor do que eu ao examinar as especificidades desse assunto:

(Mova o mouse sobre a capa do livro para obter mais informações.)

Effective TCP/IP Programming: 44 Tips to Improve Your Network Programs TCP/IP Sockets in C: Practical Guide for Programmers Programming Windows, Fifth Edition 1 year subscription to Maxim magazine

Eu concordo com Thomas Bleeker (MadWizard) que "a programação de rede parece mais fácil do que é". Não consigo impressionar a importância de praticar o uso dessas funções junto com um depurador para que você possa ver o que está acontecendo. Em última análise, você terá uma compreensão muito melhor de como as coisas funcionam se você entende errado, investigue por que você entendeu errado e experimente o prazer de corrigir isso. Cometer erros, em outras palavras, é como aprendemos.

Como posso melhorar?

Existe alguma coisa que precise de esclarecimentos? O tutorial não aborda um tópico relacionado ao Winsock sobre o qual você queria saber? O tutorial atendeu suas necessidades como desenvolvedor de software? É divertido? escrito com inteligência? excessivamente simplista? ou simplesmente?