< Select Program Example | Winsock2 I/O Method Main | WSAEventSelect Model >


 

 

Winsock 2 I/O Methods 5 Part 3

 

 

What do we have in this chapter 5 part 3?

  1. The Client-server Testing

  2. The WSAAsyncSelect() Model

  3. Message Notification

  4. The WSAAsyncSelect() Model Program Example

 

The Client-server Testing

 

We will test the client and server communication. Firstly we run the server.

 

Winsock 2 socket I/O Methods: Running the select() server program

 

Then we run the client form local computer in the same subnet.

 

Winsock 2 socket I/O Methods: Running the select() client program

 

The previous server screenshot sample output is shown below.

 

Winsock 2 socket I/O Methods: The select() server sample output when communication was completed

 

The WSAAsyncSelect() Model

 

Winsock provides a useful asynchronous I/O model that allows an application to receive Windows message–based notification of network events on a socket. This is accomplished by calling the WSAAsyncSelect() function after creating a socket. Before we continue, however, we need to make one subtle distinction. The WSAAsyncSelect() and WSAEventSelect() models provide asynchronous notification of the capability to read or write data. It does not provide asynchronous data transfer like the overlapped and completion port models.

This model originally existed in Winsock 1.1 implementations to help application programmers cope with the cooperative multitasking message-based environment of 16-bit Windows platforms, such as Windows for Workgroups. Applications can still benefit from this model, especially if they manage window messages in a standard Windows procedure, usually referred to as a winproc. This model is also used by the Microsoft Foundation Class (MFC) CSocket object.

 

Message Notification

 

To use the WSAAsyncSelect() model, your application must first create a window using the CreateWindow() function and supply a window procedure (winproc) support function for it. You can also use a dialog box with a dialog procedure instead of a window because dialog boxes are windows. For our purposes, we will demonstrate this model using a simple window with a supporting window procedure. Once you have set up the window infrastructure, you can begin creating sockets and turning on window message notification by calling the WSAAsyncSelect() function, which is defined as:

 

int WSAAsyncSelect(

    SOCKET s,

    HWND hWnd,

    unsigned int wMsg,

    long lEvent

);

 

The s parameter represents the socket we are interested in. The hWnd parameter is a window handle identifying the window or the dialog box that receives a message when a network event occurs. The wMsg parameter identifies the message to be received when a network event occurs. This message is posted to the window that is identified by the hWnd window handle. Applications usually set this message to a value greater than the Windows WM_USER value to avoid confusing a network window message with a predefined standard window message. The last parameter, lEvent, represents a bitmask that specifies a combination of network events, listed in Table 5-3, that the application is interested in. Most applications are typically interested in the FD_READ, FD_WRITE, FD_ACCEPT, FD_CONNECT, and FD_CLOSE network event types. Of course, the use of the FD_ACCEPT or the FD_CONNECT type depends on whether your application is a client or a server. If your application is interested in more than one network event, simply set this field by performing a bitwise OR on the types and assigning them to lEvent. For example:

 

WSAAsyncSelect(s, hwnd, WM_SOCKET, FD_CONNECT │ FD_READ │ FD_WRITE │ FD_CLOSE);

 

This allows our application to get connect, send, receive, and socket-closure network event notifications on socket s. It is impossible to register multiple events one at a time on the socket. Also note that once you turn on event notification on a socket, it remains on unless the socket is closed by a call to closesocket or the application changes the registered network event types by calling WSAAsyncSelect() (again, on the socket). Setting the lEvent parameter to 0 effectively stops all network event notification on the socket.

When your application calls WSAAsyncSelect() on a socket, the socket mode is automatically changed from blocking to the non-blocking mode that we described previously. As a result, if a Winsock I/O call such as WSARecv() is called and has to wait for data, it will fail with error WSAEWOULDBLOCK. To avoid this error, applications should rely on the user-defined window message specified in the wMsg parameter of WSAAsyncSelect() to indicate when network event types occur on the socket.

 

Table 5-3 Network Event Types for the WSAAsyncSelect() Function

 

Event Type

Meaning

FD_READ

The application wants to receive notification of readiness for reading.

FD_WRITE

The application wants to receive notification of readiness for writing.

FD_OOB

The application wants to receive notification of the arrival of OOB data.

