|
|
#include "stdafx.h"
#include "gkwsock.h"
// ASYNC_ACCEPT --------------------------------------------------------------------------
ASYNC_ACCEPT::ASYNC_ACCEPT (void) { AcceptSocket = INVALID_SOCKET; ClientSocket = INVALID_SOCKET; AcceptFunc = NULL; AcceptFuncContext = NULL; ReferenceCount = 0L; StopNotifyEvent = NULL; }
ASYNC_ACCEPT::~ASYNC_ACCEPT (void) { assert (AcceptSocket == INVALID_SOCKET); assert (ClientSocket == INVALID_SOCKET); assert (ReferenceCount == 0L); assert (!StopNotifyEvent); }
HRESULT ASYNC_ACCEPT::StartIo ( IN SOCKADDR_IN * SocketAddress, IN ASYNC_ACCEPT_FUNC ArgAcceptFunc, IN PVOID ArgAcceptContext) { HRESULT Result;
assert (SocketAddress); assert (ArgAcceptFunc);
Lock ();
if (AcceptSocket == INVALID_SOCKET && ReferenceCount == 0L) { // this object is not currently in use
// so, it's acceptable to use it
assert (!AcceptFunc); assert (!AcceptFuncContext); assert (!StopNotifyEvent);
// This increase in reference count is needed
// to shut down the service gracefully
// Reference count on ASYNC_ACCEPT objects
// will never drop to zero unless StopWait is called.
// StopWait will call matching Release, which will
// bring the reference count to the expected value of 0.
AddRef ();
Result = StartIoLocked (SocketAddress);
if (Result == S_OK) {
assert (AcceptSocket != INVALID_SOCKET);
AcceptFunc = ArgAcceptFunc; AcceptFuncContext = ArgAcceptContext; } else {
Release (); } } else { Debug (_T("ASYNC_ACCEPT::StartIo: this object is already in use, must first call Stop and wait for sync counter\n")); Result = E_FAIL; }
Unlock();
return Result; }
HRESULT ASYNC_ACCEPT::StartIoLocked ( IN SOCKADDR_IN * SocketAddress) { HRESULT Result; BOOL KeepaliveOption;
assert (SocketAddress); assert (AcceptSocket == INVALID_SOCKET); assert (ClientSocket == INVALID_SOCKET); assert (ReferenceCount == 1); assert (!StopNotifyEvent); StopNotifyEvent = CreateEvent (NULL, TRUE, FALSE, NULL);
if (!StopNotifyEvent) {
Result = GetLastErrorAsResult (); DebugLastError (_T("ASYNC_ACCEPT::StartIoLocked: failed to create stop notify event\n"));
} else {
AcceptSocket = WSASocket (AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (AcceptSocket == INVALID_SOCKET) {
Result = GetLastErrorAsResult (); DebugLastError (_T("ASYNC_ACCEPT::StartIoLocked: failed to create accept socket\n"));
} else {
//
// Set RCV and SND buffers to zero
// Yes, it is ugly and bad practice but this is a QFE
// for details look up bug# WinSE 31054, 691666 (read both 35928 and 33546).
//
ULONG Option = 0; setsockopt( AcceptSocket, SOL_SOCKET, SO_SNDBUF, (PCHAR)&Option, sizeof(Option) ); Option = 0; setsockopt( AcceptSocket, SOL_SOCKET, SO_SNDBUF, (PCHAR)&Option, sizeof(Option) );
if (bind (AcceptSocket, (SOCKADDR *) SocketAddress, sizeof (SOCKADDR_IN))) {
Result = GetLastErrorAsResult (); DebugLastErrorF (_T("ASYNC_ACCEPT::StartIoLocked: failed to bind accept socket to address %08X:%04X\n"), ntohl (SocketAddress -> sin_addr.s_addr), ntohs (SocketAddress -> sin_port));
} else {
// Set keepalive on the socket
KeepaliveOption = TRUE; if (SOCKET_ERROR == setsockopt (AcceptSocket, SOL_SOCKET, SO_KEEPALIVE, (PCHAR) &KeepaliveOption, sizeof (KeepaliveOption))) { Result = GetLastErrorAsResult (); DebugLastError (_T("ASYNC_ACCEPT: Failed to set keepalive on accept socket.\n"));
} else {
if (listen (AcceptSocket, 10)) {
Result = GetLastErrorAsResult (); DebugLastError (_T("ASYNC_ACCEPT::StartIoLocked: failed to listen on accept socket\n"));
} else { if (!BindIoCompletionCallback ((HANDLE) AcceptSocket, ASYNC_ACCEPT::IoCompletionCallback, 0)) { Result = GetLastErrorAsResult (); DebugLastError (_T("ASYNC_ACCEPT::StartIoLocked: failed to bind i/o completion callback\n")); } else { Result = IssueAccept (); if (Result == S_OK) { return Result; } } } } }
closesocket (AcceptSocket); AcceptSocket = INVALID_SOCKET; }
CloseHandle (StopNotifyEvent); StopNotifyEvent = NULL; }
return Result; }
HRESULT ASYNC_ACCEPT::GetListenSocketAddress ( OUT SOCKADDR_IN * ReturnSocketAddress) { HRESULT Result; INT SocketAddressLength;
Lock();
if (AcceptSocket != INVALID_SOCKET) {
SocketAddressLength = sizeof (SOCKADDR_IN); if (getsockname (AcceptSocket, (SOCKADDR *) ReturnSocketAddress, &SocketAddressLength) == SOCKET_ERROR) { Result = GetLastErrorAsResult(); } else { Result = S_OK; } } else { Result = E_INVALIDARG; }
Unlock();
return Result; }
HRESULT ASYNC_ACCEPT::IssueAccept (void) { HRESULT Result; BOOL KeepaliveOption;
AssertLocked(); assert (ClientSocket == INVALID_SOCKET); // assert (ReferenceCount == 0);
ClientSocket = WSASocket (AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED); if (ClientSocket == INVALID_SOCKET) { Result = GetLastErrorAsResult (); DebugLastError (_T("ASYNC_ACCEPT::IssueAccept: failed to create client socket.\n")); return Result; } //
// Set RCV and SND buffers to zero
// Yes, it is ugly and bad practice but this is a QFE
// for details look up bug# WinSE 31054, 691666 (read both 35928 and 33546).
//
ULONG Option = 0; setsockopt( ClientSocket, SOL_SOCKET, SO_SNDBUF, (PCHAR)&Option, sizeof(Option) ); Option = 0; setsockopt( ClientSocket, SOL_SOCKET, SO_SNDBUF, (PCHAR)&Option, sizeof(Option) );
ZeroMemory (&Overlapped, sizeof (OVERLAPPED));
AddRef();
if (!AcceptEx (AcceptSocket, ClientSocket, ClientInfoBuffer, 0, sizeof (SOCKADDR_IN) + 0x10, sizeof (SOCKADDR_IN) + 0x10, &ClientInfoBufferLength, &Overlapped)) {
if (WSAGetLastError() != WSA_IO_PENDING) { // an error occurred
Release (); Result = GetLastErrorAsResult (); DebugLastError (_T("ASYNC_ACCEPT::IssueAccept: failed to issue accept.\n")); return Result; }
// Set keepalive on the socket
KeepaliveOption = TRUE; if (SOCKET_ERROR == setsockopt (ClientSocket, SOL_SOCKET, SO_KEEPALIVE, (PCHAR) &KeepaliveOption, sizeof (KeepaliveOption))) { Release (); Result = GetLastErrorAsResult (); DebugLastError (_T("ASYNC_ACCEPT: IssueAccept: Failed to set keepalive on client socket.\n")); return Result;
} }
return S_OK; }
// static
void ASYNC_ACCEPT::IoCompletionCallback (DWORD Status, DWORD BytesTransferred, LPOVERLAPPED Overlapped) { ASYNC_ACCEPT * AsyncAccept;
AsyncAccept = CONTAINING_RECORD (Overlapped, ASYNC_ACCEPT, Overlapped);
AsyncAccept -> IoComplete (Status, BytesTransferred);
AsyncAccept -> Release (); }
void ASYNC_ACCEPT::IoComplete (DWORD Status, DWORD BytesTransferred) { ASYNC_ACCEPT_FUNC LocalAcceptFunc; PVOID LocalAcceptFuncContext; SOCKADDR_IN LocalAddressCopy; SOCKADDR_IN RemoteAddressCopy; SOCKET LocalClientSocket; SOCKADDR * LocalAddress; INT LocalAddressLength; SOCKADDR * RemoteAddress; INT RemoteAddressLength; INT Result;
Lock();
assert (ClientSocket != INVALID_SOCKET); assert (ReferenceCount > 0);
if (AcceptSocket == INVALID_SOCKET) { // Stop has been called
// just immediately disconnect the client
// we'll deal with object lifetime below
closesocket (ClientSocket); ClientSocket = INVALID_SOCKET; } else { // the context is in the normal state
// continue processing
if (Status == ERROR_SUCCESS) { // a client has successfully connected
GetAcceptExSockaddrs ( ClientInfoBuffer, 0, // no initial recv
sizeof (SOCKADDR_IN) + 0x10, sizeof (SOCKADDR_IN) + 0x10, &LocalAddress, &LocalAddressLength, &RemoteAddress, &RemoteAddressLength);
// copy information out of the context
// so that it will be valid after we issue a new accept and unlock
LocalAddressCopy = *(SOCKADDR_IN *) LocalAddress; RemoteAddressCopy = *(SOCKADDR_IN *) RemoteAddress; LocalClientSocket = ClientSocket; LocalAcceptFunc = AcceptFunc; LocalAcceptFuncContext = AcceptFuncContext;
ClientSocket = INVALID_SOCKET;
// update the accept context
Result = setsockopt (ClientSocket, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, reinterpret_cast <char *> (&AcceptSocket), sizeof (SOCKET));
// issue a new accept
IssueAccept();
Unlock();
(*LocalAcceptFunc) (LocalAcceptFuncContext, LocalClientSocket, &LocalAddressCopy, &RemoteAddressCopy);
Lock(); } else { // some error has occurred
// this is usually (but not always) fatal
assert (ClientSocket != INVALID_SOCKET);
closesocket (ClientSocket); ClientSocket = INVALID_SOCKET;
switch (Status) { case STATUS_CANCELLED: Debug (_T("ASYNC_ACCEPT::IoComplete: accept failed, STATUS_CANCELED, original thread probably exited, resubmitting request...\n")); break;
default: DebugError (Status, _T("AsyncAccept: async accept FAILED, sleeping 2000ms and retrying...\n")); Sleep (2000); break; }
IssueAccept(); } }
Unlock(); }
void ASYNC_ACCEPT::StopWait (void) { DWORD Status;
Lock();
if (AcceptSocket != INVALID_SOCKET) {
// closing the socket cancels all pending i/o
// we do NOT close the ClientSocket
// only the i/o completion callback path may do that
closesocket (AcceptSocket); AcceptSocket = INVALID_SOCKET; AcceptFunc = NULL; AcceptFuncContext = NULL;
if (ClientSocket != INVALID_SOCKET) { // an accept is still pending. it may complete successfully,
// or it may complete with STATUS_CANCELED (since we just closed AcceptSocket)
// in either case, we must wait for the i/o complete callback to run.
// AcceptSocket = INVALID_SOCKET is an indicator to the completion callback
// that it should abort / return immediately.
assert (StopNotifyEvent);
Unlock ();
// This is the counterpart to the AddRef called in
// StartIoLocked (see comment there)
Release ();
DebugF (_T("ASYNC_ACCEPT::StopWait: waiting for i/o completion thread...\n")); Status = WaitForSingleObject (StopNotifyEvent, INFINITE);
assert (Status == WAIT_OBJECT_0);
Lock (); } } else { assert (!AcceptFunc); assert (!AcceptFuncContext); }
if (StopNotifyEvent) { CloseHandle (StopNotifyEvent); StopNotifyEvent = NULL; }
Unlock(); }
void ASYNC_ACCEPT::AddRef (void) { assert (ReferenceCount >= 0L); InterlockedIncrement (&ReferenceCount); }
void ASYNC_ACCEPT::Release (void) { LONG Count;
assert (ReferenceCount >= 0L);
Count = InterlockedDecrement (&ReferenceCount);
if (Count == 0L) { DebugF (_T("ASYNC_ACCEPT::Release -- Reference count dropped to zero. (this is %x)\n"), this);
if (StopNotifyEvent) { SetEvent (StopNotifyEvent); } else { DebugF (_T("ASYNC_ACCEPT::Release � notify-event object was NULL (%x)\n"), this); } } }
|