Winsock Tutorial 7

Async Sockets in TCP/IP (The Server)

In this tutorial we are going to examine how to create a server using the Async (or asynchronous) Winsock model.

Once again, if you are not too familiar with basic window creation and creation of edit boxes and buttons, I recommend reviewing Windows tutorials 1 through to 3.

Prerequisites

Project type: Windows
Include files: winsock2.h, windows.h
Library files: ws2_32.lib



The async server

If you successfully completed Winsock Tutorial 6, you will be happy to know that the server code is almost the same, plus a couple of necessary additions.

Lets jump straight into it!

The first major difference between the client and the server begins right after the socket creation in the WM_CREATE event.

Our SOCKADDR_IN structure differs slightly as we are soon going to be listening on the socket. So, we need to tell SOCKADDR_IN to allow use of any available adapter. The main point of interest being the last line.
SOCKADDR_IN SockAddr;
SockAddr.sin_port=htons(nPort);
SockAddr.sin_family=AF_INET;
SockAddr.sin_addr.s_addr=htonl(INADDR_ANY);
As you can see, there is no difference here when compared to the previous Winsock server tutorials.

Then we can 'bind' the socket as per normal.
if(bind(Socket,(LPSOCKADDR)&SockAddr,sizeof(SockAddr))==SOCKET_ERROR)
{
	MessageBox(hWnd,"Unable to bind socket","Error",MB_OK);
	SendMessage(hWnd,WM_DESTROY,NULL,NULL);
}
Again, nothing new here, except that we now use a message box to convey any problems, instead of std::cout, like we used in the console based server tutorials.

Now we need to tell our newly created socket to notify windows of any socket events, similar to what we did in the previous tutorial.
nResult=WSAAsyncSelect(Socket,hWnd,WM_SOCKET,(FD_CLOSE|FD_ACCEPT|FD_READ));
if(nResult)
{
	MessageBox(hWnd,"WSAAsyncSelect failed","Critical Error",MB_ICONERROR);
	SendMessage(hWnd,WM_DESTROY,NULL,NULL);
	break;
}
Previously, we were only interested in receiving the FD_CLOSE and FD_READ events.

Being a server that we are creating, we need to also monitor the FD_ACCEPT event. FD_ACCEPT is used to notify our program that a new connection has been made to the server. We can process the event in our callback procedure.

Then we need to tell our socket to 'listen' for incoming connections, like so;
if(listen(Socket,(1))==SOCKET_ERROR)
{
	MessageBox(hWnd,"Unable to listen!","Error",MB_OK);
	SendMessage(hWnd,WM_DESTROY,NULL,NULL);
	break;
}
Our async socket is now initialized, listening, and ready to receive connections. All that we need to do now is process those wonderful Windows messages!

Fortunately for all of us, the FD_READ and FD_CLOSE messages are processed the same way as the were in the previous tutorial, so they really don't need any further explanation.

The only new event we need to watch out for is the FD_ACCEPT event. From a server point of view, this event is extremely important as this event is the deciding factor for whether a client connects or not. So, we had better get it right.

Like FD_CLOSE and FD_READ, FD_ACCEPT is placed in the WSAGETSELECTEVENT(lParam) switch.

Our FD_ACCEPT event looks like so;
case FD_ACCEPT:
{
	int size=sizeof(sockaddr);
	Socket=accept(wParam,&sockAddrClient,&size);                
	if (Socket==INVALID_SOCKET)
	{
		int nret = WSAGetLastError();
		WSACleanup();
	}
	SendMessage(hEditIn,WM_SETTEXT,NULL,(LPARAM)"Client connected!");
}
break;
Surprisingly, the actual 'accept' command looks the same as the one used in our console based servers. The only difference being that we can use wParam (for the event) as the socket descriptor.

Thats all there is to it! It is now time to go and compile the full source code and try it for yourself.

The Full Code

#include <winsock2.h>
#include <windows.h>

#pragma comment(lib,"ws2_32.lib")

#define IDC_EDIT_IN		101
#define IDC_EDIT_OUT		102
#define IDC_MAIN_BUTTON		103
#define WM_SOCKET		104

int nPort=5555;

HWND hEditIn=NULL;
HWND hEditOut=NULL;
SOCKET Socket=NULL;
char szHistory[10000];
sockaddr sockAddrClient;

LRESULT CALLBACK WinProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);

