Moved source to src
This commit is contained in:
149
src/OSSupport/BlockingTCPLink.cpp
Normal file
149
src/OSSupport/BlockingTCPLink.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
28
src/OSSupport/BlockingTCPLink.h
Normal file
28
src/OSSupport/BlockingTCPLink.h
Normal 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
|
||||
|
||||
|
||||
|
||||
|
||||
188
src/OSSupport/CriticalSection.cpp
Normal file
188
src/OSSupport/CriticalSection.cpp
Normal 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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
80
src/OSSupport/CriticalSection.h
Normal file
80
src/OSSupport/CriticalSection.h
Normal 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
118
src/OSSupport/Event.cpp
Normal 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
47
src/OSSupport/Event.h
Normal 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
375
src/OSSupport/File.cpp
Normal 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
138
src/OSSupport/File.h
Normal 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
107
src/OSSupport/GZipFile.cpp
Normal 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
52
src/OSSupport/GZipFile.h
Normal 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
172
src/OSSupport/IsThread.cpp
Normal 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
100
src/OSSupport/IsThread.h
Normal 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
|
||||
|
||||
|
||||
|
||||
|
||||
238
src/OSSupport/ListenThread.cpp
Normal file
238
src/OSSupport/ListenThread.cpp
Normal 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)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
83
src/OSSupport/ListenThread.h
Normal file
83
src/OSSupport/ListenThread.h
Normal 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;
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
91
src/OSSupport/Semaphore.cpp
Normal file
91
src/OSSupport/Semaphore.cpp
Normal 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
17
src/OSSupport/Semaphore.h
Normal 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
19
src/OSSupport/Sleep.cpp
Normal 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
7
src/OSSupport/Sleep.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
class cSleep
|
||||
{
|
||||
public:
|
||||
static void MilliSleep( unsigned int a_MilliSeconds );
|
||||
};
|
||||
396
src/OSSupport/Socket.cpp
Normal file
396
src/OSSupport/Socket.cpp
Normal 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
101
src/OSSupport/Socket.h
Normal 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;
|
||||
};
|
||||
675
src/OSSupport/SocketThreads.cpp
Normal file
675
src/OSSupport/SocketThreads.cpp
Normal 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]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
169
src/OSSupport/SocketThreads.h
Normal file
169
src/OSSupport/SocketThreads.h
Normal 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
128
src/OSSupport/Thread.cpp
Normal 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
26
src/OSSupport/Thread.h
Normal 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
37
src/OSSupport/Timer.cpp
Normal 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
32
src/OSSupport/Timer.h
Normal 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
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user