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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值