Winsock Tutorial 1

Blocking Sockets in TCP/IP (The Client)

This tutorial is to designed to get a feel for the most basic Winsock model, the blocking socket. By the end you will have a working client. The server can be found in Tutorial 2.

First we should clear up some terminology, Socket, TCP/IP, UDP.

For the uninitiated, a socket is an abstraction where an application can communicate over a network or dial-up connection. Essentially you send data to the socket, the socket sends the data across the network or maybe even the internet, and all going well, someone will recieve the data at the other end.


TCP/IP stands for Transmission Control Protocol/Internet Protocol. This handles all of the behind the scenes handshaking and guarantees that data will travel to the reciever (provided someones network cable hasn't been unplugged). If the send command comes back successful, then the data is guaranteed to have arrived at the other end. We will be concentrating on TCP/IP for Tutorial 1.

UDP stands for User Datagram Protocol. UDP is connectionless, which means the application does not have to go through initial setup of TCP/IP which creates a connection to another socket. With UDP, packets are simply fired at an IP address and port, whether someone is there to recieve them or not is not known. UDP is not reliable. Packets can arrive out of sequence, be doubled up, or simply never arrive at all. Therefore, it is upto the programmer to decide how to handle loss of data and duplications. Most online games use UDP as it does not have the overhead of TCP/IP, but this comes at the price of reliability.

There are various methods of using Winsock. Eg. Blocking, non-blocking, asyncronous, overlapped I/O. We will be concentrating on blocking sockets at the moment.

Prerequisties

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



The Client

As expected the client's purpose is the connect to a server to send and recieve data. For readability we will omit error checking until the final code at the end of the section.

First we need to initialise Winsock.

WSADATA WsaDat;
WSAStartup(MAKEWORD(2,2),&WsaDat);


These lines tell Winsock to use version 2.2. Winsock functions will normally return 0 if the command executed sucessfully.

Now that Winsock has been initialised we need to create a socket.

SOCKET Socket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
This sets up a socket convieniently called 'Socket'. The first parameter is always AF_INET for TCP/IP sockets. The second and third parameters are always set as shown when using TCP/IP protocol.

The next code snippet is used to resolve a hostname to an IP address. The neat thing about it is that you can still replace the hostname with an IP address is nessesary and it will still work. In this example we are connecting to ourselves (localhost).

struct hostent *host;
host=gethostbyname("localhost");

Next we setup the IP address and port that we want to communicate with. A port is comparable to a unit number, where an IP address might be a street address. When the postman comes to deliver the mail he needs to know what letterbox to put the mail into.

SOCKADDR_IN SockAddr;
SockAddr.sin_port=htons(8888);
SockAddr.sin_family=AF_INET;
SockAddr.sin_addr.s_addr=*((unsigned long*)host->h_addr);

Now that all of our initial setup is taken care of, we can now attempt to connect our socket to the server.

connect(Socket,(SOCKADDR*)(&SockAddr),sizeof(SockAddr));
All going well, we should be now connected to the server, ready to send and recieve data.

Assuming the server sends out some sort of welcome message we can wait and listen for it.

char buffer[1000];
int nDataLength=recv(Socket,buffer,1000,0);
std::cout<<buffer;

The first line creates a buffer for our incoming data. The 'recv' command waits (or blocks) until data is available. When data is recieved on our socket it is placed directly into our buffer. The third parameter of the recieve call is how many bytes we are willing to recieve from our socket, per call.

Coming towards the end of our application we need to 'shutdown' our socket.

shutdown(socket,SD_SEND);
Now that our socket has been shutdown, we can still recieve data, so we can tie up any loose ends (so to speak) but can no longer send data back to the server.
The second parameter for shutdown can be any of the following, depending on the design of your application;
	SD_SEND		0	Socket can only send
	SD_RECIEVE	1	Socket can only recieve
	SD_BOTH		2	Socket can no longer send or recieve
All going well we can now close our socket entirely. No more sending or recieving from here onwards.

closesocket(socket);
Once we have finished using Winsock we must perform a quick cleanup to free memory and other resources.

WSACleanup();
Easy, wasn't it?

The catch with using blocking sockets is exactly that, they 'block'. This sort of networking model is fine for certain applications but is not really designed for games or similar. The reason being, each send or recieve call will stop your application from continuing until they succeed (or if the connection is closed at the other side).
Non-blocking sockets, overlapped I/O, or asyncronous sockets are better suited for an environment where the program needs to process other information.

The Full Code

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

int main()
{
	// Initialise Winsock
	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;
	}

	// Display message from server
	char buffer[1000];
	memset(buffer,0,999);
	int inDataLength=recv(Socket,buffer,1000,0);
	std::cout<<buffer;

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

	// Close our socket entirely
	closesocket(Socket);

	// Cleanup Winsock
	WSACleanup();
	system("PAUSE");
	return 0;
}


Things to try

Try changing the port to 110 and the hostname to your mail server provider (eg. mail.bigpond.com). You should be greeted with a welcome message from your mail server.

struct hostent *host; host=gethostbyname("mail.bigpond.com");
SockAddr.sin_port=htons(110);


Additional informaton

For additional information we have provided the following links.

Microsoft (MSDN) - Getting started with Winsock


Next tutorial

Tutorial 2 - Basic Blocking Sockets in TCP/IP (The Server)