FD_ACCEPT

The application wants to receive notification of incoming connections.

FD_CONNECT

The application wants to receive notification of a completed connection or a multipoint join operation.

FD_CLOSE

The application wants to receive notification of socket closure.

FD_QOS

The application wants to receive notification of socket QOS changes.

FD_GROUP_QOS

The application wants to receive notification of socket group QOS changes (reserved for future use with socket groups).

FD_ROUTING_INTERFACE_CHANGE

The application wants to receive notification of routing interface changes for the specified destination(s).

FD_ADDRESS_LIST_CHANGE

The application wants to receive notification of local address list changes for the socket's protocol family.

 

After your application successfully calls WSAAsyncSelect() on a socket, the application begins to receive network event notification as Windows messages in the window procedure associated with the hWnd parameter window handle. A window procedure is normally defined as:

 

LRESULT CALLBACK WindowProc(

    HWND hWnd,

    UINT uMsg,

    WPARAM wParam,

    LPARAM lParam

);

 

The hWnd parameter is a handle to the window that invoked the window procedure. The uMsg parameter indicates which message needs to be processed. In your case, you will be looking for the message defined in the WSAAsyncSelect() call. The wParam parameter identifies the socket on which a network event has occurred. This is important if you have more than one socket assigned to this window procedure. The lParam parameter contains two important pieces of information, the low word of lParam specifies the network event that has occurred, and the high word of lParam contains any error code.

When network event messages arrive at a window procedure, the application should first check the lParam high-word bits to determine whether a network error has occurred on the socket. There is a special macro, WSAGETSELECTERROR, that returns the value of the high-word bits error information. After the application has verified that no error occurred on the socket, the application should determine which network event type caused the Windows message to fire by reading the low-word bits of lParam. Another special macro, WSAGETSELECTEVENT, returns the value of the low-word portion of lParam.

The following example demonstrates how to manage window messages when using the WSAAsyncSelect() I/O model. The code highlights the steps needed to develop a basic server application and removes the programming details of developing a fully featured Windows application.

 

#define WM_SOCKET WM_USER + 1

#include <winsock2.h>

#include <windows.h>

 

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)

{

    WSADATA wsd;

    SOCKET Listen;

    SOCKADDR_IN InternetAddr;

    HWND Window;

 

    // Create a window and assign the ServerWinProc below to it

    Window = CreateWindow();

    // Start Winsock and create a socket

    WSAStartup(MAKEWORD(2, 2), &wsd);

    Listen = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);

    // Bind the socket to port 5150 and begin listening for connections

    InternetAddr.sin_family = AF_INET;

    InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);

    InternetAddr.sin_port = htons(5150);

 

    bind(Listen, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr));

 

    // Set up window message notification on

    // the new socket using the WM_SOCKET define above

    WSAAsyncSelect(Listen, Window, WM_SOCKET, FD_ACCEPT │ FD_CLOSE);

 

    listen(Listen, 5);

 

    // Translate and dispatch window messages until the application terminates

    while (1)

{

     // ...

}

}

 

BOOL CALLBACK ServerWinProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)

{

    SOCKET Accept;

 

    switch(wMsg)

    {

        case WM_PAINT:

            // Process window paint messages

            break;

        case WM_SOCKET:

            // Determine whether an error occurred on the

            // socket by using the WSAGETSELECTERROR() macro

            if (WSAGETSELECTERROR(lParam))

            {

                 // Display the error and close the socket

                closesocket( (SOCKET) wParam);

                break;

            }

            // Determine what event occurred on the socket

            switch(WSAGETSELECTEVENT(lParam))

            {

                case FD_ACCEPT:

                    // Accept an incoming connection

                    Accept = accept(wParam, NULL, NULL);

                    // Prepare accepted socket for read, write, and close notification

                    WSAAsyncSelect(Accept, hDlg, WM_SOCKET, FD_READ │ FD_WRITE │ FD_CLOSE);

                    break;

                case FD_READ:

                    // Receive data from the socket in wParam

                    break;

                case FD_WRITE:

                    // The socket in wParam is ready for sending data

                    break;

                case FD_CLOSE:

                    // The connection is now closed

                    closesocket((SOCKET)wParam);

                    break;

            }

            break;

    }

    return TRUE;

}

 

