From 7abb5f7604bb9a0a716e89f3b27e330b016a38b9 Mon Sep 17 00:00:00 2001 From: "madmaxoft@gmail.com" Date: Sun, 23 Sep 2012 21:23:33 +0000 Subject: 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 --- source/OSSupport/BlockingTCPLink.cpp | 149 ++++++++ source/OSSupport/BlockingTCPLink.h | 28 ++ source/OSSupport/CriticalSection.cpp | 187 +++++++++ source/OSSupport/CriticalSection.h | 80 ++++ source/OSSupport/Event.cpp | 118 ++++++ source/OSSupport/Event.h | 47 +++ source/OSSupport/File.cpp | 271 +++++++++++++ source/OSSupport/File.h | 108 ++++++ source/OSSupport/IsThread.cpp | 167 ++++++++ source/OSSupport/IsThread.h | 78 ++++ source/OSSupport/MakeDir.cpp | 25 ++ source/OSSupport/MakeDir.h | 16 + source/OSSupport/Semaphore.cpp | 91 +++++ source/OSSupport/Semaphore.h | 17 + source/OSSupport/Sleep.cpp | 19 + source/OSSupport/Sleep.h | 7 + source/OSSupport/Socket.cpp | 331 ++++++++++++++++ source/OSSupport/Socket.h | 79 ++++ source/OSSupport/SocketThreads.cpp | 713 +++++++++++++++++++++++++++++++++++ source/OSSupport/SocketThreads.h | 172 +++++++++ source/OSSupport/TCPLink.cpp | 128 +++++++ source/OSSupport/TCPLink.h | 22 ++ source/OSSupport/Thread.cpp | 128 +++++++ source/OSSupport/Thread.h | 26 ++ source/OSSupport/Timer.cpp | 40 ++ source/OSSupport/Timer.h | 15 + 26 files changed, 3062 insertions(+) create mode 100644 source/OSSupport/BlockingTCPLink.cpp create mode 100644 source/OSSupport/BlockingTCPLink.h create mode 100644 source/OSSupport/CriticalSection.cpp create mode 100644 source/OSSupport/CriticalSection.h create mode 100644 source/OSSupport/Event.cpp create mode 100644 source/OSSupport/Event.h create mode 100644 source/OSSupport/File.cpp create mode 100644 source/OSSupport/File.h create mode 100644 source/OSSupport/IsThread.cpp create mode 100644 source/OSSupport/IsThread.h create mode 100644 source/OSSupport/MakeDir.cpp create mode 100644 source/OSSupport/MakeDir.h create mode 100644 source/OSSupport/Semaphore.cpp create mode 100644 source/OSSupport/Semaphore.h create mode 100644 source/OSSupport/Sleep.cpp create mode 100644 source/OSSupport/Sleep.h create mode 100644 source/OSSupport/Socket.cpp create mode 100644 source/OSSupport/Socket.h create mode 100644 source/OSSupport/SocketThreads.cpp create mode 100644 source/OSSupport/SocketThreads.h create mode 100644 source/OSSupport/TCPLink.cpp create mode 100644 source/OSSupport/TCPLink.h create mode 100644 source/OSSupport/Thread.cpp create mode 100644 source/OSSupport/Thread.h create mode 100644 source/OSSupport/Timer.cpp create mode 100644 source/OSSupport/Timer.h (limited to 'source/OSSupport') diff --git a/source/OSSupport/BlockingTCPLink.cpp b/source/OSSupport/BlockingTCPLink.cpp new file mode 100644 index 000000000..55454a4b5 --- /dev/null +++ b/source/OSSupport/BlockingTCPLink.cpp @@ -0,0 +1,149 @@ + +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "BlockingTCPLink.h" + + + + + +#ifdef _WIN32 + #define MSG_NOSIGNAL (0) +#endif +#ifdef __MACH__ + #define MSG_NOSIGNAL (0) +#endif + + + + + +cBlockingTCPLink::cBlockingTCPLink(void) +{ +} + + + + + +cBlockingTCPLink::~cBlockingTCPLink() +{ + CloseSocket(); +} + + + + + +void cBlockingTCPLink::CloseSocket() +{ + if (!m_Socket.IsValid()) + { + m_Socket.CloseSocket(); + } +} + + + + + +bool cBlockingTCPLink::Connect(const char * iAddress, unsigned int iPort) +{ + ASSERT(!m_Socket.IsValid()); + if (m_Socket.IsValid()) + { + LOGWARN("WARNING: cTCPLink Connect() called while still connected."); + m_Socket.CloseSocket(); + } + + struct hostent *hp; + unsigned int addr; + struct sockaddr_in server; + + m_Socket = socket(AF_INET, SOCK_STREAM, 0); + if (!m_Socket.IsValid()) + { + LOGERROR("cTCPLink: Cannot create a socket"); + return false; + } + + addr = inet_addr(iAddress); + hp = gethostbyaddr((char *)&addr, sizeof(addr), AF_INET); + if (hp == NULL) + { + //LOGWARN("cTCPLink: gethostbyaddr returned NULL"); + hp = gethostbyname(iAddress); + if (hp == NULL) + { + LOGWARN("cTCPLink: Could not resolve %s", iAddress); + CloseSocket(); + return false; + } + } + + server.sin_addr.s_addr = *((unsigned long *)hp->h_addr); + server.sin_family = AF_INET; + server.sin_port = htons( (unsigned short)iPort); + if (connect(m_Socket, (struct sockaddr *)&server, sizeof(server))) + { + LOGWARN("cTCPLink: Connection to \"%s:%d\" failed (%s)", iAddress, iPort, cSocket::GetErrorString( cSocket::GetLastError() ).c_str() ); + CloseSocket(); + return false; + } + + return true; +} + + + + + +int cBlockingTCPLink::Send(char * a_Data, unsigned int a_Size, int a_Flags /* = 0 */ ) +{ + ASSERT(m_Socket.IsValid()); + if (!m_Socket.IsValid()) + { + LOGERROR("cBlockingTCPLink: Trying to send data without a valid connection!"); + return -1; + } + return m_Socket.Send(a_Data, a_Size); +} + + + + + +int cBlockingTCPLink::SendMessage( const char* a_Message, int a_Flags /* = 0 */ ) +{ + ASSERT(m_Socket.IsValid()); + if (!m_Socket.IsValid()) + { + LOGWARN("cBlockingTCPLink: Trying to send message without a valid connection!"); + return -1; + } + return m_Socket.Send(a_Message, strlen(a_Message)); +} + + + + + +void cBlockingTCPLink::ReceiveData(AString & oData) +{ + ASSERT(m_Socket.IsValid()); + if (!m_Socket.IsValid()) + { + return; + } + + int Received = 0; + char Buffer[256]; + while ((Received = recv(m_Socket, Buffer, sizeof(Buffer), 0)) > 0) + { + oData.append(Buffer, Received); + } +} + + + + diff --git a/source/OSSupport/BlockingTCPLink.h b/source/OSSupport/BlockingTCPLink.h new file mode 100644 index 000000000..4ee0ccb3b --- /dev/null +++ b/source/OSSupport/BlockingTCPLink.h @@ -0,0 +1,28 @@ + +#pragma once + +#include "Socket.h" + + + + + +class cBlockingTCPLink //tolua_export +{ //tolua_export +public: //tolua_export + cBlockingTCPLink(void); //tolua_export + ~cBlockingTCPLink(); //tolua_export + + bool Connect( const char* a_Address, unsigned int a_Port ); //tolua_export + int Send( char* a_Data, unsigned int a_Size, int a_Flags = 0 ); //tolua_export + int SendMessage( const char* a_Message, int a_Flags = 0 ); //tolua_export + void CloseSocket(); //tolua_export + void ReceiveData(AString & oData); //tolua_export +protected: + + cSocket m_Socket; +}; //tolua_export + + + + diff --git a/source/OSSupport/CriticalSection.cpp b/source/OSSupport/CriticalSection.cpp new file mode 100644 index 000000000..f87a2b3ba --- /dev/null +++ b/source/OSSupport/CriticalSection.cpp @@ -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(); +} + + + + diff --git a/source/OSSupport/CriticalSection.h b/source/OSSupport/CriticalSection.h new file mode 100644 index 000000000..9852a2e6c --- /dev/null +++ b/source/OSSupport/CriticalSection.h @@ -0,0 +1,80 @@ + +#pragma once + + + + + +class cCriticalSection +{ +public: + cCriticalSection(void); + ~cCriticalSection(); + + void Lock(void); + void Unlock(void); + + #ifdef _DEBUG + bool IsLocked(void); + bool IsLockedByCurrentThread(void); + #endif // _DEBUG + +private: + #ifdef _DEBUG + 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); +} ; + + + + diff --git a/source/OSSupport/Event.cpp b/source/OSSupport/Event.cpp new file mode 100644 index 000000000..13b5c1d3f --- /dev/null +++ b/source/OSSupport/Event.cpp @@ -0,0 +1,118 @@ + +// Event.cpp + +// Implements the cEvent object representing an OS-specific synchronization primitive that can be waited-for +// Implemented as an Event on Win and as a 1-semaphore on *nix + +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#include "Event.h" + + + + + +cEvent::cEvent(void) +{ +#ifdef _WIN32 + m_Event = CreateEvent( 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 +} + + + + diff --git a/source/OSSupport/Event.h b/source/OSSupport/Event.h new file mode 100644 index 000000000..71f418c0c --- /dev/null +++ b/source/OSSupport/Event.h @@ -0,0 +1,47 @@ + +// Event.h + +// Interfaces to the cEvent object representing an OS-specific synchronization primitive that can be waited-for +// Implemented as an Event on Win and as a 1-semaphore on *nix + + + + + +#pragma once +#ifndef CEVENT_H_INCLUDED +#define CEVENT_H_INCLUDED + + + + + +class cEvent +{ +public: + cEvent(void); + ~cEvent(); + + void Wait(void); + void Set (void); + +private: + + #ifdef _WIN32 + HANDLE m_Event; + #else + sem_t * m_Event; + bool m_bIsNamed; + #endif +} ; + + + + + + +#endif // CEVENT_H_INCLUDED + + + + diff --git a/source/OSSupport/File.cpp b/source/OSSupport/File.cpp new file mode 100644 index 000000000..fdae0b34e --- /dev/null +++ b/source/OSSupport/File.cpp @@ -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(); +} + + + + diff --git a/source/OSSupport/File.h b/source/OSSupport/File.h new file mode 100644 index 000000000..d16784236 --- /dev/null +++ b/source/OSSupport/File.h @@ -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 + + + + diff --git a/source/OSSupport/IsThread.cpp b/source/OSSupport/IsThread.cpp new file mode 100644 index 000000000..9dcbc43eb --- /dev/null +++ b/source/OSSupport/IsThread.cpp @@ -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 +} + + + + diff --git a/source/OSSupport/IsThread.h b/source/OSSupport/IsThread.h new file mode 100644 index 000000000..ed9a32852 --- /dev/null +++ b/source/OSSupport/IsThread.h @@ -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 + + + + diff --git a/source/OSSupport/MakeDir.cpp b/source/OSSupport/MakeDir.cpp new file mode 100644 index 000000000..10ccfe9ec --- /dev/null +++ b/source/OSSupport/MakeDir.cpp @@ -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 +} + + + + diff --git a/source/OSSupport/MakeDir.h b/source/OSSupport/MakeDir.h new file mode 100644 index 000000000..e66cf1071 --- /dev/null +++ b/source/OSSupport/MakeDir.h @@ -0,0 +1,16 @@ + +#pragma once + + + + + +class cMakeDir +{ +public: + static void MakeDir(const AString & a_Directory); +}; + + + + diff --git a/source/OSSupport/Semaphore.cpp b/source/OSSupport/Semaphore.cpp new file mode 100644 index 000000000..468de6858 --- /dev/null +++ b/source/OSSupport/Semaphore.cpp @@ -0,0 +1,91 @@ + +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + + + + + +cSemaphore::cSemaphore( unsigned int a_MaxCount, unsigned int a_InitialCount /* = 0 */ ) +#ifndef _WIN32 + : m_bNamed( false ) +#endif +{ +#ifndef _WIN32 + (void)a_MaxCount; + m_Handle = new sem_t; + if (sem_init( (sem_t*)m_Handle, 0, 0)) + { + LOG("WARNING cSemaphore: Could not create unnamed semaphore, fallback to named."); + delete (sem_t*)m_Handle; // named semaphores return their own address + m_bNamed = true; + + AString Name; + Printf(Name, "cSemaphore%p", this ); + m_Handle = sem_open(Name.c_str(), O_CREAT, 777, a_InitialCount); + if( m_Handle == SEM_FAILED ) + { + LOG("ERROR: Could not create Semaphore. (%i)", errno ); + } + else + { + if( sem_unlink(Name.c_str()) != 0 ) + { + LOG("ERROR: Could not unlink cSemaphore. (%i)", errno); + } + } + } +#else + m_Handle = CreateSemaphore( + NULL, // security attribute + a_InitialCount, // initial count + a_MaxCount, // maximum count + 0 // name (optional) + ); +#endif +} + +cSemaphore::~cSemaphore() +{ +#ifdef _WIN32 + CloseHandle( m_Handle ); +#else + if( m_bNamed ) + { + if( sem_close( (sem_t*)m_Handle ) != 0 ) + { + LOG("ERROR: Could not close cSemaphore. (%i)", errno); + } + } + else + { + sem_destroy( (sem_t*)m_Handle ); + delete (sem_t*)m_Handle; + } + m_Handle = 0; + +#endif +} + +void cSemaphore::Wait() +{ +#ifndef _WIN32 + if( sem_wait( (sem_t*)m_Handle ) != 0) + { + LOG("ERROR: Could not wait for cSemaphore. (%i)", errno); + } +#else + WaitForSingleObject( m_Handle, INFINITE); +#endif +} + +void cSemaphore::Signal() +{ +#ifndef _WIN32 + if( sem_post( (sem_t*)m_Handle ) != 0 ) + { + LOG("ERROR: Could not signal cSemaphore. (%i)", errno); + } +#else + ReleaseSemaphore( m_Handle, 1, NULL ); +#endif +} diff --git a/source/OSSupport/Semaphore.h b/source/OSSupport/Semaphore.h new file mode 100644 index 000000000..fbe8907f1 --- /dev/null +++ b/source/OSSupport/Semaphore.h @@ -0,0 +1,17 @@ +#pragma once + +class cSemaphore +{ +public: + cSemaphore( unsigned int a_MaxCount, unsigned int a_InitialCount = 0 ); + ~cSemaphore(); + + void Wait(); + void Signal(); +private: + void* m_Handle; // HANDLE pointer + +#ifndef _WIN32 + bool m_bNamed; +#endif +}; diff --git a/source/OSSupport/Sleep.cpp b/source/OSSupport/Sleep.cpp new file mode 100644 index 000000000..70fb06b40 --- /dev/null +++ b/source/OSSupport/Sleep.cpp @@ -0,0 +1,19 @@ + +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + +#ifndef _WIN32 + #include +#endif + + + + + +void cSleep::MilliSleep( unsigned int a_MilliSeconds ) +{ +#ifdef _WIN32 + Sleep(a_MilliSeconds); // Don't tick too much +#else + usleep(a_MilliSeconds*1000); +#endif +} diff --git a/source/OSSupport/Sleep.h b/source/OSSupport/Sleep.h new file mode 100644 index 000000000..5298c15da --- /dev/null +++ b/source/OSSupport/Sleep.h @@ -0,0 +1,7 @@ +#pragma once + +class cSleep +{ +public: + static void MilliSleep( unsigned int a_MilliSeconds ); +}; \ No newline at end of file diff --git a/source/OSSupport/Socket.cpp b/source/OSSupport/Socket.cpp new file mode 100644 index 000000000..8d6a5ee94 --- /dev/null +++ b/source/OSSupport/Socket.cpp @@ -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 + #include + #include //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); +} + + + + diff --git a/source/OSSupport/Socket.h b/source/OSSupport/Socket.h new file mode 100644 index 000000000..f1c3f233c --- /dev/null +++ b/source/OSSupport/Socket.h @@ -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; +}; \ No newline at end of file diff --git a/source/OSSupport/SocketThreads.cpp b/source/OSSupport/SocketThreads.cpp new file mode 100644 index 000000000..ae23b4496 --- /dev/null +++ b/source/OSSupport/SocketThreads.cpp @@ -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[] +} + + + + diff --git a/source/OSSupport/SocketThreads.h b/source/OSSupport/SocketThreads.h new file mode 100644 index 000000000..4683e0e4d --- /dev/null +++ b/source/OSSupport/SocketThreads.h @@ -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 cSocketThreadList; + + + cCriticalSection m_CS; + cSocketThreadList m_Threads; +} ; + + + + + +#endif // CSOCKETTHREADS_H_INCLUDED + + + + diff --git a/source/OSSupport/TCPLink.cpp b/source/OSSupport/TCPLink.cpp new file mode 100644 index 000000000..d4c423b94 --- /dev/null +++ b/source/OSSupport/TCPLink.cpp @@ -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(); + } +} + + + + diff --git a/source/OSSupport/TCPLink.h b/source/OSSupport/TCPLink.h new file mode 100644 index 000000000..7fca10d7f --- /dev/null +++ b/source/OSSupport/TCPLink.h @@ -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 diff --git a/source/OSSupport/Thread.cpp b/source/OSSupport/Thread.cpp new file mode 100644 index 000000000..3df75f0e7 --- /dev/null +++ b/source/OSSupport/Thread.cpp @@ -0,0 +1,128 @@ + +#include "Globals.h" // NOTE: MSVC stupidness requires this to be the same across all modules + + + + + +// When in MSVC, the debugger provides "thread naming" by catching special exceptions. Interface here: +#ifdef _MSC_VER +// +// Usage: SetThreadName (-1, "MainThread"); +// +typedef struct tagTHREADNAME_INFO +{ + DWORD dwType; // must be 0x1000 + LPCSTR szName; // pointer to name (in user addr space) + DWORD dwThreadID; // thread ID (-1=caller thread) + DWORD dwFlags; // reserved for future use, must be zero +} THREADNAME_INFO; + +void SetThreadName( DWORD dwThreadID, LPCSTR szThreadName) +{ + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = szThreadName; + info.dwThreadID = dwThreadID; + info.dwFlags = 0; + + __try + { + RaiseException( 0x406D1388, 0, sizeof(info)/sizeof(DWORD), (DWORD*)&info ); + } + __except(EXCEPTION_CONTINUE_EXECUTION) + { + } +} +#endif // _MSC_VER + + + + + +cThread::cThread( ThreadFunc a_ThreadFunction, void* a_Param, const char* a_ThreadName /* = 0 */ ) + : m_ThreadFunction( a_ThreadFunction ) + , m_Param( a_Param ) + , m_Event( new cEvent() ) + , m_StopEvent( 0 ) +{ + if( a_ThreadName ) + { + m_ThreadName.assign(a_ThreadName); + } +} + + + + + +cThread::~cThread() +{ + delete m_Event; + + if( m_StopEvent ) + { + m_StopEvent->Wait(); + delete m_StopEvent; + } +} + + + + + +void cThread::Start( bool a_bWaitOnDelete /* = true */ ) +{ + if( a_bWaitOnDelete ) + m_StopEvent = new cEvent(); + +#ifndef _WIN32 + pthread_t SndThread; + if( pthread_create( &SndThread, NULL, MyThread, this) ) + LOGERROR("ERROR: Could not create thread!"); +#else + DWORD ThreadID = 0; + HANDLE hThread = CreateThread( 0 // security + ,0 // stack size + , (LPTHREAD_START_ROUTINE) MyThread // function name + ,this // parameters + ,0 // flags + ,&ThreadID ); // thread id + CloseHandle( hThread ); + + #ifdef _MSC_VER + if (!m_ThreadName.empty()) + { + SetThreadName(ThreadID, m_ThreadName.c_str()); + } + #endif // _MSC_VER +#endif + + // Wait until thread has actually been created + m_Event->Wait(); +} + + + + + +#ifdef _WIN32 +unsigned long cThread::MyThread(void* a_Param ) +#else +void *cThread::MyThread( void *a_Param ) +#endif +{ + cThread* self = (cThread*)a_Param; + cEvent* StopEvent = self->m_StopEvent; + + ThreadFunc* ThreadFunction = self->m_ThreadFunction; + void* ThreadParam = self->m_Param; + + // Set event to let other thread know this thread has been created and it's safe to delete the cThread object + self->m_Event->Set(); + + ThreadFunction( ThreadParam ); + + if( StopEvent ) StopEvent->Set(); + return 0; +} diff --git a/source/OSSupport/Thread.h b/source/OSSupport/Thread.h new file mode 100644 index 000000000..3c9316424 --- /dev/null +++ b/source/OSSupport/Thread.h @@ -0,0 +1,26 @@ +#pragma once + +class cThread +{ +public: + typedef void (ThreadFunc)(void*); + cThread( ThreadFunc a_ThreadFunction, void* a_Param, const char* a_ThreadName = 0 ); + ~cThread(); + + void Start( bool a_bWaitOnDelete = true ); + void WaitForThread(); +private: + ThreadFunc* m_ThreadFunction; + +#ifdef _WIN32 + static unsigned long MyThread(void* a_Param ); +#else + static void *MyThread( void *lpParam ); +#endif + + void* m_Param; + cEvent* m_Event; + cEvent* m_StopEvent; + + AString m_ThreadName; +}; \ No newline at end of file diff --git a/source/OSSupport/Timer.cpp b/source/OSSupport/Timer.cpp new file mode 100644 index 000000000..ab7325b5e --- /dev/null +++ b/source/OSSupport/Timer.cpp @@ -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 +} \ No newline at end of file diff --git a/source/OSSupport/Timer.h b/source/OSSupport/Timer.h new file mode 100644 index 000000000..5969d0fc9 --- /dev/null +++ b/source/OSSupport/Timer.h @@ -0,0 +1,15 @@ +#pragma once + +class cTimer +{ +public: + cTimer(); + ~cTimer(); + + long long GetNowTime(); +private: + +#ifdef _WIN32 + void* m_TicksPerSecond; // LARGE_INTEGER* +#endif +}; \ No newline at end of file -- cgit v1.2.3