Source files cleanup: OSSupport-related files in a separate subfolder, renamed.

git-svn-id: http://mc-server.googlecode.com/svn/trunk@885 0a769ca7-a7f5-676a-18bf-c427514a06d6
This commit is contained in:
madmaxoft@gmail.com
2012-09-23 21:23:33 +00:00
parent 3953277568
commit 7abb5f7604
50 changed files with 78 additions and 86 deletions
+149
View File
@@ -0,0 +1,149 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "BlockingTCPLink.h"
#ifdef _WIN32
#define MSG_NOSIGNAL (0)
#endif
#ifdef __MACH__
#define MSG_NOSIGNAL (0)
#endif
cBlockingTCPLink::cBlockingTCPLink(void)
{
}
cBlockingTCPLink::~cBlockingTCPLink()
{
CloseSocket();
}
void cBlockingTCPLink::CloseSocket()
{
if (!m_Socket.IsValid())
{
m_Socket.CloseSocket();
}
}
bool cBlockingTCPLink::Connect(const char * iAddress, unsigned int iPort)
{
ASSERT(!m_Socket.IsValid());
if (m_Socket.IsValid())
{
LOGWARN("WARNING: cTCPLink Connect() called while still connected.");
m_Socket.CloseSocket();
}
struct hostent *hp;
unsigned int addr;
struct sockaddr_in server;
m_Socket = socket(AF_INET, SOCK_STREAM, 0);
if (!m_Socket.IsValid())
{
LOGERROR("cTCPLink: Cannot create a socket");
return false;
}
addr = inet_addr(iAddress);
hp = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET);
if (hp == NULL)
{
//LOGWARN("cTCPLink: gethostbyaddr returned NULL");
hp = gethostbyname(iAddress);
if (hp == NULL)
{
LOGWARN("cTCPLink: Could not resolve %s", iAddress);
CloseSocket();
return false;
}
}
server.sin_addr.s_addr = *((unsigned long *)hp->h_addr);
server.sin_family = AF_INET;
server.sin_port = htons( (unsigned short)iPort);
if (connect(m_Socket, (struct sockaddr *)&server, sizeof(server)))
{
LOGWARN("cTCPLink: Connection to \"%s:%d\" failed (%s)", iAddress, iPort, cSocket::GetErrorString( cSocket::GetLastError() ).c_str() );
CloseSocket();
return false;
}
return true;
}
int cBlockingTCPLink::Send(char * a_Data, unsigned int a_Size, int a_Flags /* = 0 */ )
{
ASSERT(m_Socket.IsValid());
if (!m_Socket.IsValid())
{
LOGERROR("cBlockingTCPLink: Trying to send data without a valid connection!");
return -1;
}
return m_Socket.Send(a_Data, a_Size);
}
int cBlockingTCPLink::SendMessage( const char* a_Message, int a_Flags /* = 0 */ )
{
ASSERT(m_Socket.IsValid());
if (!m_Socket.IsValid())
{
LOGWARN("cBlockingTCPLink: Trying to send message without a valid connection!");
return -1;
}
return m_Socket.Send(a_Message, strlen(a_Message));
}
void cBlockingTCPLink::ReceiveData(AString & oData)
{
ASSERT(m_Socket.IsValid());
if (!m_Socket.IsValid())
{
return;
}
int Received = 0;
char Buffer[256];
while ((Received = recv(m_Socket, Buffer, sizeof(Buffer), 0)) > 0)
{
oData.append(Buffer, Received);
}
}
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include "Socket.h"
class cBlockingTCPLink //tolua_export
{ //tolua_export
public: //tolua_export
cBlockingTCPLink(void); //tolua_export
~cBlockingTCPLink(); //tolua_export
bool Connect( const char* a_Address, unsigned int a_Port ); //tolua_export
int Send( char* a_Data, unsigned int a_Size, int a_Flags = 0 ); //tolua_export
int SendMessage( const char* a_Message, int a_Flags = 0 ); //tolua_export
void CloseSocket(); //tolua_export
void ReceiveData(AString & oData); //tolua_export
protected:
cSocket m_Socket;
}; //tolua_export
+187
View File
@@ -0,0 +1,187 @@
#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
m_Attributes = new pthread_mutexattr_t;
pthread_mutexattr_init((pthread_mutexattr_t*)m_Attributes);
pthread_mutexattr_settype((pthread_mutexattr_t*)m_Attributes, PTHREAD_MUTEX_RECURSIVE);
m_CriticalSectionPtr = new pthread_mutex_t;
if( pthread_mutex_init( (pthread_mutex_t*)m_CriticalSectionPtr, (pthread_mutexattr_t*)m_Attributes ) != 0 )
{
LOG("ERROR: Could not initialize Critical Section!");
}
#endif
}
cCriticalSection::~cCriticalSection()
{
#ifdef _WIN32
DeleteCriticalSection( &m_CriticalSection );
#else
if( pthread_mutex_destroy( (pthread_mutex_t*)m_CriticalSectionPtr ) != 0 )
{
LOG("ERROR: Could not destroy Critical Section!");
}
delete (pthread_mutex_t*)m_CriticalSectionPtr;
pthread_mutexattr_destroy( (pthread_mutexattr_t*)m_Attributes );
delete (pthread_mutexattr_t*)m_Attributes;
#endif
}
void cCriticalSection::Lock()
{
#ifdef _WIN32
EnterCriticalSection( &m_CriticalSection );
#else
pthread_mutex_lock( (pthread_mutex_t*)m_CriticalSectionPtr );
#endif
#ifdef _DEBUG
m_IsLocked = true;
m_OwningThreadID = cIsThread::GetCurrentID();
#endif // _DEBUG
}
void cCriticalSection::Unlock()
{
#ifdef _DEBUG
m_IsLocked = false;
#endif // _DEBUG
#ifdef _WIN32
LeaveCriticalSection( &m_CriticalSection );
#else
pthread_mutex_unlock( (pthread_mutex_t*)m_CriticalSectionPtr );
#endif
}
#ifdef _DEBUG
bool cCriticalSection::IsLocked(void)
{
return m_IsLocked;
}
bool cCriticalSection::IsLockedByCurrentThread(void)
{
return m_IsLocked && (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
View File
@@ -0,0 +1,80 @@
#pragma once
class cCriticalSection
{
public:
cCriticalSection(void);
~cCriticalSection();
void Lock(void);
void Unlock(void);
#ifdef _DEBUG
bool IsLocked(void);
bool IsLockedByCurrentThread(void);
#endif // _DEBUG
private:
#ifdef _DEBUG
bool m_IsLocked;
unsigned long m_OwningThreadID;
#endif // _DEBUG
#ifdef _WIN32
CRITICAL_SECTION m_CriticalSection;
#else // _WIN32
void* m_CriticalSectionPtr ALIGN_8; // Pointer to a CRITICAL_SECTION object
void* m_Attributes ALIGN_8;
#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
View File
@@ -0,0 +1,118 @@
// Event.cpp
// Implements the cEvent object representing an OS-specific synchronization primitive that can be waited-for
// Implemented as an Event on Win and as a 1-semaphore on *nix
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "Event.h"
cEvent::cEvent(void)
{
#ifdef _WIN32
m_Event = CreateEvent( 0, FALSE, FALSE, 0 );
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
View File
@@ -0,0 +1,47 @@
// Event.h
// Interfaces to the cEvent object representing an OS-specific synchronization primitive that can be waited-for
// Implemented as an Event on Win and as a 1-semaphore on *nix
#pragma once
#ifndef CEVENT_H_INCLUDED
#define CEVENT_H_INCLUDED
class cEvent
{
public:
cEvent(void);
~cEvent();
void Wait(void);
void Set (void);
private:
#ifdef _WIN32
HANDLE m_Event;
#else
sem_t * m_Event;
bool m_bIsNamed;
#endif
} ;
#endif // CEVENT_H_INCLUDED
+271
View File
@@ -0,0 +1,271 @@
// 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"
/// Simple constructor - creates an unopened file object, use Open() to open / create a real file
cFile::cFile(void) :
#ifdef USE_STDIO_FILE
m_File(NULL)
#else
m_File(INVALID_HANDLE_VALUE)
#endif // USE_STDIO_FILE
{
// Nothing needed yet
}
/// Constructs and opens / creates the file specified, use IsOpen() to check for success
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);
}
/// Auto-closes the file, if open
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)
{
ASSERT(IsOpen()); // You should not close file objects that don't have an open file.
if (!IsOpen())
{
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);
}
/// Reads up to iNumBytes bytes into iBuffer, returns the number of bytes actually read, or -1 on failure; asserts if not open
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
}
/// Writes up to iNumBytes bytes from iBuffer, returns the number of bytes actually written, or -1 on failure; asserts if not open
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;
}
/// Seeks to iPosition bytes from file start, returns old position or -1 for failure
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);
}
/// Returns the current position (bytes from file start)
int cFile::Tell (void) const
{
ASSERT(IsOpen());
if (!IsOpen())
{
return -1;
}
return ftell(m_File);
}
/// Returns the size of file, in bytes, or -1 for failure; asserts if not open
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();
}
+108
View File
@@ -0,0 +1,108 @@
// 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 CFILE_H_INCLUDED
#define CFILE_H_INCLUDED
#ifndef _WIN32
#define USE_STDIO_FILE
#endif // _WIN32
// DEBUG:
#define USE_STDIO_FILE
class cFile
{
public:
/// 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);
/// Returns true if the file specified exists
static bool Exists(const AString & a_FileName);
private:
#ifdef USE_STDIO_FILE
FILE * m_File;
#else
HANDLE m_File;
#endif
} ;
#endif // CFILE_H_INCLUDED
+167
View File
@@ -0,0 +1,167 @@
// 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),
#ifdef _WIN32
m_Handle(NULL)
#else // _WIN32
m_HasStarted(false)
#endif // else _WIN32
{
}
cIsThread::~cIsThread()
{
m_ShouldTerminate = true;
Wait();
}
bool cIsThread::Start(void)
{
#ifdef _WIN32
ASSERT(m_Handle == NULL); // Has already started one thread?
// 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
ASSERT(!m_HasStarted);
if (pthread_create(&m_Handle, NULL, thrExecute, this))
{
LOGERROR("ERROR: Could not create thread \"%s\", !", m_ThreadName.c_str());
return false;
}
m_HasStarted = true;
#endif // else _WIN32
return true;
}
bool cIsThread::Wait(void)
{
#ifdef _WIN32
if (m_Handle == NULL)
{
return true;
}
// Cannot log, logger may already be stopped:
// LOG("Waiting for thread \"%s\" to terminate.", m_ThreadName.c_str());
int res = WaitForSingleObject(m_Handle, INFINITE);
m_Handle = NULL;
// Cannot log, logger may already be stopped:
// LOG("Thread \"%s\" %s terminated, GLE = %d", m_ThreadName.c_str(), (res == WAIT_OBJECT_0) ? "" : "not", GetLastError());
return (res == WAIT_OBJECT_0);
#else // _WIN32
if (!m_HasStarted)
{
return true;
}
// Cannot log, logger may already be stopped:
// LOG("Waiting for thread \"%s\" to terminate.", m_ThreadName.c_str());
int res = pthread_join(m_Handle, NULL);
m_HasStarted = false;
// Cannot log, logger may already be stopped:
// LOG("Thread \"%s\" %s terminated, errno = %d", m_ThreadName.c_str(), (res == 0) ? "" : "not", errno);
return (res == 0);
#endif // else _WIN32
}
unsigned long cIsThread::GetCurrentID(void)
{
#ifdef _WIN32
return (unsigned long) GetCurrentThreadId();
#else
return (unsigned long) pthread_self();
#endif
}
+78
View File
@@ -0,0 +1,78 @@
// 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:
virtual void Execute(void) = 0; // This function is called in the new thread's context
volatile bool m_ShouldTerminate; // The overriden Execute() method should check this periodically and terminate if this is true
public:
cIsThread(const AString & iThreadName);
~cIsThread();
bool Start(void); // Starts the thread
bool Wait(void); // Waits for the thread to finish
static unsigned long GetCurrentID(void); // Returns the OS-dependent thread ID for the caller's thread
private:
AString m_ThreadName;
#ifdef _WIN32
HANDLE m_Handle;
static DWORD_PTR __stdcall thrExecute(LPVOID a_Param)
{
((cIsThread *)a_Param)->Execute();
return 0;
}
#else // _WIN32
pthread_t m_Handle;
bool m_HasStarted;
static void * thrExecute(void * a_Param)
{
((cIsThread *)a_Param)->Execute();
return NULL;
}
#endif // else _WIN32
} ;
#endif // CISTHREAD_H_INCLUDED
+25
View File
@@ -0,0 +1,25 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "MakeDir.h"
void cMakeDir::MakeDir(const AString & a_Directory)
{
#ifdef _WIN32
SECURITY_ATTRIBUTES Attrib;
Attrib.nLength = sizeof(SECURITY_ATTRIBUTES);
Attrib.lpSecurityDescriptor = NULL;
Attrib.bInheritHandle = false;
::CreateDirectory( (FILE_IO_PREFIX + a_Directory).c_str(), &Attrib);
#else
mkdir( (FILE_IO_PREFIX + a_Directory).c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
#endif
}
+16
View File
@@ -0,0 +1,16 @@
#pragma once
class cMakeDir
{
public:
static void MakeDir(const AString & a_Directory);
};
+91
View File
@@ -0,0 +1,91 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
cSemaphore::cSemaphore( unsigned int a_MaxCount, unsigned int a_InitialCount /* = 0 */ )
#ifndef _WIN32
: m_bNamed( false )
#endif
{
#ifndef _WIN32
(void)a_MaxCount;
m_Handle = new sem_t;
if (sem_init( (sem_t*)m_Handle, 0, 0))
{
LOG("WARNING cSemaphore: Could not create unnamed semaphore, fallback to named.");
delete (sem_t*)m_Handle; // named semaphores return their own address
m_bNamed = true;
AString Name;
Printf(Name, "cSemaphore%p", this );
m_Handle = sem_open(Name.c_str(), O_CREAT, 777, a_InitialCount);
if( m_Handle == SEM_FAILED )
{
LOG("ERROR: Could not create Semaphore. (%i)", errno );
}
else
{
if( sem_unlink(Name.c_str()) != 0 )
{
LOG("ERROR: Could not unlink cSemaphore. (%i)", errno);
}
}
}
#else
m_Handle = CreateSemaphore(
NULL, // security attribute
a_InitialCount, // initial count
a_MaxCount, // maximum count
0 // name (optional)
);
#endif
}
cSemaphore::~cSemaphore()
{
#ifdef _WIN32
CloseHandle( m_Handle );
#else
if( m_bNamed )
{
if( sem_close( (sem_t*)m_Handle ) != 0 )
{
LOG("ERROR: Could not close cSemaphore. (%i)", errno);
}
}
else
{
sem_destroy( (sem_t*)m_Handle );
delete (sem_t*)m_Handle;
}
m_Handle = 0;
#endif
}
void cSemaphore::Wait()
{
#ifndef _WIN32
if( sem_wait( (sem_t*)m_Handle ) != 0)
{
LOG("ERROR: Could not wait for cSemaphore. (%i)", errno);
}
#else
WaitForSingleObject( m_Handle, INFINITE);
#endif
}
void cSemaphore::Signal()
{
#ifndef _WIN32
if( sem_post( (sem_t*)m_Handle ) != 0 )
{
LOG("ERROR: Could not signal cSemaphore. (%i)", errno);
}
#else
ReleaseSemaphore( m_Handle, 1, NULL );
#endif
}
+17
View File
@@ -0,0 +1,17 @@
#pragma once
class cSemaphore
{
public:
cSemaphore( unsigned int a_MaxCount, unsigned int a_InitialCount = 0 );
~cSemaphore();
void Wait();
void Signal();
private:
void* m_Handle; // HANDLE pointer
#ifndef _WIN32
bool m_bNamed;
#endif
};
+19
View File
@@ -0,0 +1,19 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#ifndef _WIN32
#include <unistd.h>
#endif
void cSleep::MilliSleep( unsigned int a_MilliSeconds )
{
#ifdef _WIN32
Sleep(a_MilliSeconds); // Don't tick too much
#else
usleep(a_MilliSeconds*1000);
#endif
}
+7
View File
@@ -0,0 +1,7 @@
#pragma once
class cSleep
{
public:
static void MilliSleep( unsigned int a_MilliSeconds );
};
+331
View File
@@ -0,0 +1,331 @@
#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::IsValid(void) const
{
#ifdef _WIN32
return (m_Socket != INVALID_SOCKET);
#else // _WIN32
return (m_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 (%s): %s", m_IPString.c_str(), GetLastErrorString().c_str());
}
if (close(m_Socket) != 0)
{
LOGWARN("Error closing socket (%s): %s", 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
}
int cSocket::SetReuseAddress()
{
#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));
}
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()
{
return socket(AF_INET,SOCK_STREAM,0);
}
unsigned long cSocket::INTERNET_ADDRESS_LOCALHOST(void)
{
static unsigned long LocalHost = 0;
if (LocalHost == 0)
{
LocalHost = inet_addr("127.0.0.1"); // GCC won't accept this as a global var assignment
}
return LocalHost;
}
int cSocket::Bind(SockAddr_In& a_Address)
{
sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = a_Address.Family;
local.sin_addr.s_addr = a_Address.Address;
local.sin_port = htons((u_short)a_Address.Port);
return bind(m_Socket, (sockaddr*)&local, sizeof(local));
}
int cSocket::Listen(int a_Backlog)
{
return listen(m_Socket, a_Backlog);
}
cSocket cSocket::Accept()
{
sockaddr_in from;
socklen_t fromlen=sizeof(from);
cSocket SClient = accept(m_Socket, (sockaddr*)&from, &fromlen);
if (from.sin_addr.s_addr && SClient.IsValid()) // Get IP in string form
{
SClient.m_IPString = inet_ntoa(from.sin_addr);
//LOG("cSocket::Accept() %s", SClient.m_IPString);
}
return SClient;
}
int cSocket::Connect(SockAddr_In & a_Address)
{
sockaddr_in local;
local.sin_family = a_Address.Family;
local.sin_addr.s_addr = a_Address.Address;
local.sin_port = htons((u_short)a_Address.Port);
return connect(m_Socket, (sockaddr *)&local, sizeof(local));
}
int cSocket::Connect(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));
}
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);
}
+79
View File
@@ -0,0 +1,79 @@
#pragma once
class cSocket
{
public:
#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;
void CloseSocket();
operator xSocket() const;
xSocket GetSocket() const;
bool operator == (const cSocket & a_Other) {return m_Socket == a_Other.m_Socket; }
void SetSocket( xSocket a_Socket );
int SetReuseAddress();
static int WSAStartup();
static AString GetErrorString( int a_ErrNo );
static int GetLastError();
static AString GetLastErrorString(void)
{
return GetErrorString(GetLastError());
}
static cSocket CreateSocket();
inline static bool IsSocketError( int a_ReturnedValue )
{
#ifdef _WIN32
return (a_ReturnedValue == SOCKET_ERROR || a_ReturnedValue == 0);
#else
return (a_ReturnedValue <= 0);
#endif
}
struct SockAddr_In
{
short Family;
unsigned short Port;
unsigned long Address;
};
static const short ADDRESS_FAMILY_INTERNET = 2;
static const unsigned long INTERNET_ADDRESS_ANY = 0;
static unsigned long INTERNET_ADDRESS_LOCALHOST(void); // 127.0.0.1 represented in network byteorder; must be a function due to GCC :(
int Bind( SockAddr_In& a_Address );
int Listen( int a_Backlog );
cSocket Accept();
int Connect(SockAddr_In & a_Address); // Returns 0 on success, !0 on failure
int Connect(const AString & a_HostNameOrAddr, unsigned short a_Port); // Returns 0 on success, !0 on failure
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;
};
+713
View File
@@ -0,0 +1,713 @@
// 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(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 cSocket * a_Socket, const AString & a_Data)
{
// Puts a_Data into outgoing data queue for a_Socket
if (!a_Socket->IsValid())
{
// Socket already closed, ignore the request
return;
}
cCSLock Lock(m_CS);
for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
{
if ((*itr)->Write(a_Socket, 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 cSocket * a_Socket)
{
if (!a_Socket->IsValid())
{
// Already closed, ignore the request
return;
}
cCSLock Lock(m_CS);
for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
{
if ((*itr)->QueueClose(a_Socket))
{
return;
}
} // for itr - m_Threads[]
ASSERT(!"Queueing close of an unknown socket");
}
////////////////////////////////////////////////////////////////////////////////
// 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(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_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->GetSocket() == a_Socket->GetSocket())
{
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 cSocket * a_Socket, 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_Socket == a_Socket)
{
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_Client = NULL;
m_Slots[i].m_ShouldClose = false;
// Notify the thread that there's a stop reading request:
ASSERT(m_ControlSocket2.IsValid());
m_ControlSocket2.Send("s", 1);
return true;
}
} // for i - m_Slots[]
return false;
}
bool cSocketThreads::cSocketThread::QueueClose(const cSocket * a_Socket)
{
// Returns true if socket handled by this thread
for (int i = m_NumSlots - 1; i >= 0; --i)
{
if (m_Slots[i].m_Socket == a_Socket)
{
ASSERT(m_Slots[i].m_Client == NULL); // Should have stopped reading first
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();
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;
}
cSocket::SockAddr_In Addr;
Addr.Family = cSocket::ADDRESS_FAMILY_INTERNET;
Addr.Address = cSocket::INTERNET_ADDRESS_LOCALHOST();
Addr.Port = 0; // Any free port is okay
if (m_ControlSocket2.Bind(Addr) != 0)
{
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) != 0)
{
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.Accept();
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::SockAddr_In Addr;
Addr.Family = cSocket::ADDRESS_FAMILY_INTERNET;
Addr.Address = cSocket::INTERNET_ADDRESS_LOCALHOST();
Addr.Port = m_ControlSocket2.GetPort();
ASSERT(Addr.Port != 0); // We checked in the Start() method, but let's be sure
if (m_ControlSocket1.Connect(Addr) != 0)
{
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);
RemoveClosedSockets();
} // 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)
{
if (!FD_ISSET(m_Slots[i].m_Socket->GetSocket(), 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_Client != NULL)
{
m_Slots[i].m_Client->SocketClosed();
}
}
else if (Received > 0)
{
if (m_Slots[i].m_Client != NULL)
{
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_Client != NULL)
{
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 Socket(*(m_Slots[i].m_Socket));
if (!Socket.IsValid() || !FD_ISSET(Socket.GetSocket(), a_Write))
{
continue;
}
if (m_Slots[i].m_Outgoing.empty())
{
// Request another chunk of outgoing data:
if (m_Slots[i].m_Client != NULL)
{
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_Client == NULL) && m_Slots[i].m_ShouldClose)
{
// Socket was queued for closing and there's no more data to send, close it now:
m_Slots[i].m_Socket->CloseSocket();
m_Slots[i] = m_Slots[--m_NumSlots];
}
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_Client != NULL)
{
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]
}
void cSocketThreads::cSocketThread::RemoveClosedSockets(void)
{
// Removes sockets that have closed from m_Slots[]
cCSLock Lock(m_Parent->m_CS);
for (int i = m_NumSlots - 1; i >= 0; --i)
{
if (m_Slots[i].m_Socket->IsValid())
{
continue;
}
m_Slots[i] = m_Slots[--m_NumSlots];
} // for i - m_Slots[]
}
+172
View File
@@ -0,0 +1,172 @@
// 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(cSocket * a_Socket, cCallback * a_Client);
/// Remove the socket (and associated client) from processing
void RemoveClient(const cSocket * a_Socket);
/// Remove the associated socket and the client from processing
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_Socket
void Write(const cSocket * a_Socket, const AString & a_Data);
/// Stops reading from the socket - when this call returns, no more calls to the callbacks are made
void StopReading(const cCallback * a_Client);
/// Queues the socket for closing, as soon as its outgoing data is sent
void QueueClose(const cSocket * a_Socket);
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 (cSocket * a_Socket, cCallback * a_Client);
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 cSocket * a_Socket, const AString & a_Data); // Returns true if socket handled by this thread
bool StopReading (const cCallback * a_Client); // Returns true if client handled by this thread
bool QueueClose (const cSocket * a_Socket); // Returns true if socket 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;
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
} ;
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
void RemoveClosedSockets(void); // Removes sockets that have closed from m_Slots[]
} ;
typedef std::list<cSocketThread *> cSocketThreadList;
cCriticalSection m_CS;
cSocketThreadList m_Threads;
} ;
#endif // CSOCKETTHREADS_H_INCLUDED
+128
View File
@@ -0,0 +1,128 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "TCPLink.h"
#ifdef _WIN32
#define MSG_NOSIGNAL (0)
#endif
#ifdef __MACH__
#define MSG_NOSIGNAL (0)
#endif
cTCPLink::cTCPLink()
: m_Socket( 0 )
, m_StopEvent( new cEvent() )
{
}
cTCPLink::~cTCPLink()
{
if( m_Socket )
{
CloseSocket();
m_StopEvent->Wait();
}
delete m_StopEvent;
}
void cTCPLink::CloseSocket()
{
if( m_Socket )
{
m_Socket.CloseSocket();
m_Socket = 0;
}
}
bool cTCPLink::Connect( const AString & a_Address, unsigned int a_Port )
{
if( m_Socket )
{
LOGWARN("WARNING: cTCPLink Connect() called while still connected. ALWAYS disconnect before re-connecting!");
}
m_Socket = cSocket::CreateSocket();
if( !m_Socket.IsValid() )
{
LOGERROR("cTCPLink: Failed to create socket");
return false;
}
if (m_Socket.Connect(a_Address, a_Port) != 0)
{
LOGWARN("cTCPLink: Cannot connect to server \"%s\" (%s)", m_Socket.GetLastErrorString().c_str());
m_Socket.CloseSocket();
return false;
}
cThread( ReceiveThread, this );
return true;
}
int cTCPLink::Send(const char * a_Data, unsigned int a_Size, int a_Flags /* = 0 */ )
{
(void)a_Flags;
if (!m_Socket.IsValid())
{
LOGWARN("cTCPLink: Trying to send data without a valid connection!");
return -1;
}
return m_Socket.Send(a_Data, a_Size);
}
int cTCPLink::SendMessage(const char * a_Message, int a_Flags /* = 0 */ )
{
(void)a_Flags;
if (!m_Socket.IsValid())
{
LOGWARN("cTCPLink: Trying to send message without a valid connection!");
return -1;
}
return m_Socket.Send(a_Message, strlen(a_Message));
}
void cTCPLink::ReceiveThread( void* a_Param)
{
cTCPLink* self = (cTCPLink*)a_Param;
cSocket Socket = self->m_Socket;
int Received = 0;
do
{
char Data[256];
Received = Socket.Receive(Data, sizeof(Data), 0);
self->ReceivedData( Data, ((Received > 0) ? Received : -1) );
} while ( Received > 0 );
LOGINFO("cTCPLink Disconnected (%i)", Received );
if (Socket == self->m_Socket)
{
self->m_StopEvent->Set();
}
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include "Socket.h"
class cTCPLink //tolua_export
{ //tolua_export
public: //tolua_export
cTCPLink(); //tolua_export
~cTCPLink(); //tolua_export
bool Connect (const AString & a_Address, unsigned int a_Port ); //tolua_export
int Send (const 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
protected: //tolua_export
virtual void ReceivedData( char a_Data[256], int a_Size ) = 0; //tolua_export
static void ReceiveThread( void* a_Param );
cSocket m_Socket;
cEvent* m_StopEvent;
}; //tolua_export
+128
View File
@@ -0,0 +1,128 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
// When in MSVC, the debugger provides "thread naming" by catching special exceptions. Interface here:
#ifdef _MSC_VER
//
// Usage: SetThreadName (-1, "MainThread");
//
typedef struct tagTHREADNAME_INFO
{
DWORD dwType; // must be 0x1000
LPCSTR szName; // pointer to name (in user addr space)
DWORD dwThreadID; // thread ID (-1=caller thread)
DWORD dwFlags; // reserved for future use, must be zero
} THREADNAME_INFO;
void SetThreadName( DWORD dwThreadID, LPCSTR szThreadName)
{
THREADNAME_INFO info;
info.dwType = 0x1000;
info.szName = szThreadName;
info.dwThreadID = dwThreadID;
info.dwFlags = 0;
__try
{
RaiseException( 0x406D1388, 0, sizeof(info)/sizeof(DWORD), (DWORD*)&info );
}
__except(EXCEPTION_CONTINUE_EXECUTION)
{
}
}
#endif // _MSC_VER
cThread::cThread( ThreadFunc a_ThreadFunction, void* a_Param, const char* a_ThreadName /* = 0 */ )
: m_ThreadFunction( a_ThreadFunction )
, m_Param( a_Param )
, m_Event( new cEvent() )
, m_StopEvent( 0 )
{
if( a_ThreadName )
{
m_ThreadName.assign(a_ThreadName);
}
}
cThread::~cThread()
{
delete m_Event;
if( m_StopEvent )
{
m_StopEvent->Wait();
delete m_StopEvent;
}
}
void cThread::Start( bool a_bWaitOnDelete /* = true */ )
{
if( a_bWaitOnDelete )
m_StopEvent = new cEvent();
#ifndef _WIN32
pthread_t SndThread;
if( pthread_create( &SndThread, NULL, MyThread, this) )
LOGERROR("ERROR: Could not create thread!");
#else
DWORD ThreadID = 0;
HANDLE hThread = CreateThread( 0 // security
,0 // stack size
, (LPTHREAD_START_ROUTINE) MyThread // function name
,this // parameters
,0 // flags
,&ThreadID ); // thread id
CloseHandle( hThread );
#ifdef _MSC_VER
if (!m_ThreadName.empty())
{
SetThreadName(ThreadID, m_ThreadName.c_str());
}
#endif // _MSC_VER
#endif
// Wait until thread has actually been created
m_Event->Wait();
}
#ifdef _WIN32
unsigned long cThread::MyThread(void* a_Param )
#else
void *cThread::MyThread( void *a_Param )
#endif
{
cThread* self = (cThread*)a_Param;
cEvent* StopEvent = self->m_StopEvent;
ThreadFunc* ThreadFunction = self->m_ThreadFunction;
void* ThreadParam = self->m_Param;
// Set event to let other thread know this thread has been created and it's safe to delete the cThread object
self->m_Event->Set();
ThreadFunction( ThreadParam );
if( StopEvent ) StopEvent->Set();
return 0;
}
+26
View File
@@ -0,0 +1,26 @@
#pragma once
class cThread
{
public:
typedef void (ThreadFunc)(void*);
cThread( ThreadFunc a_ThreadFunction, void* a_Param, const char* a_ThreadName = 0 );
~cThread();
void Start( bool a_bWaitOnDelete = true );
void WaitForThread();
private:
ThreadFunc* m_ThreadFunction;
#ifdef _WIN32
static unsigned long MyThread(void* a_Param );
#else
static void *MyThread( void *lpParam );
#endif
void* m_Param;
cEvent* m_Event;
cEvent* m_StopEvent;
AString m_ThreadName;
};
+40
View File
@@ -0,0 +1,40 @@
#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules
#include "Timer.h"
cTimer::cTimer()
#ifdef _WIN32
: m_TicksPerSecond( new LARGE_INTEGER )
#endif
{
#ifdef _WIN32
QueryPerformanceFrequency( (LARGE_INTEGER*)m_TicksPerSecond );
#endif
}
cTimer::~cTimer()
{
#ifdef _WIN32
delete (LARGE_INTEGER*)m_TicksPerSecond;
#endif
}
long long cTimer::GetNowTime()
{
#ifdef _WIN32
LARGE_INTEGER now;
QueryPerformanceCounter( &now );
LARGE_INTEGER & tps = *((LARGE_INTEGER*)m_TicksPerSecond);
return ((now.QuadPart*1000) / tps.QuadPart );
#else
struct timeval now;
gettimeofday(&now, NULL);
return (long long)(now.tv_sec*1000 + now.tv_usec/1000);
#endif
}
+15
View File
@@ -0,0 +1,15 @@
#pragma once
class cTimer
{
public:
cTimer();
~cTimer();
long long GetNowTime();
private:
#ifdef _WIN32
void* m_TicksPerSecond; // LARGE_INTEGER*
#endif
};