1
0

Moved source to src

This commit is contained in:
Alexander Harkness
2013-11-24 14:19:41 +00:00
parent 1480d6d64d
commit 675b4aa878
469 changed files with 0 additions and 0 deletions

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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);
} ;

118
src/OSSupport/Event.cpp Normal file
View File

@@ -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
}

47
src/OSSupport/Event.h Normal file
View File

@@ -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

375
src/OSSupport/File.cpp Normal file
View File

@@ -0,0 +1,375 @@
// 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
}
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());
}

138
src/OSSupport/File.h Normal file
View File

@@ -0,0 +1,138 @@
// 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);
// tolua_end
int Printf(const char * a_Fmt, ...);
private:
#ifdef USE_STDIO_FILE
FILE * m_File;
#else
HANDLE m_File;
#endif
} ; // tolua_export

107
src/OSSupport/GZipFile.cpp Normal file
View File

@@ -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);
}

52
src/OSSupport/GZipFile.h Normal file
View File

@@ -0,0 +1,52 @@
// GZipFile.h
// Declares the cGZipFile class representing a RAII wrapper over zlib's GZip file routines
#pragma once
#include "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;
} ;

172
src/OSSupport/IsThread.cpp Normal file
View File

@@ -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
}

100
src/OSSupport/IsThread.h Normal file
View File

@@ -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

View File

@@ -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)
}

View File

@@ -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;
} ;

View File

@@ -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
}

17
src/OSSupport/Semaphore.h Normal file
View File

@@ -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
};

19
src/OSSupport/Sleep.cpp Normal file
View File

@@ -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
}

7
src/OSSupport/Sleep.h Normal file
View File

@@ -0,0 +1,7 @@
#pragma once
class cSleep
{
public:
static void MilliSleep( unsigned int a_MilliSeconds );
};

396
src/OSSupport/Socket.cpp Normal file
View File

@@ -0,0 +1,396 @@
#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()
{
#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)
{
// Cannot use socckaddr_in6, because it is not defined in the default VS2008 SDK
// Must jump through hoops here
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());
hostent * hp = gethostbyaddr((char*)&addr, sizeof(addr), AF_INET);
if (hp == NULL)
{
// It is not an IP Address string, but rather a regular hostname, resolve:
hp = gethostbyname(a_HostNameOrAddr.c_str());
if (hp == NULL)
{
LOGWARN("cTCPLink: Could not resolve hostname \"%s\"", a_HostNameOrAddr.c_str());
CloseSocket();
return false;
}
}
sockaddr_in server;
server.sin_addr.s_addr = *((unsigned long*)hp->h_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);
}

101
src/OSSupport/Socket.h Normal file
View File

@@ -0,0 +1,101 @@
#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);
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;
};

View File

@@ -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]
}

View File

@@ -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

128
src/OSSupport/Thread.cpp Normal file
View File

@@ -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;
}

26
src/OSSupport/Thread.h Normal file
View File

@@ -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;
};

37
src/OSSupport/Timer.cpp Normal file
View File

@@ -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
}

32
src/OSSupport/Timer.h Normal file
View File

@@ -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
} ;