Winsock Tutorial 3

Non-Blocking Sockets in TCP/IP (The Client)

This tutorial is very similar to tutorial 1, the main difference being that we are now working with Non-Blocking sockets. Non-blocking sockets do not halt program operation. Remember, if the recv() function is called, the program will wait (or block) until data arrives at the socket. Non-blocking sockets will continue whether or not data was received on the socket. This Winsock model is more appropriate for games and other applications that require continuous program flow.


We also touch on a little bit of error handling. We will delve more into proper error handling in an up-coming tutorial.

Prerequisites

Project type: Console
Include files: winsock2.h
Library files: ws2_32.lib



The non-blocking client

The code I am going to use is the same as tutorial 1. The main change is that we tell our socket not to 'block' and we now have a main program loop, much like what you would expect in a game. The main reason for this is to allow continuous program flow as pointed out earlier. This allows us to process other things like frame rendering, playing of sounds, etc..

The first addition to our code is to turn on non blocking mode for our socket.

u_long iMode=1;
ioctlsocket(Socket,FIONBIO,&iMode);

Simply put, if iMode=0 our socket will block. If iMode=1 our socket will no longer block.

In the final example code (below), you will notice that I have placed this call after the client is connected - for simplicity sake. If I had placed it before the client was connected, I would have to deal with loops, retries etc. Which is fine if you are creating a lobby, for example, but in our case we are happy to block until we are connected to the server.

Now that we have connected to our server we want to wait for a welcome message as before. But, now that we are using non-blocking sockets, we need to use a main program loop.

Here is the loop in its entirety.
for(;;)
{
	// Display message from server
	char buffer[1000];
	memset(buffer,0,999);
	int inDataLength=recv(Socket,buffer,1000,0);
	std::cout<<buffer;
	
	int nError=WSAGetLastError();
	if(nError!=WSAEWOULDBLOCK&&nError!=0)
	{
		std::cout<<"Winsock error code: "<<nError<<"\r\n";
		std::cout<<"Server disconnected!\r\n";
		break;
	}
	Sleep(1000);
}

Our main loop is just an infinite loop. As you can see the receive call is identical to the one used in tutorial 1.

The next part is new to us. Winsock has a handy way of catching error conditions. If a Winsock function returns -1, an error has occurred. Because we are using a continuous loop. You can bet that the server will not always be sending data when recv() is being called. This will cause our receive call to return -1.

Knowing that we are getting errors (soon to become apparent why), we can call WSAGetLastError(). This will return a Winsock error code. We can then find out what Winsock is complaining about. In our tutorial Appendix page you can see a list of Winsock error codes.


Because our program in looping constantly you can bet that you will get error 100035 (WSAEWOULDBLOCK) occurring constantly. This is not really a bad thing. All this is telling us is that there was no data on the socket at the time of checking and that the recv() call would have 'blocked' if we were in a blocking environment. So, it is up to the programmer how to handle this. But, most of the time we may even choose to ignore this warning and check the socket next time around.

Looking at the error handling part of the loop more closely...
if(nError!=WSAEWOULDBLOCK&&nError!=0)
{
	std::cout<<"Winsock error code: "<<nError<<"\r\n";
	std::cout<<"Server disconnected!\r\n";

	// Shutdown our socket
	shutdown(Socket,SD_SEND);

	// Close our socket entirely
	closesocket(Socket);

	break;
}

...what we have done here is to ignore the WSAWOULDBLOCK error and break out of the loop on any other error condition.

The only real way out of the loop in this example is if the server disconnects. When the server (in our next tutorial) disconnects you will notice error 100054 (WSAECONNRESET) or in plain english 'The server is no longer connected'.

The Full Code

#include <iostream>
#include <winsock2.h>
#pragma comment(lib,"ws2_32.lib")

int main(void)
{
	WSADATA WsaDat;
	if(WSAStartup(MAKEWORD(2,2),&WsaDat)!=0)
	{
		std::cout<<"Winsock error - Winsock initialization failed\r\n";
		WSACleanup();
		system("PAUSE");
		return 0;
	}
	
	// Create our socket
SOCKET Socket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(Socket==INVALID_SOCKET) { std::cout<<"Winsock error - Socket creation Failed!\r\n"; WSACleanup(); system("PAUSE"); return 0; } // Resolve IP address for hostname struct hostent *host; if((host=gethostbyname("localhost"))==NULL) { std::cout<<"Failed to resolve hostname.\r\n"; WSACleanup(); system("PAUSE"); return 0; } // Setup our socket address structure SOCKADDR_IN SockAddr; SockAddr.sin_port=htons(8888); SockAddr.sin_family=AF_INET; SockAddr.sin_addr.s_addr=*((unsigned long*)host->h_addr); // Attempt to connect to server if(connect(Socket,(SOCKADDR*)(&SockAddr),sizeof(SockAddr))!=0) { std::cout<<"Failed to establish connection with server\r\n"; WSACleanup(); system("PAUSE"); return 0; } // If iMode!=0, non-blocking mode is enabled. u_long iMode=1; ioctlsocket(Socket,FIONBIO,&iMode); // Main loop for(;;) { // Display message from server char buffer[1000]; memset(buffer,0,999); int inDataLength=recv(Socket,buffer,1000,0); std::cout<<buffer; int nError=WSAGetLastError(); if(nError!=WSAEWOULDBLOCK&&nError!=0) { std::cout<<"Winsock error code: "<<nError<<"\r\n"; std::cout<<"Server disconnected!\r\n"; // Shutdown our socket shutdown(Socket,SD_SEND); // Close our socket entirely closesocket(Socket); break; } Sleep(1000); } WSACleanup(); system("PAUSE"); return 0; }


Things to try

Try inducing some winsock errors and see what WSAGetLastError() reports.


Additional information

For additional information we have provided the following links.

Microsoft (MSDN) - Getting started with Winsock
Microsoft (MSDN) - More information on the ioctlsocket() function


Next tutorial

Tutorial 4 - Non-Blocking Sockets in TCP/IP (The Server)