You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1221 lines
34 KiB
1221 lines
34 KiB
/*==LICENSE==* |
|
|
|
CyanWorlds.com Engine - MMOG client, server and tools |
|
Copyright (C) 2011 Cyan Worlds, Inc. |
|
|
|
This program is free software: you can redistribute it and/or modify |
|
it under the terms of the GNU General Public License as published by |
|
the Free Software Foundation, either version 3 of the License, or |
|
(at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
GNU General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
You can contact Cyan Worlds, Inc. by email legal@cyan.com |
|
or by snail mail at: |
|
Cyan Worlds, Inc. |
|
14617 N Newport Hwy |
|
Mead, WA 99021 |
|
|
|
*==LICENSE==*/ |
|
/***************************************************************************** |
|
* |
|
* $/Plasma20/Sources/Plasma/NucleusLib/pnAsyncCoreExe/Private/W9x/pnAceW9xSocket.cpp |
|
* |
|
***/ |
|
|
|
#include "../../Pch.h" |
|
#pragma hdrstop |
|
|
|
#include "pnAceW9xInt.h" |
|
|
|
|
|
namespace W9x { |
|
|
|
/***************************************************************************** |
|
* |
|
* Private data |
|
* |
|
***/ |
|
|
|
static HWND s_window; |
|
|
|
|
|
/**************************************************************************** |
|
* |
|
* CSocketMapper |
|
* |
|
***/ |
|
|
|
class CSocket; |
|
|
|
class CSocketMapper { |
|
private: |
|
struct Map { |
|
AsyncCancelId sequence; |
|
SOCKET sock; |
|
UINT message; |
|
CSocket * object; |
|
FAsyncNotifySocketProc notifyProc; |
|
}; |
|
|
|
ARRAY(Map) m_mapTable; |
|
|
|
public: |
|
void Add ( |
|
AsyncCancelId sequence, |
|
CSocket * object, |
|
UINT message, |
|
SOCKET sock, |
|
FAsyncNotifySocketProc notifyProc |
|
); |
|
void Delete (CSocket * object); |
|
CSocket * Find (AsyncCancelId sequence) const; |
|
CSocket * Find (CSocket * object) const; |
|
CSocket * Find (UINT message, SOCKET sock) const; |
|
CSocket * Find (FAsyncNotifySocketProc notifyProc, unsigned index) const; |
|
|
|
}; |
|
|
|
//=========================================================================== |
|
void CSocketMapper::Add ( |
|
AsyncCancelId sequence, |
|
CSocket * object, |
|
UINT message, |
|
SOCKET sock, |
|
FAsyncNotifySocketProc notifyProc |
|
) { |
|
Map * mapping = m_mapTable.New(); |
|
mapping->sequence = sequence; |
|
mapping->object = object; |
|
mapping->message = message; |
|
mapping->sock = sock; |
|
mapping->notifyProc = notifyProc; |
|
} |
|
|
|
//=========================================================================== |
|
void CSocketMapper::Delete (CSocket * object) { |
|
for (unsigned index = m_mapTable.Count() - 1; index < m_mapTable.Count(); ) |
|
if (m_mapTable[index].object == object) |
|
if (index + 1 < m_mapTable.Count()) |
|
m_mapTable[index] = m_mapTable.Pop(); |
|
else |
|
m_mapTable.Pop(); |
|
else |
|
--index; |
|
} |
|
|
|
//=========================================================================== |
|
CSocket * CSocketMapper::Find (AsyncCancelId sequence) const { |
|
for (const Map * curr = m_mapTable.Ptr(), * term = m_mapTable.Term(); |
|
curr != term; |
|
++curr) |
|
if (curr->sequence == sequence) |
|
return curr->object; |
|
return nil; |
|
} |
|
|
|
//=========================================================================== |
|
CSocket * CSocketMapper::Find (CSocket * object) const { |
|
for (const Map * curr = m_mapTable.Ptr(), * term = m_mapTable.Term(); |
|
curr != term; |
|
++curr) |
|
if (curr->object == object) |
|
return curr->object; |
|
return nil; |
|
} |
|
|
|
//=========================================================================== |
|
CSocket * CSocketMapper::Find (UINT message, SOCKET sock) const { |
|
for (const Map * curr = m_mapTable.Ptr(), * term = m_mapTable.Term(); |
|
curr != term; |
|
++curr) |
|
if ((curr->message == message) && (curr->sock == sock)) |
|
return curr->object; |
|
return nil; |
|
} |
|
|
|
//=========================================================================== |
|
CSocket * CSocketMapper::Find (FAsyncNotifySocketProc notifyProc, unsigned index) const { |
|
for (const Map * curr = m_mapTable.Ptr(), * term = m_mapTable.Term(); |
|
curr != term; |
|
++curr) |
|
if ((curr->notifyProc == notifyProc) && !index--) |
|
return curr->object; |
|
return nil; |
|
} |
|
|
|
static CSocketMapper s_mapper; |
|
|
|
|
|
/**************************************************************************** |
|
* |
|
* CSocket |
|
* |
|
***/ |
|
|
|
class CSocket { |
|
private: |
|
|
|
// These constants are used to track whether a socket has been connected, |
|
// and whether it has been disconnected. We do not track whether we have |
|
// dispatched a connection failure notification, because socket objects |
|
// are deleted immediately after dispatching that notification. |
|
enum { kDispatchedConnectSuccess = (1 << 0) }; |
|
enum { kDispatchedDisconnect = (1 << 1) }; |
|
|
|
struct Command { |
|
LINK(Command) link; |
|
enum Code { |
|
CONNECT, |
|
WRITE, |
|
} code; |
|
void * param; |
|
union { |
|
struct { |
|
byte connType; |
|
} connect; |
|
struct { |
|
const void * data; // pointer to application's data |
|
unsigned bytes; |
|
} write; |
|
}; |
|
}; |
|
|
|
// These variables are protected by the critical section |
|
CCritSect m_critSect; |
|
LISTDECL(Command, link) m_commandList; |
|
ARRAY(byte) m_sendQueue; |
|
|
|
// These variables are never modified outside the constructor and |
|
// destructor |
|
UINT m_message; |
|
FAsyncNotifySocketProc m_notifyProc; |
|
AsyncCancelId m_sequence; |
|
SOCKET m_sock; |
|
|
|
// These variables are only ever touched during a callback from the |
|
// window procedure, which is single threaded |
|
unsigned m_dispatched; |
|
byte m_readBuffer[1460 * 2]; |
|
unsigned m_readBytes; |
|
void * m_userState; |
|
|
|
public: |
|
CSocket ( |
|
AsyncCancelId sequence, |
|
UINT message, |
|
SOCKET sock, |
|
FAsyncNotifySocketProc notifyProc |
|
); |
|
~CSocket (); |
|
|
|
void EnableNagling (bool enable); |
|
void Disconnect (bool hardClose); |
|
|
|
inline AsyncCancelId GetSequence () const; |
|
inline bool IsConnected () const; |
|
inline bool IsDisconnected () const; |
|
|
|
void OnClose (); |
|
void OnConnect (); |
|
void OnReadReady (); |
|
void OnWriteReady (); |
|
|
|
void ProcessQueue (); |
|
void QueueConnect ( |
|
void * param, |
|
byte connType |
|
); |
|
void QueueWrite ( |
|
void * param, |
|
const void * data, |
|
unsigned bytes |
|
); |
|
|
|
bool Send ( // returns false if disconnected |
|
const void * data, |
|
unsigned bytes |
|
); |
|
|
|
}; |
|
|
|
//=========================================================================== |
|
CSocket::CSocket ( |
|
AsyncCancelId sequence, |
|
UINT message, |
|
SOCKET sock, |
|
FAsyncNotifySocketProc notifyProc |
|
) : |
|
m_dispatched(0), |
|
m_notifyProc(notifyProc), |
|
m_readBytes(0), |
|
m_sequence(sequence), |
|
m_message(message), |
|
m_sock(sock), |
|
m_userState(nil) |
|
{ |
|
|
|
// Create a mapping for this socket |
|
s_mapper.Add(sequence, this, message, sock, notifyProc); |
|
|
|
} |
|
|
|
//=========================================================================== |
|
CSocket::~CSocket () { |
|
|
|
// If we dispatched a connect notification, verify that we also |
|
// dispatched a disconnect notification |
|
ASSERT(IsDisconnected() || !IsConnected()); |
|
|
|
// Delete the mapping for this socket |
|
s_mapper.Delete(this); |
|
|
|
// Close the socket |
|
closesocket(m_sock); |
|
m_sock = INVALID_SOCKET; |
|
|
|
// Free memory |
|
m_commandList.Clear(); |
|
|
|
} |
|
|
|
//=========================================================================== |
|
void CSocket::EnableNagling (bool enable) { |
|
BOOL arg = !enable; |
|
int result = setsockopt( |
|
m_sock, |
|
IPPROTO_TCP, |
|
TCP_NODELAY, |
|
(const char *)&arg, |
|
sizeof(arg) |
|
); |
|
if (result) |
|
LogMsg(kLogFatal, "failed: setsockopt"); |
|
} |
|
|
|
//=========================================================================== |
|
void CSocket::Disconnect (bool hardClose) { |
|
|
|
// This function is called outside the critical section, which is |
|
// necessary to avoid deadlocks, so it must not modify the object state. |
|
// The purpose of this function is to cause WinSock to generate a |
|
// FD_CLOSE notification, which will initiate synchronous destruction |
|
// of the socket. |
|
|
|
if (m_sock == INVALID_SOCKET) |
|
return; |
|
|
|
// Shutdown the socket |
|
// const unsigned SD_BOTH = 2; |
|
shutdown(m_sock, SD_BOTH); |
|
|
|
// Windows always waits for an acknowledgement back from the other end |
|
// of the connection, so we post our own FD_CLOSE notification if we need |
|
// to terminate this socket immediately. Our notification processing code |
|
// is robust against receiving the eventual FD_CLOSE from Windows even |
|
// if another socket has been allocated in the interrim with the same |
|
// socket handle. |
|
if (hardClose) |
|
PostMessage(s_window, m_message, m_sock, MAKELONG(FD_CLOSE, 0)); |
|
|
|
} |
|
|
|
//=========================================================================== |
|
AsyncCancelId CSocket::GetSequence () const { |
|
return m_sequence; |
|
} |
|
|
|
//=========================================================================== |
|
bool CSocket::IsConnected () const { |
|
return (m_dispatched & kDispatchedConnectSuccess) != 0; |
|
} |
|
|
|
//=========================================================================== |
|
bool CSocket::IsDisconnected () const { |
|
return (m_dispatched & kDispatchedDisconnect) != 0; |
|
} |
|
|
|
//=========================================================================== |
|
void CSocket::OnClose () { |
|
|
|
// If we haven't yet dispatched a connection notification, then |
|
// dispatch a failure to connect rather than dispatching a disconnect |
|
// notification |
|
if (!IsConnected()) { |
|
OnConnect(); |
|
return; |
|
} |
|
|
|
// Verify that we haven't already dispatched a disconnect notification |
|
if (IsDisconnected()) |
|
return; |
|
|
|
// Process any remaining queued commands |
|
ProcessQueue(); |
|
|
|
// Remove the mapping for this object so that we will never dispatch |
|
// another event to it |
|
s_mapper.Delete(this); |
|
|
|
// Dispatch the disconnection notificaton |
|
m_dispatched |= kDispatchedDisconnect; |
|
m_notifyProc( |
|
(AsyncSocket)this, |
|
kNotifySocketDisconnect, |
|
nil, // notify |
|
&m_userState |
|
); |
|
|
|
// This function must not perform any processing after returning from |
|
// the notification, because the application may have deleted the socket |
|
// out from under us |
|
|
|
} |
|
|
|
//=========================================================================== |
|
void CSocket::OnConnect () { |
|
|
|
// Verify that we haven't already dispatched a connection success |
|
// message. We know that we haven't already dispatched a connection |
|
// failure message, because the socket object would have already been |
|
// deleted in that case. |
|
if (IsConnected()) |
|
return; |
|
|
|
// Verify that a connect command is queued |
|
m_critSect.Enter(); |
|
Command * command = m_commandList.Head(); |
|
if (command && (command->code == command->CONNECT)) |
|
m_commandList.Unlink(command); |
|
else |
|
command = nil; |
|
m_critSect.Leave(); |
|
if (!command) |
|
return; |
|
|
|
// Check for an error |
|
int error; |
|
int errorLen = sizeof(error); |
|
int result = getsockopt( |
|
m_sock, |
|
SOL_SOCKET, |
|
SO_ERROR, |
|
(char *)&error, |
|
&errorLen |
|
); |
|
if (result) |
|
LogMsg(kLogFatal, "failed: getsockopt"); |
|
|
|
// Get addresses for the connection notification |
|
AsyncNotifySocketConnect notify; |
|
ZERO(notify.localAddr); |
|
ZERO(notify.remoteAddr); |
|
int nameLen = sizeof(notify.localAddr); |
|
if (getsockname(m_sock, (sockaddr *)¬ify.localAddr, &nameLen)) |
|
if (GetLastError() == WSAENOTCONN) |
|
error = WSAENOTCONN; |
|
else |
|
LogMsg(kLogFatal, "failed: getsockname"); |
|
nameLen = sizeof(notify.remoteAddr); |
|
if (getpeername(m_sock, (sockaddr *)¬ify.remoteAddr, &nameLen)) |
|
if (GetLastError() == WSAENOTCONN) |
|
error = WSAENOTCONN; |
|
else |
|
LogMsg(kLogFatal, "failed: getpeername"); |
|
|
|
// Dispatch the connection notification |
|
notify.param = command->param; |
|
notify.asyncId = 0; |
|
notify.connType = command->connect.connType; |
|
bool notifyResult = m_notifyProc( |
|
error ? nil : (AsyncSocket)this, |
|
error ? kNotifySocketConnectFailed : kNotifySocketConnectSuccess, |
|
¬ify, |
|
&m_userState |
|
); |
|
|
|
// Delete the connect command |
|
DEL(command); |
|
|
|
// Handle failure to connect |
|
if (error) { |
|
|
|
// Destroy the socket |
|
DEL(this); |
|
|
|
} |
|
|
|
// Handle a successful connection |
|
else { |
|
|
|
// Mark the socket as connected |
|
m_dispatched |= kDispatchedConnectSuccess; |
|
|
|
// If the application requested the socket closed, disconnect it |
|
if (!notifyResult) { |
|
Disconnect(true); |
|
OnClose(); |
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
//=========================================================================== |
|
void CSocket::OnReadReady () { |
|
for (;;) { |
|
|
|
// Issue the read |
|
int recvResult = recv( |
|
m_sock, |
|
(char *)(m_readBuffer + m_readBytes), |
|
sizeof(m_readBuffer) - m_readBytes, |
|
0 // flags |
|
); |
|
if (recvResult == SOCKET_ERROR) |
|
if (GetLastError() == WSAEWOULDBLOCK) |
|
return; |
|
else |
|
LogMsg(kLogFatal, "failed: recv"); |
|
|
|
// If the socket reported disconnection, close it |
|
if ((recvResult == SOCKET_ERROR) || !recvResult) { |
|
Disconnect(true); |
|
OnClose(); |
|
return; |
|
} |
|
|
|
// Update the read buffer state |
|
m_readBytes += recvResult; |
|
|
|
// Dispatch a read notification |
|
AsyncNotifySocketRead notify; |
|
notify.param = nil; |
|
notify.asyncId = 0; |
|
notify.buffer = m_readBuffer; |
|
notify.bytes = m_readBytes; |
|
notify.bytesProcessed = 0; |
|
bool notifyResult = m_notifyProc( |
|
(AsyncSocket)this, |
|
kNotifySocketRead, |
|
¬ify, |
|
&m_userState |
|
); |
|
|
|
// If the application processed data, remove it from the read buffer |
|
if (notify.bytesProcessed >= m_readBytes) |
|
m_readBytes = 0; |
|
else if (notify.bytesProcessed) { |
|
MemMove( |
|
&m_readBuffer[0], |
|
&m_readBuffer[notify.bytesProcessed], |
|
m_readBytes - notify.bytesProcessed |
|
); |
|
m_readBytes -= notify.bytesProcessed; |
|
} |
|
|
|
// If the application returned false from its notification procedure, |
|
// disconnect |
|
if (!notifyResult) { |
|
Disconnect(false); |
|
return; |
|
} |
|
|
|
} |
|
} |
|
|
|
//=========================================================================== |
|
void CSocket::OnWriteReady () { |
|
|
|
// Verify that the socket is ready to send data |
|
if (IsDisconnected() || !IsConnected()) |
|
return; |
|
|
|
// Enter the critical section and verify that data is queued |
|
m_critSect.Enter(); |
|
if (!m_sendQueue.Bytes()) { |
|
m_critSect.Leave(); |
|
return; |
|
} |
|
|
|
// Attempt to send queued data |
|
int result = send( |
|
m_sock, |
|
(const char *)m_sendQueue.Ptr(), |
|
m_sendQueue.Bytes(), |
|
0 |
|
); |
|
if (result == SOCKET_ERROR) |
|
if (GetLastError() == WSAEWOULDBLOCK) |
|
result = 0; |
|
else |
|
LogMsg(kLogFatal, "failed: send"); |
|
|
|
// Dequeue sent bytes |
|
if (result != SOCKET_ERROR) |
|
if ((unsigned)result == m_sendQueue.Bytes()) |
|
m_sendQueue.Clear(); |
|
else if (result) { |
|
MemMove( |
|
&m_sendQueue[0], |
|
&m_sendQueue[result], |
|
m_sendQueue.Bytes() - result |
|
); |
|
COMPILER_ASSERT(sizeof(m_sendQueue[0]) == sizeof(byte)); |
|
m_sendQueue.SetCount(m_sendQueue.Count() - result); |
|
} |
|
|
|
// Leave the critical section |
|
m_critSect.Leave(); |
|
|
|
// If the send failed, close the socket |
|
if (result == SOCKET_ERROR) { |
|
Disconnect(true); |
|
OnClose(); |
|
} |
|
|
|
|
|
} |
|
|
|
//=========================================================================== |
|
void CSocket::ProcessQueue () { |
|
|
|
// Verify that the socket is connected |
|
if (IsDisconnected() || !IsConnected()) |
|
return; |
|
|
|
// Dispatch each command in the queue |
|
for (;;) { |
|
|
|
// Remove the next command from the queue |
|
m_critSect.Enter(); |
|
Command * command = m_commandList.Head(); |
|
if (command) |
|
m_commandList.Unlink(command); |
|
m_critSect.Leave(); |
|
if (!command) |
|
break; |
|
|
|
// Dispatch it |
|
switch (command->code) { |
|
|
|
case command->WRITE: { |
|
AsyncNotifySocketWrite notify; |
|
notify.param = command->param; |
|
notify.asyncId = 0; |
|
notify.buffer = (byte *)command->write.data; |
|
notify.bytes = command->write.bytes; |
|
notify.bytesProcessed = 0; |
|
bool notifyResult = m_notifyProc( |
|
(AsyncSocket)this, |
|
kNotifySocketWrite, |
|
¬ify, |
|
&m_userState |
|
); |
|
if (!notifyResult) |
|
Disconnect(false); |
|
} |
|
break; |
|
|
|
DEFAULT_FATAL(command->code); |
|
} |
|
|
|
// Delete the command |
|
DEL(command); |
|
|
|
} |
|
|
|
} |
|
|
|
//=========================================================================== |
|
void CSocket::QueueConnect ( |
|
void * param, |
|
byte connType |
|
) { |
|
ASSERT(!IsConnected() && !IsDisconnected()); |
|
|
|
Command * command = NEW(Command); |
|
command->code = command->CONNECT; |
|
command->param = param; |
|
command->connect.connType = connType; |
|
m_commandList.Link(command); |
|
} |
|
|
|
//=========================================================================== |
|
void CSocket::QueueWrite ( |
|
void * param, |
|
const void * data, |
|
unsigned bytes |
|
) { |
|
ASSERT(!IsDisconnected()); |
|
|
|
Command * command = NEW(Command); |
|
command->code = command->CONNECT; |
|
command->param = param; |
|
command->write.data = data; |
|
command->write.bytes = bytes; |
|
m_commandList.Link(command); |
|
} |
|
|
|
//=========================================================================== |
|
bool CSocket::Send ( // returns false if disconnected |
|
const void * data, |
|
unsigned bytes |
|
) { |
|
|
|
// Verify that the socket has not been disconnected |
|
if (IsDisconnected()) |
|
return false; |
|
|
|
// Enter the critical section |
|
m_critSect.Enter(); |
|
|
|
// Attempt to send the data immediately |
|
int result; |
|
if (IsConnected() && !m_sendQueue.Bytes()) { |
|
result = send( |
|
m_sock, |
|
(const char *)data, |
|
bytes, |
|
0 |
|
); |
|
if ((result == SOCKET_ERROR) && (GetLastError() != WSAEWOULDBLOCK)) |
|
LogMsg(kLogFatal, "failed: send"); |
|
} |
|
else |
|
result = 0; |
|
|
|
// If we were unable to send the entire message, queue the unsent portion |
|
if ((unsigned)result < bytes) { |
|
m_sendQueue.Add( |
|
(const byte *)data + result, |
|
bytes - result |
|
); |
|
} |
|
|
|
// Leave the critical section |
|
m_critSect.Leave(); |
|
|
|
return true; |
|
} |
|
|
|
|
|
/**************************************************************************** |
|
* |
|
* Window procedure |
|
* |
|
***/ |
|
|
|
#define CLASS_NAME "AsyncSelectWindow" |
|
|
|
#define WM_CANCEL_CONNECT (WM_USER + 0) |
|
#define WM_PROCESS_QUEUE (WM_USER + 1) |
|
|
|
static CCritSect s_critSect; |
|
static HANDLE s_readyEvent; |
|
static HANDLE s_thread; |
|
|
|
static unsigned THREADCALL W9xSocketThreadProc (AsyncThread *); |
|
|
|
static LRESULT CALLBACK WndProc ( |
|
HWND window, |
|
UINT message, |
|
WPARAM wParam, |
|
LPARAM lParam |
|
); |
|
|
|
//=========================================================================== |
|
static void OnCancelConnect ( |
|
AsyncCancelId sequence |
|
) { |
|
|
|
// Find the associated object |
|
s_critSect.Enter(); |
|
CSocket * object = s_mapper.Find(sequence); |
|
s_critSect.Leave(); |
|
|
|
// Force the object to dispatch connect success or failure. We must not |
|
// reference this object after the call to OnConnect() returns, because |
|
// it may have been deleted. |
|
if (object) |
|
object->OnConnect(); |
|
|
|
} |
|
|
|
//=========================================================================== |
|
static void OnProcessQueue ( |
|
AsyncCancelId sequence |
|
) { |
|
|
|
// Find the associated object |
|
s_critSect.Enter(); |
|
CSocket * object = s_mapper.Find(sequence); |
|
s_critSect.Leave(); |
|
|
|
// Process the object's dispatch queue |
|
if (object) |
|
object->ProcessQueue(); |
|
|
|
} |
|
|
|
//=========================================================================== |
|
static void OnSocketEvent ( |
|
UINT message, |
|
SOCKET sock, |
|
long event |
|
) { |
|
|
|
// Enter the critical section |
|
s_critSect.Enter(); |
|
|
|
// Find the associated object |
|
CSocket * object = s_mapper.Find(message, sock); |
|
|
|
// Verify that the object has not been disconnected. An object that |
|
// has been disconnected can be deleted at any time, and therefore |
|
// can't be referenced outside the critical section. However, the |
|
// object cannot possibly have been disconnected here because objects |
|
// once connected are only disconnected during a call to OnClose(), |
|
// which is the last event we process for an object. |
|
ASSERT(!object || !object->IsDisconnected()); |
|
|
|
// Leave the critical section |
|
s_critSect.Leave(); |
|
|
|
// Dispatch the event |
|
if (object) |
|
switch (event) { |
|
case FD_CLOSE: object->OnClose(); break; |
|
case FD_CONNECT: object->OnConnect(); break; |
|
case FD_READ: object->OnReadReady(); break; |
|
case FD_WRITE: object->OnWriteReady(); break; |
|
} |
|
|
|
// We must not reference the object after dispatching the event, |
|
// because the event processor may have deleted it |
|
|
|
} |
|
|
|
//=========================================================================== |
|
static void PostCancelConnect (AsyncCancelId sequence) { |
|
s_critSect.Enter(); |
|
if (s_window) |
|
PostMessage(s_window, WM_CANCEL_CONNECT, 0, (LPARAM)sequence); |
|
s_critSect.Leave(); |
|
} |
|
|
|
//=========================================================================== |
|
static void PostProcessQueue (AsyncCancelId sequence) { |
|
s_critSect.Enter(); |
|
if (s_window) |
|
PostMessage(s_window, WM_PROCESS_QUEUE, 0, (LPARAM)sequence); |
|
s_critSect.Leave(); |
|
} |
|
|
|
//=========================================================================== |
|
static void ShutdownWindow () { |
|
s_critSect.Enter(); |
|
|
|
// Close the window |
|
if (s_window) { |
|
PostMessage(s_window, WM_CLOSE, 0, 0); |
|
s_window = 0; |
|
} |
|
|
|
// Close the thread |
|
if (s_thread) { |
|
HANDLE thread = s_thread; |
|
s_thread = 0; |
|
s_critSect.Leave(); |
|
WaitForSingleObject(thread, INFINITE); |
|
s_critSect.Enter(); |
|
} |
|
|
|
// Close the ready event |
|
if (s_readyEvent) { |
|
CloseHandle(s_readyEvent); |
|
s_readyEvent = 0; |
|
} |
|
|
|
s_critSect.Leave(); |
|
} |
|
|
|
//=========================================================================== |
|
static HWND StartupWindow () { |
|
|
|
s_critSect.Enter(); |
|
|
|
// Check whether we need to create the window |
|
if (!s_window) { |
|
|
|
// Check whether we need to create the thread |
|
if (!s_thread) { |
|
|
|
// Create an object which the thread can use to signal creation |
|
// of the window |
|
ASSERT(!s_readyEvent); |
|
s_readyEvent = CreateEvent(nil, TRUE, FALSE, nil); |
|
|
|
// Create a thread which will create the window and run its |
|
// message loop |
|
s_thread = (HANDLE) AsyncThreadCreate( |
|
W9xSocketThreadProc, |
|
nil, |
|
L"W9xSockThread" |
|
); |
|
|
|
} |
|
|
|
// Wait for the thread to create the window |
|
HANDLE event = s_readyEvent; |
|
s_critSect.Leave(); |
|
WaitForSingleObject(event, INFINITE); |
|
s_critSect.Enter(); |
|
|
|
} |
|
|
|
// Return the window handle |
|
HWND window = s_window; |
|
s_critSect.Leave(); |
|
return window; |
|
|
|
} |
|
|
|
//=========================================================================== |
|
static unsigned THREADCALL W9xSocketThreadProc (AsyncThread *) { |
|
ASSERT(!s_window); |
|
|
|
// Enter the critical section |
|
s_critSect.Enter(); |
|
|
|
// Register the window class |
|
HINSTANCE instance = (HINSTANCE)GetModuleHandle(nil); |
|
WNDCLASS wndClass; |
|
ZERO(wndClass); |
|
wndClass.lpfnWndProc = WndProc; |
|
wndClass.hInstance = instance; |
|
wndClass.lpszClassName = CLASS_NAME; |
|
RegisterClass(&wndClass); |
|
|
|
// Create the window |
|
s_window = CreateWindow( |
|
CLASS_NAME, |
|
CLASS_NAME, |
|
WS_OVERLAPPED, |
|
CW_USEDEFAULT, |
|
CW_USEDEFAULT, |
|
CW_USEDEFAULT, |
|
CW_USEDEFAULT, |
|
0, // parent window |
|
0, // menu |
|
instance, |
|
nil // param |
|
); |
|
|
|
// Leave the critical section |
|
s_critSect.Leave(); |
|
|
|
// Trigger an event to allow anyone blocking on window creation to proceed |
|
SetEvent(s_readyEvent); |
|
|
|
// Dispatch messages |
|
MSG msg; |
|
while (GetMessage(&msg, nil, 0, 0)) |
|
if (msg.message == WM_CLOSE) |
|
break; |
|
else { |
|
TranslateMessage(&msg); |
|
DispatchMessage(&msg); |
|
} |
|
|
|
// Destroy the window |
|
s_critSect.Enter(); |
|
if (s_window) { |
|
DestroyWindow(s_window); |
|
s_window = 0; |
|
} |
|
s_critSect.Leave(); |
|
|
|
return 0; |
|
} |
|
|
|
//=========================================================================== |
|
static LRESULT CALLBACK WndProc ( |
|
HWND window, |
|
UINT message, |
|
WPARAM wParam, |
|
LPARAM lParam |
|
) { |
|
switch (message) { |
|
|
|
case WM_CANCEL_CONNECT: |
|
OnCancelConnect((AsyncCancelId)lParam); |
|
return 0; |
|
|
|
case WM_PROCESS_QUEUE: |
|
OnProcessQueue((AsyncCancelId)lParam); |
|
return 0; |
|
|
|
default: |
|
if ((message >= WM_APP) && (message < 0xc000)) |
|
OnSocketEvent(message, (SOCKET)wParam, WSAGETSELECTEVENT(lParam)); |
|
else |
|
return DefWindowProc(window, message, wParam, lParam); |
|
break; |
|
|
|
} |
|
return 0; |
|
} |
|
|
|
|
|
/**************************************************************************** |
|
* |
|
* Exported functions |
|
* |
|
***/ |
|
|
|
//=========================================================================== |
|
void W9xSocketConnect ( |
|
AsyncCancelId * cancelId, |
|
const NetAddress & netAddr, |
|
FAsyncNotifySocketProc notifyProc, |
|
void * param, |
|
const void * sendData, |
|
unsigned sendBytes, |
|
unsigned connectMs, |
|
unsigned localPort |
|
) { |
|
// Not supported for W9X |
|
ref(connectMs); |
|
ref(localPort); |
|
|
|
// If necessary, startup the window and message queue |
|
HWND window = StartupWindow(); |
|
|
|
// Create a socket |
|
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); |
|
if (!sock) |
|
LogMsg(kLogFatal, "failed: socket"); |
|
|
|
// Assign a sequence number |
|
static unsigned s_sequence; |
|
AsyncCancelId sequence = (AsyncCancelId)++s_sequence; |
|
if (!sequence) |
|
sequence = (AsyncCancelId)++s_sequence; |
|
*cancelId = sequence; |
|
|
|
// Assign a message id |
|
static UINT s_message; |
|
UINT message = (s_message++ & 0x3fff) | WM_APP; // range 0x8000 - 0xbfff |
|
|
|
// Create a socket object |
|
CSocket * object = NEW(CSocket)( |
|
sequence, |
|
message, |
|
sock, |
|
notifyProc |
|
); |
|
|
|
// Queue a connect notification for the socket |
|
object->QueueConnect( |
|
param, |
|
sendBytes ? ((const byte *)sendData)[0] : (byte)0 |
|
); |
|
|
|
// Queue sending data |
|
if (sendBytes) |
|
object->Send(sendData, sendBytes); |
|
|
|
// Setup notifications for the socket |
|
int result = WSAAsyncSelect( |
|
sock, |
|
window, |
|
message, |
|
FD_CLOSE | FD_CONNECT | FD_READ | FD_WRITE |
|
); |
|
if (result) |
|
LogMsg(kLogFatal, "failed: WSAAsyncSelect"); |
|
|
|
// Start the connection |
|
if ( connect(sock, (const sockaddr *)&netAddr, sizeof(sockaddr)) && |
|
(GetLastError() != WSAEWOULDBLOCK) ) |
|
LogMsg(kLogFatal, "failed: connect. err=%u", GetLastError()); |
|
|
|
} |
|
|
|
//=========================================================================== |
|
void W9xSocketConnectCancel ( |
|
FAsyncNotifySocketProc notifyProc, |
|
AsyncCancelId cancelId |
|
) { |
|
|
|
// Enter the critical section |
|
s_critSect.Enter(); |
|
|
|
// If a cancel id was given, cancel that one connection |
|
if (cancelId) |
|
PostCancelConnect(cancelId); |
|
|
|
// Otherwise, cancel all connections using the specified notification |
|
// procedure |
|
else |
|
for (unsigned index = 0; ; ++index) { |
|
|
|
// Find the next socket object |
|
CSocket * object = s_mapper.Find(notifyProc, index); |
|
if (!object) |
|
break; |
|
|
|
// If the object exists and is not yet connected, cancel it |
|
if (object) |
|
PostCancelConnect(object->GetSequence()); |
|
|
|
} |
|
|
|
// Leave the critical section |
|
s_critSect.Leave(); |
|
|
|
} |
|
|
|
//=========================================================================== |
|
void W9xSocketDelete ( |
|
AsyncSocket sock |
|
) { |
|
|
|
// Dereference the object |
|
CSocket * object = (CSocket *)sock; |
|
|
|
// Delete the object |
|
s_critSect.Enter(); |
|
DEL(object); |
|
s_critSect.Leave(); |
|
|
|
} |
|
|
|
//=========================================================================== |
|
void W9xSocketDestroy () { |
|
|
|
// Shutdown the window and message queue |
|
ShutdownWindow(); |
|
|
|
} |
|
|
|
//=========================================================================== |
|
void W9xSocketEnableNagling ( |
|
AsyncSocket sock, |
|
bool enable |
|
) { |
|
|
|
// Dereference the object |
|
CSocket * object = (CSocket *)sock; |
|
|
|
// set nagling state |
|
object->EnableNagling(enable); |
|
|
|
} |
|
|
|
//=========================================================================== |
|
void W9xSocketDisconnect ( |
|
AsyncSocket sock, |
|
bool hardClose |
|
) { |
|
|
|
// Dereference the object |
|
CSocket * object = (CSocket *)sock; |
|
|
|
// Disconnect |
|
object->Disconnect(hardClose); |
|
|
|
} |
|
|
|
//=========================================================================== |
|
unsigned W9xSocketStartListening ( |
|
const NetAddress & listenAddr, |
|
FAsyncNotifySocketProc notifyProc |
|
) { |
|
ref(listenAddr); |
|
ref(notifyProc); |
|
return 0; |
|
|
|
} |
|
|
|
//=========================================================================== |
|
void W9xSocketStopListening ( |
|
const NetAddress & listenAddr, |
|
FAsyncNotifySocketProc notifyProc |
|
) { |
|
ref(listenAddr); |
|
ref(notifyProc); |
|
} |
|
|
|
//=========================================================================== |
|
bool W9xSocketSend ( |
|
AsyncSocket sock, |
|
const void * data, |
|
unsigned bytes |
|
) { |
|
|
|
// Dereference the object |
|
CSocket * object = (CSocket *)sock; |
|
|
|
// Send the data |
|
return object->Send(data, bytes); |
|
|
|
} |
|
|
|
//=========================================================================== |
|
void W9xSocketSetNotifyProc ( |
|
AsyncSocket sock, |
|
FAsyncNotifySocketProc notifyProc |
|
) { |
|
ref(sock); |
|
ref(notifyProc); |
|
|
|
// This provider does not allow changing the notification procedure |
|
FATAL("SocketSetNotifyProc"); |
|
} |
|
|
|
//=========================================================================== |
|
void W9xSocketSetBacklogAlloc ( |
|
AsyncSocket sock, |
|
unsigned bufferSize |
|
) { |
|
|
|
// This provider does not limit the maximum backlog allocation |
|
ref(sock); |
|
ref(bufferSize); |
|
|
|
} |
|
|
|
//=========================================================================== |
|
bool W9xSocketWrite ( |
|
AsyncSocket sock, |
|
const void * data, |
|
unsigned bytes, |
|
void * param |
|
) { |
|
|
|
// Dereference the object |
|
CSocket * object = (CSocket *)sock; |
|
|
|
// Send the data |
|
if (!object->Send(data, bytes)) |
|
return false; |
|
|
|
// Queue a completion notification |
|
object->QueueWrite(param, data, bytes); |
|
|
|
// Trigger processing the queue |
|
PostProcessQueue(object->GetSequence()); |
|
|
|
return true; |
|
} |
|
|
|
|
|
} // namespace W9x
|
|
|