Hilarity Ensues   A simple collection of Johnnie's musings about his life
h o m e | m y   s o f t w a r e | a b o u t   m e | c o n t a c t   m e
Student StarCraft AI Tournament
Advertise here?
last updated:
Mar
12

---
average rating:

---
comments:
Johnnie's Winsock Tutorial   By Johnnie Rose, Jr.

Anja Skrba has graciously translated my tutorial into Serbo-Croatian. Thanks, Anja!

If you'd like to translate the tutorial into another language, let me know.

If you've purposely arrived at my Winsock tutorial, you've most likely found the idea of your own applications communicating via the Internet as fascinating a prospect as I have. Or, perhaps someone else has found the prospect equally interesting and you have been entrusted with bringing this vision into reality. In either case, the Winsock network service and this tutorial will assist you in achieving your goals of commercial enterprise, simply exploring the realm of network programming for personal use, or something in between.

Here's what we'll be covering:

  • Creating a listening socket: Given a small army of networking functions, can we build a program that patiently waits for incoming connections? (Yes, we can.)
  • Making your own connections: Given a few more functions, can we create a program that successfully links with a listening server? (Yes, we can.)
  • More Tutorials and Links: What resources are there above and beyond this tutorial? I highlight 3 that should keep you busy for a while (after you've digested my tutorial, of course :-).

Though you may be eager to reach that awe-inspiring point at which your application successfully makes its first connection, be aware of the concepts behind the code. Try to avoid simply manipulating the given code to suit your immediate needs and instead identify the requirements of your application and only then implement what seems to be the best solution. That's enough of my Zen of Software Development advice for now; let's do some network programming...

Feel free to download the entire tutorial code listing. Remember that any code presented in this tutorial should be linked with the Winsock library, usually wsock32.lib or something similarly named. Also, when using code exactly as presented in the tutorial in your own IDE (Dev-C++, Microsoft VC++, C++ Builder, etc.), choose to build a Windows project with a WinMain() to avoid errors.

Creating a Listening Socket

Applications servicing outside machines are called servers. Server applications listen for clients by initializing one or more listening sockets. When a client connects to one of these listening sockets, the server receives a notification from Winsock, accepts the connection, and begins to dispatch and intercept messages to and from the new client. Perhaps the most simplistic method by which servers handle multiple clients is to spawn a new thread for each client connection. This server model most often utilizes blocking sockets, which pause temporarily to wait for incoming data, a new connection, and other network events. First, let's identify some structures we'll need to initialize a blocking socket:

  • WSADATA: This structure is used to query the operating system for the version of Winsock our code requires. An application calls WSAStartup() to initialize the correct Winsock DLL.
  • SOCKET: An object (in fact, it's defined as a u_int, unsigned integer, in winsock.h---good to know for smalltalk at parties) used by applications to store a socket handle.
  • SOCKADDR_IN: An application utilizes this structure to specify how a socket should operate. SOCKADDR_IN contains fields for an IP address and port number:


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

};

The first field is the protocol type, which is usually AF_INET (TCP/IP). As a listening socket isn't concerned with the network address of the machine on which it resides, Winsock automatically assigns an IP address and port number to listening sockets upon creation.

We'll build our first listening server with the above structures and a small army of network functions:


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

}

One thing you may immediately notice about the code is the amount of effort put into error checking. Whenever an error occurs, the code obtains a specific error code with WSAGetLastError() and stores the result in nret. The error code is then sent along with a string indicating the name of the failed function to a custom function named ReportError(). There, an error message is constructed and shown to the user with a call to MessageBox(), which is part of the standard WinAPI. For example, had listen() failed with an error code of 10093 (defined as WSANOTINITIALISED), the finished error string would be "Call to listen() returned error 10093!". You, the prudent developer, would then look up the code and discover that the error occurred because a successful call to WSAStartup() hadn't yet been made.

Aleksandar Pavlov expanded this ReportError() to include descriptions for about a dozen common socket errors. Using his upgraded version, you'll no longer need to lookup what the code means, and your program becomes much more user-friendly with very little effort on your part.

Also included are defines for NETWORK_ERROR and NETWORK_OK. These might be useful when checking the return value of your own networking functions. If your functions returned one of these values, the calling function could perform a simple equality test to reveal any errors: if (myNetworkingFunction() == NETWORK_ERROR) {...}. The calling function could then obtain a specific code with WSAGetLastError() and handle the error accordingly. Ultimately, implementing a good error-handling scheme now will save you many days or weeks of development time as you'll instantly know why your program has failed.

