summaryrefslogtreecommitdiffstats
path: root/src/OSSupport
diff options
context:
space:
mode:
Diffstat (limited to 'src/OSSupport')
-rw-r--r--src/OSSupport/BlockingTCPLink.cpp149
-rw-r--r--src/OSSupport/BlockingTCPLink.h28
-rw-r--r--src/OSSupport/CriticalSection.cpp188
-rw-r--r--src/OSSupport/CriticalSection.h80
-rw-r--r--src/OSSupport/Event.cpp118
-rw-r--r--src/OSSupport/Event.h47
-rw-r--r--src/OSSupport/File.cpp452
-rw-r--r--src/OSSupport/File.h144
-rw-r--r--src/OSSupport/GZipFile.cpp107
-rw-r--r--src/OSSupport/GZipFile.h52
-rw-r--r--src/OSSupport/IsThread.cpp172
-rw-r--r--src/OSSupport/IsThread.h100
-rw-r--r--src/OSSupport/ListenThread.cpp238
-rw-r--r--src/OSSupport/ListenThread.h83
-rw-r--r--src/OSSupport/Semaphore.cpp91
-rw-r--r--src/OSSupport/Semaphore.h17
-rw-r--r--src/OSSupport/Sleep.cpp19
-rw-r--r--src/OSSupport/Sleep.h7
-rw-r--r--src/OSSupport/Socket.cpp393
-rw-r--r--src/OSSupport/Socket.h102
-rw-r--r--src/OSSupport/SocketThreads.cpp675
-rw-r--r--src/OSSupport/SocketThreads.h169
-rw-r--r--src/OSSupport/Thread.cpp128
-rw-r--r--src/OSSupport/Thread.h26
-rw-r--r--src/OSSupport/Timer.cpp37
-rw-r--r--src/OSSupport/Timer.h32
26 files changed, 3654 insertions, 0 deletions
diff --git a/src/OSSupport/BlockingTCPLink.cpp b/src/OSSupport/BlockingTCPLink.cpp
new file mode 100644
index 000000000..55454a4b5
--- /dev/null
+++ b/src/OSSupport/BlockingTCPLink.cpp
@@ -0,0 +1,149 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#include "BlockingTCPLink.h"
+
+
+
+
+
+#ifdef _WIN32
+ #define MSG_NOSIGNAL (0)
+#endif
+#ifdef __MACH__
+ #define MSG_NOSIGNAL (0)
+#endif
+
+
+
+
+
+cBlockingTCPLink::cBlockingTCPLink(void)
+{
+}
+
+
+
+
+
+cBlockingTCPLink::~cBlockingTCPLink()
+{
+ CloseSocket();
+}
+
+
+
+
+
+void cBlockingTCPLink::CloseSocket()
+{
+ if (!m_Socket.IsValid())
+ {
+ m_Socket.CloseSocket();
+ }
+}
+
+
+
+
+
+bool cBlockingTCPLink::Connect(const char * iAddress, unsigned int iPort)
+{
+ ASSERT(!m_Socket.IsValid());
+ if (m_Socket.IsValid())
+ {
+ LOGWARN("WARNING: cTCPLink Connect() called while still connected.");
+ m_Socket.CloseSocket();
+ }
+
+ struct hostent *hp;
+ unsigned int addr;
+ struct sockaddr_in server;
+
+ m_Socket = socket(AF_INET, SOCK_STREAM, 0);
+ if (!m_Socket.IsValid())
+ {
+ LOGERROR("cTCPLink: Cannot create a socket");
+ return false;
+ }
+
+ addr = inet_addr(iAddress);
+ hp = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET);
+ if (hp == NULL)
+ {
+ //LOGWARN("cTCPLink: gethostbyaddr returned NULL");
+ hp = gethostbyname(iAddress);
+ if (hp == NULL)
+ {
+ LOGWARN("cTCPLink: Could not resolve %s", iAddress);
+ CloseSocket();
+ return false;
+ }
+ }
+
+ server.sin_addr.s_addr = *((unsigned long *)hp->h_addr);
+ server.sin_family = AF_INET;
+ server.sin_port = htons( (unsigned short)iPort);
+ if (connect(m_Socket, (struct sockaddr *)&server, sizeof(server)))
+ {
+ LOGWARN("cTCPLink: Connection to \"%s:%d\" failed (%s)", iAddress, iPort, cSocket::GetErrorString( cSocket::GetLastError() ).c_str() );
+ CloseSocket();
+ return false;
+ }
+
+ return true;
+}
+
+
+
+
+
+int cBlockingTCPLink::Send(char * a_Data, unsigned int a_Size, int a_Flags /* = 0 */ )
+{
+ ASSERT(m_Socket.IsValid());
+ if (!m_Socket.IsValid())
+ {
+ LOGERROR("cBlockingTCPLink: Trying to send data without a valid connection!");
+ return -1;
+ }
+ return m_Socket.Send(a_Data, a_Size);
+}
+
+
+
+
+
+int cBlockingTCPLink::SendMessage( const char* a_Message, int a_Flags /* = 0 */ )
+{
+ ASSERT(m_Socket.IsValid());
+ if (!m_Socket.IsValid())
+ {
+ LOGWARN("cBlockingTCPLink: Trying to send message without a valid connection!");
+ return -1;
+ }
+ return m_Socket.Send(a_Message, strlen(a_Message));
+}
+
+
+
+
+
+void cBlockingTCPLink::ReceiveData(AString & oData)
+{
+ ASSERT(m_Socket.IsValid());
+ if (!m_Socket.IsValid())
+ {
+ return;
+ }
+
+ int Received = 0;
+ char Buffer[256];
+ while ((Received = recv(m_Socket, Buffer, sizeof(Buffer), 0)) > 0)
+ {
+ oData.append(Buffer, Received);
+ }
+}
+
+
+
+
diff --git a/src/OSSupport/BlockingTCPLink.h b/src/OSSupport/BlockingTCPLink.h
new file mode 100644
index 000000000..cb5f9e3f4
--- /dev/null
+++ b/src/OSSupport/BlockingTCPLink.h
@@ -0,0 +1,28 @@
+
+#pragma once
+
+#include "Socket.h"
+
+
+
+
+
+class cBlockingTCPLink // tolua_export
+{ // tolua_export
+public: // tolua_export
+ cBlockingTCPLink(void); // tolua_export
+ ~cBlockingTCPLink(); // tolua_export
+
+ bool Connect( const char* a_Address, unsigned int a_Port ); // tolua_export
+ int Send( char* a_Data, unsigned int a_Size, int a_Flags = 0 ); // tolua_export
+ int SendMessage( const char* a_Message, int a_Flags = 0 ); // tolua_export
+ void CloseSocket(); // tolua_export
+ void ReceiveData(AString & oData); // tolua_export
+protected:
+
+ cSocket m_Socket;
+}; // tolua_export
+
+
+
+
diff --git a/src/OSSupport/CriticalSection.cpp b/src/OSSupport/CriticalSection.cpp
new file mode 100644
index 000000000..bda97e3a1
--- /dev/null
+++ b/src/OSSupport/CriticalSection.cpp
@@ -0,0 +1,188 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+#include "IsThread.h"
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cCriticalSection:
+
+cCriticalSection::cCriticalSection()
+{
+ #ifdef _WIN32
+ InitializeCriticalSection(&m_CriticalSection);
+ #else
+ pthread_mutexattr_init(&m_Attributes);
+ pthread_mutexattr_settype(&m_Attributes, PTHREAD_MUTEX_RECURSIVE);
+
+ if (pthread_mutex_init(&m_CriticalSection, &m_Attributes) != 0)
+ {
+ LOGERROR("Could not initialize Critical Section!");
+ }
+ #endif
+
+ #ifdef _DEBUG
+ m_IsLocked = 0;
+ #endif // _DEBUG
+}
+
+
+
+
+
+cCriticalSection::~cCriticalSection()
+{
+ #ifdef _WIN32
+ DeleteCriticalSection(&m_CriticalSection);
+ #else
+ if (pthread_mutex_destroy(&m_CriticalSection) != 0)
+ {
+ LOGWARNING("Could not destroy Critical Section!");
+ }
+ pthread_mutexattr_destroy(&m_Attributes);
+ #endif
+}
+
+
+
+
+
+void cCriticalSection::Lock()
+{
+ #ifdef _WIN32
+ EnterCriticalSection(&m_CriticalSection);
+ #else
+ pthread_mutex_lock(&m_CriticalSection);
+ #endif
+
+ #ifdef _DEBUG
+ m_IsLocked += 1;
+ m_OwningThreadID = cIsThread::GetCurrentID();
+ #endif // _DEBUG
+}
+
+
+
+
+
+void cCriticalSection::Unlock()
+{
+ #ifdef _DEBUG
+ ASSERT(m_IsLocked > 0);
+ m_IsLocked -= 1;
+ #endif // _DEBUG
+
+ #ifdef _WIN32
+ LeaveCriticalSection(&m_CriticalSection);
+ #else
+ pthread_mutex_unlock(&m_CriticalSection);
+ #endif
+}
+
+
+
+
+
+#ifdef _DEBUG
+bool cCriticalSection::IsLocked(void)
+{
+ return (m_IsLocked > 0);
+}
+
+
+
+
+
+bool cCriticalSection::IsLockedByCurrentThread(void)
+{
+ return ((m_IsLocked > 0) && (m_OwningThreadID == cIsThread::GetCurrentID()));
+}
+#endif // _DEBUG
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cCSLock
+
+cCSLock::cCSLock(cCriticalSection * a_CS)
+ : m_CS(a_CS)
+ , m_IsLocked(false)
+{
+ Lock();
+}
+
+
+
+
+
+cCSLock::cCSLock(cCriticalSection & a_CS)
+ : m_CS(&a_CS)
+ , m_IsLocked(false)
+{
+ Lock();
+}
+
+
+
+
+
+cCSLock::~cCSLock()
+{
+ if (!m_IsLocked)
+ {
+ return;
+ }
+ Unlock();
+}
+
+
+
+
+
+void cCSLock::Lock(void)
+{
+ ASSERT(!m_IsLocked);
+ m_IsLocked = true;
+ m_CS->Lock();
+}
+
+
+
+
+
+void cCSLock::Unlock(void)
+{
+ ASSERT(m_IsLocked);
+ m_IsLocked = false;
+ m_CS->Unlock();
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cCSUnlock:
+
+cCSUnlock::cCSUnlock(cCSLock & a_Lock) :
+ m_Lock(a_Lock)
+{
+ m_Lock.Unlock();
+}
+
+
+
+
+
+cCSUnlock::~cCSUnlock()
+{
+ m_Lock.Lock();
+}
+
+
+
+
diff --git a/src/OSSupport/CriticalSection.h b/src/OSSupport/CriticalSection.h
new file mode 100644
index 000000000..1bfe81439
--- /dev/null
+++ b/src/OSSupport/CriticalSection.h
@@ -0,0 +1,80 @@
+
+#pragma once
+
+
+
+
+
+class cCriticalSection
+{
+public:
+ cCriticalSection(void);
+ ~cCriticalSection();
+
+ void Lock(void);
+ void Unlock(void);
+
+ #ifdef _DEBUG
+ bool IsLocked(void);
+ bool IsLockedByCurrentThread(void);
+ #endif // _DEBUG
+
+private:
+ #ifdef _DEBUG
+ int m_IsLocked; // Number of times this CS is locked
+ unsigned long m_OwningThreadID;
+ #endif // _DEBUG
+
+ #ifdef _WIN32
+ CRITICAL_SECTION m_CriticalSection;
+ #else // _WIN32
+ pthread_mutex_t m_CriticalSection;
+ pthread_mutexattr_t m_Attributes;
+ #endif // else _WIN32
+} ALIGN_8;
+
+
+
+
+/// RAII for cCriticalSection - locks the CS on creation, unlocks on destruction
+class cCSLock
+{
+ cCriticalSection * m_CS;
+
+ // Unlike a cCriticalSection, this object should be used from a single thread, therefore access to m_IsLocked is not threadsafe
+ // In Windows, it is an error to call cCriticalSection::Unlock() multiple times if the lock is not held,
+ // therefore we need to check this value whether we are locked or not.
+ bool m_IsLocked;
+
+public:
+ cCSLock(cCriticalSection * a_CS);
+ cCSLock(cCriticalSection & a_CS);
+ ~cCSLock();
+
+ // Temporarily unlock or re-lock:
+ void Lock(void);
+ void Unlock(void);
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(cCSLock);
+} ;
+
+
+
+
+
+/// Temporary RAII unlock for a cCSLock. Useful for unlock-wait-relock scenarios
+class cCSUnlock
+{
+ cCSLock & m_Lock;
+public:
+ cCSUnlock(cCSLock & a_Lock);
+ ~cCSUnlock();
+
+private:
+ DISALLOW_COPY_AND_ASSIGN(cCSUnlock);
+} ;
+
+
+
+
diff --git a/src/OSSupport/Event.cpp b/src/OSSupport/Event.cpp
new file mode 100644
index 000000000..cbacbba17
--- /dev/null
+++ b/src/OSSupport/Event.cpp
@@ -0,0 +1,118 @@
+
+// Event.cpp
+
+// Implements the cEvent object representing an OS-specific synchronization primitive that can be waited-for
+// Implemented as an Event on Win and as a 1-semaphore on *nix
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#include "Event.h"
+
+
+
+
+
+cEvent::cEvent(void)
+{
+#ifdef _WIN32
+ m_Event = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (m_Event == NULL)
+ {
+ LOGERROR("cEvent: cannot create event, GLE = %d. Aborting server.", GetLastError());
+ abort();
+ }
+#else // *nix
+ m_bIsNamed = false;
+ m_Event = new sem_t;
+ if (sem_init(m_Event, 0, 0))
+ {
+ // This path is used by MacOS, because it doesn't support unnamed semaphores.
+ delete m_Event;
+ m_bIsNamed = true;
+
+ AString EventName;
+ Printf(EventName, "cEvent%p", this);
+ m_Event = sem_open(EventName.c_str(), O_CREAT, 777, 0 );
+ if (m_Event == SEM_FAILED)
+ {
+ LOGERROR("cEvent: Cannot create event, errno = %i. Aborting server.", errno);
+ abort();
+ }
+ // Unlink the semaphore immediately - it will continue to function but will not pollute the namespace
+ // We don't store the name, so can't call this in the destructor
+ if (sem_unlink(EventName.c_str()) != 0)
+ {
+ LOGWARN("ERROR: Could not unlink cEvent. (%i)", errno);
+ }
+ }
+#endif // *nix
+}
+
+
+
+
+
+cEvent::~cEvent()
+{
+#ifdef _WIN32
+ CloseHandle(m_Event);
+#else
+ if (m_bIsNamed)
+ {
+ if (sem_close(m_Event) != 0)
+ {
+ LOGERROR("ERROR: Could not close cEvent. (%i)", errno);
+ }
+ }
+ else
+ {
+ sem_destroy(m_Event);
+ delete m_Event;
+ }
+#endif
+}
+
+
+
+
+
+void cEvent::Wait(void)
+{
+ #ifdef _WIN32
+ DWORD res = WaitForSingleObject(m_Event, INFINITE);
+ if (res != WAIT_OBJECT_0)
+ {
+ LOGWARN("cEvent: waiting for the event failed: %d, GLE = %d. Continuing, but server may be unstable.", res, GetLastError());
+ }
+ #else
+ int res = sem_wait(m_Event);
+ if (res != 0 )
+ {
+ LOGWARN("cEvent: waiting for the event failed: %i, errno = %i. Continuing, but server may be unstable.", res, errno);
+ }
+ #endif
+}
+
+
+
+
+
+void cEvent::Set(void)
+{
+ #ifdef _WIN32
+ if (!SetEvent(m_Event))
+ {
+ LOGWARN("cEvent: Could not set cEvent: GLE = %d", GetLastError());
+ }
+ #else
+ int res = sem_post(m_Event);
+ if (res != 0)
+ {
+ LOGWARN("cEvent: Could not set cEvent: %i, errno = %d", res, errno);
+ }
+ #endif
+}
+
+
+
+
diff --git a/src/OSSupport/Event.h b/src/OSSupport/Event.h
new file mode 100644
index 000000000..71f418c0c
--- /dev/null
+++ b/src/OSSupport/Event.h
@@ -0,0 +1,47 @@
+
+// Event.h
+
+// Interfaces to the cEvent object representing an OS-specific synchronization primitive that can be waited-for
+// Implemented as an Event on Win and as a 1-semaphore on *nix
+
+
+
+
+
+#pragma once
+#ifndef CEVENT_H_INCLUDED
+#define CEVENT_H_INCLUDED
+
+
+
+
+
+class cEvent
+{
+public:
+ cEvent(void);
+ ~cEvent();
+
+ void Wait(void);
+ void Set (void);
+
+private:
+
+ #ifdef _WIN32
+ HANDLE m_Event;
+ #else
+ sem_t * m_Event;
+ bool m_bIsNamed;
+ #endif
+} ;
+
+
+
+
+
+
+#endif // CEVENT_H_INCLUDED
+
+
+
+
diff --git a/src/OSSupport/File.cpp b/src/OSSupport/File.cpp
new file mode 100644
index 000000000..9f7c0d439
--- /dev/null
+++ b/src/OSSupport/File.cpp
@@ -0,0 +1,452 @@
+
+// cFile.cpp
+
+// Implements the cFile class providing an OS-independent abstraction of a file.
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#include "File.h"
+#include <fstream>
+
+
+
+
+
+cFile::cFile(void) :
+ #ifdef USE_STDIO_FILE
+ m_File(NULL)
+ #else
+ m_File(INVALID_HANDLE_VALUE)
+ #endif // USE_STDIO_FILE
+{
+ // Nothing needed yet
+}
+
+
+
+
+
+cFile::cFile(const AString & iFileName, eMode iMode) :
+ #ifdef USE_STDIO_FILE
+ m_File(NULL)
+ #else
+ m_File(INVALID_HANDLE_VALUE)
+ #endif // USE_STDIO_FILE
+{
+ Open(iFileName, iMode);
+}
+
+
+
+
+
+cFile::~cFile()
+{
+ if (IsOpen())
+ {
+ Close();
+ }
+}
+
+
+
+
+
+bool cFile::Open(const AString & iFileName, eMode iMode)
+{
+ ASSERT(!IsOpen()); // You should close the file before opening another one
+
+ if (IsOpen())
+ {
+ Close();
+ }
+
+ const char * Mode = NULL;
+ switch (iMode)
+ {
+ case fmRead: Mode = "rb"; break;
+ case fmWrite: Mode = "wb"; break;
+ case fmReadWrite: Mode = "rb+"; break;
+ default:
+ {
+ ASSERT(!"Unhandled file mode");
+ return false;
+ }
+ }
+ m_File = fopen( (FILE_IO_PREFIX + iFileName).c_str(), Mode);
+ if ((m_File == NULL) && (iMode == fmReadWrite))
+ {
+ // Fix for MS not following C spec, opening "a" mode files for writing at the end only
+ // The file open operation has been tried with "read update", fails if file not found
+ // So now we know either the file doesn't exist or we don't have rights, no need to worry about file contents.
+ // Simply re-open for read-writing, erasing existing contents:
+ m_File = fopen( (FILE_IO_PREFIX + iFileName).c_str(), "wb+");
+ }
+ return (m_File != NULL);
+}
+
+
+
+
+
+void cFile::Close(void)
+{
+ if (!IsOpen())
+ {
+ // Closing an unopened file is a legal nop
+ return;
+ }
+
+ fclose(m_File);
+ m_File = NULL;
+}
+
+
+
+
+
+bool cFile::IsOpen(void) const
+{
+ return (m_File != NULL);
+}
+
+
+
+
+
+bool cFile::IsEOF(void) const
+{
+ ASSERT(IsOpen());
+
+ if (!IsOpen())
+ {
+ // Unopened files behave as at EOF
+ return true;
+ }
+
+ return (feof(m_File) != 0);
+}
+
+
+
+
+
+int cFile::Read (void * iBuffer, int iNumBytes)
+{
+ ASSERT(IsOpen());
+
+ if (!IsOpen())
+ {
+ return -1;
+ }
+
+ return fread(iBuffer, 1, iNumBytes, m_File); // fread() returns the portion of Count parameter actually read, so we need to send iNumBytes as Count
+}
+
+
+
+
+
+int cFile::Write(const void * iBuffer, int iNumBytes)
+{
+ ASSERT(IsOpen());
+
+ if (!IsOpen())
+ {
+ return -1;
+ }
+
+ int res = fwrite(iBuffer, 1, iNumBytes, m_File); // fwrite() returns the portion of Count parameter actually written, so we need to send iNumBytes as Count
+ return res;
+}
+
+
+
+
+
+int cFile::Seek (int iPosition)
+{
+ ASSERT(IsOpen());
+
+ if (!IsOpen())
+ {
+ return -1;
+ }
+
+ if (fseek(m_File, iPosition, SEEK_SET) != 0)
+ {
+ return -1;
+ }
+ return ftell(m_File);
+}
+
+
+
+
+
+
+int cFile::Tell (void) const
+{
+ ASSERT(IsOpen());
+
+ if (!IsOpen())
+ {
+ return -1;
+ }
+
+ return ftell(m_File);
+}
+
+
+
+
+
+int cFile::GetSize(void) const
+{
+ ASSERT(IsOpen());
+
+ if (!IsOpen())
+ {
+ return -1;
+ }
+
+ int CurPos = ftell(m_File);
+ if (CurPos < 0)
+ {
+ return -1;
+ }
+ if (fseek(m_File, 0, SEEK_END) != 0)
+ {
+ return -1;
+ }
+ int res = ftell(m_File);
+ if (fseek(m_File, CurPos, SEEK_SET) != 0)
+ {
+ return -1;
+ }
+ return res;
+}
+
+
+
+
+
+int cFile::ReadRestOfFile(AString & a_Contents)
+{
+ ASSERT(IsOpen());
+
+ if (!IsOpen())
+ {
+ return -1;
+ }
+
+ int DataSize = GetSize() - Tell();
+
+ // HACK: This depends on the internal knowledge that AString's data() function returns the internal buffer directly
+ a_Contents.assign(DataSize, '\0');
+ return Read((void *)a_Contents.data(), DataSize);
+}
+
+
+
+
+
+bool cFile::Exists(const AString & a_FileName)
+{
+ cFile test(a_FileName, fmRead);
+ return test.IsOpen();
+}
+
+
+
+
+
+bool cFile::Delete(const AString & a_FileName)
+{
+ return (remove(a_FileName.c_str()) == 0);
+}
+
+
+
+
+
+bool cFile::Rename(const AString & a_OrigFileName, const AString & a_NewFileName)
+{
+ return (rename(a_OrigFileName.c_str(), a_NewFileName.c_str()) == 0);
+}
+
+
+
+
+
+bool cFile::Copy(const AString & a_SrcFileName, const AString & a_DstFileName)
+{
+ #ifdef _WIN32
+ return (CopyFile(a_SrcFileName.c_str(), a_DstFileName.c_str(), true) != 0);
+ #else
+ // Other OSs don't have a direct CopyFile equivalent, do it the harder way:
+ std::ifstream src(a_SrcFileName.c_str(), std::ios::binary);
+ std::ofstream dst(a_DstFileName.c_str(), std::ios::binary);
+ if (dst.good())
+ {
+ dst << src.rdbuf();
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ #endif
+}
+
+
+
+
+
+bool cFile::IsFolder(const AString & a_Path)
+{
+ #ifdef _WIN32
+ DWORD FileAttrib = GetFileAttributes(a_Path.c_str());
+ return ((FileAttrib != INVALID_FILE_ATTRIBUTES) && ((FileAttrib & FILE_ATTRIBUTE_DIRECTORY) != 0));
+ #else
+ struct stat st;
+ return ((stat(a_Path.c_str(), &st) == 0) && S_ISDIR(st.st_mode));
+ #endif
+}
+
+
+
+
+
+bool cFile::IsFile(const AString & a_Path)
+{
+ #ifdef _WIN32
+ DWORD FileAttrib = GetFileAttributes(a_Path.c_str());
+ return ((FileAttrib != INVALID_FILE_ATTRIBUTES) && ((FileAttrib & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_DEVICE)) == 0));
+ #else
+ struct stat st;
+ return ((stat(a_Path.c_str(), &st) == 0) && S_ISREG(st.st_mode));
+ #endif
+}
+
+
+
+
+
+int cFile::GetSize(const AString & a_FileName)
+{
+ struct stat st;
+ if (stat(a_FileName.c_str(), &st) == 0)
+ {
+ return st.st_size;
+ }
+ return -1;
+}
+
+
+
+
+
+bool cFile::CreateFolder(const AString & a_FolderPath)
+{
+ #ifdef _WIN32
+ return (CreateDirectory(a_FolderPath.c_str(), NULL) != 0);
+ #else
+ return (mkdir(a_FolderPath.c_str(), S_IRWXU | S_IRWXG | S_IRWXO) == 0);
+ #endif
+}
+
+
+
+
+
+AStringVector cFile::GetFolderContents(const AString & a_Folder)
+{
+ AStringVector AllFiles;
+
+ #ifdef _WIN32
+
+ // If the folder name doesn't contain the terminating slash / backslash, add it:
+ AString FileFilter = a_Folder;
+ if (
+ !FileFilter.empty() &&
+ (FileFilter[FileFilter.length() - 1] != '\\') &&
+ (FileFilter[FileFilter.length() - 1] != '/')
+ )
+ {
+ FileFilter.push_back('\\');
+ }
+
+ // Find all files / folders:
+ FileFilter.append("*.*");
+ HANDLE hFind;
+ WIN32_FIND_DATA FindFileData;
+ if ((hFind = FindFirstFile(FileFilter.c_str(), &FindFileData)) != INVALID_HANDLE_VALUE)
+ {
+ do
+ {
+ AllFiles.push_back(FindFileData.cFileName);
+ } while (FindNextFile(hFind, &FindFileData));
+ FindClose(hFind);
+ }
+
+ #else // _WIN32
+
+ DIR * dp;
+ struct dirent *dirp;
+ AString Folder = a_Folder;
+ if (Folder.empty())
+ {
+ Folder = ".";
+ }
+ if ((dp = opendir(Folder.c_str())) == NULL)
+ {
+ LOGERROR("Error (%i) opening directory \"%s\"\n", errno, Folder.c_str());
+ }
+ else
+ {
+ while ((dirp = readdir(dp)) != NULL)
+ {
+ AllFiles.push_back(dirp->d_name);
+ }
+ closedir(dp);
+ }
+
+ #endif // else _WIN32
+
+ return AllFiles;
+}
+
+
+
+
+
+AString cFile::ReadWholeFile(const AString & a_FileName)
+{
+ cFile f;
+ if (!f.Open(a_FileName, fmRead))
+ {
+ return "";
+ }
+ AString Contents;
+ f.ReadRestOfFile(Contents);
+ return Contents;
+}
+
+
+
+
+
+int cFile::Printf(const char * a_Fmt, ...)
+{
+ AString buf;
+ va_list args;
+ va_start(args, a_Fmt);
+ AppendVPrintf(buf, a_Fmt, args);
+ va_end(args);
+ return Write(buf.c_str(), buf.length());
+}
+
+
+
+
diff --git a/src/OSSupport/File.h b/src/OSSupport/File.h
new file mode 100644
index 000000000..01663a229
--- /dev/null
+++ b/src/OSSupport/File.h
@@ -0,0 +1,144 @@
+
+// cFile.h
+
+// Interfaces to the cFile class providing an OS-independent abstraction of a file.
+
+/*
+The object is optimized towards binary reads.
+The object has no multithreading locks, don't use from multiple threads!
+Usage:
+1, Construct a cFile instance (no-param constructor)
+2, Open a file using Open(), check return value for success
+3, Read / write
+4, Destroy the instance
+
+-- OR --
+
+1, Construct a cFile instance opening the file (filename-param constructor)
+2, Check if the file was opened using IsOpen()
+3, Read / write
+4, Destroy the instance
+*/
+
+
+
+
+
+#pragma once
+
+
+
+
+
+#ifndef _WIN32
+ #define USE_STDIO_FILE
+#endif // _WIN32
+
+// DEBUG:
+#define USE_STDIO_FILE
+
+
+
+
+
+// tolua_begin
+
+class cFile
+{
+public:
+
+ // tolua_end
+
+ #ifdef _WIN32
+ static const char PathSeparator = '\\';
+ #else
+ static const char PathSeparator = '/';
+ #endif
+
+ /// The mode in which to open the file
+ enum eMode
+ {
+ fmRead, // Read-only. If the file doesn't exist, object will not be valid
+ fmWrite, // Write-only. If the file already exists, it will be overwritten
+ fmReadWrite // Read/write. If the file already exists, it will be left intact; writing will overwrite the data from the beginning
+ } ;
+
+ /// Simple constructor - creates an unopened file object, use Open() to open / create a real file
+ cFile(void);
+
+ /// Constructs and opens / creates the file specified, use IsOpen() to check for success
+ cFile(const AString & iFileName, eMode iMode);
+
+ /// Auto-closes the file, if open
+ ~cFile();
+
+ bool Open(const AString & iFileName, eMode iMode);
+ void Close(void);
+ bool IsOpen(void) const;
+ bool IsEOF(void) const;
+
+ /// Reads up to iNumBytes bytes into iBuffer, returns the number of bytes actually read, or -1 on failure; asserts if not open
+ int Read (void * iBuffer, int iNumBytes);
+
+ /// Writes up to iNumBytes bytes from iBuffer, returns the number of bytes actually written, or -1 on failure; asserts if not open
+ int Write(const void * iBuffer, int iNumBytes);
+
+ /// Seeks to iPosition bytes from file start, returns old position or -1 for failure; asserts if not open
+ int Seek (int iPosition);
+
+ /// Returns the current position (bytes from file start) or -1 for failure; asserts if not open
+ int Tell (void) const;
+
+ /// Returns the size of file, in bytes, or -1 for failure; asserts if not open
+ int GetSize(void) const;
+
+ /// Reads the file from current position till EOF into an AString; returns the number of bytes read or -1 for error
+ int ReadRestOfFile(AString & a_Contents);
+
+ // tolua_begin
+
+ /// Returns true if the file specified exists
+ static bool Exists(const AString & a_FileName);
+
+ /// Deletes a file, returns true if successful
+ static bool Delete(const AString & a_FileName);
+
+ /// Renames a file or folder, returns true if successful. May fail if dest already exists (libc-dependant)!
+ static bool Rename(const AString & a_OrigPath, const AString & a_NewPath);
+
+ /// Copies a file, returns true if successful.
+ static bool Copy(const AString & a_SrcFileName, const AString & a_DstFileName);
+
+ /// Returns true if the specified path is a folder
+ static bool IsFolder(const AString & a_Path);
+
+ /// Returns true if the specified path is a regular file
+ static bool IsFile(const AString & a_Path);
+
+ /// Returns the size of the file, or a negative number on error
+ static int GetSize(const AString & a_FileName);
+
+ /// Creates a new folder with the specified name. Returns true if successful. Path may be relative or absolute
+ static bool CreateFolder(const AString & a_FolderPath);
+
+ /// Returns the entire contents of the specified file as a string. Returns empty string on error.
+ static AString ReadWholeFile(const AString & a_FileName);
+
+ // tolua_end
+
+ /// Returns the list of all items in the specified folder (files, folders, nix pipes, whatever's there).
+ static AStringVector GetFolderContents(const AString & a_Folder); // Exported in ManualBindings.cpp
+
+ int Printf(const char * a_Fmt, ...);
+
+private:
+ #ifdef USE_STDIO_FILE
+ FILE * m_File;
+ #else
+ HANDLE m_File;
+ #endif
+} ; // tolua_export
+
+
+
+
diff --git a/src/OSSupport/GZipFile.cpp b/src/OSSupport/GZipFile.cpp
new file mode 100644
index 000000000..cbf6be6c4
--- /dev/null
+++ b/src/OSSupport/GZipFile.cpp
@@ -0,0 +1,107 @@
+
+// GZipFile.cpp
+
+// Implements the cGZipFile class representing a RAII wrapper over zlib's GZip file routines
+
+#include "Globals.h"
+#include "GZipFile.h"
+
+
+
+
+
+cGZipFile::cGZipFile(void) :
+ m_File(NULL)
+{
+}
+
+
+
+
+
+cGZipFile::~cGZipFile()
+{
+ Close();
+}
+
+
+
+
+
+bool cGZipFile::Open(const AString & a_FileName, eMode a_Mode)
+{
+ if (m_File != NULL)
+ {
+ ASSERT(!"A file is already open in this object");
+ return false;
+ }
+ m_File = gzopen(a_FileName.c_str(), (a_Mode == fmRead) ? "r" : "w");
+ m_Mode = a_Mode;
+ return (m_File != NULL);
+}
+
+
+
+
+
+void cGZipFile::Close(void)
+{
+ if (m_File != NULL)
+ {
+ gzclose(m_File);
+ m_File = NULL;
+ }
+}
+
+
+
+
+
+int cGZipFile::ReadRestOfFile(AString & a_Contents)
+{
+ if (m_File == NULL)
+ {
+ ASSERT(!"No file has been opened");
+ return -1;
+ }
+
+ if (m_Mode != fmRead)
+ {
+ ASSERT(!"Bad file mode, cannot read");
+ return -1;
+ }
+
+ // Since the gzip format doesn't really support getting the uncompressed length, we need to read incrementally. Yuck!
+ int NumBytesRead = 0;
+ char Buffer[64 KiB];
+ while ((NumBytesRead = gzread(m_File, Buffer, sizeof(Buffer))) > 0)
+ {
+ a_Contents.append(Buffer, NumBytesRead);
+ }
+ return NumBytesRead;
+}
+
+
+
+
+
+bool cGZipFile::Write(const char * a_Contents, int a_Size)
+{
+ if (m_File == NULL)
+ {
+ ASSERT(!"No file has been opened");
+ return false;
+ }
+
+ if (m_Mode != fmWrite)
+ {
+ ASSERT(!"Bad file mode, cannot write");
+ return false;
+ }
+
+ return (gzwrite(m_File, a_Contents, a_Size) != 0);
+}
+
+
+
+
diff --git a/src/OSSupport/GZipFile.h b/src/OSSupport/GZipFile.h
new file mode 100644
index 000000000..dcc171a2a
--- /dev/null
+++ b/src/OSSupport/GZipFile.h
@@ -0,0 +1,52 @@
+
+// GZipFile.h
+
+// Declares the cGZipFile class representing a RAII wrapper over zlib's GZip file routines
+
+
+
+
+
+#pragma once
+
+#include "lib/zlib/zlib.h"
+
+
+
+
+
+class cGZipFile
+{
+public:
+ enum eMode
+ {
+ fmRead, // Read-only. If the file doesn't exist, object will not be valid
+ fmWrite, // Write-only. If the file already exists, it will be overwritten
+ } ;
+
+ cGZipFile(void);
+ ~cGZipFile();
+
+ /// Opens the file. Returns true if successful. Fails if a file has already been opened through this object.
+ bool Open(const AString & a_FileName, eMode a_Mode);
+
+ /// Closes the file, flushing all buffers. This object may be then reused for a different file and / or mode
+ void Close(void);
+
+ /// Reads the rest of the file and decompresses it into a_Contents. Returns the number of decompressed bytes, <0 for error
+ int ReadRestOfFile(AString & a_Contents);
+
+ /// Writes a_Contents into file, compressing it along the way. Returns true if successful. Multiple writes are supported.
+ bool Write(const AString & a_Contents) { return Write(a_Contents.data(), (int)(a_Contents.size())); }
+
+ bool Write(const char * a_Data, int a_Size);
+
+protected:
+ gzFile m_File;
+ eMode m_Mode;
+} ;
+
+
+
+
+
diff --git a/src/OSSupport/IsThread.cpp b/src/OSSupport/IsThread.cpp
new file mode 100644
index 000000000..4da9f9949
--- /dev/null
+++ b/src/OSSupport/IsThread.cpp
@@ -0,0 +1,172 @@
+
+// IsThread.cpp
+
+// Implements the cIsThread class representing an OS-independent wrapper for a class that implements a thread.
+// This class will eventually suupersede the old cThread class
+
+#include "Globals.h"
+
+#include "IsThread.h"
+
+
+
+
+
+// When in MSVC, the debugger provides "thread naming" by catching special exceptions. Interface here:
+#if defined(_MSC_VER) && defined(_DEBUG)
+//
+// Usage: SetThreadName (-1, "MainThread");
+//
+
+static void SetThreadName( DWORD dwThreadID, LPCSTR szThreadName)
+{
+ struct
+ {
+ DWORD dwType; // must be 0x1000
+ LPCSTR szName; // pointer to name (in user addr space)
+ DWORD dwThreadID; // thread ID (-1=caller thread)
+ DWORD dwFlags; // reserved for future use, must be zero
+ } info;
+
+ info.dwType = 0x1000;
+ info.szName = szThreadName;
+ info.dwThreadID = dwThreadID;
+ info.dwFlags = 0;
+
+ __try
+ {
+ RaiseException(0x406D1388, 0, sizeof(info) / sizeof(DWORD), (DWORD *)&info);
+ }
+ __except(EXCEPTION_CONTINUE_EXECUTION)
+ {
+ }
+}
+#endif // _MSC_VER && _DEBUG
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cIsThread:
+
+cIsThread::cIsThread(const AString & iThreadName) :
+ m_ThreadName(iThreadName),
+ m_ShouldTerminate(false),
+ m_Handle(NULL_HANDLE)
+{
+}
+
+
+
+
+
+cIsThread::~cIsThread()
+{
+ m_ShouldTerminate = true;
+ Wait();
+}
+
+
+
+
+
+bool cIsThread::Start(void)
+{
+ ASSERT(m_Handle == NULL_HANDLE); // Has already started one thread?
+ #ifdef _WIN32
+ // Create the thread suspended, so that the mHandle variable is valid in the thread procedure
+ DWORD ThreadID = 0;
+ m_Handle = CreateThread(NULL, 0, thrExecute, this, CREATE_SUSPENDED, &ThreadID);
+ if (m_Handle == NULL)
+ {
+ LOGERROR("ERROR: Could not create thread \"%s\", GLE = %d!", m_ThreadName.c_str(), GetLastError());
+ return false;
+ }
+ ResumeThread(m_Handle);
+
+ #if defined(_DEBUG) && defined(_MSC_VER)
+ // Thread naming is available only in MSVC
+ if (!m_ThreadName.empty())
+ {
+ SetThreadName(ThreadID, m_ThreadName.c_str());
+ }
+ #endif // _DEBUG and _MSC_VER
+
+ #else // _WIN32
+ if (pthread_create(&m_Handle, NULL, thrExecute, this))
+ {
+ LOGERROR("ERROR: Could not create thread \"%s\", !", m_ThreadName.c_str());
+ return false;
+ }
+ #endif // else _WIN32
+
+ return true;
+}
+
+
+
+
+
+void cIsThread::Stop(void)
+{
+ if (m_Handle == NULL_HANDLE)
+ {
+ return;
+ }
+ m_ShouldTerminate = true;
+ Wait();
+}
+
+
+
+
+
+bool cIsThread::Wait(void)
+{
+ if (m_Handle == NULL)
+ {
+ return true;
+ }
+
+ #ifdef LOGD // ProtoProxy doesn't have LOGD
+ LOGD("Waiting for thread %s to finish", m_ThreadName.c_str());
+ #endif // LOGD
+
+ #ifdef _WIN32
+ int res = WaitForSingleObject(m_Handle, INFINITE);
+ m_Handle = NULL;
+
+ #ifdef LOGD // ProtoProxy doesn't have LOGD
+ LOGD("Thread %s finished", m_ThreadName.c_str());
+ #endif // LOGD
+
+ return (res == WAIT_OBJECT_0);
+ #else // _WIN32
+ int res = pthread_join(m_Handle, NULL);
+ m_Handle = NULL;
+
+ #ifdef LOGD // ProtoProxy doesn't have LOGD
+ LOGD("Thread %s finished", m_ThreadName.c_str());
+ #endif // LOGD
+
+ return (res == 0);
+ #endif // else _WIN32
+}
+
+
+
+
+
+unsigned long cIsThread::GetCurrentID(void)
+{
+ #ifdef _WIN32
+ return (unsigned long) GetCurrentThreadId();
+ #else
+ return (unsigned long) pthread_self();
+ #endif
+}
+
+
+
+
diff --git a/src/OSSupport/IsThread.h b/src/OSSupport/IsThread.h
new file mode 100644
index 000000000..b8784ea33
--- /dev/null
+++ b/src/OSSupport/IsThread.h
@@ -0,0 +1,100 @@
+
+// IsThread.h
+
+// Interfaces to the cIsThread class representing an OS-independent wrapper for a class that implements a thread.
+// This class will eventually suupersede the old cThread class
+
+/*
+Usage:
+To have a new thread, declare a class descending from cIsClass.
+Then override its Execute() method to provide your thread processing.
+In the descending class' constructor call the Start() method to start the thread once you're finished with initialization.
+*/
+
+
+
+
+
+#pragma once
+#ifndef CISTHREAD_H_INCLUDED
+#define CISTHREAD_H_INCLUDED
+
+
+
+
+
+class cIsThread
+{
+protected:
+ /// This is the main thread entrypoint
+ virtual void Execute(void) = 0;
+
+ /// The overriden Execute() method should check this value periodically and terminate if this is true
+ volatile bool m_ShouldTerminate;
+
+public:
+ cIsThread(const AString & iThreadName);
+ ~cIsThread();
+
+ /// Starts the thread; returns without waiting for the actual start
+ bool Start(void);
+
+ /// Signals the thread to terminate and waits until it's finished
+ void Stop(void);
+
+ /// Waits for the thread to finish. Doesn't signalize the ShouldTerminate flag
+ bool Wait(void);
+
+ /// Returns the OS-dependent thread ID for the caller's thread
+ static unsigned long GetCurrentID(void);
+
+protected:
+ AString m_ThreadName;
+
+ // Value used for "no handle":
+ #ifdef _WIN32
+ #define NULL_HANDLE NULL
+ #else
+ #define NULL_HANDLE 0
+ #endif
+
+ #ifdef _WIN32
+
+ HANDLE m_Handle;
+
+ static DWORD_PTR __stdcall thrExecute(LPVOID a_Param)
+ {
+ // Create a window so that the thread can be identified by 3rd party tools:
+ HWND IdentificationWnd = CreateWindow("STATIC", ((cIsThread *)a_Param)->m_ThreadName.c_str(), 0, 0, 0, 0, WS_OVERLAPPED, NULL, NULL, NULL, NULL);
+
+ // Run the thread:
+ ((cIsThread *)a_Param)->Execute();
+
+ // Destroy the identification window:
+ DestroyWindow(IdentificationWnd);
+
+ return 0;
+ }
+
+ #else // _WIN32
+
+ pthread_t m_Handle;
+
+ static void * thrExecute(void * a_Param)
+ {
+ ((cIsThread *)a_Param)->Execute();
+ return NULL;
+ }
+
+ #endif // else _WIN32
+} ;
+
+
+
+
+
+#endif // CISTHREAD_H_INCLUDED
+
+
+
+
diff --git a/src/OSSupport/ListenThread.cpp b/src/OSSupport/ListenThread.cpp
new file mode 100644
index 000000000..ba3198764
--- /dev/null
+++ b/src/OSSupport/ListenThread.cpp
@@ -0,0 +1,238 @@
+
+// ListenThread.cpp
+
+// Implements the cListenThread class representing the thread that listens for client connections
+
+#include "Globals.h"
+#include "ListenThread.h"
+
+
+
+
+
+cListenThread::cListenThread(cCallback & a_Callback, cSocket::eFamily a_Family, const AString & a_ServiceName) :
+ super(Printf("ListenThread %s", a_ServiceName.c_str())),
+ m_Callback(a_Callback),
+ m_Family(a_Family),
+ m_ShouldReuseAddr(false),
+ m_ServiceName(a_ServiceName)
+{
+}
+
+
+
+
+
+cListenThread::~cListenThread()
+{
+ Stop();
+}
+
+
+
+
+
+bool cListenThread::Initialize(const AString & a_PortsString)
+{
+ ASSERT(m_Sockets.empty()); // Not yet started
+
+ if (!CreateSockets(a_PortsString))
+ {
+ return false;
+ }
+
+ return true;
+}
+
+
+
+
+
+bool cListenThread::Start(void)
+{
+ if (m_Sockets.empty())
+ {
+ // There are no sockets listening, either forgotten to initialize or the user specified no listening ports
+ // Report as successful, though
+ return true;
+ }
+ return super::Start();
+}
+
+
+
+
+
+void cListenThread::Stop(void)
+{
+ if (m_Sockets.empty())
+ {
+ // No sockets means no thread was running in the first place
+ return;
+ }
+
+ m_ShouldTerminate = true;
+
+ // Close one socket to wake the thread up from the select() call
+ m_Sockets[0].CloseSocket();
+
+ // Wait for the thread to finish
+ super::Wait();
+
+ // Close all the listening sockets:
+ for (cSockets::iterator itr = m_Sockets.begin() + 1, end = m_Sockets.end(); itr != end; ++itr)
+ {
+ itr->CloseSocket();
+ } // for itr - m_Sockets[]
+ m_Sockets.clear();
+}
+
+
+
+
+
+void cListenThread::SetReuseAddr(bool a_Reuse)
+{
+ ASSERT(m_Sockets.empty()); // Must not have been Initialize()d yet
+
+ m_ShouldReuseAddr = a_Reuse;
+}
+
+
+
+
+
+bool cListenThread::CreateSockets(const AString & a_PortsString)
+{
+ AStringVector Ports = StringSplitAndTrim(a_PortsString, ",");
+
+ if (Ports.empty())
+ {
+ return false;
+ }
+
+ AString FamilyStr = m_ServiceName;
+ switch (m_Family)
+ {
+ case cSocket::IPv4: FamilyStr.append(" IPv4"); break;
+ case cSocket::IPv6: FamilyStr.append(" IPv6"); break;
+ default:
+ {
+ ASSERT(!"Unknown address family");
+ break;
+ }
+ }
+
+ for (AStringVector::const_iterator itr = Ports.begin(), end = Ports.end(); itr != end; ++itr)
+ {
+ int Port = atoi(itr->c_str());
+ if ((Port <= 0) || (Port > 65535))
+ {
+ LOGWARNING("%s: Invalid port specified: \"%s\".", FamilyStr.c_str(), itr->c_str());
+ continue;
+ }
+ m_Sockets.push_back(cSocket::CreateSocket(m_Family));
+ if (!m_Sockets.back().IsValid())
+ {
+ LOGWARNING("%s: Cannot create listening socket for port %d: \"%s\"", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str());
+ m_Sockets.pop_back();
+ continue;
+ }
+
+ if (m_ShouldReuseAddr)
+ {
+ if (!m_Sockets.back().SetReuseAddress())
+ {
+ LOG("%s: Port %d cannot reuse addr, syscall failed: \"%s\".", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str());
+ }
+ }
+
+ // Bind to port:
+ bool res = false;
+ switch (m_Family)
+ {
+ case cSocket::IPv4: res = m_Sockets.back().BindToAnyIPv4(Port); break;
+ case cSocket::IPv6: res = m_Sockets.back().BindToAnyIPv6(Port); break;
+ default:
+ {
+ ASSERT(!"Unknown address family");
+ res = false;
+ }
+ }
+ if (!res)
+ {
+ LOGWARNING("%s: Cannot bind port %d: \"%s\".", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str());
+ m_Sockets.pop_back();
+ continue;
+ }
+
+ if (!m_Sockets.back().Listen())
+ {
+ LOGWARNING("%s: Cannot listen on port %d: \"%s\".", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str());
+ m_Sockets.pop_back();
+ continue;
+ }
+
+ LOGINFO("%s: Port %d is open for connections", FamilyStr.c_str(), Port);
+ } // for itr - Ports[]
+
+ return !(m_Sockets.empty());
+}
+
+
+
+
+
+void cListenThread::Execute(void)
+{
+ if (m_Sockets.empty())
+ {
+ LOGD("Empty cListenThread, ending thread now.");
+ return;
+ }
+
+ // Find the highest socket number:
+ cSocket::xSocket Highest = m_Sockets[0].GetSocket();
+ for (cSockets::iterator itr = m_Sockets.begin(), end = m_Sockets.end(); itr != end; ++itr)
+ {
+ if (itr->GetSocket() > Highest)
+ {
+ Highest = itr->GetSocket();
+ }
+ } // for itr - m_Sockets[]
+
+ while (!m_ShouldTerminate)
+ {
+ // Put all sockets into a FD set:
+ fd_set fdRead;
+ FD_ZERO(&fdRead);
+ for (cSockets::iterator itr = m_Sockets.begin(), end = m_Sockets.end(); itr != end; ++itr)
+ {
+ FD_SET(itr->GetSocket(), &fdRead);
+ } // for itr - m_Sockets[]
+
+ timeval tv; // On Linux select() doesn't seem to wake up when socket is closed, so let's kinda busy-wait:
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+ if (select(Highest + 1, &fdRead, NULL, NULL, &tv) == -1)
+ {
+ LOG("select(R) call failed in cListenThread: \"%s\"", cSocket::GetLastErrorString().c_str());
+ continue;
+ }
+ for (cSockets::iterator itr = m_Sockets.begin(), end = m_Sockets.end(); itr != end; ++itr)
+ {
+ if (itr->IsValid() && FD_ISSET(itr->GetSocket(), &fdRead))
+ {
+ cSocket Client = (m_Family == cSocket::IPv4) ? itr->AcceptIPv4() : itr->AcceptIPv6();
+ if (Client.IsValid())
+ {
+ m_Callback.OnConnectionAccepted(Client);
+ }
+ }
+ } // for itr - m_Sockets[]
+ } // while (!m_ShouldTerminate)
+}
+
+
+
+
diff --git a/src/OSSupport/ListenThread.h b/src/OSSupport/ListenThread.h
new file mode 100644
index 000000000..4e337d814
--- /dev/null
+++ b/src/OSSupport/ListenThread.h
@@ -0,0 +1,83 @@
+
+// ListenThread.h
+
+// Declares the cListenThread class representing the thread that listens for client connections
+
+
+
+
+
+#pragma once
+
+#include "IsThread.h"
+#include "Socket.h"
+
+
+
+
+
+// fwd:
+class cServer;
+
+
+
+
+
+class cListenThread :
+ public cIsThread
+{
+ typedef cIsThread super;
+
+public:
+ /// Used as the callback for connection events
+ class cCallback
+ {
+ public:
+ /// This callback is called whenever a socket connection is accepted
+ virtual void OnConnectionAccepted(cSocket & a_Socket) = 0;
+ } ;
+
+ cListenThread(cCallback & a_Callback, cSocket::eFamily a_Family, const AString & a_ServiceName = "");
+ ~cListenThread();
+
+ /// Creates all the sockets, returns trus if successful, false if not.
+ bool Initialize(const AString & a_PortsString);
+
+ bool Start(void);
+
+ void Stop(void);
+
+ /// Call before Initialize() to set the "reuse" flag on the sockets
+ void SetReuseAddr(bool a_Reuse = true);
+
+protected:
+ typedef std::vector<cSocket> cSockets;
+
+ /// The callback which to notify of incoming connections
+ cCallback & m_Callback;
+
+ /// Socket address family to use
+ cSocket::eFamily m_Family;
+
+ /// Sockets that are being monitored
+ cSockets m_Sockets;
+
+ /// If set to true, the SO_REUSEADDR socket option is set to true
+ bool m_ShouldReuseAddr;
+
+ /// Name of the service that's listening on the ports; for logging purposes only
+ AString m_ServiceName;
+
+
+ /** Fills in m_Sockets with individual sockets, each for one port specified in a_PortsString.
+ Returns true if successful and at least one socket has been created
+ */
+ bool CreateSockets(const AString & a_PortsString);
+
+ // cIsThread override:
+ virtual void Execute(void) override;
+} ;
+
+
+
+
diff --git a/src/OSSupport/Semaphore.cpp b/src/OSSupport/Semaphore.cpp
new file mode 100644
index 000000000..468de6858
--- /dev/null
+++ b/src/OSSupport/Semaphore.cpp
@@ -0,0 +1,91 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+
+
+
+
+cSemaphore::cSemaphore( unsigned int a_MaxCount, unsigned int a_InitialCount /* = 0 */ )
+#ifndef _WIN32
+ : m_bNamed( false )
+#endif
+{
+#ifndef _WIN32
+ (void)a_MaxCount;
+ m_Handle = new sem_t;
+ if (sem_init( (sem_t*)m_Handle, 0, 0))
+ {
+ LOG("WARNING cSemaphore: Could not create unnamed semaphore, fallback to named.");
+ delete (sem_t*)m_Handle; // named semaphores return their own address
+ m_bNamed = true;
+
+ AString Name;
+ Printf(Name, "cSemaphore%p", this );
+ m_Handle = sem_open(Name.c_str(), O_CREAT, 777, a_InitialCount);
+ if( m_Handle == SEM_FAILED )
+ {
+ LOG("ERROR: Could not create Semaphore. (%i)", errno );
+ }
+ else
+ {
+ if( sem_unlink(Name.c_str()) != 0 )
+ {
+ LOG("ERROR: Could not unlink cSemaphore. (%i)", errno);
+ }
+ }
+ }
+#else
+ m_Handle = CreateSemaphore(
+ NULL, // security attribute
+ a_InitialCount, // initial count
+ a_MaxCount, // maximum count
+ 0 // name (optional)
+ );
+#endif
+}
+
+cSemaphore::~cSemaphore()
+{
+#ifdef _WIN32
+ CloseHandle( m_Handle );
+#else
+ if( m_bNamed )
+ {
+ if( sem_close( (sem_t*)m_Handle ) != 0 )
+ {
+ LOG("ERROR: Could not close cSemaphore. (%i)", errno);
+ }
+ }
+ else
+ {
+ sem_destroy( (sem_t*)m_Handle );
+ delete (sem_t*)m_Handle;
+ }
+ m_Handle = 0;
+
+#endif
+}
+
+void cSemaphore::Wait()
+{
+#ifndef _WIN32
+ if( sem_wait( (sem_t*)m_Handle ) != 0)
+ {
+ LOG("ERROR: Could not wait for cSemaphore. (%i)", errno);
+ }
+#else
+ WaitForSingleObject( m_Handle, INFINITE);
+#endif
+}
+
+void cSemaphore::Signal()
+{
+#ifndef _WIN32
+ if( sem_post( (sem_t*)m_Handle ) != 0 )
+ {
+ LOG("ERROR: Could not signal cSemaphore. (%i)", errno);
+ }
+#else
+ ReleaseSemaphore( m_Handle, 1, NULL );
+#endif
+}
diff --git a/src/OSSupport/Semaphore.h b/src/OSSupport/Semaphore.h
new file mode 100644
index 000000000..fbe8907f1
--- /dev/null
+++ b/src/OSSupport/Semaphore.h
@@ -0,0 +1,17 @@
+#pragma once
+
+class cSemaphore
+{
+public:
+ cSemaphore( unsigned int a_MaxCount, unsigned int a_InitialCount = 0 );
+ ~cSemaphore();
+
+ void Wait();
+ void Signal();
+private:
+ void* m_Handle; // HANDLE pointer
+
+#ifndef _WIN32
+ bool m_bNamed;
+#endif
+};
diff --git a/src/OSSupport/Sleep.cpp b/src/OSSupport/Sleep.cpp
new file mode 100644
index 000000000..70fb06b40
--- /dev/null
+++ b/src/OSSupport/Sleep.cpp
@@ -0,0 +1,19 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#ifndef _WIN32
+ #include <unistd.h>
+#endif
+
+
+
+
+
+void cSleep::MilliSleep( unsigned int a_MilliSeconds )
+{
+#ifdef _WIN32
+ Sleep(a_MilliSeconds); // Don't tick too much
+#else
+ usleep(a_MilliSeconds*1000);
+#endif
+}
diff --git a/src/OSSupport/Sleep.h b/src/OSSupport/Sleep.h
new file mode 100644
index 000000000..5298c15da
--- /dev/null
+++ b/src/OSSupport/Sleep.h
@@ -0,0 +1,7 @@
+#pragma once
+
+class cSleep
+{
+public:
+ static void MilliSleep( unsigned int a_MilliSeconds );
+}; \ No newline at end of file
diff --git a/src/OSSupport/Socket.cpp b/src/OSSupport/Socket.cpp
new file mode 100644
index 000000000..f25f800c2
--- /dev/null
+++ b/src/OSSupport/Socket.cpp
@@ -0,0 +1,393 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#include "Socket.h"
+
+#ifndef _WIN32
+ #include <netdb.h>
+ #include <unistd.h>
+ #include <arpa/inet.h> //inet_ntoa()
+#else
+ #define socklen_t int
+#endif
+
+
+
+
+
+cSocket::cSocket(xSocket a_Socket)
+ : m_Socket(a_Socket)
+{
+}
+
+
+
+
+
+cSocket::~cSocket()
+{
+ // Do NOT close the socket; this class is an API wrapper, not a RAII!
+}
+
+
+
+
+
+cSocket::operator cSocket::xSocket() const
+{
+ return m_Socket;
+}
+
+
+
+
+
+cSocket::xSocket cSocket::GetSocket() const
+{
+ return m_Socket;
+}
+
+
+
+
+
+bool cSocket::IsValidSocket(cSocket::xSocket a_Socket)
+{
+ #ifdef _WIN32
+ return (a_Socket != INVALID_SOCKET);
+ #else // _WIN32
+ return (a_Socket >= 0);
+ #endif // else _WIN32
+}
+
+
+
+
+
+void cSocket::CloseSocket()
+{
+ #ifdef _WIN32
+
+ closesocket(m_Socket);
+
+ #else // _WIN32
+
+ if (shutdown(m_Socket, SHUT_RDWR) != 0)//SD_BOTH);
+ {
+ LOGWARN("Error on shutting down socket %d (%s): %s", m_Socket, m_IPString.c_str(), GetLastErrorString().c_str());
+ }
+ if (close(m_Socket) != 0)
+ {
+ LOGWARN("Error closing socket %d (%s): %s", m_Socket, m_IPString.c_str(), GetLastErrorString().c_str());
+ }
+
+ #endif // else _WIN32
+
+ // Invalidate the socket so that this object can be re-used for another connection
+ m_Socket = INVALID_SOCKET;
+}
+
+
+
+
+
+AString cSocket::GetErrorString( int a_ErrNo )
+{
+ char buffer[ 1024 ];
+ AString Out;
+
+ #ifdef _WIN32
+
+ FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, a_ErrNo, 0, buffer, ARRAYCOUNT(buffer), NULL);
+ Printf(Out, "%d: %s", a_ErrNo, buffer);
+ if (!Out.empty() && (Out[Out.length() - 1] == '\n'))
+ {
+ Out.erase(Out.length() - 2);
+ }
+ return Out;
+
+ #else // _WIN32
+
+ // According to http://linux.die.net/man/3/strerror_r there are two versions of strerror_r():
+
+ #if ( _GNU_SOURCE ) && !defined(ANDROID_NDK) // GNU version of strerror_r()
+
+ char * res = strerror_r( errno, buffer, ARRAYCOUNT(buffer) );
+ if( res != NULL )
+ {
+ Printf(Out, "%d: %s", a_ErrNo, res);
+ return Out;
+ }
+
+ #else // XSI version of strerror_r():
+
+ int res = strerror_r( errno, buffer, ARRAYCOUNT(buffer) );
+ if( res == 0 )
+ {
+ Printf(Out, "%d: %s", a_ErrNo, buffer);
+ return Out;
+ }
+
+ #endif // strerror_r() version
+
+ else
+ {
+ Printf(Out, "Error %d while getting error string for error #%d!", errno, a_ErrNo);
+ return Out;
+ }
+
+ #endif // else _WIN32
+}
+
+
+
+
+int cSocket::GetLastError()
+{
+#ifdef _WIN32
+ return WSAGetLastError();
+#else
+ return errno;
+#endif
+}
+
+
+
+
+
+bool cSocket::SetReuseAddress(void)
+{
+ #if defined(_WIN32) || defined(ANDROID_NDK)
+ char yes = 1;
+ #else
+ int yes = 1;
+ #endif
+ return (setsockopt(m_Socket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == 0);
+}
+
+
+
+
+
+int cSocket::WSAStartup(void)
+{
+#ifdef _WIN32
+ WSADATA wsaData;
+ memset(&wsaData, 0, sizeof(wsaData));
+ return ::WSAStartup(MAKEWORD(2, 2),&wsaData);
+#else
+ return 0;
+#endif
+}
+
+
+
+
+
+cSocket cSocket::CreateSocket(eFamily a_Family)
+{
+ return socket((int)a_Family, SOCK_STREAM, 0);
+}
+
+
+
+
+
+bool cSocket::BindToAnyIPv4(unsigned short a_Port)
+{
+ sockaddr_in local;
+ memset(&local, 0, sizeof(local));
+
+ local.sin_family = AF_INET;
+ local.sin_port = htons((u_short)a_Port);
+
+ return (bind(m_Socket, (sockaddr *)&local, sizeof(local)) == 0);
+}
+
+
+
+
+
+bool cSocket::BindToAnyIPv6(unsigned short a_Port)
+{
+ sockaddr_in6 local;
+ memset(&local, 0, sizeof(local));
+
+ local.sin6_family = AF_INET6;
+ local.sin6_port = htons((u_short)a_Port);
+
+ return (bind(m_Socket, (sockaddr *)&local, sizeof(local)) == 0);
+}
+
+
+
+
+
+bool cSocket::BindToLocalhostIPv4(unsigned short a_Port)
+{
+ sockaddr_in local;
+ memset(&local, 0, sizeof(local));
+
+ local.sin_family = AF_INET;;
+ local.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ local.sin_port = htons((u_short)a_Port);
+
+ return (bind(m_Socket, (sockaddr*)&local, sizeof(local)) == 0);
+}
+
+
+
+
+
+bool cSocket::Listen(int a_Backlog)
+{
+ return (listen(m_Socket, a_Backlog) == 0);
+}
+
+
+
+
+
+cSocket cSocket::AcceptIPv4(void)
+{
+ sockaddr_in from;
+ socklen_t fromlen = sizeof(from);
+
+ cSocket SClient = accept(m_Socket, (sockaddr *)&from, &fromlen);
+
+ if (SClient.IsValid() && (from.sin_addr.s_addr != 0)) // Get IP in string form
+ {
+ SClient.m_IPString = inet_ntoa(from.sin_addr);
+ }
+ return SClient;
+}
+
+
+
+
+
+cSocket cSocket::AcceptIPv6(void)
+{
+ sockaddr_in6 from;
+ socklen_t fromlen = sizeof(from);
+
+ cSocket SClient = accept(m_Socket, (sockaddr *)&from, &fromlen);
+
+ // Get IP in string form:
+ if (SClient.IsValid())
+ {
+ #if defined(_WIN32)
+ // Windows XP doesn't have inet_ntop, so we need to improvise. And MSVC has different headers than GCC
+ #ifdef _MSC_VER
+ // MSVC version
+ Printf(SClient.m_IPString, "%x:%x:%x:%x:%x:%x:%x:%x",
+ from.sin6_addr.u.Word[0],
+ from.sin6_addr.u.Word[1],
+ from.sin6_addr.u.Word[2],
+ from.sin6_addr.u.Word[3],
+ from.sin6_addr.u.Word[4],
+ from.sin6_addr.u.Word[5],
+ from.sin6_addr.u.Word[6],
+ from.sin6_addr.u.Word[7]
+ );
+ #else // _MSC_VER
+ // MinGW
+ Printf(SClient.m_IPString, "%x:%x:%x:%x:%x:%x:%x:%x",
+ from.sin6_addr.s6_addr16[0],
+ from.sin6_addr.s6_addr16[1],
+ from.sin6_addr.s6_addr16[2],
+ from.sin6_addr.s6_addr16[3],
+ from.sin6_addr.s6_addr16[4],
+ from.sin6_addr.s6_addr16[5],
+ from.sin6_addr.s6_addr16[6],
+ from.sin6_addr.s6_addr16[7]
+ );
+ #endif // else _MSC_VER
+ #else
+ char buffer[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, &(from.sin6_addr), buffer, sizeof(buffer));
+ SClient.m_IPString.assign(buffer);
+ #endif // _WIN32
+ }
+ return SClient;
+}
+
+
+
+
+
+bool cSocket::ConnectToLocalhostIPv4(unsigned short a_Port)
+{
+ sockaddr_in server;
+ server.sin_family = AF_INET;
+ server.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ server.sin_port = htons(a_Port);
+ return (connect(m_Socket, (sockaddr *)&server, sizeof(server)) == 0);
+}
+
+
+
+
+
+bool cSocket::ConnectIPv4(const AString & a_HostNameOrAddr, unsigned short a_Port)
+{
+ // First try IP Address string to hostent conversion, because it's faster
+ unsigned long addr = inet_addr(a_HostNameOrAddr.c_str());
+ if (addr == INADDR_NONE)
+ {
+ // It is not an IP Address string, but rather a regular hostname, resolve:
+ hostent * hp = gethostbyname(a_HostNameOrAddr.c_str());
+ if (hp == NULL)
+ {
+ LOGWARNING("%s: Could not resolve hostname \"%s\"", __FUNCTION__, a_HostNameOrAddr.c_str());
+ CloseSocket();
+ return false;
+ }
+ addr = *((unsigned long*)hp->h_addr);
+ }
+
+ sockaddr_in server;
+ server.sin_addr.s_addr = addr;
+ server.sin_family = AF_INET;
+ server.sin_port = htons((unsigned short)a_Port);
+ return (connect(m_Socket, (sockaddr *)&server, sizeof(server)) == 0);
+}
+
+
+
+
+
+int cSocket::Receive(char* a_Buffer, unsigned int a_Length, unsigned int a_Flags)
+{
+ return recv(m_Socket, a_Buffer, a_Length, a_Flags);
+}
+
+
+
+
+
+int cSocket::Send(const char * a_Buffer, unsigned int a_Length)
+{
+ return send(m_Socket, a_Buffer, a_Length, 0);
+}
+
+
+
+
+
+unsigned short cSocket::GetPort(void) const
+{
+ ASSERT(IsValid());
+
+ sockaddr_in Addr;
+ socklen_t AddrSize = sizeof(Addr);
+ if (getsockname(m_Socket, (sockaddr *)&Addr, &AddrSize) != 0)
+ {
+ return 0;
+ }
+ return ntohs(Addr.sin_port);
+}
+
+
+
+
diff --git a/src/OSSupport/Socket.h b/src/OSSupport/Socket.h
new file mode 100644
index 000000000..81bfd28fc
--- /dev/null
+++ b/src/OSSupport/Socket.h
@@ -0,0 +1,102 @@
+
+#pragma once
+
+
+
+
+
+class cSocket
+{
+public:
+ enum eFamily
+ {
+ IPv4 = AF_INET,
+ IPv6 = AF_INET6,
+ } ;
+
+#ifdef _WIN32
+ typedef SOCKET xSocket;
+#else
+ typedef int xSocket;
+ static const int INVALID_SOCKET = -1;
+#endif
+
+ cSocket(void) : m_Socket(INVALID_SOCKET) {}
+ cSocket(xSocket a_Socket);
+ ~cSocket();
+
+ bool IsValid(void) const { return IsValidSocket(m_Socket); }
+ void CloseSocket(void);
+
+ operator xSocket(void) const;
+ xSocket GetSocket(void) const;
+
+ bool operator == (const cSocket & a_Other) {return m_Socket == a_Other.m_Socket; }
+
+ void SetSocket(xSocket a_Socket);
+
+ /// Sets the address-reuse socket flag; returns true on success
+ bool SetReuseAddress(void);
+
+ /// Initializes the network stack. Returns 0 on success, or another number as an error code.
+ static int WSAStartup(void);
+
+ static AString GetErrorString(int a_ErrNo);
+ static int GetLastError();
+ static AString GetLastErrorString(void)
+ {
+ return GetErrorString(GetLastError());
+ }
+
+ /// Creates a new socket of the specified address family
+ static cSocket CreateSocket(eFamily a_Family);
+
+ inline static bool IsSocketError(int a_ReturnedValue)
+ {
+ #ifdef _WIN32
+ return (a_ReturnedValue == SOCKET_ERROR || a_ReturnedValue == 0);
+ #else
+ return (a_ReturnedValue <= 0);
+ #endif
+ }
+
+ static bool IsValidSocket(xSocket a_Socket);
+
+ static const unsigned short ANY_PORT = 0; // When given to Bind() functions, they will find a free port
+ static const int DEFAULT_BACKLOG = 10;
+
+ /// Binds to the specified port on "any" interface (0.0.0.0). Returns true if successful.
+ bool BindToAnyIPv4(unsigned short a_Port);
+
+ /// Binds to the specified port on "any" interface (::/128). Returns true if successful.
+ bool BindToAnyIPv6(unsigned short a_Port);
+
+ /// Binds to the specified port on localhost interface (127.0.0.1) through IPv4. Returns true if successful.
+ bool BindToLocalhostIPv4(unsigned short a_Port);
+
+ /// Sets the socket to listen for incoming connections. Returns true if successful.
+ bool Listen(int a_Backlog = DEFAULT_BACKLOG);
+
+ /// Accepts an IPv4 incoming connection. Blocks if none available.
+ cSocket AcceptIPv4(void);
+
+ /// Accepts an IPv6 incoming connection. Blocks if none available.
+ cSocket AcceptIPv6(void);
+
+ /// Connects to a localhost socket on the specified port using IPv4; returns true if successful.
+ bool ConnectToLocalhostIPv4(unsigned short a_Port);
+
+ /// Connects to the specified host or string IP address and port, using IPv4. Returns true if successful.
+ bool ConnectIPv4(const AString & a_HostNameOrAddr, unsigned short a_Port);
+
+ int Receive(char * a_Buffer, unsigned int a_Length, unsigned int a_Flags);
+ int Send (const char * a_Buffer, unsigned int a_Length);
+
+ unsigned short GetPort(void) const; // Returns 0 on failure
+
+ const AString & GetIPString(void) const { return m_IPString; }
+
+private:
+ xSocket m_Socket;
+ AString m_IPString;
+}; \ No newline at end of file
diff --git a/src/OSSupport/SocketThreads.cpp b/src/OSSupport/SocketThreads.cpp
new file mode 100644
index 000000000..3e505616c
--- /dev/null
+++ b/src/OSSupport/SocketThreads.cpp
@@ -0,0 +1,675 @@
+
+// cSocketThreads.cpp
+
+// Implements the cSocketThreads class representing the heart of MCS's client networking.
+// This object takes care of network communication, groups sockets into threads and uses as little threads as possible for full read / write support
+// For more detail, see http://forum.mc-server.org/showthread.php?tid=327
+
+#include "Globals.h"
+#include "SocketThreads.h"
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cSocketThreads:
+
+cSocketThreads::cSocketThreads(void)
+{
+}
+
+
+
+
+
+cSocketThreads::~cSocketThreads()
+{
+ for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
+ {
+ delete *itr;
+ } // for itr - m_Threads[]
+ m_Threads.clear();
+}
+
+
+
+
+
+
+bool cSocketThreads::AddClient(const cSocket & a_Socket, cCallback * a_Client)
+{
+ // Add a (socket, client) pair for processing, data from a_Socket is to be sent to a_Client
+
+ // Try to add to existing threads:
+ cCSLock Lock(m_CS);
+ for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
+ {
+ if ((*itr)->IsValid() && (*itr)->HasEmptySlot())
+ {
+ (*itr)->AddClient(a_Socket, a_Client);
+ return true;
+ }
+ }
+
+ // No thread has free space, create a new one:
+ LOGD("Creating a new cSocketThread (currently have %d)", m_Threads.size());
+ cSocketThread * Thread = new cSocketThread(this);
+ if (!Thread->Start())
+ {
+ // There was an error launching the thread (but it was already logged along with the reason)
+ LOGERROR("A new cSocketThread failed to start");
+ delete Thread;
+ return false;
+ }
+ Thread->AddClient(a_Socket, a_Client);
+ m_Threads.push_back(Thread);
+ return true;
+}
+
+
+
+
+
+/*
+void cSocketThreads::RemoveClient(const cSocket * a_Socket)
+{
+ // Remove the socket (and associated client) from processing
+
+ cCSLock Lock(m_CS);
+ for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
+ {
+ if ((*itr)->RemoveSocket(a_Socket))
+ {
+ return;
+ }
+ } // for itr - m_Threads[]
+
+ // Cannot assert here, this may actually happen legally, since cClientHandle has to clean up the socket and it may have already closed in the meantime
+ // ASSERT(!"Removing an unknown socket");
+}
+*/
+
+
+
+
+
+void cSocketThreads::RemoveClient(const cCallback * a_Client)
+{
+ // Remove the associated socket and the client from processing
+
+ cCSLock Lock(m_CS);
+ for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
+ {
+ if ((*itr)->RemoveClient(a_Client))
+ {
+ return;
+ }
+ } // for itr - m_Threads[]
+
+ ASSERT(!"Removing an unknown client");
+}
+
+
+
+
+
+void cSocketThreads::NotifyWrite(const cCallback * a_Client)
+{
+ // Notifies the thread responsible for a_Client that the client has something to write
+
+ cCSLock Lock(m_CS);
+ for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
+ {
+ if ((*itr)->NotifyWrite(a_Client))
+ {
+ return;
+ }
+ } // for itr - m_Threads[]
+
+ // Cannot assert - this normally happens if a client disconnects and has pending packets, the cServer::cNotifyWriteThread will call this on invalid clients too
+ // ASSERT(!"Notifying write to an unknown client");
+}
+
+
+
+
+
+void cSocketThreads::Write(const cCallback * a_Client, const AString & a_Data)
+{
+ // Puts a_Data into outgoing data queue for a_Client
+ cCSLock Lock(m_CS);
+ for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
+ {
+ if ((*itr)->Write(a_Client, a_Data))
+ {
+ return;
+ }
+ } // for itr - m_Threads[]
+
+ // This may be perfectly legal, if the socket has been destroyed and the client is finishing up
+ // ASSERT(!"Writing to an unknown socket");
+}
+
+
+
+
+
+/// Stops reading from the socket - when this call returns, no more calls to the callbacks are made
+void cSocketThreads::StopReading(const cCallback * a_Client)
+{
+ cCSLock Lock(m_CS);
+ for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
+ {
+ if ((*itr)->StopReading(a_Client))
+ {
+ return;
+ }
+ } // for itr - m_Threads[]
+
+ // Cannot assert, this normally happens if the socket is closed before the client deinitializes
+ // ASSERT(!"Stopping reading on an unknown client");
+}
+
+
+
+
+
+/// Queues the socket for closing, as soon as its outgoing data is sent
+void cSocketThreads::QueueClose(const cCallback * a_Client)
+{
+ LOGD("QueueClose(client %p)", a_Client);
+
+ cCSLock Lock(m_CS);
+ for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
+ {
+ if ((*itr)->QueueClose(a_Client))
+ {
+ return;
+ }
+ } // for itr - m_Threads[]
+
+ ASSERT(!"Queueing close of an unknown client");
+}
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cSocketThreads::cSocketThread:
+
+cSocketThreads::cSocketThread::cSocketThread(cSocketThreads * a_Parent) :
+ cIsThread("cSocketThread"),
+ m_Parent(a_Parent),
+ m_NumSlots(0)
+{
+ // Nothing needed yet
+}
+
+
+
+
+
+cSocketThreads::cSocketThread::~cSocketThread()
+{
+ m_ShouldTerminate = true;
+
+ // Notify the thread:
+ ASSERT(m_ControlSocket2.IsValid());
+ m_ControlSocket2.Send("a", 1);
+
+ // Wait for the thread to finish:
+ Wait();
+
+ // Close the control sockets:
+ m_ControlSocket1.CloseSocket();
+ m_ControlSocket2.CloseSocket();
+}
+
+
+
+
+
+void cSocketThreads::cSocketThread::AddClient(const cSocket & a_Socket, cCallback * a_Client)
+{
+ ASSERT(m_NumSlots < MAX_SLOTS); // Use HasEmptySlot() to check before adding
+
+ m_Slots[m_NumSlots].m_Client = a_Client;
+ m_Slots[m_NumSlots].m_Socket = a_Socket;
+ m_Slots[m_NumSlots].m_Outgoing.clear();
+ m_Slots[m_NumSlots].m_ShouldClose = false;
+ m_Slots[m_NumSlots].m_ShouldCallClient = true;
+ m_NumSlots++;
+
+ // Notify the thread of the change:
+ ASSERT(m_ControlSocket2.IsValid());
+ m_ControlSocket2.Send("a", 1);
+}
+
+
+
+
+
+bool cSocketThreads::cSocketThread::RemoveClient(const cCallback * a_Client)
+{
+ // Returns true if removed, false if not found
+
+ if (m_NumSlots == 0)
+ {
+ return false;
+ }
+
+ for (int i = m_NumSlots - 1; i >= 0 ; --i)
+ {
+ if (m_Slots[i].m_Client != a_Client)
+ {
+ continue;
+ }
+
+ // Found, remove it:
+ m_Slots[i] = m_Slots[--m_NumSlots];
+
+ // Notify the thread of the change:
+ ASSERT(m_ControlSocket2.IsValid());
+ m_ControlSocket2.Send("r", 1);
+ return true;
+ } // for i - m_Slots[]
+
+ // Not found
+ return false;
+}
+
+
+
+
+
+bool cSocketThreads::cSocketThread::RemoveSocket(const cSocket * a_Socket)
+{
+ // Returns true if removed, false if not found
+
+ for (int i = m_NumSlots - 1; i >= 0 ; --i)
+ {
+ if (m_Slots[i].m_Socket != *a_Socket)
+ {
+ continue;
+ }
+
+ // Found, remove it:
+ m_Slots[i] = m_Slots[--m_NumSlots];
+
+ // Notify the thread of the change:
+ ASSERT(m_ControlSocket2.IsValid());
+ m_ControlSocket2.Send("r", 1);
+ return true;
+ } // for i - m_Slots[]
+
+ // Not found
+ return false;
+}
+
+
+
+
+
+bool cSocketThreads::cSocketThread::HasClient(const cCallback * a_Client) const
+{
+ for (int i = m_NumSlots - 1; i >= 0; --i)
+ {
+ if (m_Slots[i].m_Client == a_Client)
+ {
+ return true;
+ }
+ } // for i - m_Slots[]
+ return false;
+}
+
+
+
+
+
+bool cSocketThreads::cSocketThread::HasSocket(const cSocket * a_Socket) const
+{
+ for (int i = m_NumSlots - 1; i >= 0; --i)
+ {
+ if (m_Slots[i].m_Socket == *a_Socket)
+ {
+ return true;
+ }
+ } // for i - m_Slots[]
+ return false;
+}
+
+
+
+
+
+bool cSocketThreads::cSocketThread::NotifyWrite(const cCallback * a_Client)
+{
+ if (HasClient(a_Client))
+ {
+ // Notify the thread that there's another packet in the queue:
+ ASSERT(m_ControlSocket2.IsValid());
+ m_ControlSocket2.Send("q", 1);
+ return true;
+ }
+ return false;
+}
+
+
+
+
+
+bool cSocketThreads::cSocketThread::Write(const cCallback * a_Client, const AString & a_Data)
+{
+ // Returns true if socket handled by this thread
+ for (int i = m_NumSlots - 1; i >= 0; --i)
+ {
+ if (m_Slots[i].m_Client == a_Client)
+ {
+ m_Slots[i].m_Outgoing.append(a_Data);
+
+ // Notify the thread that there's data in the queue:
+ ASSERT(m_ControlSocket2.IsValid());
+ m_ControlSocket2.Send("q", 1);
+
+ return true;
+ }
+ } // for i - m_Slots[]
+ return false;
+}
+
+
+
+
+
+bool cSocketThreads::cSocketThread::StopReading (const cCallback * a_Client)
+{
+ // Returns true if client handled by this thread
+ for (int i = m_NumSlots - 1; i >= 0; --i)
+ {
+ if (m_Slots[i].m_Client == a_Client)
+ {
+ m_Slots[i].m_ShouldCallClient = false;
+ return true;
+ }
+ } // for i - m_Slots[]
+ return false;
+}
+
+
+
+
+
+bool cSocketThreads::cSocketThread::QueueClose(const cCallback * a_Client)
+{
+ // Returns true if socket handled by this thread
+ for (int i = m_NumSlots - 1; i >= 0; --i)
+ {
+ if (m_Slots[i].m_Client == a_Client)
+ {
+ m_Slots[i].m_ShouldClose = true;
+
+ // Notify the thread that there's a close queued (in case its conditions are already met):
+ ASSERT(m_ControlSocket2.IsValid());
+ m_ControlSocket2.Send("c", 1);
+
+ return true;
+ }
+ } // for i - m_Slots[]
+ return false;
+}
+
+
+
+
+
+bool cSocketThreads::cSocketThread::Start(void)
+{
+ // Create the control socket listener
+ m_ControlSocket2 = cSocket::CreateSocket(cSocket::IPv4);
+ if (!m_ControlSocket2.IsValid())
+ {
+ LOGERROR("Cannot create a Control socket for a cSocketThread (\"%s\"); continuing, but server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
+ return false;
+ }
+ if (!m_ControlSocket2.BindToLocalhostIPv4(cSocket::ANY_PORT))
+ {
+ LOGERROR("Cannot bind a Control socket for a cSocketThread (\"%s\"); continuing, but server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
+ m_ControlSocket2.CloseSocket();
+ return false;
+ }
+ if (!m_ControlSocket2.Listen(1))
+ {
+ LOGERROR("Cannot listen on a Control socket for a cSocketThread (\"%s\"); continuing, but server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
+ m_ControlSocket2.CloseSocket();
+ return false;
+ }
+ if (m_ControlSocket2.GetPort() == 0)
+ {
+ LOGERROR("Cannot determine Control socket port (\"%s\"); conitnuing, but the server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
+ m_ControlSocket2.CloseSocket();
+ return false;
+ }
+
+ // Start the thread
+ if (!super::Start())
+ {
+ LOGERROR("Cannot start new cSocketThread");
+ m_ControlSocket2.CloseSocket();
+ return false;
+ }
+
+ // Finish connecting the control socket by accepting connection from the thread's socket
+ cSocket tmp = m_ControlSocket2.AcceptIPv4();
+ if (!tmp.IsValid())
+ {
+ LOGERROR("Cannot link Control sockets for a cSocketThread (\"%s\"); continuing, but server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
+ m_ControlSocket2.CloseSocket();
+ return false;
+ }
+ m_ControlSocket2.CloseSocket();
+ m_ControlSocket2 = tmp;
+
+ return true;
+}
+
+
+
+
+
+void cSocketThreads::cSocketThread::Execute(void)
+{
+ // Connect the "client" part of the Control socket:
+ m_ControlSocket1 = cSocket::CreateSocket(cSocket::IPv4);
+ ASSERT(m_ControlSocket2.GetPort() != 0); // We checked in the Start() method, but let's be sure
+ if (!m_ControlSocket1.ConnectToLocalhostIPv4(m_ControlSocket2.GetPort()))
+ {
+ LOGERROR("Cannot connect Control sockets for a cSocketThread (\"%s\"); continuing, but the server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
+ m_ControlSocket2.CloseSocket();
+ return;
+ }
+
+ // The main thread loop:
+ while (!m_ShouldTerminate)
+ {
+ // Put all sockets into the Read set:
+ fd_set fdRead;
+ cSocket::xSocket Highest = m_ControlSocket1.GetSocket();
+
+ PrepareSet(&fdRead, Highest);
+
+ // Wait for the sockets:
+ if (select(Highest + 1, &fdRead, NULL, NULL, NULL) == -1)
+ {
+ LOG("select(R) call failed in cSocketThread: \"%s\"", cSocket::GetLastErrorString().c_str());
+ continue;
+ }
+
+ ReadFromSockets(&fdRead);
+
+ // Test sockets for writing:
+ fd_set fdWrite;
+ Highest = m_ControlSocket1.GetSocket();
+ PrepareSet(&fdWrite, Highest);
+ timeval Timeout;
+ Timeout.tv_sec = 0;
+ Timeout.tv_usec = 0;
+ if (select(Highest + 1, NULL, &fdWrite, NULL, &Timeout) == -1)
+ {
+ LOG("select(W) call failed in cSocketThread: \"%s\"", cSocket::GetLastErrorString().c_str());
+ continue;
+ }
+
+ WriteToSockets(&fdWrite);
+ } // while (!mShouldTerminate)
+}
+
+
+
+
+
+void cSocketThreads::cSocketThread::PrepareSet(fd_set * a_Set, cSocket::xSocket & a_Highest)
+{
+ FD_ZERO(a_Set);
+ FD_SET(m_ControlSocket1.GetSocket(), a_Set);
+
+ cCSLock Lock(m_Parent->m_CS);
+ for (int i = m_NumSlots - 1; i >= 0; --i)
+ {
+ if (!m_Slots[i].m_Socket.IsValid())
+ {
+ continue;
+ }
+ cSocket::xSocket s = m_Slots[i].m_Socket.GetSocket();
+ FD_SET(s, a_Set);
+ if (s > a_Highest)
+ {
+ a_Highest = s;
+ }
+ } // for i - m_Slots[]
+}
+
+
+
+
+
+void cSocketThreads::cSocketThread::ReadFromSockets(fd_set * a_Read)
+{
+ // Read on available sockets:
+
+ // Reset Control socket state:
+ if (FD_ISSET(m_ControlSocket1.GetSocket(), a_Read))
+ {
+ char Dummy[128];
+ m_ControlSocket1.Receive(Dummy, sizeof(Dummy), 0);
+ }
+
+ // Read from clients:
+ cCSLock Lock(m_Parent->m_CS);
+ for (int i = m_NumSlots - 1; i >= 0; --i)
+ {
+ cSocket::xSocket Socket = m_Slots[i].m_Socket.GetSocket();
+ if (!cSocket::IsValidSocket(Socket) || !FD_ISSET(Socket, a_Read))
+ {
+ continue;
+ }
+ char Buffer[1024];
+ int Received = m_Slots[i].m_Socket.Receive(Buffer, ARRAYCOUNT(Buffer), 0);
+ if (Received == 0)
+ {
+ // The socket has been closed by the remote party, close our socket and let it be removed after we process all reading
+ m_Slots[i].m_Socket.CloseSocket();
+ if (m_Slots[i].m_ShouldCallClient)
+ {
+ m_Slots[i].m_Client->SocketClosed();
+ }
+ }
+ else if (Received > 0)
+ {
+ if (m_Slots[i].m_ShouldCallClient)
+ {
+ m_Slots[i].m_Client->DataReceived(Buffer, Received);
+ }
+ }
+ else
+ {
+ // The socket has encountered an error, close it and let it be removed after we process all reading
+ m_Slots[i].m_Socket.CloseSocket();
+ if (m_Slots[i].m_ShouldCallClient)
+ {
+ m_Slots[i].m_Client->SocketClosed();
+ }
+ }
+ } // for i - m_Slots[]
+}
+
+
+
+
+
+void cSocketThreads::cSocketThread::WriteToSockets(fd_set * a_Write)
+{
+ // Write to available client sockets:
+ cCSLock Lock(m_Parent->m_CS);
+ for (int i = m_NumSlots - 1; i >= 0; --i)
+ {
+ cSocket::xSocket Socket = m_Slots[i].m_Socket.GetSocket();
+ if (!cSocket::IsValidSocket(Socket) || !FD_ISSET(Socket, a_Write))
+ {
+ continue;
+ }
+ if (m_Slots[i].m_Outgoing.empty())
+ {
+ // Request another chunk of outgoing data:
+ if (m_Slots[i].m_ShouldCallClient)
+ {
+ m_Slots[i].m_Client->GetOutgoingData(m_Slots[i].m_Outgoing);
+ }
+ if (m_Slots[i].m_Outgoing.empty())
+ {
+ // Nothing ready
+ if (m_Slots[i].m_ShouldClose)
+ {
+ // Socket was queued for closing and there's no more data to send, close it now:
+
+ // DEBUG
+ LOGD("Socket was queued for closing, closing now. Slot %d, client %p, socket %d", i, m_Slots[i].m_Client, m_Slots[i].m_Socket.GetSocket());
+
+ m_Slots[i].m_Socket.CloseSocket();
+ // The slot must be freed actively by the client, using RemoveClient()
+ }
+ continue;
+ }
+ } // if (outgoing data is empty)
+
+ int Sent = m_Slots[i].m_Socket.Send(m_Slots[i].m_Outgoing.data(), m_Slots[i].m_Outgoing.size());
+ if (Sent < 0)
+ {
+ int Err = cSocket::GetLastError();
+ LOGWARNING("Error %d while writing to client \"%s\", disconnecting. \"%s\"", Err, m_Slots[i].m_Socket.GetIPString().c_str(), cSocket::GetErrorString(Err).c_str());
+ m_Slots[i].m_Socket.CloseSocket();
+ if (m_Slots[i].m_ShouldCallClient)
+ {
+ m_Slots[i].m_Client->SocketClosed();
+ }
+ return;
+ }
+ m_Slots[i].m_Outgoing.erase(0, Sent);
+
+ // _X: If there's data left, it means the client is not reading fast enough, the server would unnecessarily spin in the main loop with zero actions taken; so signalling is disabled
+ // This means that if there's data left, it will be sent only when there's incoming data or someone queues another packet (for any socket handled by this thread)
+ /*
+ // If there's any data left, signalize the Control socket:
+ if (!m_Slots[i].m_Outgoing.empty())
+ {
+ ASSERT(m_ControlSocket2.IsValid());
+ m_ControlSocket2.Send("q", 1);
+ }
+ */
+ } // for i - m_Slots[i]
+}
+
+
+
+
diff --git a/src/OSSupport/SocketThreads.h b/src/OSSupport/SocketThreads.h
new file mode 100644
index 000000000..ecbac3aeb
--- /dev/null
+++ b/src/OSSupport/SocketThreads.h
@@ -0,0 +1,169 @@
+
+// SocketThreads.h
+
+// Interfaces to the cSocketThreads class representing the heart of MCS's client networking.
+// This object takes care of network communication, groups sockets into threads and uses as little threads as possible for full read / write support
+// For more detail, see http://forum.mc-server.org/showthread.php?tid=327
+
+/*
+Additional details:
+When a client is terminating a connection:
+- they call the StopReading() method to disable callbacks for the incoming data
+- they call the Write() method to queue any outstanding outgoing data
+- they call the QueueClose() method to queue the socket to close after outgoing data has been sent.
+When a socket slot is marked as having no callback, it is kept alive until its outgoing data queue is empty and its m_ShouldClose flag is set.
+This means that the socket can be written to several times before finally closing it via QueueClose()
+*/
+
+
+
+
+
+/// How many clients should one thread handle? (must be less than FD_SETSIZE for your platform)
+#define MAX_SLOTS 63
+
+
+
+
+
+#pragma once
+#ifndef CSOCKETTHREADS_H_INCLUDED
+#define CSOCKETTHREADS_H_INCLUDED
+
+#include "Socket.h"
+#include "IsThread.h"
+
+
+
+
+// Check MAX_SLOTS:
+#if MAX_SLOTS >= FD_SETSIZE
+ #error "MAX_SLOTS must be less than FD_SETSIZE for your platform! (otherwise select() won't work)"
+#endif
+
+
+
+
+
+// fwd:
+class cSocket;
+class cClientHandle;
+
+
+
+
+
+class cSocketThreads
+{
+public:
+
+ // Clients of cSocketThreads must implement this interface to be able to communicate
+ class cCallback
+ {
+ public:
+ /// Called when data is received from the remote party
+ virtual void DataReceived(const char * a_Data, int a_Size) = 0;
+
+ /// Called when data can be sent to remote party; the function is supposed to append outgoing data to a_Data
+ virtual void GetOutgoingData(AString & a_Data) = 0;
+
+ /// Called when the socket has been closed for any reason
+ virtual void SocketClosed(void) = 0;
+ } ;
+
+
+ cSocketThreads(void);
+ ~cSocketThreads();
+
+ /// Add a (socket, client) pair for processing, data from a_Socket is to be sent to a_Client; returns true if successful
+ bool AddClient(const cSocket & a_Socket, cCallback * a_Client);
+
+ /// Remove the associated socket and the client from processing. The socket is left to send its data and is removed only after all its m_OutgoingData is sent
+ void RemoveClient(const cCallback * a_Client);
+
+ /// Notify the thread responsible for a_Client that the client has something to write
+ void NotifyWrite(const cCallback * a_Client);
+
+ /// Puts a_Data into outgoing data queue for a_Client
+ void Write(const cCallback * a_Client, const AString & a_Data);
+
+ /// Stops reading from the client - when this call returns, no more calls to the callbacks are made
+ void StopReading(const cCallback * a_Client);
+
+ /// Queues the client for closing, as soon as its outgoing data is sent
+ void QueueClose(const cCallback * a_Client);
+
+private:
+
+ class cSocketThread :
+ public cIsThread
+ {
+ typedef cIsThread super;
+
+ public:
+
+ cSocketThread(cSocketThreads * a_Parent);
+ ~cSocketThread();
+
+ // All these methods assume parent's m_CS is locked
+ bool HasEmptySlot(void) const {return m_NumSlots < MAX_SLOTS; }
+ bool IsEmpty (void) const {return m_NumSlots == 0; }
+
+ void AddClient (const cSocket & a_Socket, cCallback * a_Client); // Takes ownership of the socket
+ bool RemoveClient(const cCallback * a_Client); // Returns true if removed, false if not found
+ bool RemoveSocket(const cSocket * a_Socket); // Returns true if removed, false if not found
+ bool HasClient (const cCallback * a_Client) const;
+ bool HasSocket (const cSocket * a_Socket) const;
+ bool NotifyWrite (const cCallback * a_Client); // Returns true if client handled by this thread
+ bool Write (const cCallback * a_Client, const AString & a_Data); // Returns true if client handled by this thread
+ bool StopReading (const cCallback * a_Client); // Returns true if client handled by this thread
+ bool QueueClose (const cCallback * a_Client); // Returns true if client handled by this thread
+
+ bool Start(void); // Hide the cIsThread's Start method, we need to provide our own startup to create the control socket
+
+ bool IsValid(void) const {return m_ControlSocket2.IsValid(); } // If the Control socket dies, the thread is not valid anymore
+
+ private:
+
+ cSocketThreads * m_Parent;
+
+ // Two ends of the control socket, the first is select()-ed, the second is written to for notifications
+ cSocket m_ControlSocket1;
+ cSocket m_ControlSocket2;
+
+ // Socket-client-packetqueues triplets.
+ // Manipulation with these assumes that the parent's m_CS is locked
+ struct sSlot
+ {
+ cSocket m_Socket; // The socket is primarily owned by this
+ cCallback * m_Client;
+ AString m_Outgoing; // If sending writes only partial data, the rest is stored here for another send
+ bool m_ShouldClose; // If true, the socket is to be closed after sending all outgoing data
+ bool m_ShouldCallClient; // If true, the client callbacks are called. Set to false in StopReading()
+ } ;
+ sSlot m_Slots[MAX_SLOTS];
+ int m_NumSlots; // Number of slots actually used
+
+ virtual void Execute(void) override;
+
+ void PrepareSet (fd_set * a_Set, cSocket::xSocket & a_Highest); // Puts all sockets into the set, along with m_ControlSocket1
+ void ReadFromSockets(fd_set * a_Read); // Reads from sockets indicated in a_Read
+ void WriteToSockets (fd_set * a_Write); // Writes to sockets indicated in a_Write
+ } ;
+
+ typedef std::list<cSocketThread *> cSocketThreadList;
+
+
+ cCriticalSection m_CS;
+ cSocketThreadList m_Threads;
+} ;
+
+
+
+
+
+#endif // CSOCKETTHREADS_H_INCLUDED
+
+
+
+
diff --git a/src/OSSupport/Thread.cpp b/src/OSSupport/Thread.cpp
new file mode 100644
index 000000000..3df75f0e7
--- /dev/null
+++ b/src/OSSupport/Thread.cpp
@@ -0,0 +1,128 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+
+
+
+
+// When in MSVC, the debugger provides "thread naming" by catching special exceptions. Interface here:
+#ifdef _MSC_VER
+//
+// Usage: SetThreadName (-1, "MainThread");
+//
+typedef struct tagTHREADNAME_INFO
+{
+ DWORD dwType; // must be 0x1000
+ LPCSTR szName; // pointer to name (in user addr space)
+ DWORD dwThreadID; // thread ID (-1=caller thread)
+ DWORD dwFlags; // reserved for future use, must be zero
+} THREADNAME_INFO;
+
+void SetThreadName( DWORD dwThreadID, LPCSTR szThreadName)
+{
+ THREADNAME_INFO info;
+ info.dwType = 0x1000;
+ info.szName = szThreadName;
+ info.dwThreadID = dwThreadID;
+ info.dwFlags = 0;
+
+ __try
+ {
+ RaiseException( 0x406D1388, 0, sizeof(info)/sizeof(DWORD), (DWORD*)&info );
+ }
+ __except(EXCEPTION_CONTINUE_EXECUTION)
+ {
+ }
+}
+#endif // _MSC_VER
+
+
+
+
+
+cThread::cThread( ThreadFunc a_ThreadFunction, void* a_Param, const char* a_ThreadName /* = 0 */ )
+ : m_ThreadFunction( a_ThreadFunction )
+ , m_Param( a_Param )
+ , m_Event( new cEvent() )
+ , m_StopEvent( 0 )
+{
+ if( a_ThreadName )
+ {
+ m_ThreadName.assign(a_ThreadName);
+ }
+}
+
+
+
+
+
+cThread::~cThread()
+{
+ delete m_Event;
+
+ if( m_StopEvent )
+ {
+ m_StopEvent->Wait();
+ delete m_StopEvent;
+ }
+}
+
+
+
+
+
+void cThread::Start( bool a_bWaitOnDelete /* = true */ )
+{
+ if( a_bWaitOnDelete )
+ m_StopEvent = new cEvent();
+
+#ifndef _WIN32
+ pthread_t SndThread;
+ if( pthread_create( &SndThread, NULL, MyThread, this) )
+ LOGERROR("ERROR: Could not create thread!");
+#else
+ DWORD ThreadID = 0;
+ HANDLE hThread = CreateThread( 0 // security
+ ,0 // stack size
+ , (LPTHREAD_START_ROUTINE) MyThread // function name
+ ,this // parameters
+ ,0 // flags
+ ,&ThreadID ); // thread id
+ CloseHandle( hThread );
+
+ #ifdef _MSC_VER
+ if (!m_ThreadName.empty())
+ {
+ SetThreadName(ThreadID, m_ThreadName.c_str());
+ }
+ #endif // _MSC_VER
+#endif
+
+ // Wait until thread has actually been created
+ m_Event->Wait();
+}
+
+
+
+
+
+#ifdef _WIN32
+unsigned long cThread::MyThread(void* a_Param )
+#else
+void *cThread::MyThread( void *a_Param )
+#endif
+{
+ cThread* self = (cThread*)a_Param;
+ cEvent* StopEvent = self->m_StopEvent;
+
+ ThreadFunc* ThreadFunction = self->m_ThreadFunction;
+ void* ThreadParam = self->m_Param;
+
+ // Set event to let other thread know this thread has been created and it's safe to delete the cThread object
+ self->m_Event->Set();
+
+ ThreadFunction( ThreadParam );
+
+ if( StopEvent ) StopEvent->Set();
+ return 0;
+}
diff --git a/src/OSSupport/Thread.h b/src/OSSupport/Thread.h
new file mode 100644
index 000000000..3c9316424
--- /dev/null
+++ b/src/OSSupport/Thread.h
@@ -0,0 +1,26 @@
+#pragma once
+
+class cThread
+{
+public:
+ typedef void (ThreadFunc)(void*);
+ cThread( ThreadFunc a_ThreadFunction, void* a_Param, const char* a_ThreadName = 0 );
+ ~cThread();
+
+ void Start( bool a_bWaitOnDelete = true );
+ void WaitForThread();
+private:
+ ThreadFunc* m_ThreadFunction;
+
+#ifdef _WIN32
+ static unsigned long MyThread(void* a_Param );
+#else
+ static void *MyThread( void *lpParam );
+#endif
+
+ void* m_Param;
+ cEvent* m_Event;
+ cEvent* m_StopEvent;
+
+ AString m_ThreadName;
+}; \ No newline at end of file
diff --git a/src/OSSupport/Timer.cpp b/src/OSSupport/Timer.cpp
new file mode 100644
index 000000000..ed16f9e3a
--- /dev/null
+++ b/src/OSSupport/Timer.cpp
@@ -0,0 +1,37 @@
+
+#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
+
+#include "Timer.h"
+
+
+
+
+
+
+cTimer::cTimer(void)
+{
+ #ifdef _WIN32
+ QueryPerformanceFrequency(&m_TicksPerSecond);
+ #endif
+}
+
+
+
+
+
+long long cTimer::GetNowTime(void)
+{
+ #ifdef _WIN32
+ LARGE_INTEGER now;
+ QueryPerformanceCounter(&now);
+ return ((now.QuadPart * 1000) / m_TicksPerSecond.QuadPart);
+ #else
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ return (long long)(now.tv_sec * 1000 + now.tv_usec / 1000);
+ #endif
+}
+
+
+
+
diff --git a/src/OSSupport/Timer.h b/src/OSSupport/Timer.h
new file mode 100644
index 000000000..a059daa41
--- /dev/null
+++ b/src/OSSupport/Timer.h
@@ -0,0 +1,32 @@
+
+// Timer.h
+
+// Declares the cTimer class representing an OS-independent of retrieving current time with msec accuracy
+
+
+
+
+
+#pragma once
+
+
+
+
+
+class cTimer
+{
+public:
+ cTimer(void);
+
+ // Returns the current time expressed in milliseconds
+ long long GetNowTime(void);
+private:
+
+ #ifdef _WIN32
+ LARGE_INTEGER m_TicksPerSecond;
+ #endif
+} ;
+
+
+
+