int WINAPI WinMain(HINSTANCE hInst,HINSTANCE hPrevInst,LPSTR lpCmdLine,int nShowCmd)
{
	WNDCLASSEX wClass;
	ZeroMemory(&wClass,sizeof(WNDCLASSEX));
	wClass.cbClsExtra=NULL;
	wClass.cbSize=sizeof(WNDCLASSEX);
	wClass.cbWndExtra=NULL;
	wClass.hbrBackground=(HBRUSH)COLOR_WINDOW;
	wClass.hCursor=LoadCursor(NULL,IDC_ARROW);
	wClass.hIcon=NULL;
	wClass.hIconSm=NULL;
	wClass.hInstance=hInst;
	wClass.lpfnWndProc=(WNDPROC)WinProc;
	wClass.lpszClassName="Window Class";
	wClass.lpszMenuName=NULL;
	wClass.style=CS_HREDRAW|CS_VREDRAW;

	if(!RegisterClassEx(&wClass))
	{
		int nResult=GetLastError();
		MessageBox(NULL,
			"Window class creation failed\r\nError code:",
			"Window Class Failed",
			MB_ICONERROR);
	}

	HWND hWnd=CreateWindowEx(NULL,
			"Window Class",
			"Winsock Async Server",
			WS_OVERLAPPEDWINDOW,
			200,
			200,
			640,
			480,
			NULL,
			NULL,
			hInst,
			NULL);

	if(!hWnd)
	{
		int nResult=GetLastError();

		MessageBox(NULL,
			"Window creation failed\r\nError code:",
			"Window Creation Failed",
			MB_ICONERROR);
	}

    ShowWindow(hWnd,nShowCmd);

	MSG msg;
	ZeroMemory(&msg,sizeof(MSG));

	while(GetMessage(&msg,NULL,0,0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return 0;
}

LRESULT CALLBACK WinProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
	switch(msg)
    {
		case WM_COMMAND:
			switch(LOWORD(wParam))
            {
				case IDC_MAIN_BUTTON:
				{
					char szBuffer[1024];
					ZeroMemory(szBuffer,sizeof(szBuffer));

					SendMessage(hEditOut,
						WM_GETTEXT,
						sizeof(szBuffer),
						reinterpret_cast<LPARAM>(szBuffer));

					send(Socket,szBuffer,strlen(szBuffer),0);

					SendMessage(hEditOut,WM_SETTEXT,NULL,(LPARAM)"");
				}
				break;
			}
			break;
		case WM_CREATE: 
		{
			ZeroMemory(szHistory,sizeof(szHistory));

			// Create incoming message box
			hEditIn=CreateWindowEx(WS_EX_CLIENTEDGE,
				"EDIT",
				"",
				WS_CHILD|WS_VISIBLE|ES_MULTILINE|
				ES_AUTOVSCROLL|ES_AUTOHSCROLL,
				50,
				120,
				400,
				200,
				hWnd,
				(HMENU)IDC_EDIT_IN,
				GetModuleHandle(NULL),
				NULL);
			if(!hEditIn)
			{
				MessageBox(hWnd,
					"Could not create incoming edit box.",
					"Error",
					MB_OK|MB_ICONERROR);
			}
			HGDIOBJ hfDefault=GetStockObject(DEFAULT_GUI_FONT);
			SendMessage(hEditIn,
					WM_SETFONT,
					(WPARAM)hfDefault,
					MAKELPARAM(FALSE,0));
			SendMessage(hEditIn,
					WM_SETTEXT,
					NULL,
					(LPARAM)"Waiting for client to connect...");

			// Create outgoing message box
			hEditOut=CreateWindowEx(WS_EX_CLIENTEDGE,
						"EDIT",
						"",
						WS_CHILD|WS_VISIBLE|ES_MULTILINE|
						ES_AUTOVSCROLL|ES_AUTOHSCROLL,
						50,
						50,
						400,
						60,
						hWnd,
						(HMENU)IDC_EDIT_IN,
						GetModuleHandle(NULL),
						NULL);
			if(!hEditOut)
			{
				MessageBox(hWnd,
					"Could not create outgoing edit box.",
					"Error",
					MB_OK|MB_ICONERROR);
			}

			SendMessage(hEditOut,
					WM_SETFONT,
					(WPARAM)hfDefault,
					MAKELPARAM(FALSE,0));
			SendMessage(hEditOut,
					WM_SETTEXT,
					NULL,
					(LPARAM)"Type message here...");

			// Create a push button
			HWND hWndButton=CreateWindow( 
					    "BUTTON",
						"Send",
						WS_TABSTOP|WS_VISIBLE|
						WS_CHILD|BS_DEFPUSHBUTTON,
						50,
						330,
						75,
						23,
						hWnd,
						(HMENU)IDC_MAIN_BUTTON,
						GetModuleHandle(NULL),
						NULL);
			
			SendMessage(hWndButton,
				WM_SETFONT,
				(WPARAM)hfDefault,
				MAKELPARAM(FALSE,0));

			WSADATA WsaDat;
			int nResult=WSAStartup(MAKEWORD(2,2),&WsaDat);
			if(nResult!=0)
			{
				MessageBox(hWnd,
					"Winsock initialization failed",
					"Critical Error",
					MB_ICONERROR);
				SendMessage(hWnd,WM_DESTROY,NULL,NULL);
				break;
			}

			Socket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
			if(Socket==INVALID_SOCKET)
			{
				MessageBox(hWnd,
					"Socket creation failed",
					"Critical Error",
					MB_ICONERROR);
				SendMessage(hWnd,WM_DESTROY,NULL,NULL);
				break;
			}

			SOCKADDR_IN SockAddr;
			SockAddr.sin_port=htons(nPort);
			SockAddr.sin_family=AF_INET;
			SockAddr.sin_addr.s_addr=htonl(INADDR_ANY);

			if(bind(Socket,(LPSOCKADDR)&SockAddr,sizeof(SockAddr))==SOCKET_ERROR)
			{
				MessageBox(hWnd,"Unable to bind socket","Error",MB_OK);
				SendMessage(hWnd,WM_DESTROY,NULL,NULL);
				break;
			}

			nResult=WSAAsyncSelect(Socket,
					hWnd,
					WM_SOCKET,
					(FD_CLOSE|FD_ACCEPT|FD_READ));
			if(nResult)
			{
				MessageBox(hWnd,
					"WSAAsyncSelect failed",
					"Critical Error",
					MB_ICONERROR);
				SendMessage(hWnd,WM_DESTROY,NULL,NULL);
				break;
			}

			if(listen(Socket,(1))==SOCKET_ERROR)
			{
				MessageBox(hWnd,
					"Unable to listen!",
					"Error",
					MB_OK);
				SendMessage(hWnd,WM_DESTROY,NULL,NULL);
				break;
			}
		}
		break;

		case WM_DESTROY:
		{
			PostQuitMessage(0);
			shutdown(Socket,SD_BOTH);
			closesocket(Socket);
			WSACleanup();
			return 0;
		}
		break;

		case WM_SOCKET:
		{
			switch(WSAGETSELECTEVENT(lParam))
			{
				case FD_READ:
				{
					char szIncoming[1024];
					ZeroMemory(szIncoming,sizeof(szIncoming));

					int inDataLength=recv(Socket,
						(char*)szIncoming,
						sizeof(szIncoming)/sizeof(szIncoming[0]),
						0);

					strncat(szHistory,szIncoming,inDataLength);
					strcat(szHistory,"\r\n");

					SendMessage(hEditIn,
						WM_SETTEXT,
						sizeof(szIncoming)-1,
						reinterpret_cast<LPARAM>(&szHistory));
				}
				break;

				case FD_CLOSE:
				{
					MessageBox(hWnd,
						"Client closed connection",
						"Connection closed!",
						MB_ICONINFORMATION|MB_OK);
					closesocket(Socket);
					SendMessage(hWnd,WM_DESTROY,NULL,NULL);
				}
				break;

				case FD_ACCEPT:
				{
					int size=sizeof(sockaddr);
					Socket=accept(wParam,&sockAddrClient,&size);                
					if (Socket==INVALID_SOCKET)
					{
						int nret = WSAGetLastError();
						WSACleanup();
					}
					SendMessage(hEditIn,
						WM_SETTEXT,
						NULL,
						(LPARAM)"Client connected!");
				}
				break;
    			}   
			}
		}
    
    return DefWindowProc(hWnd,msg,wParam,lParam);
}


Things to try

Here is a big challenge! See if you can modify the server to accept multiple clients. Make the server send and receive chat text, so it ends up going to the right recipient.

Additional information

For additional information we have provided the following links.

Microsoft (MSDN) - The WSAAsyncSelect() function


Next tutorial

Tutorial 8 - Handling multiple clients