In addition to returning a new client connection, accept() allows the server to extract information about the client rather than through methods requiring extra function calls or time (which may become an issue in game servers, where the speed of the accept loop is especially critical). To take advantage of this functionality, pass in the address of a sockaddr_in struct cast to a sockaddr pointer, i.e. (LPSOCKADDR)&aSockaddrInStructure. Also, declare an integer variable, set the value of the int to the sizeof the sockaddr struct, and pass the address of the integer as the third parameter. If address information is to be returned after the function call, the length parameter must be present.

jdarnold warns us not to believe the MSDN documentation regarding this third parameter: "The MSDN docs imply you don't have to pass in addrlen, that it is just an optional output parameter, but they are wrong. Inbound it says how many bytes are in the sockaddr buffer, and outbound [Winsock] fills in how many [Winsock] used. If you pass zero as the len, [Winsock] won't touch the buffer."

This isn't much of a server since it waits for only one user to connect and then immediately disconnects, but that is the most basic design. Just to clear things up, a call to WSAStartup() includes a WORD specifying what version you want to load (in this case it's 1.1) and the address of a WSADATA structure. Next, we'll cover how to connect with other computers.

Making Your Own Connections

Creating a socket to connect to someone else uses most of the same functions, with the exception of the HOSTENT struct:

  • HOSTENT: A structure used to tell the socket to which computer and port to connect. These structures commonly appear as LPHOSTENT variables, which are merely pointers to HOSTENT structures. As you code for Windows, you'll generally find that any data type preceded by LP denotes that the type is actually a pointer to a "base" type (for example, LPCSTR is a pointer to a C string, also known as char *).

So, let's get right to the 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);

}

The most complicated line in the listing is the following:

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

because it performs several operations---one of them relatively hidden---at once. Let's take it apart step by step:

The h_addr_list member of the HOSTENT struct is basically defined as char **h_addr_list, which is an array of strings, or char *'s. gethostbyname() identified and copied all of the known addresses of the server into this list. However, does the concept of multiple addresses fundamentally make sense? Actually, it does. Your computer, in fact, has an array of general networking addresses. Your Internet address may be 205.182.67.96, your LAN address may be 10.0.0.2, and all computers on which Windows is installed naturally have a "loopback" address of 127.0.0.1, used by the computer to refer to itself on the local network. The same concept applies in the realm of Internet addresses or IP's, which is why a list is needed rather than storage space for a single address. Note that the preferred address, that is, the most accessible address, is always copied into the first element of the list, followed by the second preferred or other addresses.

What's *hostEntry->h_addr_list doing? You might guess that the deference operator (*) is being used to access a single address in the list. However, by failing to provide a specific index, the dereference operation automatically reveals the first, preferred address. That particular section is equivalent to *hostEntry->h_addr_list[0], which is guaranteed to exist since the server must have at least one address.

Next, the char * returned by the dereferencing operation is cast into an in_addr * or LPIN_ADDR. Finally, another deference operation is performed to return the in_addr struct referred to by the pointer, which can only hold a single address. The resulting in_addr struct is then assigned to serverInfo.sin_addr. The subsequent connect() takes the one address as a parameter when forming a connection to the server.

If the server's IP address is known, a valid HOSTENT may be obtained through the use of gethostbyaddr() (as opposed to gethostbyname() used in the previous listing):


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

}

In this case, inet_addr() is utilized to copy a string denoting the IP address directly into an in_addr struct. Afterwards, the address of the struct is cast into a const char * as required by gethostbyaddr(). Both methods are referred to as resolving the server address since Winsock returns full address records from partial information.

A few additional notes: port 80 was used simply because Internet web page transfers occur over that port. If you were to send a string to a web server requesting a specific file and attempt to receive something back, you'd have a very simple web browser. Of course, that string must include a full HTTP command. It's great that we can listen and connect to other computers, but communication also involves sending and receiving.

Sending and Receiving

Sending is handled, conveniently enough, by the send() function:


int send(

  SOCKET s,

  const char * FAR buf,

  int len,

  int flags

);

Basically you would copy whatever you wanted into a buffer and use the send() function on a connected socket to make the data go to the other end:


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

}

Receiving is the same process, backwards:


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 note is that there's a button on the toolbar in Microsoft Outlook labeled "Send/Recv." Is "Receive" abbreviated to "Recv" simply to ensure the button looks right, or is it a programmer's habit from typing recv() so many times? Form your own conspiracy theories (again, good for smalltalk at parties).

