0ad/source/network/SocketBase.cpp
janwas c0ed950657 had to remove uint and ulong from lib/types.h due to conflict with other library.
this snowballed into a massive search+destroy of the hodgepodge of
mostly equivalent types we had in use (int, uint, unsigned, unsigned
int, i32, u32, ulong, uintN).

it is more efficient to use 64-bit types in 64-bit mode, so the
preferred default is size_t (for anything remotely resembling a size or
index). tile coordinates are ssize_t to allow more efficient conversion
to/from floating point. flags are int because we almost never need more
than 15 distinct bits, bit test/set is not slower and int is fastest to
type. finally, some data that is pretty much directly passed to OpenGL
is now typed accordingly.

after several hours, the code now requires fewer casts and less
guesswork.

other changes:
- unit and player IDs now have an "invalid id" constant in the
respective class to avoid casting and -1
- fix some endian/64-bit bugs in the map (un)packing. added a
convenience function to write/read a size_t.
- ia32: change CPUID interface to allow passing in ecx (required for
cache topology detection, which I need at work). remove some unneeded
functions from asm, replace with intrinsics where possible.

This was SVN commit r5942.
2008-05-11 18:48:32 +00:00

980 lines
22 KiB
C++

#include "precompiled.h"
#if OS_WIN
#include "lib/sysdep/win/win.h"
#include "lib/sysdep/win/wposix/wsock_internal.h"
#endif
#include "Network.h"
#include "NetworkInternal.h"
#include "ps/CStr.h"
// ERROR is defined by some windows header. Undef it
#undef ERROR
#include "ps/CLogger.h"
#include "NetLog.h"
#if !OS_WIN
# include <fcntl.h>
# include <signal.h>
# include <sys/ioctl.h>
#endif
// Record global transfer statistics (sent/recvd bytes). This will put a lock
// /unlock pair in all read and write operations.
#define RECORD_GLOBAL_STATS 1
#define GLOBAL_LOCK() pthread_mutex_lock(&g_SocketSetInternal.m_Mutex)
#define GLOBAL_UNLOCK() pthread_mutex_unlock(&g_SocketSetInternal.m_Mutex)
CSocketSetInternal g_SocketSetInternal;
DEFINE_ERROR(NO_SUCH_HOST, "Host not found");
DEFINE_ERROR(CONNECT_TIMEOUT, "The connection attempt timed out");
DEFINE_ERROR(CONNECT_REFUSED, "The connection attempt was refused");
DEFINE_ERROR(NO_ROUTE_TO_HOST, "No route to host");
DEFINE_ERROR(CONNECTION_BROKEN, "The connection has been closed");
DEFINE_ERROR(CONNECT_IN_PROGRESS, "The connect attempt has started, but is not yet complete");
DEFINE_ERROR(PORT_IN_USE, "The port is already in use by another process");
DEFINE_ERROR(INVALID_PORT, "The port specified is either invalid, or forbidden by system or firewall policy");
DEFINE_ERROR(INVALID_PROTOCOL, "The socket type or protocol is not supported by the operating system. Make sure that the TCP/IP protocol is installed and activated");
// Map an OS error number to a PS_RESULT
PS_RESULT GetPS_RESULT(int error)
{
switch (error)
{
case EWOULDBLOCK:
case EINPROGRESS:
return PS_OK;
case ENETUNREACH:
case ENETDOWN:
case EADDRNOTAVAIL:
return NO_ROUTE_TO_HOST;
case ETIMEDOUT:
return CONNECT_TIMEOUT;
case ECONNREFUSED:
return CONNECT_REFUSED;
default:
char buf[256];
Network_GetErrorString(error, buf, sizeof(buf));
LOG(CLogger::Error, LOG_CAT_NET, "SocketBase.cpp::GetPS_RESULT(): Unrecognized error %s[%d]", buf, error);
return PS_FAIL;
}
}
CSocketAddress::CSocketAddress(int port, ESocketProtocol proto)
{
memset(&m_Union, 0, sizeof(m_Union));
switch (proto)
{
case IPv4:
m_Union.m_IPv4.sin_family=PF_INET;
m_Union.m_IPv4.sin_addr.s_addr=htonl(INADDR_ANY);
m_Union.m_IPv4.sin_port=htons(port);
break;
case IPv6:
m_Union.m_IPv6.sin6_family=PF_INET6;
cpu_memcpy(&m_Union.m_IPv6.sin6_addr, &in6addr_any, sizeof(in6addr_any));
m_Union.m_IPv6.sin6_port=htons(port);
break;
default:
debug_warn("CSocketAddress::CSocketAddress: Bad proto");
}
}
CSocketAddress CSocketAddress::Loopback(int port, ESocketProtocol proto)
{
CSocketAddress ret;
switch (proto)
{
case IPv4:
ret.m_Union.m_IPv4.sin_family=PF_INET;
ret.m_Union.m_IPv4.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
ret.m_Union.m_IPv4.sin_port=htons(port);
break;
case IPv6:
ret.m_Union.m_IPv6.sin6_family=PF_INET6;
cpu_memcpy(&ret.m_Union.m_IPv6.sin6_addr, &in6addr_loopback, sizeof(in6addr_loopback));
ret.m_Union.m_IPv6.sin6_port=htons(port);
break;
default:
debug_warn("CSocketAddress::CSocketAddress: Bad proto");
}
return ret;
}
PS_RESULT CSocketAddress::Resolve(const char *name, int port, CSocketAddress &addr)
{
// Use IPV4 by default, ignoring address type.
memset(&addr.m_Union, 0, sizeof(addr.m_Union));
hostent *he;
addr.m_Union.m_IPv4.sin_family=AF_INET;
addr.m_Union.m_IPv4.sin_port=htons(port);
// Try to parse dot-notation IP
addr.m_Union.m_IPv4.sin_addr.s_addr=inet_addr(name);
if (addr.m_Union.m_IPv4.sin_addr.s_addr==INADDR_NONE) // Not a dotted IP, try name resolution
{
he=gethostbyname(name);
if (!he)
return NO_SUCH_HOST;
addr.m_Union.m_IPv4.sin_addr=*(struct in_addr *)(he->h_addr_list[0]);
}
return PS_OK;
}
CStr CSocketAddress::GetString() const
{
char convBuf[NI_MAXHOST];
int res=getnameinfo((struct sockaddr *)&m_Union, sizeof(struct sockaddr_in),
convBuf, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
if (res == 0)
return CStr(convBuf);
// getnameinfo won't return a string for the IPv6 unspecified address
else if (m_Union.m_Family == IPv6 && res==EAI_NONAME)
return "::";
// supported, but failed
else if (errno != ENOSYS)
return "";
// else: IPv6 not supported, fall back to IPv4
if (m_Union.m_Family == IPv4)
{
sprintf(convBuf, "%d.%d.%d.%d",
m_Union.m_IPv4.sin_addr.s_addr&0xff,
(m_Union.m_IPv4.sin_addr.s_addr>>8)&0xff,
(m_Union.m_IPv4.sin_addr.s_addr>>16)&0xff,
(m_Union.m_IPv4.sin_addr.s_addr>>24)&0xff);
return CStr(convBuf);
}
else
return CStr();
}
int CSocketAddress::GetPort() const
{
switch (m_Union.m_Family)
{
case IPv4:
case IPv6:
return ntohs(m_Union.m_IPv4.sin_port);
}
return -1;
}
CSocketBase::CSocketBase()
{
m_pInternal=new CSocketInternal;
m_Proto=UNSPEC;
m_NonBlocking=true;
m_State=SS_UNCONNECTED;
m_Error=PS_OK;
}
CSocketBase::CSocketBase(CSocketInternal *pInt)
{
m_pInternal=pInt;
m_Proto=pInt->m_RemoteAddr.GetProtocol();
m_State=SS_CONNECTED;
m_Error=PS_OK;
SetNonBlocking(true);
NET_LOG2( "CSocketBase::CSocketBase(): Created socket from fd %d", pInt->m_fd );
}
CSocketBase::~CSocketBase()
{
NET_LOG4( "CSocketBase::~CSocketBase(): fd is %d. "
"Received: %lld bytes. Sent: %lld bytes.",
m_pInternal->m_fd,
m_pInternal->m_RecvBytes,
m_pInternal->m_SentBytes );
Destroy();
delete m_pInternal;
}
void CSocketBase::Shutdown()
{
GLOBAL_LOCK();
if (g_SocketSetInternal.m_NumSockets)
{
NET_LOG2( "CSocketBase::Shutdown(): %d sockets still open! (forcing network shutdown)", g_SocketSetInternal.m_NumSockets );
}
#if RECORD_GLOBAL_STATS
NET_LOG3( "GLOBAL SOCKET STATISTICS: "
"Received: %lld bytes. Sent: %lld bytes.",
g_SocketSetInternal.m_GlobalRecvBytes,
g_SocketSetInternal.m_GlobalSentBytes);
#endif
GLOBAL_UNLOCK();
if (g_SocketSetInternal.m_Thread)
{
AbortWaitLoop();
pthread_join(g_SocketSetInternal.m_Thread, NULL);
}
}
void *WaitLoopThreadMain(void *)
{
debug_set_thread_name("net_wait");
GLOBAL_LOCK();
CSocketBase::RunWaitLoop();
g_SocketSetInternal.m_Thread=0;
GLOBAL_UNLOCK();
return NULL;
}
PS_RESULT CSocketBase::Initialize(ESocketProtocol proto)
{
// Use IPV4 by default, ignoring address type.
int res=socket(AF_INET, SOCK_STREAM, 0);
NET_LOG2("CSocketBase::Initialize(): socket() res: %d", res);
if (res == -1)
{
return INVALID_PROTOCOL;
}
m_pInternal->m_fd=res;
m_Proto=proto;
GLOBAL_LOCK();
if (g_SocketSetInternal.m_NumSockets == 0)
pthread_create(&g_SocketSetInternal.m_Thread, NULL, WaitLoopThreadMain, NULL);
g_SocketSetInternal.m_NumSockets++;
GLOBAL_UNLOCK();
SetNonBlocking(m_NonBlocking);
return PS_OK;
}
void CSocketBase::Close()
{
shutdown(m_pInternal->m_fd, SHUT_WR);
m_State=SS_CLOSED_LOCALLY;
}
void CSocketBase::Destroy()
{
if (m_pInternal->m_fd == -1)
{
m_State=SS_UNCONNECTED;
return;
}
// Remove any data associated with the file descriptor
GLOBAL_LOCK();
debug_assert(g_SocketSetInternal.m_NumSockets > 0);
g_SocketSetInternal.m_NumSockets--;
g_SocketSetInternal.m_HandleMap.erase(m_pInternal->m_fd);
if (!g_SocketSetInternal.m_NumSockets)
AbortWaitLoop();
GLOBAL_UNLOCK();
// Disconnect the socket, if it is still connected
if (m_State == SS_CONNECTED || m_State == SS_CLOSED_LOCALLY)
{
// This makes the other end receive a RST, but since
// we've had no chance to close cleanly and the socket must
// be destroyed immediately, we've got no choice
shutdown(m_pInternal->m_fd, SHUT_RDWR);
m_State=SS_UNCONNECTED;
}
// Destroy the socket
closesocket(m_pInternal->m_fd);
m_pInternal->m_fd=-1;
}
void CSocketBase::SetNonBlocking(bool nonblocking)
{
m_NonBlocking=nonblocking;
#if OS_WIN
unsigned long nb=nonblocking;
if(!nonblocking)
SendWaitLoopUpdate(); // Need to call WSAAsyncSelect with event=0 before ioctlsocket
int res=ioctlsocket(m_pInternal->m_fd, FIONBIO, &nb);
if (res != 0)
NET_LOG2("SetNonBlocking: res %d", res);
#else
int oldflags=fcntl(m_pInternal->m_fd, F_GETFL, 0);
if (oldflags != -1)
{
if (nonblocking)
oldflags |= O_NONBLOCK;
else
oldflags &= ~O_NONBLOCK;
fcntl(m_pInternal->m_fd, F_SETFL, oldflags);
}
#endif
}
void CSocketBase::SetTcpNoDelay(bool tcpNoDelay)
{
// Disable Nagle's Algorithm
int data=tcpNoDelay;
setsockopt(m_pInternal->m_fd, SOL_SOCKET, TCP_NODELAY, (const char *)&data, sizeof(data));
}
PS_RESULT CSocketBase::Read(void *buf, size_t len, size_t *bytesRead)
{
int res;
char errbuf[256];
res=recv(m_pInternal->m_fd, (char *)buf, len, 0);
if (res < 0)
{
*bytesRead=0;
int error=Network_LastError;
switch (error)
{
case EWOULDBLOCK:
return PS_OK;
/*case ENETDOWN:
case ENETRESET:
case ENOTCONN:
case ESHUTDOWN:
case ECONNABORTED:
case ECONNRESET:
case ETIMEDOUT:*/
default:
Network_GetErrorString(error, errbuf, sizeof(errbuf));
NET_LOG3("Read error %s [%d]", errbuf, error);
m_State=SS_UNCONNECTED;
m_Error=GetPS_RESULT(error);
return m_Error;
}
}
if (res == 0 && len > 0) // EOF - Cleanly closed socket
{
*bytesRead=0;
m_State=SS_UNCONNECTED;
m_Error=PS_OK;
return CONNECTION_BROKEN;
}
*bytesRead=res;
m_pInternal->m_RecvBytes += res;
#if RECORD_GLOBAL_STATS
GLOBAL_LOCK();
g_SocketSetInternal.m_GlobalRecvBytes += res;
GLOBAL_UNLOCK();
#endif
return PS_OK;
}
PS_RESULT CSocketBase::Write(void *buf, size_t len, size_t *bytesWritten)
{
int res;
char errbuf[256];
res=send(m_pInternal->m_fd, (char *)buf, len, 0);
if (res < 0)
{
*bytesWritten=0;
int err=Network_LastError;
switch (err)
{
case EWOULDBLOCK:
return PS_OK;
/*case ENETDOWN:
case ENETRESET:
case ENOTCONN:
case ESHUTDOWN:
case ECONNABORTED:
case ECONNRESET:
case ETIMEDOUT:
case EHOSTUNREACH:*/
default:
Network_GetErrorString(err, errbuf, sizeof(errbuf));
NET_LOG3("Write error %s [%d]", errbuf, err);
m_State=SS_UNCONNECTED;
return CONNECTION_BROKEN;
}
}
*bytesWritten=res;
m_pInternal->m_SentBytes += res;
#if RECORD_GLOBAL_STATS
GLOBAL_LOCK();
g_SocketSetInternal.m_GlobalSentBytes += res;
GLOBAL_UNLOCK();
#endif
return PS_OK;
}
PS_RESULT CSocketBase::Connect(const CSocketAddress &addr)
{
int res = connect(m_pInternal->m_fd, (struct sockaddr *)(&addr.m_Union), sizeof(struct sockaddr_in));
NET_LOG3("connect returned %d [%d]", res, m_NonBlocking);
if (res != 0)
{
int error=Network_LastError;
NET_LOG2("last error was %d", error);
if (m_NonBlocking && error == EWOULDBLOCK)
{
m_State=SS_CONNECT_STARTED;
}
else
{
m_State=SS_UNCONNECTED;
m_Error=GetPS_RESULT(error);
}
}
else
{
m_State=SS_CONNECTED;
m_Error=PS_OK;
}
return m_Error;
}
PS_RESULT CSocketBase::Bind(const CSocketAddress &address)
{
char errBuf[256];
int res;
Initialize(address.GetProtocol());
SetOpMask(READ);
res=bind(m_pInternal->m_fd, (struct sockaddr *)&address, sizeof(struct sockaddr_in));
if (res == -1)
{
PS_RESULT ret=PS_FAIL;
int err=Network_LastError;
switch (err)
{
case EADDRINUSE:
ret=PORT_IN_USE;
break;
case EACCES:
case EADDRNOTAVAIL:
ret=INVALID_PORT;
break;
default:
Network_GetErrorString(err, errBuf, sizeof(errBuf));
LOG(CLogger::Error, LOG_CAT_NET, "CServerSocket::Bind(): bind: %s [%d] => PS_FAIL", errBuf, err);
}
m_State=SS_UNCONNECTED;
m_Error=ret;
return ret;
}
res=listen(m_pInternal->m_fd, 5);
if (res == -1)
{
int err=Network_LastError;
Network_GetErrorString(err, errBuf, sizeof(errBuf));
LOG(CLogger::Error, LOG_CAT_NET, "CServerSocket::Bind(): listen: %s [%d] => PS_FAIL", errBuf, err);
m_State=SS_UNCONNECTED;
return PS_FAIL;
}
m_State=SS_CONNECTED;
m_Error=PS_OK;
return PS_OK;
}
PS_RESULT CSocketBase::PreAccept(CSocketAddress &addr)
{
socklen_t addrLen=sizeof(struct sockaddr_in);
int fd=accept(m_pInternal->m_fd, (struct sockaddr *)&addr.m_Union, &addrLen);
m_pInternal->m_AcceptFd=fd;
m_pInternal->m_AcceptAddr=addr;
if (fd != -1)
return PS_OK;
else
{
PS_RESULT res=GetPS_RESULT(Network_LastError);
// GetPS_RESULT considers some errors non-failures
if (res == PS_OK)
return PS_FAIL;
else
return res;
}
}
CSocketInternal *CSocketBase::Accept()
{
if (m_pInternal->m_AcceptFd != -1)
{
CSocketInternal *pInt=new CSocketInternal();
pInt->m_fd=m_pInternal->m_AcceptFd;
pInt->m_RemoteAddr=m_pInternal->m_AcceptAddr;
GLOBAL_LOCK();
g_SocketSetInternal.m_NumSockets++;
GLOBAL_UNLOCK();
m_pInternal->m_AcceptFd=-1;
return pInt;
}
else
return NULL;
}
void CSocketBase::Reject()
{
shutdown(m_pInternal->m_AcceptFd, SHUT_RDWR);
closesocket(m_pInternal->m_AcceptFd);
}
// UNIX select loop
#if !OS_WIN
// ConnectError is called on a socket the first time it selects as ready
// after the BeginConnect, to check errors on the socket and update the
// connection status information
//
// Returns: true if error callback should be called, false if it should not
bool CSocketBase::ConnectError(CSocketBase *pSocket)
{
CSocketInternal *pInt=pSocket->m_pInternal;
size_t buf;
int res;
if (pSocket->m_State==SS_CONNECT_STARTED)
{
res=read(pInt->m_fd, &buf, 0);
// read of zero bytes should be a successful no-op, unless
// there was an error
if (res == -1)
{
pSocket->m_State=SS_UNCONNECTED;
PS_RESULT connErr=GetPS_RESULT(errno);
NET_LOG4("Connect error: %s [%d:%s]", connErr, errno, strerror(errno));
pSocket->m_Error=connErr;
return true;
}
else
{
pSocket->m_State=SS_CONNECTED;
pSocket->m_Error=PS_OK;
}
}
return false;
}
// SocketWritable is called whenever a socket selects as writable in the unix
// select loop. This will call the callback after checking for a connect error
// if there's a connect in progress, as well as update the socket's state.
//
// Locking: The global mutex must be held when entering this function, and it
// will be held upon return.
void CSocketBase::SocketWritable(CSocketBase *pSock)
{
//CSocketInternal *pInt=pSock->m_pInternal;
bool isConnectError=false;
if (pSock->m_State != SS_CONNECTED)
isConnectError=ConnectError(pSock);
GLOBAL_UNLOCK();
if (isConnectError)
pSock->OnClose(pSock->m_Error);
else
pSock->OnWrite();
GLOBAL_LOCK();
}
// SocketReadable is called whenever a socket selects as writable in the unix
// select loop. This will call the callback after checking for a connect error
// if there's a connect in progress, as well as update the socket's state.
//
// Locking: The global mutex must be held when entering this function, and it
// will be held upon return.
void CSocketBase::SocketReadable(CSocketBase *pSock)
{
bool isError=false;
if (pSock->m_State == SS_CONNECT_STARTED)
isError=ConnectError(pSock);
else if (pSock->m_State == SS_UNCONNECTED)
{
// UNCONNECTED sockets don't get callbacks
// Note that server sockets that are bound have state==SS_CONNECTED
return;
}
else if (pSock->m_State != SS_UNCONNECTED)
{
size_t nRead;
errno=0;
int res=ioctl(pSock->m_pInternal->m_fd, FIONREAD, &nRead);
// failure, errno=EINVAL means server socket
// success, nRead != 0 means alive stream socket
if (res == -1 && errno != EINVAL)
{
NET_LOG3("RunWaitLoop:ioctl: Connection broken [%d:%s]", errno, strerror(errno));
// Don't use API function - we both hold a lock and
// it is unnecessary to SendWaitLoopUpdate at this
// stage
pSock->m_pInternal->m_Ops=0;
pSock->m_State=SS_UNCONNECTED;
if (errno)
pSock->m_Error=GetPS_RESULT(errno);
else
pSock->m_Error=PS_OK;
isError=true;
}
}
GLOBAL_UNLOCK();
if (isError)
pSock->OnClose(pSock->m_Error);
else
pSock->OnRead();
GLOBAL_LOCK();
}
void CSocketBase::RunWaitLoop()
{
int res;
signal(SIGPIPE, SIG_IGN);
// Create Control Pipe
res=pipe(g_SocketSetInternal.m_Pipe);
if (res != 0)
return;
// The lock is held upon entry and exit of this loop. There are a few places
// where the lock is released and then re-acquired: when calling callbacks
// and when calling select().
while (true)
{
std::map<int, CSocketBase *>::iterator it;
fd_set rfds;
fd_set wfds;
int fd_max=g_SocketSetInternal.m_Pipe[0];
// Prepare fd_set: Read
FD_ZERO(&rfds);
FD_SET(g_SocketSetInternal.m_Pipe[0], &rfds);
// Prepare fd_set: Write
FD_ZERO(&wfds);
it=g_SocketSetInternal.m_HandleMap.begin();
while (it != g_SocketSetInternal.m_HandleMap.end())
{
size_t ops=it->second->m_pInternal->m_Ops;
if (ops && it->first > fd_max)
fd_max=it->first;
if (ops & READ)
FD_SET(it->first, &rfds);
if (ops & WRITE)
FD_SET(it->first, &wfds);
++it;
}
GLOBAL_UNLOCK();
// select, timeout infinite
res=select(fd_max+1, &rfds, &wfds, NULL, NULL);
GLOBAL_LOCK();
// Check select error
if (res == -1)
{
// It is possible for a socket to be deleted between preparing the
// fd_sets and actually performing the select - in which case it
// will fire a Bad file descriptor error. Simply retry.
if (Network_LastError == EBADF)
continue;
perror("CSocketSet::RunWaitLoop(), select");
continue;
}
// Check Control Pipe
if (FD_ISSET(g_SocketSetInternal.m_Pipe[0], &rfds))
{
char bt;
if (read(g_SocketSetInternal.m_Pipe[0], &bt, 1) == 1)
{
if (bt=='q')
break;
else if (bt=='r')
{
// Reload sockets - just skip to the beginning of the loop
continue;
}
}
FD_CLR(g_SocketSetInternal.m_Pipe[0], &rfds);
}
// Go through sockets
int i=-1;
while (++i <= fd_max)
{
if (!FD_ISSET(i, &rfds) && !FD_ISSET(i, &wfds))
continue;
it=g_SocketSetInternal.m_HandleMap.find(i);
if (it == g_SocketSetInternal.m_HandleMap.end())
continue;
CSocketBase *pSock=it->second;
if (FD_ISSET(i, &wfds))
{
SocketWritable(pSock);
}
// After the callback is called, we must check if the socket
// still exists (sockets may delete themselves in the callback)
it=g_SocketSetInternal.m_HandleMap.find(i);
if (it == g_SocketSetInternal.m_HandleMap.end())
continue;
if (FD_ISSET(i, &rfds))
{
SocketReadable(pSock);
}
}
}
// Close control pipe
close(g_SocketSetInternal.m_Pipe[0]);
close(g_SocketSetInternal.m_Pipe[1]);
return;
}
void CSocketBase::SendWaitLoopAbort()
{
char msg='q';
write(g_SocketSetInternal.m_Pipe[1], &msg, 1);
}
void CSocketBase::SendWaitLoopUpdate()
{
// NET_LOG("SendWaitLoopUpdate: fd %d, ops %u\n", m_pInternal->m_fd, m_pInternal->m_Ops);
char msg='r';
write(g_SocketSetInternal.m_Pipe[1], &msg, 1);
}
// Windows WindowProc for async event notification
#else // i.e. #if OS_WIN
void WaitLoop_SocketUpdateProc(int fd, int error, int event)
{
GLOBAL_LOCK();
CSocketBase *pSock=g_SocketSetInternal.m_HandleMap[fd];
GLOBAL_UNLOCK();
// FIXME What if the fd isn't in the handle map?
if (error)
{
PS_RESULT res=GetPS_RESULT(error);
pSock->m_Error=res;
pSock->m_State=SS_UNCONNECTED;
if (res == PS_FAIL)
pSock->OnClose(CONNECTION_BROKEN);
return;
}
if (pSock->m_State==SS_CONNECT_STARTED)
{
pSock->m_Error=PS_OK;
pSock->m_State=SS_CONNECTED;
}
switch (event)
{
case FD_ACCEPT:
case FD_READ:
pSock->OnRead();
break;
case FD_CONNECT:
case FD_WRITE:
pSock->OnWrite();
break;
case FD_CLOSE:
// If FD_CLOSE and error, OnClose has already been called above
// with the appropriate PS_RESULT
pSock->m_State=SS_UNCONNECTED;
pSock->OnClose(PS_OK);
break;
}
}
LRESULT WINAPI WaitLoop_WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
//printf("WaitLoop_WindowProc(): Windows message: %d:%d:%d\n", msg, wParam, lParam);
switch (msg)
{
case MSG_SOCKET_READY:
{
int event=LOWORD(lParam);
int error=HIWORD(lParam);
WaitLoop_SocketUpdateProc((int)wParam, error?error-WSABASEERR:0, event);
return FALSE;
}
default:
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}
// Locked on entry, must be locked on exit
void CSocketBase::RunWaitLoop()
{
int ret;
char errBuf[256] = {0};
MSG msg;
WNDCLASS wc;
ATOM atom;
memset(&wc, 0, sizeof(WNDCLASS));
wc.lpszClassName="Network Event WindowClass";
wc.lpfnWndProc=WaitLoop_WindowProc;
atom=RegisterClass(&wc);
if (!atom)
{
ret=GetLastError();
Network_GetErrorString(ret, (LPSTR)&errBuf, 256);
NET_LOG3("RegisterClass: %s [%d]", errBuf, ret);
return;
}
// Create message window
g_SocketSetInternal.m_hWnd=CreateWindow((LPCTSTR)atom, "Network Event Window", WS_POPUP, 0, 0, 0, 0, NULL, NULL, NULL, NULL);
if (!g_SocketSetInternal.m_hWnd)
{
ret=GetLastError();
Network_GetErrorString(ret, errBuf, sizeof(errBuf));
NET_LOG3("CreateWindowEx: %s [%d]", errBuf, ret);
return;
}
// If OpMasks where set in another thread before we got this far,
// WSAAsyncSelect will need to be called again
std::map<int, CSocketBase *>::iterator it;
it=g_SocketSetInternal.m_HandleMap.begin();
while (it != g_SocketSetInternal.m_HandleMap.end())
{
it->second->SetOpMask(it->second->GetOpMask());
++it;
}
NET_LOG2("Commencing message loop. hWnd %p", g_SocketSetInternal.m_hWnd);
GLOBAL_UNLOCK();
while ((ret=GetMessage(&msg, g_SocketSetInternal.m_hWnd, 0, 0))!=0)
{
//printf("RunWaitLoop(): Windows message: %d:%d:%d\n", msg.message, msg.wParam, msg.lParam);
if (ret == -1)
{
ret=GetLastError();
Network_GetErrorString(ret, errBuf, sizeof(errBuf));
NET_LOG3("GetMessage: %s [%d]", errBuf, ret);
}
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
GLOBAL_LOCK();
g_SocketSetInternal.m_Thread=0;
// FIXME Leak: Destroy window
g_SocketSetInternal.m_hWnd=0;
NET_LOG("RunWaitLoop returning");
return;
}
void CSocketBase::SendWaitLoopAbort()
{
if (g_SocketSetInternal.m_hWnd)
{
PostMessage(g_SocketSetInternal.m_hWnd, WM_QUIT, 0, 0);
}
else
NET_LOG("SendWaitLoopUpdate: No WaitLoop Running.");
}
void CSocketBase::SendWaitLoopUpdate()
{
GLOBAL_LOCK();
if (g_SocketSetInternal.m_hWnd)
{
if(m_NonBlocking == false)
{
GLOBAL_UNLOCK();
WSAAsyncSelect(m_pInternal->m_fd, g_SocketSetInternal.m_hWnd, MSG_SOCKET_READY, 0);
return;
}
long wsaOps=FD_CLOSE;
if (m_pInternal->m_Ops & READ)
wsaOps |= FD_READ|FD_ACCEPT;
if (m_pInternal->m_Ops & WRITE)
wsaOps |= FD_WRITE|FD_CONNECT;
GLOBAL_UNLOCK();
//printf("SendWaitLoopUpdate: %d: %u %x -> %p\n", m_pInternal->m_fd, m_pInternal->m_Ops, wsaOps, g_SocketSetInternal.m_hWnd);
WSAAsyncSelect(m_pInternal->m_fd, g_SocketSetInternal.m_hWnd, MSG_SOCKET_READY, wsaOps);
}
else
{
//printf("SendWaitLoopUpdate: No WaitLoop Running.\n");
GLOBAL_UNLOCK();
}
}
#endif // #if OS_WIN
void CSocketBase::AbortWaitLoop()
{
SendWaitLoopAbort();
// pthread_join(g_SocketSetInternal.m_Thread);
}
int CSocketBase::GetOpMask()
{
return m_pInternal->m_Ops;
}
void CSocketBase::SetOpMask(int ops)
{
GLOBAL_LOCK();
g_SocketSetInternal.m_HandleMap[m_pInternal->m_fd]=this;
m_pInternal->m_Ops=ops;
/*printf("SetOpMask(fd %d, ops %u) %u\n",
m_pInternal->m_fd,
ops,
g_SocketSetInternal.m_HandleMap[m_pInternal->m_fd]->m_pInternal->m_Ops);*/
GLOBAL_UNLOCK();
SendWaitLoopUpdate();
}