Asynchronous Windows Sockets Calls

By default, an application's socket calls will block until the requested operation can be completed. For example, if an application wishes to receive data from another application, its call to the recv() API will not complete until the other application has sent data which can be returned to the calling application.

This model is sufficient for simple applications, but more sophisticated applications may not wish to block for an arbitrarily long period for a network event. In fact, in Windows 3.1, blocking operations are considered poor programming practice because applications are expected to call PeekMessage() or GetMessage() regularly in order to allow other applications to run and to receive user input.

To support the sophisticated applications which fit better within the Windows programming paradigm, Windows Sockets supports the concept of "nonblocking sockets." If an application sets a socket to nonblocking, then any operation which may block for an extended period will fail with the error code WSAEWOULDBLOCK. This error indicates to the application that the system was unable to perform the requested operation immediately.

How, then, does an application know when it can successfully perform certain operations? Polling would be one (poor) solution. The optimal mechanism is to use the asynchronous notification mechanism provided by the WSAAsyncSelect() API. This routine allows an application to notify a Windows Sockets implementation of certain events which are of interest, and to receive a Windows message when the events occur. For example, an application may indicate interest in data arrival with the FD_READ message, and when data arrives the Windows Sockets DLL posts a message to the application's window handle. The application receives this message in a GetMessage() or PeekMessage() call and can then perform the corresponding operation.

The following code fragment demonstrates how an application opens and connects a TCP socket and indicates that it is interested in being notified when one of three network events occurs:


/* Static IP address for remote server for example. In reality, this would be    
   specified as a hostname or IP address by the user */

#define    SERVER            "131.107.1.121"        

#define     SOCKET_MESSAGE     WM_USER+1
#define     SERVER_PORT         4000

struct sockaddr_in        srv_addr;
SOCKET            cli_sock;

.
.
.
/* Create client-side socket */

cli_sock=socket(PF_INET,SOCK_STREAM,0);

if (cli_sock==INVALID_SOCKET){
    sprintf(buf, "Windows Sockets error %d: couldn't open socket.",
         WSAGetLastError());
    MessageBox(hWnd,buf,"Windows Sockets Error",MB_OK);
    shutdown_appl();
}

srv_addr.sin_family = AF_INET;
srv_addr.sin_addr.s_addr = inet_addr(SERVER);
srv_addr.sin_port=SERVER_PORT;

/* Connect to server */

if (connect(cli_sock,(LPSOCKADDR)&srv_addr,sizeof(srv_addr))==SOCKET_ERROR){

    sprintf(buf,"Windows Sockets error %d: Couldn't connect socket.",
         WSAGetLastError());
    MessageBox (hWnd,buf,"Windows Sockets Error",MB_OK);
    shutdown_appl();
}

/* Set up async select on FD_READ, FD_WRITE, and FD_CLOSE events */

err = WSAAsyncSelect(cli_sock, hWnd, SOCKET_MESSAGE, FD_READ|FD_WRITE|FD_CLOSE);
if (err == SOCKET_ERROR) {
    sprintf(buf,"Windows Sockets error %d: WSAAsyncSelect failure.",
         WSAGetLastError());
    MessageBox (hWnd,buf,"Windows Sockets Error",MB_OK);
    shutdown_appl();
}

When one of the specified network events occurs, the specified window handle hWnd receives a message containing a wMsg of SOCKET_MESSAGE. The wParam field of the message will have the socket handle, and lParam contains two pieces of information: the low word contains the event that occurred (FD_READ, FD_WRITE, or FD_CLOSE) and the high word contains an error code, or 0 if there was no error. Code similar to the following fragment, which would belong in an application's main window procedure, may be used to interpret a message from WSAAsyncSelect():


long FAR PASCAL _export WndProc(HWND hWnd, UINT message, UINT wParam, LONG lParam)
{
    INT err;

    switch (message) {

    case ...:
        /* handle Windows messages */
    .
    .
    .
    case SOCKET_MESSAGE:

        /* A network event has occurred on our socket.  
           Determine which network event occurred. */

        switch (WSAGETSELECTEVENT(lParam)) {
        case FD_READ:
            /* Data arrived.  Receive it. */
            err = recv(cli_sock, Buffer, BufferLength, 0);
            if (WSAGetLastError() == WSAEWOULDBLOCK) {
                /* We have already received the data. */
                break;
            }
            if (err == SOCKET_ERROR) {
                sprintf(buf, "Windows Sockets error %d: receive error.", WSAGetLastError());
                MessageBox(hWnd,buf,"Windows Sockets Error",MB_OK);
                shutdown_appl();
            }
            .
            .
            /* Do something useful with the data. */
            .
            .
            break;

        case FD_WRITE:
            /* We can send data. */

            err = send(cli_sock, Buffer, BufferLength, 0);
            if (err == SOCKET_ERROR) {
                if (WSAGetLastError() == WSAEWOULDBLOCK) {
                    /* Send buffers overflowed. */
                    break;
                }
                sprintf(buf, "Windows Sockets error %d: send failed.",
                     SAGetLastError());
                MessageBox(hWnd,buf,"Windows Sockets Error",MB_OK);
                shutdown_appl();
            }
            break;

        case FD_CLOSE:
            /* The remote closed the socket. */

            closesocket(cli_sock);
            break;
        }

        break;
    }
.

An important part of the behavior of WSAAsyncSelect() is that after the Windows Sockets DLL has posted a message for a particular network event, the DLL refrains from posting another message for that event until the application has called the "reenabling function" for that event. For example, once an FD_READ has been posted, no more FD_READ messages will be posted until the application calls recv(). This prevents an application's message queue from being overflowed with messages for a single network event.

Using WSAAsyncSelect() can simplify and improve organization in Windows applications by allowing them to be fully event-driven. Such an application responds to network events in much the same way it responds to user events such as a mouse click. In addition, applications which make use of WSAAsyncSelect() are better behaved Windows applications since they must frequently call PeekMessae() or GetMessage() in order to receive the network messages.