One final detail worth noting is how applications should process FD_WRITE event notifications. FD_WRITE notifications are sent under only three conditions:

 

  1. After a socket is first connected with connect or WSAConnect().
  2. After a socket is accepted with accept or WSAAccept().
  3. When a send(), WSASend(), sendto(), or WSASendTo() operation fails with WSAEWOULDBLOCK and buffer space becomes available.

 

Therefore, an application should assume that sends are always possible on a socket starting from the first FD_WRITE message and lasting until a send(), WSASend(), sendto, or WSASendTo() returns the socket error WSAEWOULDBLOCK. After such failure, another FD_WRITE message notifies the application that sends are once again possible.

The WSAAsyncSelect() model offers many advantages; foremost is the capability to handle many connections simultaneously without much overhead, unlike the select model's requirement of setting up the fd_set structures. The disadvantages are having to use a window if your application requires no windows (such as a service or console application). Also, having a single window procedure to service all the events on thousands of socket handles can become a performance bottleneck (meaning this model doesn't scale very well).

 

The WSAAsyncSelect() Model Program Example

 

The following example shows a server which implementing the WSAAsyncSelect() model.

 

The WSAAsyncSelect() IO Model: Program example shows a server which implementing the WSAAsyncSelect() model - Creating a new empty Win32 console mode application and add the project/solution name.

 

Add the following source code.

 

// Description:

//

//    This sample illustrates how to develop a simple echo server Winsock

//    application using the WSAAsyncSelect() I/O model. This sample is

//    implemented as a console-style application (to reduce the programming

//    complexity of writing a real Windows application) and simply prints

//    messages when connections are established and removed from the server.

//    The application listens for TCP connections on port 5150 and accepts them

//    as they arrive. When this application receives data from a client, it

//    simply echoes the data back in it's original form until the client

//            closes the connection.

//

//    Since the WSAAsyncSelect I/O model requires an application to manage

//    window messages when network event occur, this application creates

//    a window for the I/O model only. The window stays hidden during the

//    entire execution of this application.

//

//    Note: There are no command line options for this sample.

//

// Discard unnecessary/unused headers

#define WIN32_LEAN_AND_MEAN

// Take note that windows.h already contained winsock.h! And by

// putting winsock2.h first, it will block the winsock.h re-inclusion

// Link to ws2_32.lib

#include <winsock2.h>

#include <windows.h>

#include <stdio.h>

#include <conio.h>

 

#define PORT 5150

#define DATA_BUFSIZE 8192

 

// typedef definition

typedef struct _SOCKET_INFORMATION {

   BOOL RecvPosted;

   CHAR Buffer[DATA_BUFSIZE];

   WSABUF DataBuf;

   SOCKET Socket;

   DWORD BytesSEND;

   DWORD BytesRECV;

   struct _SOCKET_INFORMATION *Next;

} SOCKET_INFORMATION, *LPSOCKET_INFORMATION;

 

#define WM_SOCKET (WM_USER + 1)

 

// Prototypes

void CreateSocketInformation(SOCKET s);

LPSOCKET_INFORMATION GetSocketInformation(SOCKET s);

void FreeSocketInformation(SOCKET s);

HWND MakeWorkerWindow(void);

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

 

// Global var

LPSOCKET_INFORMATION SocketInfoList;

 

int main(int argc, char **argv)