This is where I ran into a little problem when writing my own Winsock programs. Just using recv() is great when you know exactly how much data you'll be receiving (such as in a game, where the first byte can be a command and the next byte be a parameter, etc.), but when you don't know, what do you do? If the data you're receiving is terminated by a newline character (a common problem with Java clients talking to C servers), you can write a readLine() function to capture everything up to that character. Here's what I used:


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

      }

   }

}

A vector is utilized instead of an array because its storage space may be increased automatically to suit the length of the line. If recv() returns an error (indicated by bytesReceived being less than zero), NULL is returned. Since this is a possibility, calling functions should ensure that the string returned from readLine() is valid before use. Inside the loop, a single char is received from the socket and, if not a newline character, added to the vector. If it is a newline character, the contents of the vector are copied into a C string and returned. The string is declared to be one char larger than the vector and memset()'ted to zero so that the returned line will be automatically NULL-terminated. Ending strings with NULL prevents unusual errors and is generally good programming practice.

Nor presents this cleverly improved version with support for backspaces and the ability to change the newline character easily:


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

		}

	}

}

Non-blocking and Asynchronous Sockets

Up until this point we've been talking about blocking sockets, where calling a function such as accept() waits indefinitely for a user to connect. A non-blocking socket returns immediately whenever it is told to do something, either with a successful result, an error, or nothing (indicating that there will be something to receive later). The disadvantage of using this type is that you'll have to manually query the socket to see if a result has come in on every function you call. You can pass a set of sockets to the select() function to see which ones are ready for reading, writing, or have returned errors.

Functions using asynchronous sockets also return immediately, but you can specify a message to send to your window procedure when a specified event has occurred. For example, you can have the socket send an SOCKET_GOTMSG message whenever it receives something. Usually it's smart to check for errors (cumbersome, but necessary) when you get a socket message to prevent causing unnecessary problems later. First, let's define some functions we'll use to set up an asynchronous socket:

  • int WSAAsyncSelect ( SOCKET s, HWND hwnd, unsigned int wMsg, long lEvent )
    This function is used to identify a socket as asynchronous and associate a message with it. s is the socket you're working with. hwnd is the handle to the window that will receive the message when the socket generates an event. wMsg is the message you want to send to your window procedure (an example is the SOCKET_GOTMSG message from above). The lEvent parameter takes one or more flags that tell the socket on which events to send your message. Some of those flags are:
    • FD_READ: Socket is ready to receive data
    • FD_WRITE: Socket is ready to send data
    • FD_ACCEPT: Used in servers, this message indicates a user has connected
    • FD_CONNECT: Used in client applications, this message tells you the socket has connected
    • FD_CLOSE: The socket has just been closed
  • WSAGETSELECTERROR ( LPARAM lparam )
    Determines if the socket has returned an error. Technically, this is not a function but a macro (you can really generate smalltalk at parties with this little factoid).
  • WSAGETSELECTEVENT ( LPARAM lparam )
    Another useful macro defined in winsock2.h is WSAGETSELECTEVENT(), which is used to see exactly what the socket has done.

So, let's set up an asynchronous socket:


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



	}

}

Note that you cannot define one message for each event, like SOCKET_GOTMSG for FD_READ and then SOCKET_CONNECTED for FD_CONNECT. This is because repeated calls to WSAAsyncSelect () to setup each flag will cancel the effects of the last call to WSAAsyncSelect ().

More Tutorials and Links

I wrote this tutorial in December of 2000, and the seven or so years since then have seen a constant stream of visitors and improvements. I hope you've enjoyed reading as much as I've enjoyed writing: thank you for using Johnnie's Winsock Tutorial. The above is but a brief overview of the possibilities you can reach through Winsock, and others have done a far better job than me at probing the specifics of this subject:

(Move your mouse over a book cover for more information.)

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

I agree with Thomas Bleeker (MadWizard) that "network programming seems easier than it is." I cannot impress upon you the importance of practicing the use of these functions along with a debugger so you can see what's going on. You'll ultimately have a much better grasp of how things work if you get it wrong, investigate why you got it wrong and then experience the pleasure of getting it right. Making mistakes, in other words, is how we learn.

How Can I Improve?

Is there something that needs clarification? Does the tutorial fail to cover a Winsock-related topic that you wanted to learn about? Has the tutorial met your needs as a software developer? Is it amusing? smartly-written? overly-simplistic? or just right? Click here to post a comment


 
Rose, Unedited ©2008 Johnnie Rose, Jr.