395 lines
13 KiB
C++
395 lines
13 KiB
C++
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
|
|
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
|
|
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
|
|
// PARTICULAR PURPOSE.
|
|
//
|
|
// Copyright (C) 2002 Microsoft Corporation. All Rights Reserved.
|
|
//
|
|
// Module Name: ASAccept.cpp
|
|
//
|
|
// Description:
|
|
// This file contains the functions for implementing the
|
|
// Async Select version of the accept. Many of the functions that are common
|
|
// to Non-blocking accept and Async Select accept are defined in Common.cpp.
|
|
|
|
#include "common.h"
|
|
|
|
/*
|
|
This function converts the FD_XXX events into a printable string.
|
|
As the returned strings are static strings, they must not be modified.
|
|
*/
|
|
const char *FDImage(long lEvent)
|
|
{
|
|
const char *szRetVal;
|
|
|
|
// return the printable strings corresponding to the event.
|
|
switch(lEvent)
|
|
{
|
|
case FD_READ: szRetVal = "FD_READ";
|
|
break;
|
|
case FD_WRITE: szRetVal = "FD_WRITE";
|
|
break;
|
|
case FD_ACCEPT: szRetVal = "FD_ACCEPT";
|
|
break;
|
|
case FD_CLOSE: szRetVal = "FD_CLOSE";
|
|
break;
|
|
default: szRetVal = "Unexpected";
|
|
break;
|
|
}
|
|
|
|
return szRetVal;
|
|
}
|
|
|
|
/*
|
|
This is the call back function for the WSAAsyncSelect call.
|
|
This is called by winsock whenever a event is available on a
|
|
AsyncSelect-ed socket to be processed.
|
|
wParam contains the socket handle.
|
|
lParam contains the signalled event as well as error code, if any.
|
|
*/
|
|
void ProcessAsyncSelectMessage(WPARAM wParam, LPARAM lParam)
|
|
{
|
|
// get the socket handle from wParam.
|
|
SOCKET sock = (SOCKET) wParam;
|
|
|
|
// get the type of event using the macro below.
|
|
long event = WSAGETSELECTEVENT(lParam);
|
|
|
|
// get the error associated with the event, if any, using the macro below.
|
|
int error = WSAGETSELECTERROR(lParam);
|
|
BOOL bSocketToBeClosed = FALSE;
|
|
PSOCK_INFO pSockInfo;
|
|
int rc;
|
|
|
|
printf("Entering ProcessAsyncSelectMessage() for "
|
|
"Sock = %d, Event = %s, Error = %d\n",
|
|
sock, FDImage(event), error);
|
|
|
|
// first, get the SockInfo object corresponding to the signalled socket
|
|
// to retrieve the context of this socket.
|
|
for(pSockInfo = g_AcceptContext.pSockList;pSockInfo;
|
|
pSockInfo = pSockInfo->next)
|
|
{
|
|
if (sock == pSockInfo->sock)
|
|
break; // found.
|
|
}
|
|
|
|
// check if we found one.
|
|
if (!pSockInfo)
|
|
{
|
|
// no, we couldn't find one. This will happen usually because of
|
|
// dummy FD_READs that we are posting which might show up even
|
|
// after we closed the socket and removed the SockInfo structure
|
|
// from the global sockets list. This condition is not an error,
|
|
// but a normal timing condition. So, we don't have to do anything
|
|
// with this socket.
|
|
printf("Couldn't find SockInfo. Socket might have been closed.\n");
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// if we were signalled an error event instead of a positive event
|
|
// then we don't have to process the socket.
|
|
if (error)
|
|
{
|
|
// some error has happened on this socket.
|
|
// need to close the socket.
|
|
bSocketToBeClosed = TRUE;
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// take the corresponding action depending on the event signalled.
|
|
switch(event)
|
|
{
|
|
case FD_ACCEPT:
|
|
|
|
// Accept the new connection.
|
|
pSockInfo = ProcessAcceptEvent(pSockInfo);
|
|
if (pSockInfo == NULL)
|
|
{
|
|
// some error in accepting the connection or allocating
|
|
// resources for the new socket.
|
|
break;
|
|
}
|
|
|
|
// request the specified events to be notified asynchronously
|
|
// to the hAcceptWindow for the given sock.
|
|
rc = WSAAsyncSelect(pSockInfo->sock,
|
|
g_AcceptContext.hAcceptWindow,
|
|
WM_USER_ASYNCSELECT_MSG,
|
|
FD_READ | FD_WRITE | FD_CLOSE);
|
|
|
|
// check if the request went through.
|
|
if (rc == SOCKET_ERROR)
|
|
{
|
|
printf("ERROR: WSAAsyncSelect failed on sock %d. Error = %d\n",
|
|
pSockInfo->sock, WSAGetLastError());
|
|
bSocketToBeClosed = TRUE;
|
|
}
|
|
break;
|
|
|
|
case FD_READ:
|
|
|
|
// Read data from the socket, if there's room for the new data.
|
|
bSocketToBeClosed = ProcessReadEvent(pSockInfo);
|
|
|
|
// check if the recv call indicated that the remote side has closed
|
|
// the socket and that we should also close it.
|
|
if (bSocketToBeClosed == FALSE)
|
|
{
|
|
// no, it didn't. so, go ahead and echo the received data back
|
|
// if possible.
|
|
bSocketToBeClosed = SendData(pSockInfo);
|
|
|
|
// check if the echo was successful.
|
|
if (bSocketToBeClosed == FALSE)
|
|
{
|
|
// yes it was.
|
|
// now, if this happens to be the last piece of the
|
|
// incoming data, and a FD_CLOSE was already notified to
|
|
// us, we may not receive any more FD_READ notifications.
|
|
// So, we need to post a dummy FD_READ notification to
|
|
// this socket so that we again do a recv until we get
|
|
// a 0 from recv, which means graceful disconnect from
|
|
// other side or until we receive an error from recv.
|
|
if (pSockInfo->isFdCloseRecd)
|
|
{
|
|
// post ourselves another FD_READ in case
|
|
// there's no data left.
|
|
printf("Posting a dummy FD_READ \n");
|
|
PostMessage(g_AcceptContext.hAcceptWindow,
|
|
WM_USER_ASYNCSELECT_MSG,
|
|
pSockInfo->sock,
|
|
FD_READ);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case FD_WRITE:
|
|
|
|
// since we usually echo the data immediately after receiving it
|
|
// in the FD_READ case above, we may not need this case, except
|
|
// for one important condition: when send fails with WSAEWOULDBLOCK
|
|
// then we'll be notified with this event once the remote side
|
|
// has started consuming the data and we can continue our send.
|
|
bSocketToBeClosed = SendData(pSockInfo);
|
|
|
|
// in this case, most likely, while the previous data wasn't
|
|
// sent, there would have been a FD_READ which didn't result
|
|
// in a recv call. Hence, there won't be another FD_READ posted
|
|
// unless we call recv. So, we post a dummy FD_READ ourselves
|
|
// to allow the FD_READ handling code to be explicitly called.
|
|
|
|
// Note: In a real implementation, we would have to maintain
|
|
// more elaborate state to avoid posting these possibly redundant
|
|
// window messages.
|
|
|
|
printf("Posting a dummy FD_READ \n");
|
|
PostMessage(g_AcceptContext.hAcceptWindow,
|
|
WM_USER_ASYNCSELECT_MSG,
|
|
pSockInfo->sock,
|
|
FD_READ);
|
|
|
|
break;
|
|
|
|
case FD_CLOSE:
|
|
|
|
// even though the remote side has closed the socket, there might
|
|
// still be data that's left to read. so, we should just remember
|
|
// that we received a FD_CLOSE but not close the socket until
|
|
// we have read all the data.
|
|
pSockInfo->isFdCloseRecd = TRUE;
|
|
|
|
// post ourselves another FD_READ in case there's no data
|
|
// left in which case, we'll actually close the socket when recv
|
|
// returns 0.
|
|
printf("Posting a dummy FD_READ \n");
|
|
PostMessage(g_AcceptContext.hAcceptWindow,
|
|
WM_USER_ASYNCSELECT_MSG,
|
|
pSockInfo->sock,
|
|
FD_READ);
|
|
|
|
break;
|
|
|
|
default:
|
|
printf("Unexpected event: 0x%x for socket %d\n", event, sock);
|
|
bSocketToBeClosed = TRUE;
|
|
break;
|
|
}
|
|
|
|
CLEANUP:
|
|
|
|
// if we were told in any of the above cases to close the socket,
|
|
// e.g. remote side closed and we got an error or recv returned 0,etc.
|
|
// then we close our socket.
|
|
if (bSocketToBeClosed)
|
|
{
|
|
// make sure we cancel the WSAAsyncSelect event with the event mask 0.
|
|
WSAAsyncSelect(pSockInfo->sock, g_AcceptContext.hAcceptWindow, 0, 0);
|
|
|
|
// shutdown the socket so as not to hang the client just in case it
|
|
// expects us to send any more data.
|
|
if (shutdown(pSockInfo->sock, SD_SEND) == SOCKET_ERROR)
|
|
printf("ERROR: Shutdown failed. Error = %d\n", WSAGetLastError());
|
|
|
|
// close the socket.
|
|
closesocket(pSockInfo->sock);
|
|
printf("Closed socket %d. "
|
|
"Total Bytes Recd = %d, "
|
|
"Total Bytes Sent = %d\n",
|
|
pSockInfo->sock,
|
|
pSockInfo->nTotalRecd,
|
|
pSockInfo->nTotalSent);
|
|
|
|
// delete it from the global list and free the memory.
|
|
DeleteSockInfoFromList(&g_AcceptContext.pSockList,
|
|
pSockInfo);
|
|
}
|
|
|
|
printf("Exiting ProcessAsyncSelectMessage()\n");
|
|
return ;
|
|
}
|
|
|
|
|
|
/*
|
|
This is the Windows procedure for handling all the messages for
|
|
the given window. We are interested in only one message. The
|
|
rest of them will be passed on to the default windows procedure.
|
|
*/
|
|
LRESULT CALLBACK MainWndProc(HWND hWnd, UINT message,
|
|
WPARAM wParam, LPARAM lParam)
|
|
{
|
|
LRESULT res = 0;
|
|
|
|
// check what the message type is.
|
|
switch (message) {
|
|
|
|
case WM_DESTROY:
|
|
PostQuitMessage(0);
|
|
break;
|
|
|
|
// this is the message that that we gave in WSAAsyncSelect which
|
|
// winsock will send us back for notifying us of socket events.
|
|
case WM_USER_ASYNCSELECT_MSG:
|
|
ProcessAsyncSelectMessage(wParam, lParam);
|
|
break;
|
|
|
|
default:
|
|
res = DefWindowProc(hWnd, message, wParam, lParam);
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/*
|
|
This function creates a hidden window to receive the
|
|
async select messages sent by winsock for socket events.
|
|
*/
|
|
HWND CreateAcceptWindow(void)
|
|
{
|
|
WNDCLASS window;
|
|
HWND windowHandle = NULL;
|
|
|
|
// set the window properties.
|
|
memset(&window, 0, sizeof(WNDCLASS));
|
|
window.lpszClassName = "Accept Window";
|
|
window.hInstance = NULL;
|
|
window.lpfnWndProc = (WNDPROC) MainWndProc;
|
|
window.hCursor = NULL;
|
|
window.hIcon = NULL;
|
|
window.lpszMenuName = NULL;
|
|
window.hbrBackground = NULL;
|
|
window.style = 0;
|
|
window.cbClsExtra = 0;
|
|
window.cbWndExtra = 0;
|
|
|
|
// register the window class.
|
|
if (!RegisterClass(&window)) {
|
|
printf("Registerclass failed %d\n", GetLastError());
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// create the window.
|
|
windowHandle = CreateWindow("Accept Window",
|
|
"Accept Window",
|
|
WS_OVERLAPPEDWINDOW, //WS_MINIMIZE,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
CW_USEDEFAULT,
|
|
(HWND) NULL,
|
|
(HMENU) NULL,
|
|
(HINSTANCE) NULL,
|
|
(LPVOID) NULL);
|
|
|
|
// check if a window was created.
|
|
if (windowHandle == NULL)
|
|
{
|
|
printf("CreateWindow failed %d\n", GetLastError());
|
|
return NULL;
|
|
}
|
|
|
|
// uncomment the line below to see the window on the taskbar.
|
|
// ShowWindow(windowHandle, SW_MINIMIZE);
|
|
|
|
CLEANUP:
|
|
|
|
return windowHandle;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
This function is the entry point for the AsyncSelect Accept implementation.
|
|
*/
|
|
void AsyncSelectAcceptMain()
|
|
{
|
|
PSOCK_INFO pSockInfo;
|
|
int rc;
|
|
MSG msg;
|
|
|
|
printf("Entering AsyncSelectAcceptMain()\n");
|
|
|
|
// create a dummy hidden window to receive the async select messages.
|
|
g_AcceptContext.hAcceptWindow = CreateAcceptWindow();
|
|
if (g_AcceptContext.hAcceptWindow == NULL)
|
|
{
|
|
printf("Error in creating the accept window.\n");
|
|
goto CLEANUP;
|
|
}
|
|
|
|
// Set for all the listening sockets to be signalled on FD_ACCEPT event.
|
|
for(pSockInfo = g_AcceptContext.pSockList; pSockInfo != NULL;
|
|
pSockInfo = pSockInfo->next)
|
|
{
|
|
// request asynchronous notifications for the FD_ACCEPT and FD_CLOSE
|
|
// events to be sent to the hAcceptWindow for this sock.
|
|
rc = WSAAsyncSelect(pSockInfo->sock,
|
|
g_AcceptContext.hAcceptWindow,
|
|
WM_USER_ASYNCSELECT_MSG,
|
|
FD_ACCEPT | FD_CLOSE);
|
|
if (rc == SOCKET_ERROR)
|
|
{
|
|
printf("ERROR: WSAAsyncSelect failed for sock %d. Error = %d\n",
|
|
pSockInfo->sock, WSAGetLastError());
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Main message loop to process messages to the Accept Window.
|
|
// This will indirectly call MainWndProc which will in turn call
|
|
// ProcessAsyncSelectMessage.
|
|
while (GetMessage(&msg, (HWND) NULL, 0, 0)) {
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
|
|
|
|
CLEANUP:
|
|
|
|
printf("Exiting AsyncSelectAcceptMain()\n");
|
|
return ;
|
|
}
|