{

   MSG msg;

   DWORD Ret;

   SOCKET Listen;

   SOCKADDR_IN InternetAddr;

   HWND Window;

   WSADATA wsaData;

 

   if ((Window = MakeWorkerWindow()) == NULL)

   {

      printf("MakeWorkerWindow() failed!\n");

      return 1;

   }

   else

      printf("MakeWorkerWindow() is OK!\n");

 

   // Prepare echo server

   if (WSAStartup((2,2), &wsaData) != 0)

   {

      printf("WSAStartup() failed with error %d\n", WSAGetLastError());

      return 1;

   }

   else

      printf("WSAStartup() is OK!\n");

 

   if ((Listen = socket(PF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)

   {

      printf("socket() failed with error %d\n", WSAGetLastError());

      return 1;

   }

   else

      printf("socket() is pretty fine!\n");

 

   if(WSAAsyncSelect(Listen, Window, WM_SOCKET, FD_ACCEPT|FD_CLOSE) == 0)

      printf("WSAAsyncSelect() is OK lol!\n");

   else

      printf("WSAAsyncSelect() failed with error code %d\n", WSAGetLastError());

 

   InternetAddr.sin_family = AF_INET;

   InternetAddr.sin_addr.s_addr = htonl(INADDR_ANY);

   InternetAddr.sin_port = htons(PORT);

 

   if (bind(Listen, (PSOCKADDR) &InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR)

   {

      printf("bind() failed with error %d\n", WSAGetLastError());

      return 1;

   }

   else

      printf("bind() is OK maaa!\n");

 

   if (listen(Listen, 5))

   {

      printf("listen() failed with error %d\n", WSAGetLastError());

      return 1;

   }

   else

      printf("listen() is also OK! I am listening now...\n");

                       

   // Translate and dispatch window messages for the application thread

   while(Ret = GetMessage(&msg, NULL, 0, 0))

   {

      if (Ret == -1)

      {

         printf("\nGetMessage() failed with error %d\n", GetLastError());

         return 1;

      }

      else

        printf("\nGetMessage() is pretty fine!\n");

 

      printf("Translating a message...\n");

      TranslateMessage(&msg);

      printf("Dispatching a message...\n");

      DispatchMessage(&msg);

   }

}

 

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

   SOCKET Accept;

   LPSOCKET_INFORMATION SocketInfo;

   DWORD RecvBytes;

   DWORD SendBytes;

   DWORD Flags;

 

   if (uMsg == WM_SOCKET)

   {

      if (WSAGETSELECTERROR(lParam))

      {

         printf("Socket failed with error %d\n", WSAGETSELECTERROR(lParam));

         FreeSocketInformation(wParam);

      }

      else

      {

         printf("Socket looks fine!\n");

         switch(WSAGETSELECTEVENT(lParam))

         {

            case FD_ACCEPT:

               if ((Accept = accept(wParam, NULL, NULL)) == INVALID_SOCKET)

               {                   

                  printf("accept() failed with error %d\n", WSAGetLastError());

                  break;

               }

               else

                  printf("accept() is OK!\n");

 

 

               // Create a socket information structure to associate with the socket for processing I/O

               CreateSocketInformation(Accept);

               printf("Socket number %d connected\n", Accept);

               WSAAsyncSelect(Accept, hwnd, WM_SOCKET, FD_READ|FD_WRITE|FD_CLOSE);

               break;

            case FD_READ:

               SocketInfo = GetSocketInformation(wParam);

               // Read data only if the receive buffer is empty

               if (SocketInfo->BytesRECV != 0)

               {

                  SocketInfo->RecvPosted = TRUE;

                  return 0;

               }

               else

               {

                  SocketInfo->DataBuf.buf = SocketInfo->Buffer;

                  SocketInfo->DataBuf.len = DATA_BUFSIZE;

 

                  Flags = 0;

                  if (WSARecv(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &RecvBytes,

                     &Flags, NULL, NULL) == SOCKET_ERROR)

                  {

                     if (WSAGetLastError() != WSAEWOULDBLOCK)

                     {

                        printf("WSARecv() failed with error %d\n", WSAGetLastError());

                        FreeSocketInformation(wParam);

                        return 0;

                     }

                  }

                  else // No error so update the byte count

                  {

                     printf("WSARecv() is OK!\n");

                     SocketInfo->BytesRECV = RecvBytes;

                  }

               }

               // DO NOT BREAK HERE SINCE WE GOT A SUCCESSFUL RECV. Go ahead

               // and begin writing data to the client

            case FD_WRITE:

               SocketInfo = GetSocketInformation(wParam);

               if (SocketInfo->BytesRECV > SocketInfo->BytesSEND)

               {

                  SocketInfo->DataBuf.buf = SocketInfo->Buffer + SocketInfo->BytesSEND;

                  SocketInfo->DataBuf.len = SocketInfo->BytesRECV - SocketInfo->BytesSEND;

 

                  if (WSASend(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &SendBytes, 0,

                     NULL, NULL) == SOCKET_ERROR)

                  {

                     if (WSAGetLastError() != WSAEWOULDBLOCK)

                     {

                        printf("WSASend() failed with error %d\n", WSAGetLastError());

                        FreeSocketInformation(wParam);

                        return 0;

                     }

                  }

                  else // No error so update the byte count

                  {

                     printf("WSASend() is OK!\n");

                     SocketInfo->BytesSEND += SendBytes;

                  }

               }

 

               if (SocketInfo->BytesSEND == SocketInfo->BytesRECV)

               {

                  SocketInfo->BytesSEND = 0;

                  SocketInfo->BytesRECV = 0;

                  // If a RECV occurred during our SENDs then we need to post an FD_READ notification on the socket

                  if (SocketInfo->RecvPosted == TRUE)

                  {

                     SocketInfo->RecvPosted = FALSE;

                     PostMessage(hwnd, WM_SOCKET, wParam, FD_READ);

                  }

               }

               break;

            case FD_CLOSE:

               printf("Closing socket %d\n", wParam);

               FreeSocketInformation(wParam);

               break;

         }

      }

      return 0;

   }

   return DefWindowProc(hwnd, uMsg, wParam, lParam);

}

 

void CreateSocketInformation(SOCKET s)

{

   LPSOCKET_INFORMATION SI;

 

   if ((SI = (LPSOCKET_INFORMATION) GlobalAlloc(GPTR, sizeof(SOCKET_INFORMATION))) == NULL)

   {

      printf("GlobalAlloc() failed with error %d\n", GetLastError());

      return;

   }

   else

     printf("GlobalAlloc() for SOCKET_INFORMATION is OK!\n");

 

   // Prepare SocketInfo structure for use

   SI->Socket = s;

   SI->RecvPosted = FALSE;

   SI->BytesSEND = 0;

   SI->BytesRECV = 0;

   SI->Next = SocketInfoList;

   SocketInfoList = SI;

}

 

LPSOCKET_INFORMATION GetSocketInformation(SOCKET s)

{

   SOCKET_INFORMATION *SI = SocketInfoList;

 

   while(SI)

   {

      if (SI->Socket == s)

         return SI;

 

      SI = SI->Next;

   }

 

   return NULL;

}

 

void FreeSocketInformation(SOCKET s)

{

   SOCKET_INFORMATION *SI = SocketInfoList;

   SOCKET_INFORMATION *PrevSI = NULL;

 

   while(SI)

   {

      if (SI->Socket == s)

      {

         if (PrevSI)

            PrevSI->Next = SI->Next;

         else

            SocketInfoList = SI->Next;

 

         closesocket(SI->Socket);

         GlobalFree(SI);

         return;

      }

 

      PrevSI = SI;

      SI = SI->Next;

   }

}

 

HWND MakeWorkerWindow(void)

{

   WNDCLASS wndclass;

   CHAR *ProviderClass = "AsyncSelect";

   HWND Window;

 

   wndclass.style = CS_HREDRAW | CS_VREDRAW;

   wndclass.lpfnWndProc = (WNDPROC)WindowProc;

   wndclass.cbClsExtra = 0;

   wndclass.cbWndExtra = 0;

   wndclass.hInstance = NULL;

   wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);

   wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);

   wndclass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);

   wndclass.lpszMenuName = NULL;

   wndclass.lpszClassName = (LPCWSTR)ProviderClass;

 

   if (RegisterClass(&wndclass) == 0)

   {

      printf("RegisterClass() failed with error %d\n", GetLastError());

      return NULL;

   }

   else

      printf("RegisterClass() is OK!\n");

 

   // Create a window

   if ((Window = CreateWindow(

      (LPCWSTR)ProviderClass,

      L"",

      WS_OVERLAPPEDWINDOW,

      CW_USEDEFAULT,

      CW_USEDEFAULT,

      CW_USEDEFAULT,

      CW_USEDEFAULT,

      NULL,

      NULL,

      NULL,

      NULL)) == NULL)

   {

      printf("CreateWindow() failed with error %d\n", GetLastError());

      return NULL;

   }

   else

      printf("CreateWindow() is OK!\n");

 

   return Window;

}

 

Build and run the project. The following screenshot is a sample output.

 

The WSAAsyncSelect() IO Model: Program example shows a server which implementing the WSAAsyncSelect() model - a sample output

 

 

 


< Select Program Example | Winsock2 I/O Method Main | WSAEventSelect Model >