The WSAAsyncSelect Model
from: Network Programming for Microsoft Windows
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, WSAGETSELECT EVENT , 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:
·After a socket is first connected with connect or WSAConnect
·After a socket is accepted with accept or WSAAccept
·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).