commit
fd49e34e33
99
Install/ThirdPartyLicenses/LibEvent-LICENSE.txt
Normal file
99
Install/ThirdPartyLicenses/LibEvent-LICENSE.txt
Normal file
@ -0,0 +1,99 @@
|
||||
Libevent is available for use under the following license, commonly known
|
||||
as the 3-clause (or "modified") BSD license:
|
||||
|
||||
==============================
|
||||
Copyright (c) 2000-2007 Niels Provos <provos@citi.umich.edu>
|
||||
Copyright (c) 2007-2012 Niels Provos and Nick Mathewson
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
3. The name of the author may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
==============================
|
||||
|
||||
Portions of Libevent are based on works by others, also made available by
|
||||
them under the three-clause BSD license above. The copyright notices are
|
||||
available in the corresponding source files; the license is as above. Here's
|
||||
a list:
|
||||
|
||||
log.c:
|
||||
Copyright (c) 2000 Dug Song <dugsong@monkey.org>
|
||||
Copyright (c) 1993 The Regents of the University of California.
|
||||
|
||||
strlcpy.c:
|
||||
Copyright (c) 1998 Todd C. Miller <Todd.Miller@courtesan.com>
|
||||
|
||||
win32select.c:
|
||||
Copyright (c) 2003 Michael A. Davis <mike@datanerds.net>
|
||||
|
||||
evport.c:
|
||||
Copyright (c) 2007 Sun Microsystems
|
||||
|
||||
ht-internal.h:
|
||||
Copyright (c) 2002 Christopher Clark
|
||||
|
||||
minheap-internal.h:
|
||||
Copyright (c) 2006 Maxim Yegorushkin <maxim.yegorushkin@gmail.com>
|
||||
|
||||
==============================
|
||||
|
||||
The arc4module is available under the following, sometimes called the
|
||||
"OpenBSD" license:
|
||||
|
||||
Copyright (c) 1996, David Mazieres <dm@uun.org>
|
||||
Copyright (c) 2008, Damien Miller <djm@openbsd.org>
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
==============================
|
||||
|
||||
The Windows timer code is based on code from libutp, which is
|
||||
distributed under this license, sometimes called the "MIT" license.
|
||||
|
||||
|
||||
Copyright (c) 2010 BitTorrent, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
@ -1,3 +1,4 @@
|
||||
@echo off
|
||||
echo This script generates the certificate and private key for the https webadmin
|
||||
echo Note that the generated certificate is self-signed, and therefore not trusted by browsers
|
||||
echo Note that this script requires openssl to be installed and in PATH
|
||||
|
@ -22,6 +22,18 @@
|
||||
#define ALIGN_8
|
||||
#define ALIGN_16
|
||||
|
||||
#define FORMATSTRING(formatIndex, va_argsIndex)
|
||||
|
||||
// MSVC has its own custom version of zu format
|
||||
#define SIZE_T_FMT "%Iu"
|
||||
#define SIZE_T_FMT_PRECISION(x) "%" #x "Iu"
|
||||
#define SIZE_T_FMT_HEX "%Ix"
|
||||
|
||||
#define NORETURN __declspec(noreturn)
|
||||
|
||||
// Use non-standard defines in <cmath>
|
||||
#define _USE_MATH_DEFINES
|
||||
|
||||
#elif defined(__GNUC__)
|
||||
|
||||
// TODO: Can GCC explicitly mark classes as abstract (no instances can be created)?
|
||||
@ -38,6 +50,29 @@
|
||||
// Some portability macros :)
|
||||
#define stricmp strcasecmp
|
||||
|
||||
#define FORMATSTRING(formatIndex, va_argsIndex) __attribute__((format (printf, formatIndex, va_argsIndex)))
|
||||
|
||||
#if defined(_WIN32)
|
||||
// We're compiling on MinGW, which uses an old MSVCRT library that has no support for size_t printfing.
|
||||
// We need direct size formats:
|
||||
#if defined(_WIN64)
|
||||
#define SIZE_T_FMT "%I64u"
|
||||
#define SIZE_T_FMT_PRECISION(x) "%" #x "I64u"
|
||||
#define SIZE_T_FMT_HEX "%I64x"
|
||||
#else
|
||||
#define SIZE_T_FMT "%u"
|
||||
#define SIZE_T_FMT_PRECISION(x) "%" #x "u"
|
||||
#define SIZE_T_FMT_HEX "%x"
|
||||
#endif
|
||||
#else
|
||||
// We're compiling on Linux, so we can use libc's size_t printf format:
|
||||
#define SIZE_T_FMT "%zu"
|
||||
#define SIZE_T_FMT_PRECISION(x) "%" #x "zu"
|
||||
#define SIZE_T_FMT_HEX "%zx"
|
||||
#endif
|
||||
|
||||
#define NORETURN __attribute((__noreturn__))
|
||||
|
||||
#else
|
||||
|
||||
#error "You are using an unsupported compiler, you might need to #define some stuff here for your compiler"
|
||||
@ -74,6 +109,8 @@ typedef unsigned long long UInt64;
|
||||
typedef unsigned int UInt32;
|
||||
typedef unsigned short UInt16;
|
||||
|
||||
typedef unsigned char Byte;
|
||||
|
||||
|
||||
|
||||
|
||||
@ -94,7 +131,7 @@ typedef unsigned short UInt16;
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
|
||||
#define _WIN32_WINNT 0x501 // We want to target WinXP and higher
|
||||
#define _WIN32_WINNT 0x502 // We want to target WinXP SP2 and higher
|
||||
|
||||
#include <Windows.h>
|
||||
#include <winsock2.h>
|
||||
@ -175,7 +212,8 @@ typedef unsigned short UInt16;
|
||||
#include "StringUtils.h"
|
||||
#include "OSSupport/CriticalSection.h"
|
||||
#include "OSSupport/File.h"
|
||||
#include "MCLogger.h"
|
||||
#include "OSSupport/Event.h"
|
||||
#include "Logger.h"
|
||||
|
||||
|
||||
|
||||
|
@ -80,14 +80,14 @@ bool cRCONPacketizer::SendPacket(int a_PacketType, const AString & a_PacketPaylo
|
||||
size_t Length = Packet.size();
|
||||
if (!m_Socket.Send((const char *)&Length, 4))
|
||||
{
|
||||
fprintf(stderr, "Network error while sending packet: %d (%s). Aborting.",
|
||||
fprintf(stderr, "Network error while sending packet: %d (%s). Aborting.\n",
|
||||
cSocket::GetLastError(), cSocket::GetLastErrorString().c_str()
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (!m_Socket.Send(Packet.data(), Packet.size()))
|
||||
{
|
||||
fprintf(stderr, "Network error while sending packet: %d (%s). Aborting.",
|
||||
fprintf(stderr, "Network error while sending packet: %d (%s). Aborting.\n",
|
||||
cSocket::GetLastError(), cSocket::GetLastErrorString().c_str()
|
||||
);
|
||||
return false;
|
||||
@ -110,12 +110,12 @@ bool cRCONPacketizer::ReceiveResponse(void)
|
||||
int NumReceived = m_Socket.Receive(buf, sizeof(buf), 0);
|
||||
if (NumReceived == 0)
|
||||
{
|
||||
fprintf(stderr, "The remote end closed the connection. Aborting.");
|
||||
fprintf(stderr, "The remote end closed the connection. Aborting.\n");
|
||||
return false;
|
||||
}
|
||||
if (NumReceived < 0)
|
||||
{
|
||||
fprintf(stderr, "Network error while receiving response: %d, %d (%s). Aborting.",
|
||||
fprintf(stderr, "Network error while receiving response: %d, %d (%s). Aborting.\n",
|
||||
NumReceived, cSocket::GetLastError(), cSocket::GetLastErrorString().c_str()
|
||||
);
|
||||
return false;
|
||||
@ -156,13 +156,13 @@ bool cRCONPacketizer::ParsePacket(cByteBuffer & a_Buffer, int a_PacketLength)
|
||||
{
|
||||
if ((RequestID == -1) && (m_RequestID == 0))
|
||||
{
|
||||
fprintf(stderr, "Login failed. Aborting.");
|
||||
fprintf(stderr, "Login failed. Aborting.\n");
|
||||
IsValid = false;
|
||||
// Continue, so that the payload is printed before the program aborts.
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "The server returned an invalid request ID, got %d, exp. %d. Aborting.", RequestID, m_RequestID);
|
||||
fprintf(stderr, "The server returned an invalid request ID, got %d, exp. %d. Aborting.\n", RequestID, m_RequestID);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -172,7 +172,7 @@ bool cRCONPacketizer::ParsePacket(cByteBuffer & a_Buffer, int a_PacketLength)
|
||||
VERIFY(a_Buffer.ReadLEInt(PacketType));
|
||||
if (PacketType != ptCommand)
|
||||
{
|
||||
fprintf(stderr, "The server returned an unknown packet type: %d. Aborting.", PacketType);
|
||||
fprintf(stderr, "The server returned an unknown packet type: %d. Aborting.\n", PacketType);
|
||||
IsValid = false;
|
||||
// Continue, so that the payload is printed before the program aborts.
|
||||
}
|
||||
@ -200,8 +200,8 @@ bool cRCONPacketizer::ParsePacket(cByteBuffer & a_Buffer, int a_PacketLength)
|
||||
|
||||
int RealMain(int argc, char * argv[])
|
||||
{
|
||||
new cMCLogger; // Create a new logger
|
||||
|
||||
cLogger::InitiateMultithreading();
|
||||
|
||||
// Parse the cmdline params for server IP, port, password and the commands to send:
|
||||
AString ServerAddress, Password;
|
||||
int ServerPort = -1;
|
||||
@ -301,6 +301,7 @@ int RealMain(int argc, char * argv[])
|
||||
}
|
||||
}
|
||||
|
||||
// Send each command:
|
||||
for (AStringVector::const_iterator itr = Commands.begin(), end = Commands.end(); itr != end; ++itr)
|
||||
{
|
||||
if (g_IsVerbose)
|
||||
|
@ -1,7 +1,9 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 10.00
|
||||
# Visual C++ Express 2008
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RCONClient", "RCONClient.vcproj", "{1A48B032-07D0-4DDD-8362-66C0FC7F7849}"
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Express 2013 for Windows Desktop
|
||||
VisualStudioVersion = 12.0.31101.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RCONClient", "RCONClient.vcxproj", "{1A48B032-07D0-4DDD-8362-66C0FC7F7849}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
|
@ -15,7 +15,6 @@
|
||||
#include "Item.h"
|
||||
#include "Mobs/Monster.h"
|
||||
#include "ChatColor.h"
|
||||
#include "OSSupport/Socket.h"
|
||||
#include "Items/ItemHandler.h"
|
||||
#include "Blocks/BlockHandler.h"
|
||||
#include "Blocks/BlockSlab.h"
|
||||
@ -56,16 +55,15 @@ int cClientHandle::s_ClientCount = 0;
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// cClientHandle:
|
||||
|
||||
cClientHandle::cClientHandle(const cSocket * a_Socket, int a_ViewDistance) :
|
||||
cClientHandle::cClientHandle(const AString & a_IPString, int a_ViewDistance) :
|
||||
m_CurrentViewDistance(a_ViewDistance),
|
||||
m_RequestedViewDistance(a_ViewDistance),
|
||||
m_IPString(a_Socket->GetIPString()),
|
||||
m_OutgoingData(64 KiB),
|
||||
m_IPString(a_IPString),
|
||||
m_Player(nullptr),
|
||||
m_HasSentDC(false),
|
||||
m_LastStreamedChunkX(0x7fffffff), // bogus chunk coords to force streaming upon login
|
||||
m_LastStreamedChunkZ(0x7fffffff),
|
||||
m_TimeSinceLastPacket(0),
|
||||
m_TicksSinceLastPacket(0),
|
||||
m_Ping(1000),
|
||||
m_PingID(1),
|
||||
m_BlockDigAnimStage(-1),
|
||||
@ -135,9 +133,6 @@ cClientHandle::~cClientHandle()
|
||||
SendDisconnect("Server shut down? Kthnxbai");
|
||||
}
|
||||
|
||||
// Close the socket as soon as it sends all outgoing data:
|
||||
cRoot::Get()->GetServer()->RemoveClient(this);
|
||||
|
||||
delete m_Protocol;
|
||||
m_Protocol = nullptr;
|
||||
|
||||
@ -150,6 +145,10 @@ cClientHandle::~cClientHandle()
|
||||
|
||||
void cClientHandle::Destroy(void)
|
||||
{
|
||||
{
|
||||
cCSLock Lock(m_CSOutgoingData);
|
||||
m_Link.reset();
|
||||
}
|
||||
{
|
||||
cCSLock Lock(m_CSDestroyingState);
|
||||
if (m_State >= csDestroying)
|
||||
@ -168,6 +167,10 @@ void cClientHandle::Destroy(void)
|
||||
RemoveFromAllChunks();
|
||||
m_Player->GetWorld()->RemoveClientFromChunkSender(this);
|
||||
}
|
||||
if (m_Player != nullptr)
|
||||
{
|
||||
m_Player->RemoveClientHandle();
|
||||
}
|
||||
m_State = csDestroyed;
|
||||
}
|
||||
|
||||
@ -326,7 +329,8 @@ void cClientHandle::Authenticate(const AString & a_Name, const AString & a_UUID,
|
||||
m_Protocol->SendLoginSuccess();
|
||||
|
||||
// Spawn player (only serversided, so data is loaded)
|
||||
m_Player = new cPlayer(this, GetUsername());
|
||||
m_Player = new cPlayer(m_Self, GetUsername());
|
||||
m_Self.reset();
|
||||
|
||||
cWorld * World = cRoot::Get()->GetWorld(m_Player->GetLoadedWorldName());
|
||||
if (World == nullptr)
|
||||
@ -689,6 +693,47 @@ void cClientHandle::HandleCreativeInventory(short a_SlotNum, const cItem & a_Hel
|
||||
|
||||
|
||||
|
||||
void cClientHandle::HandleEnchantItem(Byte a_WindowID, Byte a_Enchantment)
|
||||
{
|
||||
if (a_Enchantment > 2)
|
||||
{
|
||||
LOGWARNING("%s attempt to crash the server with invalid enchanting selection!", GetUsername().c_str());
|
||||
Kick("Invalid enchanting!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
(m_Player->GetWindow() == nullptr) ||
|
||||
(m_Player->GetWindow()->GetWindowID() != a_WindowID) ||
|
||||
(m_Player->GetWindow()->GetWindowType() != cWindow::wtEnchantment)
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cEnchantingWindow * Window = reinterpret_cast<cEnchantingWindow *>(m_Player->GetWindow());
|
||||
cItem Item = *Window->m_SlotArea->GetSlot(0, *m_Player); // Make a copy of the item
|
||||
short BaseEnchantmentLevel = Window->GetPropertyValue(a_Enchantment);
|
||||
|
||||
if (Item.EnchantByXPLevels(BaseEnchantmentLevel))
|
||||
{
|
||||
if (m_Player->IsGameModeCreative() || m_Player->DeltaExperience(-m_Player->XpForLevel(BaseEnchantmentLevel)) >= 0)
|
||||
{
|
||||
Window->m_SlotArea->SetSlot(0, *m_Player, Item);
|
||||
Window->SendSlot(*m_Player, Window->m_SlotArea, 0);
|
||||
Window->BroadcastWholeWindow();
|
||||
|
||||
Window->SetProperty(0, 0, *m_Player);
|
||||
Window->SetProperty(1, 0, *m_Player);
|
||||
Window->SetProperty(2, 0, *m_Player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cClientHandle::HandlePlayerAbilities(bool a_CanFly, bool a_IsFlying, float FlyingSpeed, float WalkingSpeed)
|
||||
{
|
||||
UNUSED(FlyingSpeed); // Ignore the client values for these
|
||||
@ -1777,44 +1822,9 @@ void cClientHandle::SendData(const char * a_Data, size_t a_Size)
|
||||
// This could crash the client, because they've already unloaded the world etc., and suddenly a wild packet appears (#31)
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
cCSLock Lock(m_CSOutgoingData);
|
||||
|
||||
// _X 2012_09_06: We need an overflow buffer, usually when streaming the initial chunks
|
||||
if (m_OutgoingDataOverflow.empty())
|
||||
{
|
||||
// No queued overflow data; if this packet fits into the ringbuffer, put it in, otherwise put it in the overflow buffer:
|
||||
size_t CanFit = m_OutgoingData.GetFreeSpace();
|
||||
if (CanFit > a_Size)
|
||||
{
|
||||
CanFit = a_Size;
|
||||
}
|
||||
if (CanFit > 0)
|
||||
{
|
||||
m_OutgoingData.Write(a_Data, CanFit);
|
||||
}
|
||||
if (a_Size > CanFit)
|
||||
{
|
||||
m_OutgoingDataOverflow.append(a_Data + CanFit, a_Size - CanFit);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// There is a queued overflow. Append to it, then send as much from its front as possible
|
||||
m_OutgoingDataOverflow.append(a_Data, a_Size);
|
||||
size_t CanFit = m_OutgoingData.GetFreeSpace();
|
||||
if (CanFit > 128)
|
||||
{
|
||||
// No point in moving the data over if it's not large enough - too much effort for too little an effect
|
||||
m_OutgoingData.Write(m_OutgoingDataOverflow.data(), CanFit);
|
||||
m_OutgoingDataOverflow.erase(0, CanFit);
|
||||
}
|
||||
}
|
||||
} // Lock(m_CSOutgoingData)
|
||||
|
||||
// Notify SocketThreads that we have something to write:
|
||||
cRoot::Get()->GetServer()->NotifyClientWrite(this);
|
||||
|
||||
cCSLock Lock(m_CSOutgoingData);
|
||||
m_OutgoingData.append(a_Data, a_Size);
|
||||
}
|
||||
|
||||
|
||||
@ -1871,10 +1881,24 @@ void cClientHandle::Tick(float a_Dt)
|
||||
cCSLock Lock(m_CSIncomingData);
|
||||
std::swap(IncomingData, m_IncomingData);
|
||||
}
|
||||
m_Protocol->DataReceived(IncomingData.data(), IncomingData.size());
|
||||
if (!IncomingData.empty())
|
||||
{
|
||||
m_Protocol->DataReceived(IncomingData.data(), IncomingData.size());
|
||||
}
|
||||
|
||||
// Send any queued outgoing data:
|
||||
AString OutgoingData;
|
||||
{
|
||||
cCSLock Lock(m_CSOutgoingData);
|
||||
std::swap(OutgoingData, m_OutgoingData);
|
||||
}
|
||||
if ((m_Link != nullptr) && !OutgoingData.empty())
|
||||
{
|
||||
m_Link->Send(OutgoingData.data(), OutgoingData.size());
|
||||
}
|
||||
|
||||
m_TimeSinceLastPacket += a_Dt;
|
||||
if (m_TimeSinceLastPacket > 30000.f) // 30 seconds time-out
|
||||
m_TicksSinceLastPacket += 1;
|
||||
if (m_TicksSinceLastPacket > 600) // 30 seconds time-out
|
||||
{
|
||||
SendDisconnect("Nooooo!! You timed out! D: Come back!");
|
||||
Destroy();
|
||||
@ -1955,7 +1979,21 @@ void cClientHandle::ServerTick(float a_Dt)
|
||||
cCSLock Lock(m_CSIncomingData);
|
||||
std::swap(IncomingData, m_IncomingData);
|
||||
}
|
||||
m_Protocol->DataReceived(IncomingData.data(), IncomingData.size());
|
||||
if (!IncomingData.empty())
|
||||
{
|
||||
m_Protocol->DataReceived(IncomingData.data(), IncomingData.size());
|
||||
}
|
||||
|
||||
// Send any queued outgoing data:
|
||||
AString OutgoingData;
|
||||
{
|
||||
cCSLock Lock(m_CSOutgoingData);
|
||||
std::swap(OutgoingData, m_OutgoingData);
|
||||
}
|
||||
if ((m_Link != nullptr) && !OutgoingData.empty())
|
||||
{
|
||||
m_Link->Send(OutgoingData.data(), OutgoingData.size());
|
||||
}
|
||||
|
||||
if (m_State == csAuthenticated)
|
||||
{
|
||||
@ -1970,8 +2008,8 @@ void cClientHandle::ServerTick(float a_Dt)
|
||||
return;
|
||||
}
|
||||
|
||||
m_TimeSinceLastPacket += a_Dt;
|
||||
if (m_TimeSinceLastPacket > 30000.f) // 30 seconds time-out
|
||||
m_TicksSinceLastPacket += 1;
|
||||
if (m_TicksSinceLastPacket > 600) // 30 seconds
|
||||
{
|
||||
SendDisconnect("Nooooo!! You timed out! D: Come back!");
|
||||
Destroy();
|
||||
@ -2843,49 +2881,13 @@ void cClientHandle::PacketError(UInt32 a_PacketType)
|
||||
|
||||
|
||||
|
||||
bool cClientHandle::DataReceived(const char * a_Data, size_t a_Size)
|
||||
{
|
||||
// Data is received from the client, store it in the buffer to be processed by the Tick thread:
|
||||
m_TimeSinceLastPacket = 0;
|
||||
cCSLock Lock(m_CSIncomingData);
|
||||
m_IncomingData.append(a_Data, a_Size);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cClientHandle::GetOutgoingData(AString & a_Data)
|
||||
{
|
||||
// Data can be sent to client
|
||||
{
|
||||
cCSLock Lock(m_CSOutgoingData);
|
||||
m_OutgoingData.ReadAll(a_Data);
|
||||
m_OutgoingData.CommitRead();
|
||||
a_Data.append(m_OutgoingDataOverflow);
|
||||
m_OutgoingDataOverflow.clear();
|
||||
}
|
||||
|
||||
// Disconnect player after all packets have been sent
|
||||
if (m_HasSentDC && a_Data.empty())
|
||||
{
|
||||
Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cClientHandle::SocketClosed(void)
|
||||
{
|
||||
// The socket has been closed for any reason
|
||||
|
||||
LOGD("Player %s @ %s disconnected", m_Username.c_str(), m_IPString.c_str());
|
||||
|
||||
if (!m_Username.empty()) // Ignore client pings
|
||||
{
|
||||
LOGD("Client %s @ %s disconnected", m_Username.c_str(), m_IPString.c_str());
|
||||
cRoot::Get()->GetPluginManager()->CallHookDisconnect(*this, "Player disconnected");
|
||||
}
|
||||
|
||||
@ -2896,41 +2898,62 @@ void cClientHandle::SocketClosed(void)
|
||||
|
||||
|
||||
|
||||
void cClientHandle::HandleEnchantItem(Byte & a_WindowID, Byte & a_Enchantment)
|
||||
void cClientHandle::SetSelf(cClientHandlePtr a_Self)
|
||||
{
|
||||
if (a_Enchantment > 2)
|
||||
{
|
||||
LOGWARNING("%s attempt to crash the server with invalid enchanting selection!", GetUsername().c_str());
|
||||
Kick("Invalid enchanting!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
(m_Player->GetWindow() == nullptr) ||
|
||||
(m_Player->GetWindow()->GetWindowID() != a_WindowID) ||
|
||||
(m_Player->GetWindow()->GetWindowType() != cWindow::wtEnchantment)
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
cEnchantingWindow * Window = (cEnchantingWindow*) m_Player->GetWindow();
|
||||
cItem Item = *Window->m_SlotArea->GetSlot(0, *m_Player);
|
||||
int BaseEnchantmentLevel = Window->GetPropertyValue(a_Enchantment);
|
||||
|
||||
if (Item.EnchantByXPLevels(BaseEnchantmentLevel))
|
||||
{
|
||||
if (m_Player->IsGameModeCreative() || m_Player->DeltaExperience(-m_Player->XpForLevel(BaseEnchantmentLevel)) >= 0)
|
||||
{
|
||||
Window->m_SlotArea->SetSlot(0, *m_Player, Item);
|
||||
Window->SendSlot(*m_Player, Window->m_SlotArea, 0);
|
||||
Window->BroadcastWholeWindow();
|
||||
|
||||
Window->SetProperty(0, 0, *m_Player);
|
||||
Window->SetProperty(1, 0, *m_Player);
|
||||
Window->SetProperty(2, 0, *m_Player);
|
||||
}
|
||||
}
|
||||
ASSERT(m_Self == nullptr);
|
||||
m_Self = a_Self;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cClientHandle::OnLinkCreated(cTCPLinkPtr a_Link)
|
||||
{
|
||||
m_Link = a_Link;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cClientHandle::OnReceivedData(const char * a_Data, size_t a_Length)
|
||||
{
|
||||
// Reset the timeout:
|
||||
m_TicksSinceLastPacket = 0;
|
||||
|
||||
// Queue the incoming data to be processed in the tick thread:
|
||||
cCSLock Lock(m_CSIncomingData);
|
||||
m_IncomingData.append(a_Data, a_Length);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cClientHandle::OnRemoteClosed(void)
|
||||
{
|
||||
{
|
||||
cCSLock Lock(m_CSOutgoingData);
|
||||
m_Link.reset();
|
||||
}
|
||||
SocketClosed();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cClientHandle::OnError(int a_ErrorCode, const AString & a_ErrorMsg)
|
||||
{
|
||||
LOGD("An error has occurred on client link for %s @ %s: %d (%s). Client disconnected.",
|
||||
m_Username.c_str(), m_IPString.c_str(), a_ErrorCode, a_ErrorMsg.c_str()
|
||||
);
|
||||
{
|
||||
cCSLock Lock(m_CSOutgoingData);
|
||||
m_Link.reset();
|
||||
}
|
||||
SocketClosed();
|
||||
}
|
||||
|
||||
|
||||
|
@ -8,12 +8,10 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
#ifndef CCLIENTHANDLE_H_INCLUDED
|
||||
#define CCLIENTHANDLE_H_INCLUDED
|
||||
|
||||
#include "OSSupport/Network.h"
|
||||
#include "Defines.h"
|
||||
#include "Vector3.h"
|
||||
#include "OSSupport/SocketThreads.h"
|
||||
#include "ChunkDef.h"
|
||||
#include "ByteBuffer.h"
|
||||
#include "Scoreboard.h"
|
||||
@ -27,6 +25,7 @@
|
||||
|
||||
|
||||
|
||||
// fwd:
|
||||
class cChunkDataSerializer;
|
||||
class cInventory;
|
||||
class cMonster;
|
||||
@ -42,25 +41,29 @@ class cItemHandler;
|
||||
class cWorld;
|
||||
class cCompositeChat;
|
||||
class cStatManager;
|
||||
class cClientHandle;
|
||||
typedef SharedPtr<cClientHandle> cClientHandlePtr;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class cClientHandle : // tolua_export
|
||||
public cSocketThreads::cCallback
|
||||
class cClientHandle // tolua_export
|
||||
: public cTCPLink::cCallbacks
|
||||
{ // tolua_export
|
||||
public:
|
||||
|
||||
#if defined(ANDROID_NDK)
|
||||
static const int DEFAULT_VIEW_DISTANCE = 4; // The default ViewDistance (used when no value is set in Settings.ini)
|
||||
#else
|
||||
static const int DEFAULT_VIEW_DISTANCE = 10;
|
||||
#endif
|
||||
public: // tolua_export
|
||||
|
||||
#if defined(ANDROID_NDK)
|
||||
static const int DEFAULT_VIEW_DISTANCE = 4; // The default ViewDistance (used when no value is set in Settings.ini)
|
||||
#else
|
||||
static const int DEFAULT_VIEW_DISTANCE = 10;
|
||||
#endif
|
||||
static const int MAX_VIEW_DISTANCE = 32;
|
||||
static const int MIN_VIEW_DISTANCE = 1;
|
||||
|
||||
cClientHandle(const cSocket * a_Socket, int a_ViewDistance);
|
||||
/** Creates a new client with the specified IP address in its description and the specified initial view distance. */
|
||||
cClientHandle(const AString & a_IPString, int a_ViewDistance);
|
||||
|
||||
virtual ~cClientHandle();
|
||||
|
||||
const AString & GetIPString(void) const { return m_IPString; } // tolua_export
|
||||
@ -276,6 +279,10 @@ public:
|
||||
void HandleCommandBlockEntityChange(int a_EntityID, const AString & a_NewCommand);
|
||||
|
||||
void HandleCreativeInventory (short a_SlotNum, const cItem & a_HeldItem);
|
||||
|
||||
/** Called when the player enchants an Item in the Enchanting table UI. */
|
||||
void HandleEnchantItem(Byte a_WindowID, Byte a_Enchantment);
|
||||
|
||||
void HandleEntityCrouch (int a_EntityID, bool a_IsCrouching);
|
||||
void HandleEntityLeaveBed (int a_EntityID);
|
||||
void HandleEntitySprinting (int a_EntityID, bool a_IsSprinting);
|
||||
@ -329,9 +336,6 @@ public:
|
||||
Sends an UnloadChunk packet for each loaded chunk and resets the streamed chunks. */
|
||||
void RemoveFromWorld(void);
|
||||
|
||||
/** Called when the player will enchant a Item */
|
||||
void HandleEnchantItem(Byte & a_WindowID, Byte & a_Enchantment);
|
||||
|
||||
/** Called by the protocol recognizer when the protocol version is known. */
|
||||
void SetProtocolVersion(UInt32 a_ProtocolVersion) { m_ProtocolVersion = a_ProtocolVersion; }
|
||||
|
||||
@ -340,6 +344,9 @@ public:
|
||||
|
||||
private:
|
||||
|
||||
friend class cServer; // Needs access to SetSelf()
|
||||
|
||||
|
||||
/** The type used for storing the names of registered plugin channels. */
|
||||
typedef std::set<AString> cChannels;
|
||||
|
||||
@ -361,13 +368,20 @@ private:
|
||||
cChunkCoordsList m_SentChunks; // Chunks that are currently sent to the client
|
||||
|
||||
cProtocol * m_Protocol;
|
||||
|
||||
|
||||
/** Protects m_IncomingData against multithreaded access. */
|
||||
cCriticalSection m_CSIncomingData;
|
||||
AString m_IncomingData;
|
||||
|
||||
|
||||
/** Queue for the incoming data received on the link until it is processed in Tick().
|
||||
Protected by m_CSIncomingData. */
|
||||
AString m_IncomingData;
|
||||
|
||||
/** Protects m_OutgoingData against multithreaded access. */
|
||||
cCriticalSection m_CSOutgoingData;
|
||||
cByteBuffer m_OutgoingData;
|
||||
AString m_OutgoingDataOverflow; ///< For data that didn't fit into the m_OutgoingData ringbuffer temporarily
|
||||
|
||||
/** Buffer for storing outgoing data from any thread; will get sent in Tick() (to prevent deadlocks).
|
||||
Protected by m_CSOutgoingData. */
|
||||
AString m_OutgoingData;
|
||||
|
||||
Vector3d m_ConfirmPosition;
|
||||
|
||||
@ -379,8 +393,8 @@ private:
|
||||
int m_LastStreamedChunkX;
|
||||
int m_LastStreamedChunkZ;
|
||||
|
||||
/** Seconds since the last packet data was received (updated in Tick(), reset in DataReceived()) */
|
||||
float m_TimeSinceLastPacket;
|
||||
/** Number of ticks since the last network packet was received (increased in Tick(), reset in OnReceivedData()) */
|
||||
int m_TicksSinceLastPacket;
|
||||
|
||||
/** Duration of the last completed client ping. */
|
||||
std::chrono::steady_clock::duration m_Ping;
|
||||
@ -458,6 +472,13 @@ private:
|
||||
/** The version of the protocol that the client is talking, or 0 if unknown. */
|
||||
UInt32 m_ProtocolVersion;
|
||||
|
||||
/** The link that is used for network communication.
|
||||
m_CSOutgoingData is used to synchronize access for sending data. */
|
||||
cTCPLinkPtr m_Link;
|
||||
|
||||
/** Shared pointer to self, so that this instance can keep itself alive when needed. */
|
||||
cClientHandlePtr m_Self;
|
||||
|
||||
|
||||
/** Returns true if the rate block interactions is within a reasonable limit (bot protection) */
|
||||
bool CheckBlockInteractionsRate(void);
|
||||
@ -483,17 +504,20 @@ private:
|
||||
/** Removes all of the channels from the list of current plugin channels. Ignores channels that are not found. */
|
||||
void UnregisterPluginChannels(const AStringVector & a_ChannelList);
|
||||
|
||||
// cSocketThreads::cCallback overrides:
|
||||
virtual bool DataReceived (const char * a_Data, size_t a_Size) override; // Data is received from the client
|
||||
virtual void GetOutgoingData(AString & a_Data) override; // Data can be sent to client
|
||||
virtual void SocketClosed (void) override; // The socket has been closed for any reason
|
||||
/** Called when the network socket has been closed. */
|
||||
void SocketClosed(void);
|
||||
|
||||
/** Called right after the instance is created to store its SharedPtr inside. */
|
||||
void SetSelf(cClientHandlePtr a_Self);
|
||||
|
||||
// cTCPLink::cCallbacks overrides:
|
||||
virtual void OnLinkCreated(cTCPLinkPtr a_Link) override;
|
||||
virtual void OnReceivedData(const char * a_Data, size_t a_Length) override;
|
||||
virtual void OnRemoteClosed(void) override;
|
||||
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override;
|
||||
}; // tolua_export
|
||||
|
||||
|
||||
|
||||
|
||||
#endif // CCLIENTHANDLE_H_INCLUDED
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -47,7 +47,7 @@ const int cPlayer::EATING_TICKS = 30;
|
||||
|
||||
|
||||
|
||||
cPlayer::cPlayer(cClientHandle* a_Client, const AString & a_PlayerName) :
|
||||
cPlayer::cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName) :
|
||||
super(etPlayer, 0.6, 1.8),
|
||||
m_bVisible(true),
|
||||
m_FoodLevel(MAX_FOOD_LEVEL),
|
||||
@ -174,7 +174,7 @@ void cPlayer::Destroyed()
|
||||
|
||||
void cPlayer::SpawnOn(cClientHandle & a_Client)
|
||||
{
|
||||
if (!m_bVisible || (m_ClientHandle == (&a_Client)))
|
||||
if (!m_bVisible || (m_ClientHandle.get() == (&a_Client)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -246,7 +246,7 @@ void cPlayer::Tick(std::chrono::milliseconds a_Dt, cChunk & a_Chunk)
|
||||
|
||||
if (CanMove)
|
||||
{
|
||||
BroadcastMovementUpdate(m_ClientHandle);
|
||||
BroadcastMovementUpdate(m_ClientHandle.get());
|
||||
}
|
||||
|
||||
if (m_Health > 0) // make sure player is alive
|
||||
@ -419,7 +419,7 @@ void cPlayer::StartChargingBow(void)
|
||||
LOGD("Player \"%s\" started charging their bow", GetName().c_str());
|
||||
m_IsChargingBow = true;
|
||||
m_BowCharge = 0;
|
||||
m_World->BroadcastEntityMetadata(*this, m_ClientHandle);
|
||||
m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get());
|
||||
}
|
||||
|
||||
|
||||
@ -432,7 +432,7 @@ int cPlayer::FinishChargingBow(void)
|
||||
int res = m_BowCharge;
|
||||
m_IsChargingBow = false;
|
||||
m_BowCharge = 0;
|
||||
m_World->BroadcastEntityMetadata(*this, m_ClientHandle);
|
||||
m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get());
|
||||
|
||||
return res;
|
||||
}
|
||||
@ -446,7 +446,7 @@ void cPlayer::CancelChargingBow(void)
|
||||
LOGD("Player \"%s\" cancelled charging their bow at a charge of %d", GetName().c_str(), m_BowCharge);
|
||||
m_IsChargingBow = false;
|
||||
m_BowCharge = 0;
|
||||
m_World->BroadcastEntityMetadata(*this, m_ClientHandle);
|
||||
m_World->BroadcastEntityMetadata(*this, m_ClientHandle.get());
|
||||
}
|
||||
|
||||
|
||||
@ -1391,7 +1391,7 @@ void cPlayer::SetVisible(bool a_bVisible)
|
||||
if (!a_bVisible && m_bVisible)
|
||||
{
|
||||
m_bVisible = false;
|
||||
m_World->BroadcastDestroyEntity(*this, m_ClientHandle); // Destroy on all clients
|
||||
m_World->BroadcastDestroyEntity(*this, m_ClientHandle.get()); // Destroy on all clients
|
||||
}
|
||||
}
|
||||
|
||||
@ -2294,6 +2294,16 @@ void cPlayer::Detach()
|
||||
|
||||
|
||||
|
||||
void cPlayer::RemoveClientHandle(void)
|
||||
{
|
||||
ASSERT(m_ClientHandle != nullptr);
|
||||
m_ClientHandle.reset();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
AString cPlayer::GetUUIDFileName(const AString & a_UUID)
|
||||
{
|
||||
AString UUID = cMojangAPI::MakeUUIDDashed(a_UUID);
|
||||
|
@ -40,7 +40,7 @@ public:
|
||||
CLASS_PROTODEF(cPlayer)
|
||||
|
||||
|
||||
cPlayer(cClientHandle * a_Client, const AString & a_PlayerName);
|
||||
cPlayer(cClientHandlePtr a_Client, const AString & a_PlayerName);
|
||||
|
||||
virtual ~cPlayer();
|
||||
|
||||
@ -222,7 +222,15 @@ public:
|
||||
/** Closes the current window if it matches the specified ID, resets current window to m_InventoryWindow */
|
||||
void CloseWindowIfID(char a_WindowID, bool a_CanRefuse = true);
|
||||
|
||||
cClientHandle * GetClientHandle(void) const { return m_ClientHandle; }
|
||||
/** Returns the raw client handle associated with the player. */
|
||||
cClientHandle * GetClientHandle(void) const { return m_ClientHandle.get(); }
|
||||
|
||||
// tolua_end
|
||||
|
||||
/** Returns the SharedPtr to client handle associated with the player. */
|
||||
cClientHandlePtr GetClientHandlePtr(void) const { return m_ClientHandle; }
|
||||
|
||||
// tolua_begin
|
||||
|
||||
void SendMessage (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtCustom); }
|
||||
void SendMessageInfo (const AString & a_Message) { m_ClientHandle->SendChat(a_Message, mtInformation); }
|
||||
@ -467,6 +475,10 @@ public:
|
||||
virtual bool IsRclking (void) const { return IsEating() || IsChargingBow(); }
|
||||
|
||||
virtual void Detach(void);
|
||||
|
||||
/** Called by cClientHandle when the client is being destroyed.
|
||||
The player removes its m_ClientHandle ownership so that the ClientHandle gets deleted. */
|
||||
void RemoveClientHandle(void);
|
||||
|
||||
protected:
|
||||
|
||||
@ -537,7 +549,7 @@ protected:
|
||||
|
||||
std::chrono::steady_clock::time_point m_LastPlayerListTime;
|
||||
|
||||
cClientHandle * m_ClientHandle;
|
||||
cClientHandlePtr m_ClientHandle;
|
||||
|
||||
cSlotNums m_InventoryPaintSlots;
|
||||
|
||||
|
@ -205,8 +205,7 @@ void cBiomeGenList::InitializeBiomes(const AString & a_Biomes)
|
||||
int Count = 1;
|
||||
if (Split2.size() >= 2)
|
||||
{
|
||||
Count = atol(Split2[1].c_str());
|
||||
if (Count <= 0)
|
||||
if (!StringToInteger(Split2[1], Count))
|
||||
{
|
||||
LOGWARNING("Cannot decode biome count: \"%s\"; using 1.", Split2[1].c_str());
|
||||
Count = 1;
|
||||
|
@ -38,8 +38,7 @@ cHTTPConnection::~cHTTPConnection()
|
||||
|
||||
void cHTTPConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Response)
|
||||
{
|
||||
AppendPrintf(m_OutgoingData, "%d %s\r\nContent-Length: 0\r\n\r\n", a_StatusCode, a_Response.c_str());
|
||||
m_HTTPServer.NotifyConnectionWrite(*this);
|
||||
SendData(Printf("%d %s\r\nContent-Length: 0\r\n\r\n", a_StatusCode, a_Response.c_str()));
|
||||
m_State = wcsRecvHeaders;
|
||||
}
|
||||
|
||||
@ -49,8 +48,7 @@ void cHTTPConnection::SendStatusAndReason(int a_StatusCode, const AString & a_Re
|
||||
|
||||
void cHTTPConnection::SendNeedAuth(const AString & a_Realm)
|
||||
{
|
||||
AppendPrintf(m_OutgoingData, "HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n", a_Realm.c_str());
|
||||
m_HTTPServer.NotifyConnectionWrite(*this);
|
||||
SendData(Printf("HTTP/1.1 401 Unauthorized\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Length: 0\r\n\r\n", a_Realm.c_str()));
|
||||
m_State = wcsRecvHeaders;
|
||||
}
|
||||
|
||||
@ -61,9 +59,10 @@ void cHTTPConnection::SendNeedAuth(const AString & a_Realm)
|
||||
void cHTTPConnection::Send(const cHTTPResponse & a_Response)
|
||||
{
|
||||
ASSERT(m_State == wcsRecvIdle);
|
||||
a_Response.AppendToData(m_OutgoingData);
|
||||
AString toSend;
|
||||
a_Response.AppendToData(toSend);
|
||||
m_State = wcsSendingResp;
|
||||
m_HTTPServer.NotifyConnectionWrite(*this);
|
||||
SendData(toSend);
|
||||
}
|
||||
|
||||
|
||||
@ -73,10 +72,10 @@ void cHTTPConnection::Send(const cHTTPResponse & a_Response)
|
||||
void cHTTPConnection::Send(const void * a_Data, size_t a_Size)
|
||||
{
|
||||
ASSERT(m_State == wcsSendingResp);
|
||||
AppendPrintf(m_OutgoingData, SIZE_T_FMT_HEX "\r\n", a_Size);
|
||||
m_OutgoingData.append((const char *)a_Data, a_Size);
|
||||
m_OutgoingData.append("\r\n");
|
||||
m_HTTPServer.NotifyConnectionWrite(*this);
|
||||
// We're sending in Chunked transfer encoding
|
||||
SendData(Printf(SIZE_T_FMT_HEX "\r\n", a_Size));
|
||||
SendData(a_Data, a_Size);
|
||||
SendData("\r\n");
|
||||
}
|
||||
|
||||
|
||||
@ -86,9 +85,8 @@ void cHTTPConnection::Send(const void * a_Data, size_t a_Size)
|
||||
void cHTTPConnection::FinishResponse(void)
|
||||
{
|
||||
ASSERT(m_State == wcsSendingResp);
|
||||
m_OutgoingData.append("0\r\n\r\n");
|
||||
SendData("0\r\n\r\n");
|
||||
m_State = wcsRecvHeaders;
|
||||
m_HTTPServer.NotifyConnectionWrite(*this);
|
||||
}
|
||||
|
||||
|
||||
@ -108,8 +106,7 @@ void cHTTPConnection::AwaitNextRequest(void)
|
||||
case wcsRecvIdle:
|
||||
{
|
||||
// The client is waiting for a response, send an "Internal server error":
|
||||
m_OutgoingData.append("HTTP/1.1 500 Internal Server Error\r\n\r\n");
|
||||
m_HTTPServer.NotifyConnectionWrite(*this);
|
||||
SendData("HTTP/1.1 500 Internal Server Error\r\n\r\n");
|
||||
m_State = wcsRecvHeaders;
|
||||
break;
|
||||
}
|
||||
@ -117,7 +114,7 @@ void cHTTPConnection::AwaitNextRequest(void)
|
||||
case wcsSendingResp:
|
||||
{
|
||||
// The response headers have been sent, we need to terminate the response body:
|
||||
m_OutgoingData.append("0\r\n\r\n");
|
||||
SendData("0\r\n\r\n");
|
||||
m_State = wcsRecvHeaders;
|
||||
break;
|
||||
}
|
||||
@ -140,15 +137,27 @@ void cHTTPConnection::Terminate(void)
|
||||
{
|
||||
m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
|
||||
}
|
||||
m_HTTPServer.CloseConnection(*this);
|
||||
m_Link.reset();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
|
||||
void cHTTPConnection::OnLinkCreated(cTCPLinkPtr a_Link)
|
||||
{
|
||||
ASSERT(m_Link == nullptr);
|
||||
m_Link = a_Link;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPConnection::OnReceivedData(const char * a_Data, size_t a_Size)
|
||||
{
|
||||
ASSERT(m_Link != nullptr);
|
||||
|
||||
switch (m_State)
|
||||
{
|
||||
case wcsRecvHeaders:
|
||||
@ -164,13 +173,14 @@ bool cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
|
||||
delete m_CurrentRequest;
|
||||
m_CurrentRequest = nullptr;
|
||||
m_State = wcsInvalid;
|
||||
m_HTTPServer.CloseConnection(*this);
|
||||
return true;
|
||||
m_Link->Close();
|
||||
m_Link.reset();
|
||||
return;
|
||||
}
|
||||
if (m_CurrentRequest->IsInHeaders())
|
||||
{
|
||||
// The request headers are not yet complete
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
// The request has finished parsing its headers successfully, notify of it:
|
||||
@ -186,11 +196,13 @@ bool cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
|
||||
// Process the rest of the incoming data into the request body:
|
||||
if (a_Size > BytesConsumed)
|
||||
{
|
||||
return cHTTPConnection::DataReceived(a_Data + BytesConsumed, a_Size - BytesConsumed);
|
||||
cHTTPConnection::OnReceivedData(a_Data + BytesConsumed, a_Size - BytesConsumed);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
return cHTTPConnection::DataReceived("", 0); // If the request has zero body length, let it be processed right-away
|
||||
cHTTPConnection::OnReceivedData("", 0); // If the request has zero body length, let it be processed right-away
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,8 +222,9 @@ bool cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
|
||||
if (!m_CurrentRequest->DoesAllowKeepAlive())
|
||||
{
|
||||
m_State = wcsInvalid;
|
||||
m_HTTPServer.CloseConnection(*this);
|
||||
return true;
|
||||
m_Link->Close();
|
||||
m_Link.reset();
|
||||
return;
|
||||
}
|
||||
delete m_CurrentRequest;
|
||||
m_CurrentRequest = nullptr;
|
||||
@ -225,32 +238,39 @@ bool cHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPConnection::GetOutgoingData(AString & a_Data)
|
||||
{
|
||||
std::swap(a_Data, m_OutgoingData);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPConnection::SocketClosed(void)
|
||||
void cHTTPConnection::OnRemoteClosed(void)
|
||||
{
|
||||
if (m_CurrentRequest != nullptr)
|
||||
{
|
||||
m_HTTPServer.RequestFinished(*this, *m_CurrentRequest);
|
||||
}
|
||||
m_HTTPServer.CloseConnection(*this);
|
||||
m_Link.reset();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPConnection::OnError(int a_ErrorCode, const AString & a_ErrorMsg)
|
||||
{
|
||||
OnRemoteClosed();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPConnection::SendData(const void * a_Data, size_t a_Size)
|
||||
{
|
||||
m_Link->Send(a_Data, a_Size);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../OSSupport/SocketThreads.h"
|
||||
#include "../OSSupport/Network.h"
|
||||
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ class cHTTPRequest;
|
||||
|
||||
|
||||
class cHTTPConnection :
|
||||
public cSocketThreads::cCallback
|
||||
public cTCPLink::cCallbacks
|
||||
{
|
||||
public:
|
||||
|
||||
@ -78,9 +78,6 @@ protected:
|
||||
/** Status in which the request currently is */
|
||||
eState m_State;
|
||||
|
||||
/** Data that is queued for sending, once the socket becomes writable */
|
||||
AString m_OutgoingData;
|
||||
|
||||
/** The request being currently received
|
||||
Valid only between having parsed the headers and finishing receiving the body. */
|
||||
cHTTPRequest * m_CurrentRequest;
|
||||
@ -88,18 +85,34 @@ protected:
|
||||
/** Number of bytes that remain to read for the complete body of the message to be received.
|
||||
Valid only in wcsRecvBody */
|
||||
size_t m_CurrentRequestBodyRemaining;
|
||||
|
||||
/** The network link attached to this connection. */
|
||||
cTCPLinkPtr m_Link;
|
||||
|
||||
|
||||
// cSocketThreads::cCallback overrides:
|
||||
/** Data is received from the client.
|
||||
Returns true if the connection has been closed as the result of parsing the data. */
|
||||
virtual bool DataReceived(const char * a_Data, size_t a_Size) override;
|
||||
// cTCPLink::cCallbacks overrides:
|
||||
/** The link instance has been created, remember it. */
|
||||
virtual void OnLinkCreated(cTCPLinkPtr a_Link) override;
|
||||
|
||||
/** Data is received from the client. */
|
||||
virtual void OnReceivedData(const char * a_Data, size_t a_Size) override;
|
||||
|
||||
/** Data can be sent to client */
|
||||
virtual void GetOutgoingData(AString & a_Data) override;
|
||||
/** The socket has been closed for any reason. */
|
||||
virtual void OnRemoteClosed(void) override;
|
||||
|
||||
/** The socket has been closed for any reason */
|
||||
virtual void SocketClosed(void) override;
|
||||
/** An error has occurred on the socket. */
|
||||
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override;
|
||||
|
||||
// Overridable:
|
||||
/** Called to send raw data over the link. Descendants may provide data transformations (SSL etc.) */
|
||||
virtual void SendData(const void * a_Data, size_t a_Size);
|
||||
|
||||
/** Sends the raw data over the link.
|
||||
Descendants may provide data transformations (SSL etc.) via the overridable SendData() function. */
|
||||
void SendData(const AString & a_Data)
|
||||
{
|
||||
SendData(a_Data.data(), a_Data.size());
|
||||
}
|
||||
} ;
|
||||
|
||||
typedef std::vector<cHTTPConnection *> cHTTPConnections;
|
||||
|
@ -55,7 +55,10 @@ void cHTTPMessage::AddHeader(const AString & a_Key, const AString & a_Value)
|
||||
}
|
||||
else if (Key == "content-length")
|
||||
{
|
||||
m_ContentLength = static_cast<size_t>(atol(m_Headers[Key].c_str()));
|
||||
if (!StringToInteger(m_Headers[Key], m_ContentLength))
|
||||
{
|
||||
m_ContentLength = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,12 +118,46 @@ class cDebugCallbacks :
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// cHTTPServerListenCallbacks:
|
||||
|
||||
class cHTTPServerListenCallbacks:
|
||||
public cNetwork::cListenCallbacks
|
||||
{
|
||||
public:
|
||||
cHTTPServerListenCallbacks(cHTTPServer & a_HTTPServer, UInt16 a_Port):
|
||||
m_HTTPServer(a_HTTPServer),
|
||||
m_Port(a_Port)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
/** The HTTP server instance that we're attached to. */
|
||||
cHTTPServer & m_HTTPServer;
|
||||
|
||||
/** The port for which this instance is responsible. */
|
||||
UInt16 m_Port;
|
||||
|
||||
// cNetwork::cListenCallbacks overrides:
|
||||
virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) override
|
||||
{
|
||||
return m_HTTPServer.OnIncomingConnection(a_RemoteIPAddress, a_RemotePort);
|
||||
}
|
||||
virtual void OnAccepted(cTCPLink & a_Link) override {}
|
||||
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override
|
||||
{
|
||||
LOGWARNING("HTTP server error on port %d: %d (%s)", m_Port, a_ErrorCode, a_ErrorMsg.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// cHTTPServer:
|
||||
|
||||
cHTTPServer::cHTTPServer(void) :
|
||||
m_ListenThreadIPv4(*this, cSocket::IPv4, "WebServer"),
|
||||
m_ListenThreadIPv6(*this, cSocket::IPv6, "WebServer"),
|
||||
m_Callbacks(nullptr)
|
||||
{
|
||||
}
|
||||
@ -141,7 +175,7 @@ cHTTPServer::~cHTTPServer()
|
||||
|
||||
|
||||
|
||||
bool cHTTPServer::Initialize(const AString & a_PortsIPv4, const AString & a_PortsIPv6)
|
||||
bool cHTTPServer::Initialize(void)
|
||||
{
|
||||
// Read the HTTPS cert + key:
|
||||
AString CertFile = cFile::ReadWholeFile("webadmin/httpscert.crt");
|
||||
@ -177,18 +211,6 @@ bool cHTTPServer::Initialize(const AString & a_PortsIPv4, const AString & a_Port
|
||||
{
|
||||
LOGINFO("WebServer: The server is running in secure HTTPS mode.");
|
||||
}
|
||||
|
||||
// Open up requested ports:
|
||||
bool HasAnyPort;
|
||||
m_ListenThreadIPv4.SetReuseAddr(true);
|
||||
m_ListenThreadIPv6.SetReuseAddr(true);
|
||||
HasAnyPort = m_ListenThreadIPv4.Initialize(a_PortsIPv4);
|
||||
HasAnyPort = m_ListenThreadIPv6.Initialize(a_PortsIPv6) || HasAnyPort;
|
||||
if (!HasAnyPort)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -196,19 +218,28 @@ bool cHTTPServer::Initialize(const AString & a_PortsIPv4, const AString & a_Port
|
||||
|
||||
|
||||
|
||||
bool cHTTPServer::Start(cCallbacks & a_Callbacks)
|
||||
bool cHTTPServer::Start(cCallbacks & a_Callbacks, const AStringVector & a_Ports)
|
||||
{
|
||||
m_Callbacks = &a_Callbacks;
|
||||
if (!m_ListenThreadIPv4.Start())
|
||||
|
||||
// Open up requested ports:
|
||||
for (auto port : a_Ports)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!m_ListenThreadIPv6.Start())
|
||||
{
|
||||
m_ListenThreadIPv4.Stop();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
UInt16 PortNum;
|
||||
if (!StringToInteger(port, PortNum))
|
||||
{
|
||||
LOGWARNING("WebServer: Invalid port value: \"%s\". Ignoring.", port.c_str());
|
||||
continue;
|
||||
}
|
||||
auto Handle = cNetwork::Listen(PortNum, std::make_shared<cHTTPServerListenCallbacks>(*this, PortNum));
|
||||
if (Handle->IsListening())
|
||||
{
|
||||
m_ServerHandles.push_back(Handle);
|
||||
}
|
||||
} // for port - a_Ports[]
|
||||
|
||||
// Report success if at least one port opened successfully:
|
||||
return !m_ServerHandles.empty();
|
||||
}
|
||||
|
||||
|
||||
@ -217,63 +248,30 @@ bool cHTTPServer::Start(cCallbacks & a_Callbacks)
|
||||
|
||||
void cHTTPServer::Stop(void)
|
||||
{
|
||||
m_ListenThreadIPv4.Stop();
|
||||
m_ListenThreadIPv6.Stop();
|
||||
|
||||
// Drop all current connections:
|
||||
cCSLock Lock(m_CSConnections);
|
||||
while (!m_Connections.empty())
|
||||
for (auto handle : m_ServerHandles)
|
||||
{
|
||||
m_Connections.front()->Terminate();
|
||||
} // for itr - m_Connections[]
|
||||
handle->Close();
|
||||
}
|
||||
m_ServerHandles.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPServer::OnConnectionAccepted(cSocket & a_Socket)
|
||||
cTCPLink::cCallbacksPtr cHTTPServer::OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort)
|
||||
{
|
||||
cHTTPConnection * Connection;
|
||||
UNUSED(a_RemoteIPAddress);
|
||||
UNUSED(a_RemotePort);
|
||||
|
||||
if (m_Cert.get() != nullptr)
|
||||
{
|
||||
Connection = new cSslHTTPConnection(*this, m_Cert, m_CertPrivKey);
|
||||
return std::make_shared<cSslHTTPConnection>(*this, m_Cert, m_CertPrivKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
Connection = new cHTTPConnection(*this);
|
||||
return std::make_shared<cHTTPConnection>(*this);
|
||||
}
|
||||
m_SocketThreads.AddClient(a_Socket, Connection);
|
||||
cCSLock Lock(m_CSConnections);
|
||||
m_Connections.push_back(Connection);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPServer::CloseConnection(cHTTPConnection & a_Connection)
|
||||
{
|
||||
m_SocketThreads.RemoveClient(&a_Connection);
|
||||
cCSLock Lock(m_CSConnections);
|
||||
for (cHTTPConnections::iterator itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr)
|
||||
{
|
||||
if (*itr == &a_Connection)
|
||||
{
|
||||
m_Connections.erase(itr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
delete &a_Connection;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cHTTPServer::NotifyConnectionWrite(cHTTPConnection & a_Connection)
|
||||
{
|
||||
m_SocketThreads.NotifyWrite(&a_Connection);
|
||||
}
|
||||
|
||||
|
||||
|
@ -9,8 +9,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "../OSSupport/ListenThread.h"
|
||||
#include "../OSSupport/SocketThreads.h"
|
||||
#include "../OSSupport/Network.h"
|
||||
#include "../IniFile.h"
|
||||
#include "PolarSSL++/RsaPrivateKey.h"
|
||||
#include "PolarSSL++/CryptoKey.h"
|
||||
@ -33,8 +32,7 @@ typedef std::vector<cHTTPConnection *> cHTTPConnections;
|
||||
|
||||
|
||||
|
||||
class cHTTPServer :
|
||||
public cListenThread::cCallback
|
||||
class cHTTPServer
|
||||
{
|
||||
public:
|
||||
class cCallbacks
|
||||
@ -42,44 +40,39 @@ public:
|
||||
public:
|
||||
virtual ~cCallbacks() {}
|
||||
|
||||
/** Called when a new request arrives over a connection and its headers have been parsed.
|
||||
The request body needn't have arrived yet.
|
||||
*/
|
||||
/** Called when a new request arrives over a connection and all its headers have been parsed.
|
||||
The request body needn't have arrived yet. */
|
||||
virtual void OnRequestBegun(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) = 0;
|
||||
|
||||
/** Called when another part of request body has arrived.
|
||||
May be called multiple times for a single request. */
|
||||
virtual void OnRequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size) = 0;
|
||||
|
||||
/// Called when the request body has been fully received in previous calls to OnRequestBody()
|
||||
/** Called when the request body has been fully received in previous calls to OnRequestBody() */
|
||||
virtual void OnRequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request) = 0;
|
||||
} ;
|
||||
|
||||
cHTTPServer(void);
|
||||
virtual ~cHTTPServer();
|
||||
|
||||
/// Initializes the server on the specified ports
|
||||
bool Initialize(const AString & a_PortsIPv4, const AString & a_PortsIPv6);
|
||||
/** Initializes the server - reads the cert files etc. */
|
||||
bool Initialize(void);
|
||||
|
||||
/// Starts the server and assigns the callbacks to use for incoming requests
|
||||
bool Start(cCallbacks & a_Callbacks);
|
||||
/** Starts the server and assigns the callbacks to use for incoming requests */
|
||||
bool Start(cCallbacks & a_Callbacks, const AStringVector & a_Ports);
|
||||
|
||||
/// Stops the server, drops all current connections
|
||||
/** Stops the server, drops all current connections */
|
||||
void Stop(void);
|
||||
|
||||
protected:
|
||||
friend class cHTTPConnection;
|
||||
friend class cSslHTTPConnection;
|
||||
friend class cHTTPServerListenCallbacks;
|
||||
|
||||
cListenThread m_ListenThreadIPv4;
|
||||
cListenThread m_ListenThreadIPv6;
|
||||
/** The cNetwork API handle for the listening socket. */
|
||||
cServerHandlePtrs m_ServerHandles;
|
||||
|
||||
cSocketThreads m_SocketThreads;
|
||||
|
||||
cCriticalSection m_CSConnections;
|
||||
cHTTPConnections m_Connections; ///< All the connections that are currently being serviced
|
||||
|
||||
/// The callbacks to call for various events
|
||||
/** The callbacks to call for various events */
|
||||
cCallbacks * m_Callbacks;
|
||||
|
||||
/** The server certificate to use for the SSL connections */
|
||||
@ -89,23 +82,18 @@ protected:
|
||||
cCryptoKeyPtr m_CertPrivKey;
|
||||
|
||||
|
||||
// cListenThread::cCallback overrides:
|
||||
virtual void OnConnectionAccepted(cSocket & a_Socket) override;
|
||||
|
||||
/// Called by cHTTPConnection to close the connection (presumably due to an error)
|
||||
void CloseConnection(cHTTPConnection & a_Connection);
|
||||
|
||||
/// Called by cHTTPConnection to notify SocketThreads that there's data to be sent for the connection
|
||||
void NotifyConnectionWrite(cHTTPConnection & a_Connection);
|
||||
|
||||
/// Called by cHTTPConnection when it finishes parsing the request header
|
||||
/** Called by cHTTPServerListenCallbacks when there's a new incoming connection.
|
||||
Returns the connection instance to be used as the cTCPLink callbacks. */
|
||||
cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort);
|
||||
|
||||
/** Called by cHTTPConnection when it finishes parsing the request header */
|
||||
void NewRequest(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
|
||||
|
||||
/** Called by cHTTPConenction when it receives more data for the request body.
|
||||
May be called multiple times for a single request. */
|
||||
void RequestBody(cHTTPConnection & a_Connection, cHTTPRequest & a_Request, const char * a_Data, size_t a_Size);
|
||||
|
||||
/// Called by cHTTPConnection when it detects that the request has finished (all of its body has been received)
|
||||
/** Called by cHTTPConnection when it detects that the request has finished (all of its body has been received) */
|
||||
void RequestFinished(cHTTPConnection & a_Connection, cHTTPRequest & a_Request);
|
||||
} ;
|
||||
|
||||
|
@ -25,14 +25,8 @@ cSslHTTPConnection::cSslHTTPConnection(cHTTPServer & a_HTTPServer, const cX509Ce
|
||||
|
||||
|
||||
|
||||
bool cSslHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
|
||||
void cSslHTTPConnection::OnReceivedData(const char * a_Data, size_t a_Size)
|
||||
{
|
||||
// If there is outgoing data in the queue, notify the server that it should write it out:
|
||||
if (!m_OutgoingData.empty())
|
||||
{
|
||||
m_HTTPServer.NotifyConnectionWrite(*this);
|
||||
}
|
||||
|
||||
// Process the received data:
|
||||
const char * Data = a_Data;
|
||||
size_t Size = a_Size;
|
||||
@ -52,17 +46,18 @@ bool cSslHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
|
||||
int NumRead = m_Ssl.ReadPlain(Buffer, sizeof(Buffer));
|
||||
if (NumRead > 0)
|
||||
{
|
||||
if (super::DataReceived(Buffer, (size_t)NumRead))
|
||||
{
|
||||
// The socket has been closed, and the object is already deleted. Bail out.
|
||||
return true;
|
||||
}
|
||||
super::OnReceivedData(Buffer, (size_t)NumRead);
|
||||
}
|
||||
else if (NumRead == POLARSSL_ERR_NET_WANT_READ)
|
||||
{
|
||||
// SSL requires us to send data to peer first, do so by "sending" empty data:
|
||||
SendData(nullptr, 0);
|
||||
}
|
||||
|
||||
// If both failed, bail out:
|
||||
if ((BytesWritten == 0) && (NumRead <= 0))
|
||||
{
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -71,18 +66,20 @@ bool cSslHTTPConnection::DataReceived(const char * a_Data, size_t a_Size)
|
||||
|
||||
|
||||
|
||||
void cSslHTTPConnection::GetOutgoingData(AString & a_Data)
|
||||
void cSslHTTPConnection::SendData(const void * a_Data, size_t a_Size)
|
||||
{
|
||||
const char * OutgoingData = reinterpret_cast<const char *>(a_Data);
|
||||
size_t pos = 0;
|
||||
for (;;)
|
||||
{
|
||||
// Write as many bytes from our buffer to SSL's encryption as possible:
|
||||
int NumWritten = 0;
|
||||
if (!m_OutgoingData.empty())
|
||||
if (pos < a_Size)
|
||||
{
|
||||
NumWritten = m_Ssl.WritePlain(m_OutgoingData.data(), m_OutgoingData.size());
|
||||
NumWritten = m_Ssl.WritePlain(OutgoingData + pos, a_Size - pos);
|
||||
if (NumWritten > 0)
|
||||
{
|
||||
m_OutgoingData.erase(0, (size_t)NumWritten);
|
||||
pos += static_cast<size_t>(NumWritten);
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,7 +88,7 @@ void cSslHTTPConnection::GetOutgoingData(AString & a_Data)
|
||||
size_t NumBytes = m_Ssl.ReadOutgoing(Buffer, sizeof(Buffer));
|
||||
if (NumBytes > 0)
|
||||
{
|
||||
a_Data.append(Buffer, NumBytes);
|
||||
m_Link->Send(Buffer, NumBytes);
|
||||
}
|
||||
|
||||
// If both failed, bail out:
|
||||
|
@ -36,8 +36,8 @@ protected:
|
||||
cCryptoKeyPtr m_PrivateKey;
|
||||
|
||||
// cHTTPConnection overrides:
|
||||
virtual bool DataReceived (const char * a_Data, size_t a_Size) override; // Data is received from the client
|
||||
virtual void GetOutgoingData(AString & a_Data) override; // Data can be sent to client
|
||||
virtual void OnReceivedData(const char * a_Data, size_t a_Size) override; // Data is received from the client
|
||||
virtual void SendData(const void * a_Data, size_t a_Size) override; // Data is to be sent to client
|
||||
} ;
|
||||
|
||||
|
||||
|
@ -888,3 +888,39 @@ void cIniFile::RemoveBom(AString & a_line) const
|
||||
|
||||
|
||||
|
||||
|
||||
AStringVector ReadUpgradeIniPorts(
|
||||
cIniFile & a_IniFile,
|
||||
const AString & a_KeyName,
|
||||
const AString & a_PortsValueName,
|
||||
const AString & a_OldIPv4ValueName,
|
||||
const AString & a_OldIPv6ValueName,
|
||||
const AString & a_DefaultValue
|
||||
)
|
||||
{
|
||||
// Read the regular value, but don't use the default (in order to detect missing value for upgrade):
|
||||
AStringVector Ports = StringSplitAndTrim(a_IniFile.GetValue(a_KeyName, a_PortsValueName), ";,");
|
||||
|
||||
if (Ports.empty())
|
||||
{
|
||||
// Historically there were two separate entries for IPv4 and IPv6, merge them and migrate:
|
||||
AString Ports4 = a_IniFile.GetValue(a_KeyName, a_OldIPv4ValueName, a_DefaultValue);
|
||||
AString Ports6 = a_IniFile.GetValue(a_KeyName, a_OldIPv6ValueName);
|
||||
Ports = MergeStringVectors(StringSplitAndTrim(Ports4, ";,"), StringSplitAndTrim(Ports6, ";,"));
|
||||
a_IniFile.DeleteValue(a_KeyName, a_OldIPv4ValueName);
|
||||
a_IniFile.DeleteValue(a_KeyName, a_OldIPv6ValueName);
|
||||
|
||||
// If those weren't present or were empty, use the default:"
|
||||
if (Ports.empty())
|
||||
{
|
||||
Ports = StringSplitAndTrim(a_DefaultValue, ";,");
|
||||
}
|
||||
a_IniFile.SetValue(a_KeyName, a_PortsValueName, StringsConcat(Ports, ','));
|
||||
}
|
||||
|
||||
return Ports;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -15,9 +15,7 @@
|
||||
!! MODIFIED BY FAKETRUTH and madmaxoft!!
|
||||
*/
|
||||
|
||||
#ifndef CIniFile_H
|
||||
#define CIniFile_H
|
||||
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
@ -215,4 +213,22 @@ public:
|
||||
|
||||
// tolua_end
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
|
||||
/** Reads the list of ports from the INI file, possibly upgrading from IPv4/IPv6-specific values into new version-agnostic value.
|
||||
Reads the list of ports from a_PortsValueName. If that value doesn't exist or is empty, the list is combined from values
|
||||
in a_OldIPv4ValueName and a_OldIPv6ValueName; in this case the old values are removed from the INI file.
|
||||
If there is none of the three values or they are all empty, the default is used and stored in the Ports value. */
|
||||
AStringVector ReadUpgradeIniPorts(
|
||||
cIniFile & a_IniFile,
|
||||
const AString & a_KeyName,
|
||||
const AString & a_PortsValueName,
|
||||
const AString & a_OldIPv4ValueName,
|
||||
const AString & a_OldIPv6ValueName,
|
||||
const AString & a_DefaultValue
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
@ -13,12 +13,9 @@ SET (SRCS
|
||||
HostnameLookup.cpp
|
||||
IPLookup.cpp
|
||||
IsThread.cpp
|
||||
ListenThread.cpp
|
||||
NetworkSingleton.cpp
|
||||
Semaphore.cpp
|
||||
ServerHandleImpl.cpp
|
||||
Socket.cpp
|
||||
SocketThreads.cpp
|
||||
StackTrace.cpp
|
||||
TCPLinkImpl.cpp
|
||||
)
|
||||
@ -32,14 +29,11 @@ SET (HDRS
|
||||
HostnameLookup.h
|
||||
IPLookup.h
|
||||
IsThread.h
|
||||
ListenThread.h
|
||||
Network.h
|
||||
NetworkSingleton.h
|
||||
Queue.h
|
||||
Semaphore.h
|
||||
ServerHandleImpl.h
|
||||
Socket.h
|
||||
SocketThreads.h
|
||||
StackTrace.h
|
||||
TCPLinkImpl.h
|
||||
)
|
||||
|
@ -126,7 +126,7 @@ public:
|
||||
|
||||
/** Returns the entire contents of the specified file as a string. Returns empty string on error. */
|
||||
static AString ReadWholeFile(const AString & a_FileName);
|
||||
|
||||
|
||||
// tolua_end
|
||||
|
||||
/** Returns the list of all items in the specified folder (files, folders, nix pipes, whatever's there). */
|
||||
|
@ -1,238 +0,0 @@
|
||||
|
||||
// ListenThread.cpp
|
||||
|
||||
// Implements the cListenThread class representing the thread that listens for client connections
|
||||
|
||||
#include "Globals.h"
|
||||
#include "ListenThread.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cListenThread::cListenThread(cCallback & a_Callback, cSocket::eFamily a_Family, const AString & a_ServiceName) :
|
||||
super(Printf("ListenThread %s", a_ServiceName.c_str())),
|
||||
m_Callback(a_Callback),
|
||||
m_Family(a_Family),
|
||||
m_ShouldReuseAddr(false),
|
||||
m_ServiceName(a_ServiceName)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cListenThread::~cListenThread()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cListenThread::Initialize(const AString & a_PortsString)
|
||||
{
|
||||
ASSERT(m_Sockets.empty()); // Not yet started
|
||||
|
||||
if (!CreateSockets(a_PortsString))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cListenThread::Start(void)
|
||||
{
|
||||
if (m_Sockets.empty())
|
||||
{
|
||||
// There are no sockets listening, either forgotten to initialize or the user specified no listening ports
|
||||
// Report as successful, though
|
||||
return true;
|
||||
}
|
||||
return super::Start();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cListenThread::Stop(void)
|
||||
{
|
||||
if (m_Sockets.empty())
|
||||
{
|
||||
// No sockets means no thread was running in the first place
|
||||
return;
|
||||
}
|
||||
|
||||
m_ShouldTerminate = true;
|
||||
|
||||
// Close one socket to wake the thread up from the select() call
|
||||
m_Sockets[0].CloseSocket();
|
||||
|
||||
// Wait for the thread to finish
|
||||
super::Wait();
|
||||
|
||||
// Close all the listening sockets:
|
||||
for (cSockets::iterator itr = m_Sockets.begin() + 1, end = m_Sockets.end(); itr != end; ++itr)
|
||||
{
|
||||
itr->CloseSocket();
|
||||
} // for itr - m_Sockets[]
|
||||
m_Sockets.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cListenThread::SetReuseAddr(bool a_Reuse)
|
||||
{
|
||||
ASSERT(m_Sockets.empty()); // Must not have been Initialize()d yet
|
||||
|
||||
m_ShouldReuseAddr = a_Reuse;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cListenThread::CreateSockets(const AString & a_PortsString)
|
||||
{
|
||||
AStringVector Ports = StringSplitAndTrim(a_PortsString, ",");
|
||||
|
||||
if (Ports.empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AString FamilyStr = m_ServiceName;
|
||||
switch (m_Family)
|
||||
{
|
||||
case cSocket::IPv4: FamilyStr.append(" IPv4"); break;
|
||||
case cSocket::IPv6: FamilyStr.append(" IPv6"); break;
|
||||
default:
|
||||
{
|
||||
ASSERT(!"Unknown address family");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (AStringVector::const_iterator itr = Ports.begin(), end = Ports.end(); itr != end; ++itr)
|
||||
{
|
||||
int Port = atoi(itr->c_str());
|
||||
if ((Port <= 0) || (Port > 65535))
|
||||
{
|
||||
LOGWARNING("%s: Invalid port specified: \"%s\".", FamilyStr.c_str(), itr->c_str());
|
||||
continue;
|
||||
}
|
||||
m_Sockets.push_back(cSocket::CreateSocket(m_Family));
|
||||
if (!m_Sockets.back().IsValid())
|
||||
{
|
||||
LOGWARNING("%s: Cannot create listening socket for port %d: \"%s\"", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str());
|
||||
m_Sockets.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_ShouldReuseAddr)
|
||||
{
|
||||
if (!m_Sockets.back().SetReuseAddress())
|
||||
{
|
||||
LOG("%s: Port %d cannot reuse addr, syscall failed: \"%s\".", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// Bind to port:
|
||||
bool res = false;
|
||||
switch (m_Family)
|
||||
{
|
||||
case cSocket::IPv4: res = m_Sockets.back().BindToAnyIPv4(Port); break;
|
||||
case cSocket::IPv6: res = m_Sockets.back().BindToAnyIPv6(Port); break;
|
||||
default:
|
||||
{
|
||||
ASSERT(!"Unknown address family");
|
||||
res = false;
|
||||
}
|
||||
}
|
||||
if (!res)
|
||||
{
|
||||
LOGWARNING("%s: Cannot bind port %d: \"%s\".", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str());
|
||||
m_Sockets.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!m_Sockets.back().Listen())
|
||||
{
|
||||
LOGWARNING("%s: Cannot listen on port %d: \"%s\".", FamilyStr.c_str(), Port, cSocket::GetLastErrorString().c_str());
|
||||
m_Sockets.pop_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
LOGINFO("%s: Port %d is open for connections", FamilyStr.c_str(), Port);
|
||||
} // for itr - Ports[]
|
||||
|
||||
return !(m_Sockets.empty());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cListenThread::Execute(void)
|
||||
{
|
||||
if (m_Sockets.empty())
|
||||
{
|
||||
LOGD("Empty cListenThread, ending thread now.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the highest socket number:
|
||||
cSocket::xSocket Highest = m_Sockets[0].GetSocket();
|
||||
for (cSockets::iterator itr = m_Sockets.begin(), end = m_Sockets.end(); itr != end; ++itr)
|
||||
{
|
||||
if (itr->GetSocket() > Highest)
|
||||
{
|
||||
Highest = itr->GetSocket();
|
||||
}
|
||||
} // for itr - m_Sockets[]
|
||||
|
||||
while (!m_ShouldTerminate)
|
||||
{
|
||||
// Put all sockets into a FD set:
|
||||
fd_set fdRead;
|
||||
FD_ZERO(&fdRead);
|
||||
for (cSockets::iterator itr = m_Sockets.begin(), end = m_Sockets.end(); itr != end; ++itr)
|
||||
{
|
||||
FD_SET(itr->GetSocket(), &fdRead);
|
||||
} // for itr - m_Sockets[]
|
||||
|
||||
timeval tv; // On Linux select() doesn't seem to wake up when socket is closed, so let's kinda busy-wait:
|
||||
tv.tv_sec = 1;
|
||||
tv.tv_usec = 0;
|
||||
if (select((int)Highest + 1, &fdRead, nullptr, nullptr, &tv) == -1)
|
||||
{
|
||||
LOG("select(R) call failed in cListenThread: \"%s\"", cSocket::GetLastErrorString().c_str());
|
||||
continue;
|
||||
}
|
||||
for (cSockets::iterator itr = m_Sockets.begin(), end = m_Sockets.end(); itr != end; ++itr)
|
||||
{
|
||||
if (itr->IsValid() && FD_ISSET(itr->GetSocket(), &fdRead))
|
||||
{
|
||||
cSocket Client = (m_Family == cSocket::IPv4) ? itr->AcceptIPv4() : itr->AcceptIPv6();
|
||||
if (Client.IsValid())
|
||||
{
|
||||
m_Callback.OnConnectionAccepted(Client);
|
||||
}
|
||||
}
|
||||
} // for itr - m_Sockets[]
|
||||
} // while (!m_ShouldTerminate)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -1,85 +0,0 @@
|
||||
|
||||
// ListenThread.h
|
||||
|
||||
// Declares the cListenThread class representing the thread that listens for client connections
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "IsThread.h"
|
||||
#include "Socket.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// fwd:
|
||||
class cServer;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class cListenThread :
|
||||
public cIsThread
|
||||
{
|
||||
typedef cIsThread super;
|
||||
|
||||
public:
|
||||
/** Used as the callback for connection events */
|
||||
class cCallback
|
||||
{
|
||||
public:
|
||||
virtual ~cCallback() {}
|
||||
|
||||
/** This callback is called whenever a socket connection is accepted */
|
||||
virtual void OnConnectionAccepted(cSocket & a_Socket) = 0;
|
||||
} ;
|
||||
|
||||
cListenThread(cCallback & a_Callback, cSocket::eFamily a_Family, const AString & a_ServiceName = "");
|
||||
~cListenThread();
|
||||
|
||||
/** Creates all the sockets, returns trus if successful, false if not. */
|
||||
bool Initialize(const AString & a_PortsString);
|
||||
|
||||
bool Start(void);
|
||||
|
||||
void Stop(void);
|
||||
|
||||
/** Call before Initialize() to set the "reuse" flag on the sockets */
|
||||
void SetReuseAddr(bool a_Reuse = true);
|
||||
|
||||
protected:
|
||||
typedef std::vector<cSocket> cSockets;
|
||||
|
||||
/** The callback which to notify of incoming connections */
|
||||
cCallback & m_Callback;
|
||||
|
||||
/** Socket address family to use */
|
||||
cSocket::eFamily m_Family;
|
||||
|
||||
/** Sockets that are being monitored */
|
||||
cSockets m_Sockets;
|
||||
|
||||
/** If set to true, the SO_REUSEADDR socket option is set to true */
|
||||
bool m_ShouldReuseAddr;
|
||||
|
||||
/** Name of the service that's listening on the ports; for logging purposes only */
|
||||
AString m_ServiceName;
|
||||
|
||||
|
||||
/** Fills in m_Sockets with individual sockets, each for one port specified in a_PortsString.
|
||||
Returns true if successful and at least one socket has been created
|
||||
*/
|
||||
bool CreateSockets(const AString & a_PortsString);
|
||||
|
||||
// cIsThread override:
|
||||
virtual void Execute(void) override;
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
@ -18,7 +18,8 @@
|
||||
|
||||
|
||||
|
||||
cNetworkSingleton::cNetworkSingleton(void)
|
||||
cNetworkSingleton::cNetworkSingleton(void):
|
||||
m_HasTerminated(false)
|
||||
{
|
||||
// Windows: initialize networking:
|
||||
#ifdef _WIN32
|
||||
@ -72,6 +73,29 @@ cNetworkSingleton::cNetworkSingleton(void)
|
||||
|
||||
cNetworkSingleton::~cNetworkSingleton()
|
||||
{
|
||||
// Check that Terminate has been called already:
|
||||
ASSERT(m_HasTerminated);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cNetworkSingleton & cNetworkSingleton::Get(void)
|
||||
{
|
||||
static cNetworkSingleton Instance;
|
||||
return Instance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cNetworkSingleton::Terminate(void)
|
||||
{
|
||||
ASSERT(!m_HasTerminated);
|
||||
m_HasTerminated = true;
|
||||
|
||||
// Wait for the LibEvent event loop to terminate:
|
||||
event_base_loopbreak(m_EventBase);
|
||||
m_EventLoopTerminated.Wait();
|
||||
@ -96,16 +120,6 @@ cNetworkSingleton::~cNetworkSingleton()
|
||||
|
||||
|
||||
|
||||
cNetworkSingleton & cNetworkSingleton::Get(void)
|
||||
{
|
||||
static cNetworkSingleton Instance;
|
||||
return Instance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cNetworkSingleton::LogCallback(int a_Severity, const char * a_Msg)
|
||||
{
|
||||
switch (a_Severity)
|
||||
@ -138,6 +152,7 @@ void cNetworkSingleton::RunEventLoop(cNetworkSingleton * a_Self)
|
||||
|
||||
void cNetworkSingleton::AddHostnameLookup(cHostnameLookupPtr a_HostnameLookup)
|
||||
{
|
||||
ASSERT(!m_HasTerminated);
|
||||
cCSLock Lock(m_CS);
|
||||
m_HostnameLookups.push_back(a_HostnameLookup);
|
||||
}
|
||||
@ -148,6 +163,7 @@ void cNetworkSingleton::AddHostnameLookup(cHostnameLookupPtr a_HostnameLookup)
|
||||
|
||||
void cNetworkSingleton::RemoveHostnameLookup(const cHostnameLookup * a_HostnameLookup)
|
||||
{
|
||||
ASSERT(!m_HasTerminated);
|
||||
cCSLock Lock(m_CS);
|
||||
for (auto itr = m_HostnameLookups.begin(), end = m_HostnameLookups.end(); itr != end; ++itr)
|
||||
{
|
||||
@ -165,6 +181,7 @@ void cNetworkSingleton::RemoveHostnameLookup(const cHostnameLookup * a_HostnameL
|
||||
|
||||
void cNetworkSingleton::AddIPLookup(cIPLookupPtr a_IPLookup)
|
||||
{
|
||||
ASSERT(!m_HasTerminated);
|
||||
cCSLock Lock(m_CS);
|
||||
m_IPLookups.push_back(a_IPLookup);
|
||||
}
|
||||
@ -175,6 +192,7 @@ void cNetworkSingleton::AddIPLookup(cIPLookupPtr a_IPLookup)
|
||||
|
||||
void cNetworkSingleton::RemoveIPLookup(const cIPLookup * a_IPLookup)
|
||||
{
|
||||
ASSERT(!m_HasTerminated);
|
||||
cCSLock Lock(m_CS);
|
||||
for (auto itr = m_IPLookups.begin(), end = m_IPLookups.end(); itr != end; ++itr)
|
||||
{
|
||||
@ -192,6 +210,7 @@ void cNetworkSingleton::RemoveIPLookup(const cIPLookup * a_IPLookup)
|
||||
|
||||
void cNetworkSingleton::AddLink(cTCPLinkImplPtr a_Link)
|
||||
{
|
||||
ASSERT(!m_HasTerminated);
|
||||
cCSLock Lock(m_CS);
|
||||
m_Connections.push_back(a_Link);
|
||||
}
|
||||
@ -202,6 +221,7 @@ void cNetworkSingleton::AddLink(cTCPLinkImplPtr a_Link)
|
||||
|
||||
void cNetworkSingleton::RemoveLink(const cTCPLinkImpl * a_Link)
|
||||
{
|
||||
ASSERT(!m_HasTerminated);
|
||||
cCSLock Lock(m_CS);
|
||||
for (auto itr = m_Connections.begin(), end = m_Connections.end(); itr != end; ++itr)
|
||||
{
|
||||
@ -219,6 +239,7 @@ void cNetworkSingleton::RemoveLink(const cTCPLinkImpl * a_Link)
|
||||
|
||||
void cNetworkSingleton::AddServer(cServerHandleImplPtr a_Server)
|
||||
{
|
||||
ASSERT(!m_HasTerminated);
|
||||
cCSLock Lock(m_CS);
|
||||
m_Servers.push_back(a_Server);
|
||||
}
|
||||
@ -229,6 +250,7 @@ void cNetworkSingleton::AddServer(cServerHandleImplPtr a_Server)
|
||||
|
||||
void cNetworkSingleton::RemoveServer(const cServerHandleImpl * a_Server)
|
||||
{
|
||||
ASSERT(!m_HasTerminated);
|
||||
cCSLock Lock(m_CS);
|
||||
for (auto itr = m_Servers.begin(), end = m_Servers.end(); itr != end; ++itr)
|
||||
{
|
||||
|
@ -4,7 +4,8 @@
|
||||
// Declares the cNetworkSingleton class representing the storage for global data pertaining to network API
|
||||
// such as a list of all connections, all listening sockets and the LibEvent dispatch thread.
|
||||
|
||||
// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead
|
||||
// This is an internal header, no-one outside OSSupport should need to include it; use Network.h instead;
|
||||
// the only exception being the main app entrypoint that needs to call Terminate before quitting.
|
||||
|
||||
|
||||
|
||||
@ -48,6 +49,11 @@ public:
|
||||
/** Returns the singleton instance of this class */
|
||||
static cNetworkSingleton & Get(void);
|
||||
|
||||
/** Terminates all network-related threads.
|
||||
To be used only on app shutdown.
|
||||
MSVC runtime requires that the LibEvent networking be shut down before the main() function is exitted; this is the way to do it. */
|
||||
void Terminate(void);
|
||||
|
||||
/** Returns the main LibEvent handle for event registering. */
|
||||
event_base * GetEventBase(void) { return m_EventBase; }
|
||||
|
||||
@ -113,6 +119,9 @@ protected:
|
||||
/** Event that gets signalled when the event loop terminates. */
|
||||
cEvent m_EventLoopTerminated;
|
||||
|
||||
/** Set to true if Terminate has been called. */
|
||||
volatile bool m_HasTerminated;
|
||||
|
||||
|
||||
/** Initializes the LibEvent internals. */
|
||||
cNetworkSingleton(void);
|
||||
|
@ -83,6 +83,9 @@ void cServerHandleImpl::Close(void)
|
||||
|
||||
// Remove the ptr to self, so that the object may be freed:
|
||||
m_SelfPtr.reset();
|
||||
|
||||
// Remove self from cNetworkSingleton:
|
||||
cNetworkSingleton::Get().RemoveServer(this);
|
||||
}
|
||||
|
||||
|
||||
@ -157,10 +160,6 @@ bool cServerHandleImpl::Listen(UInt16 a_Port)
|
||||
int res = setsockopt(MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&Zero), sizeof(Zero));
|
||||
err = EVUTIL_SOCKET_ERROR();
|
||||
NeedsTwoSockets = ((res == SOCKET_ERROR) && (err == WSAENOPROTOOPT));
|
||||
LOGD("setsockopt(IPV6_V6ONLY) returned %d, err is %d (%s). %s",
|
||||
res, err, evutil_socket_error_to_string(err),
|
||||
NeedsTwoSockets ? "Second socket will be created" : "Second socket not needed"
|
||||
);
|
||||
#else
|
||||
setsockopt(MainSock, IPPROTO_IPV6, IPV6_V6ONLY, reinterpret_cast<const char *>(&Zero), sizeof(Zero));
|
||||
#endif
|
||||
@ -256,19 +255,20 @@ void cServerHandleImpl::Callback(evconnlistener * a_Listener, evutil_socket_t a_
|
||||
|
||||
// Get the textual IP address and port number out of a_Addr:
|
||||
char IPAddress[128];
|
||||
evutil_inet_ntop(a_Addr->sa_family, a_Addr->sa_data, IPAddress, ARRAYCOUNT(IPAddress));
|
||||
UInt16 Port = 0;
|
||||
switch (a_Addr->sa_family)
|
||||
{
|
||||
case AF_INET:
|
||||
{
|
||||
sockaddr_in * sin = reinterpret_cast<sockaddr_in *>(a_Addr);
|
||||
evutil_inet_ntop(AF_INET, sin, IPAddress, ARRAYCOUNT(IPAddress));
|
||||
Port = ntohs(sin->sin_port);
|
||||
break;
|
||||
}
|
||||
case AF_INET6:
|
||||
{
|
||||
sockaddr_in6 * sin6 = reinterpret_cast<sockaddr_in6 *>(a_Addr);
|
||||
evutil_inet_ntop(AF_INET, sin6, IPAddress, ARRAYCOUNT(IPAddress));
|
||||
Port = ntohs(sin6->sin6_port);
|
||||
break;
|
||||
}
|
||||
|
@ -1,702 +0,0 @@
|
||||
|
||||
// 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"
|
||||
#include "Errors.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// cSocketThreads:
|
||||
|
||||
cSocketThreads::cSocketThreads(void)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cSocketThreads::~cSocketThreads()
|
||||
{
|
||||
for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
|
||||
{
|
||||
delete *itr;
|
||||
} // for itr - m_Threads[]
|
||||
m_Threads.clear();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cSocketThreads::AddClient(const cSocket & a_Socket, cCallback * a_Client)
|
||||
{
|
||||
// Add a (socket, client) pair for processing, data from a_Socket is to be sent to a_Client
|
||||
|
||||
// Try to add to existing threads:
|
||||
cCSLock Lock(m_CS);
|
||||
for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
|
||||
{
|
||||
if ((*itr)->IsValid() && (*itr)->HasEmptySlot())
|
||||
{
|
||||
(*itr)->AddClient(a_Socket, a_Client);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// No thread has free space, create a new one:
|
||||
LOGD("Creating a new cSocketThread (currently have " SIZE_T_FMT ")", 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;
|
||||
Thread = nullptr;
|
||||
return false;
|
||||
}
|
||||
Thread->AddClient(a_Socket, a_Client);
|
||||
m_Threads.push_back(Thread);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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[]
|
||||
|
||||
// This client wasn't found.
|
||||
// It's not an error, because it may have been removed by a different thread in the meantime.
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cSocketThreads::NotifyWrite(const cCallback * a_Client)
|
||||
{
|
||||
// Notifies the thread responsible for a_Client that the client has something to write
|
||||
|
||||
cCSLock Lock(m_CS);
|
||||
for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
|
||||
{
|
||||
if ((*itr)->NotifyWrite(a_Client))
|
||||
{
|
||||
return;
|
||||
}
|
||||
} // for itr - m_Threads[]
|
||||
|
||||
// Cannot assert - this normally happens if a client disconnects and has pending packets, the cServer::cNotifyWriteThread will call this on invalid clients too
|
||||
// ASSERT(!"Notifying write to an unknown client");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cSocketThreads::Write(const cCallback * a_Client, const AString & a_Data)
|
||||
{
|
||||
// Puts a_Data into outgoing data queue for a_Client
|
||||
cCSLock Lock(m_CS);
|
||||
for (cSocketThreadList::iterator itr = m_Threads.begin(); itr != m_Threads.end(); ++itr)
|
||||
{
|
||||
if ((*itr)->Write(a_Client, a_Data))
|
||||
{
|
||||
return;
|
||||
}
|
||||
} // for itr - m_Threads[]
|
||||
|
||||
// This may be perfectly legal, if the socket has been destroyed and the client is finishing up
|
||||
// ASSERT(!"Writing to an unknown socket");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// cSocketThreads::cSocketThread:
|
||||
|
||||
cSocketThreads::cSocketThread::cSocketThread(cSocketThreads * a_Parent) :
|
||||
cIsThread("cSocketThread"),
|
||||
m_Parent(a_Parent),
|
||||
m_NumSlots(0)
|
||||
{
|
||||
// Nothing needed yet
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cSocketThreads::cSocketThread::~cSocketThread()
|
||||
{
|
||||
m_ShouldTerminate = true;
|
||||
|
||||
// Notify the thread:
|
||||
ASSERT(m_ControlSocket2.IsValid());
|
||||
m_ControlSocket2.Send("a", 1);
|
||||
|
||||
// Wait for the thread to finish:
|
||||
Wait();
|
||||
|
||||
// Close the control sockets:
|
||||
m_ControlSocket1.CloseSocket();
|
||||
m_ControlSocket2.CloseSocket();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cSocketThreads::cSocketThread::AddClient(const cSocket & a_Socket, cCallback * a_Client)
|
||||
{
|
||||
ASSERT(m_Parent->m_CS.IsLockedByCurrentThread());
|
||||
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_Socket.SetNonBlocking();
|
||||
m_Slots[m_NumSlots].m_Outgoing.clear();
|
||||
m_Slots[m_NumSlots].m_State = sSlot::ssNormal;
|
||||
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)
|
||||
{
|
||||
ASSERT(m_Parent->m_CS.IsLockedByCurrentThread());
|
||||
|
||||
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 the slot:
|
||||
if (m_Slots[i].m_State == sSlot::ssRemoteClosed)
|
||||
{
|
||||
// The remote has already closed the socket, remove the slot altogether:
|
||||
if (m_Slots[i].m_Socket.IsValid())
|
||||
{
|
||||
m_Slots[i].m_Socket.CloseSocket();
|
||||
}
|
||||
m_Slots[i] = m_Slots[--m_NumSlots];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Query and queue the last batch of outgoing data:
|
||||
AString Data;
|
||||
m_Slots[i].m_Client->GetOutgoingData(Data);
|
||||
m_Slots[i].m_Outgoing.append(Data);
|
||||
if (m_Slots[i].m_Outgoing.empty())
|
||||
{
|
||||
// No more outgoing data, shut the socket down immediately:
|
||||
m_Slots[i].m_Socket.ShutdownReadWrite();
|
||||
m_Slots[i].m_State = sSlot::ssShuttingDown;
|
||||
}
|
||||
else
|
||||
{
|
||||
// More data to send, shut down reading and wait for the rest to get sent:
|
||||
m_Slots[i].m_State = sSlot::ssWritingRestOut;
|
||||
}
|
||||
m_Slots[i].m_Client = nullptr;
|
||||
}
|
||||
|
||||
// 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
|
||||
{
|
||||
ASSERT(m_Parent->m_CS.IsLockedByCurrentThread());
|
||||
|
||||
for (int i = m_NumSlots - 1; i >= 0; --i)
|
||||
{
|
||||
if (m_Slots[i].m_Client == a_Client)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
} // for i - m_Slots[]
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cSocketThreads::cSocketThread::HasSocket(const cSocket * a_Socket) const
|
||||
{
|
||||
for (int i = m_NumSlots - 1; i >= 0; --i)
|
||||
{
|
||||
if (m_Slots[i].m_Socket == *a_Socket)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
} // for i - m_Slots[]
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cSocketThreads::cSocketThread::NotifyWrite(const cCallback * a_Client)
|
||||
{
|
||||
ASSERT(m_Parent->m_CS.IsLockedByCurrentThread());
|
||||
|
||||
if (HasClient(a_Client))
|
||||
{
|
||||
// Notify the thread that there's another packet in the queue:
|
||||
ASSERT(m_ControlSocket2.IsValid());
|
||||
m_ControlSocket2.Send("q", 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cSocketThreads::cSocketThread::Write(const cCallback * a_Client, const AString & a_Data)
|
||||
{
|
||||
ASSERT(m_Parent->m_CS.IsLockedByCurrentThread());
|
||||
for (int i = m_NumSlots - 1; i >= 0; --i)
|
||||
{
|
||||
if (m_Slots[i].m_Client == a_Client)
|
||||
{
|
||||
m_Slots[i].m_Outgoing.append(a_Data);
|
||||
|
||||
// Notify the thread that there's data in the queue:
|
||||
ASSERT(m_ControlSocket2.IsValid());
|
||||
m_ControlSocket2.Send("q", 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
} // for i - m_Slots[]
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cSocketThreads::cSocketThread::Start(void)
|
||||
{
|
||||
// Create the control socket listener
|
||||
m_ControlSocket2 = cSocket::CreateSocket(cSocket::IPv4);
|
||||
if (!m_ControlSocket2.IsValid())
|
||||
{
|
||||
LOGERROR("Cannot create a Control socket for a cSocketThread (\"%s\"); continuing, but server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
|
||||
return false;
|
||||
}
|
||||
if (!m_ControlSocket2.BindToLocalhostIPv4(cSocket::ANY_PORT))
|
||||
{
|
||||
LOGERROR("Cannot bind a Control socket for a cSocketThread (\"%s\"); continuing, but server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
|
||||
m_ControlSocket2.CloseSocket();
|
||||
return false;
|
||||
}
|
||||
if (!m_ControlSocket2.Listen(1))
|
||||
{
|
||||
LOGERROR("Cannot listen on a Control socket for a cSocketThread (\"%s\"); continuing, but server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
|
||||
m_ControlSocket2.CloseSocket();
|
||||
return false;
|
||||
}
|
||||
if (m_ControlSocket2.GetPort() == 0)
|
||||
{
|
||||
LOGERROR("Cannot determine Control socket port (\"%s\"); conitnuing, but the server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
|
||||
m_ControlSocket2.CloseSocket();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start the thread
|
||||
if (!super::Start())
|
||||
{
|
||||
LOGERROR("Cannot start new cSocketThread");
|
||||
m_ControlSocket2.CloseSocket();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Finish connecting the control socket by accepting connection from the thread's socket
|
||||
cSocket tmp = m_ControlSocket2.AcceptIPv4();
|
||||
if (!tmp.IsValid())
|
||||
{
|
||||
LOGERROR("Cannot link Control sockets for a cSocketThread (\"%s\"); continuing, but server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
|
||||
m_ControlSocket2.CloseSocket();
|
||||
return false;
|
||||
}
|
||||
m_ControlSocket2.CloseSocket();
|
||||
m_ControlSocket2 = tmp;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cSocketThreads::cSocketThread::Execute(void)
|
||||
{
|
||||
// Connect the "client" part of the Control socket:
|
||||
m_ControlSocket1 = cSocket::CreateSocket(cSocket::IPv4);
|
||||
ASSERT(m_ControlSocket2.GetPort() != 0); // We checked in the Start() method, but let's be sure
|
||||
if (!m_ControlSocket1.ConnectToLocalhostIPv4(m_ControlSocket2.GetPort()))
|
||||
{
|
||||
LOGERROR("Cannot connect Control sockets for a cSocketThread (\"%s\"); continuing, but the server may be unreachable from now on.", cSocket::GetLastErrorString().c_str());
|
||||
m_ControlSocket2.CloseSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
// The main thread loop:
|
||||
while (!m_ShouldTerminate)
|
||||
{
|
||||
// Read outgoing data from the clients:
|
||||
QueueOutgoingData();
|
||||
|
||||
// Put sockets into the sets
|
||||
fd_set fdRead;
|
||||
fd_set fdWrite;
|
||||
cSocket::xSocket Highest = m_ControlSocket1.GetSocket();
|
||||
PrepareSets(&fdRead, &fdWrite, Highest);
|
||||
|
||||
// Wait for the sockets:
|
||||
timeval Timeout;
|
||||
Timeout.tv_sec = 5;
|
||||
Timeout.tv_usec = 0;
|
||||
if (select((int)Highest + 1, &fdRead, &fdWrite, nullptr, &Timeout) == -1)
|
||||
{
|
||||
LOG("select() call failed in cSocketThread: \"%s\"", cSocket::GetLastErrorString().c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Perform the IO:
|
||||
ReadFromSockets(&fdRead);
|
||||
WriteToSockets(&fdWrite);
|
||||
CleanUpShutSockets();
|
||||
} // while (!mShouldTerminate)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cSocketThreads::cSocketThread::PrepareSets(fd_set * a_Read, fd_set * a_Write, cSocket::xSocket & a_Highest)
|
||||
{
|
||||
FD_ZERO(a_Read);
|
||||
FD_ZERO(a_Write);
|
||||
FD_SET(m_ControlSocket1.GetSocket(), a_Read);
|
||||
|
||||
cCSLock Lock(m_Parent->m_CS);
|
||||
for (int i = m_NumSlots - 1; i >= 0; --i)
|
||||
{
|
||||
if (!m_Slots[i].m_Socket.IsValid())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (m_Slots[i].m_State == sSlot::ssRemoteClosed)
|
||||
{
|
||||
// This socket won't provide nor consume any data anymore, don't put it in the Set
|
||||
continue;
|
||||
}
|
||||
cSocket::xSocket s = m_Slots[i].m_Socket.GetSocket();
|
||||
FD_SET(s, a_Read);
|
||||
if (s > a_Highest)
|
||||
{
|
||||
a_Highest = s;
|
||||
}
|
||||
if (!m_Slots[i].m_Outgoing.empty())
|
||||
{
|
||||
// There's outgoing data for the socket, put it in the Write set
|
||||
FD_SET(s, a_Write);
|
||||
}
|
||||
} // for i - m_Slots[]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cSocketThreads::cSocketThread::ReadFromSockets(fd_set * a_Read)
|
||||
{
|
||||
// Read on available sockets:
|
||||
|
||||
// Reset Control socket state:
|
||||
if (FD_ISSET(m_ControlSocket1.GetSocket(), a_Read))
|
||||
{
|
||||
char Dummy[128];
|
||||
m_ControlSocket1.Receive(Dummy, sizeof(Dummy), 0);
|
||||
}
|
||||
|
||||
// Read from clients:
|
||||
cCSLock Lock(m_Parent->m_CS);
|
||||
for (int i = m_NumSlots - 1; i >= 0; --i)
|
||||
{
|
||||
cSocket::xSocket Socket = m_Slots[i].m_Socket.GetSocket();
|
||||
if (!cSocket::IsValidSocket(Socket) || !FD_ISSET(Socket, a_Read))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
char Buffer[1024];
|
||||
int Received = m_Slots[i].m_Socket.Receive(Buffer, ARRAYCOUNT(Buffer), 0);
|
||||
if (Received <= 0)
|
||||
{
|
||||
if (cSocket::GetLastError() != cSocket::ErrWouldBlock)
|
||||
{
|
||||
// The socket has been closed by the remote party
|
||||
switch (m_Slots[i].m_State)
|
||||
{
|
||||
case sSlot::ssNormal:
|
||||
{
|
||||
// Close the socket on our side:
|
||||
m_Slots[i].m_State = sSlot::ssRemoteClosed;
|
||||
m_Slots[i].m_Socket.CloseSocket();
|
||||
|
||||
// Notify the callback that the remote has closed the socket, *after* removing the socket:
|
||||
cCallback * client = m_Slots[i].m_Client;
|
||||
m_Slots[i] = m_Slots[--m_NumSlots];
|
||||
if (client != nullptr)
|
||||
{
|
||||
client->SocketClosed();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case sSlot::ssWritingRestOut:
|
||||
case sSlot::ssShuttingDown:
|
||||
case sSlot::ssShuttingDown2:
|
||||
{
|
||||
// Force-close the socket and remove the slot:
|
||||
m_Slots[i].m_Socket.CloseSocket();
|
||||
m_Slots[i] = m_Slots[--m_NumSlots];
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
LOG("%s: Unexpected socket state: %d (%s)",
|
||||
__FUNCTION__, m_Slots[i].m_Socket.GetSocket(), m_Slots[i].m_Socket.GetIPString().c_str()
|
||||
);
|
||||
ASSERT(!"Unexpected socket state");
|
||||
break;
|
||||
}
|
||||
} // switch (m_Slots[i].m_State)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_Slots[i].m_Client != nullptr)
|
||||
{
|
||||
m_Slots[i].m_Client->DataReceived(Buffer, Received);
|
||||
}
|
||||
}
|
||||
} // for i - m_Slots[]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cSocketThreads::cSocketThread::WriteToSockets(fd_set * a_Write)
|
||||
{
|
||||
// Write to available client sockets:
|
||||
cCSLock Lock(m_Parent->m_CS);
|
||||
for (int i = m_NumSlots - 1; i >= 0; --i)
|
||||
{
|
||||
cSocket::xSocket Socket = m_Slots[i].m_Socket.GetSocket();
|
||||
if (!cSocket::IsValidSocket(Socket) || !FD_ISSET(Socket, a_Write))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (m_Slots[i].m_Outgoing.empty())
|
||||
{
|
||||
// Request another chunk of outgoing data:
|
||||
if (m_Slots[i].m_Client != nullptr)
|
||||
{
|
||||
AString Data;
|
||||
m_Slots[i].m_Client->GetOutgoingData(Data);
|
||||
m_Slots[i].m_Outgoing.append(Data);
|
||||
}
|
||||
if (m_Slots[i].m_Outgoing.empty())
|
||||
{
|
||||
// No outgoing data is ready
|
||||
if (m_Slots[i].m_State == sSlot::ssWritingRestOut)
|
||||
{
|
||||
m_Slots[i].m_State = sSlot::ssShuttingDown;
|
||||
m_Slots[i].m_Socket.ShutdownReadWrite();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
} // if (outgoing data is empty)
|
||||
|
||||
if (m_Slots[i].m_State == sSlot::ssRemoteClosed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!SendDataThroughSocket(m_Slots[i].m_Socket, m_Slots[i].m_Outgoing))
|
||||
{
|
||||
int Err = cSocket::GetLastError();
|
||||
LOGWARNING("Error %d while writing to client \"%s\", disconnecting. \"%s\"", Err, m_Slots[i].m_Socket.GetIPString().c_str(), GetOSErrorString(Err).c_str());
|
||||
m_Slots[i].m_Socket.CloseSocket();
|
||||
if (m_Slots[i].m_Client != nullptr)
|
||||
{
|
||||
m_Slots[i].m_Client->SocketClosed();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_Slots[i].m_Outgoing.empty() && (m_Slots[i].m_State == sSlot::ssWritingRestOut))
|
||||
{
|
||||
m_Slots[i].m_State = sSlot::ssShuttingDown;
|
||||
m_Slots[i].m_Socket.ShutdownReadWrite();
|
||||
}
|
||||
|
||||
// _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]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cSocketThreads::cSocketThread::SendDataThroughSocket(cSocket & a_Socket, AString & a_Data)
|
||||
{
|
||||
// Send data in smaller chunks, so that the OS send buffers aren't overflown easily
|
||||
while (!a_Data.empty())
|
||||
{
|
||||
size_t NumToSend = std::min(a_Data.size(), (size_t)1024);
|
||||
int Sent = a_Socket.Send(a_Data.data(), NumToSend);
|
||||
if (Sent < 0)
|
||||
{
|
||||
int Err = cSocket::GetLastError();
|
||||
if (Err == cSocket::ErrWouldBlock)
|
||||
{
|
||||
// The OS send buffer is full, leave the outgoing data for the next time
|
||||
return true;
|
||||
}
|
||||
// An error has occured
|
||||
return false;
|
||||
}
|
||||
if (Sent == 0)
|
||||
{
|
||||
a_Socket.CloseSocket();
|
||||
return true;
|
||||
}
|
||||
a_Data.erase(0, Sent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cSocketThreads::cSocketThread::CleanUpShutSockets(void)
|
||||
{
|
||||
cCSLock Lock(m_Parent->m_CS);
|
||||
for (int i = m_NumSlots - 1; i >= 0; i--)
|
||||
{
|
||||
switch (m_Slots[i].m_State)
|
||||
{
|
||||
case sSlot::ssShuttingDown2:
|
||||
{
|
||||
// The socket has reached the shutdown timeout, close it and clear its slot:
|
||||
m_Slots[i].m_Socket.CloseSocket();
|
||||
m_Slots[i] = m_Slots[--m_NumSlots];
|
||||
break;
|
||||
}
|
||||
case sSlot::ssShuttingDown:
|
||||
{
|
||||
// The socket has been shut down for a single thread loop, let it loop once more before closing:
|
||||
m_Slots[i].m_State = sSlot::ssShuttingDown2;
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
} // for i - m_Slots[]
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void cSocketThreads::cSocketThread::QueueOutgoingData(void)
|
||||
{
|
||||
cCSLock Lock(m_Parent->m_CS);
|
||||
for (int i = 0; i < m_NumSlots; i++)
|
||||
{
|
||||
if (m_Slots[i].m_Client != nullptr)
|
||||
{
|
||||
AString Data;
|
||||
m_Slots[i].m_Client->GetOutgoingData(Data);
|
||||
m_Slots[i].m_Outgoing.append(Data);
|
||||
}
|
||||
if (m_Slots[i].m_Outgoing.empty())
|
||||
{
|
||||
// No outgoing data is ready
|
||||
if (m_Slots[i].m_State == sSlot::ssWritingRestOut)
|
||||
{
|
||||
// The socket doesn't want to be kept alive anymore, and doesn't have any remaining data to send.
|
||||
// Shut it down and then close it after a timeout, or when the other side agrees
|
||||
m_Slots[i].m_State = sSlot::ssShuttingDown;
|
||||
m_Slots[i].m_Socket.ShutdownReadWrite();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -1,194 +0,0 @@
|
||||
|
||||
// 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 wants to terminate the connection, they call the RemoveClient() function. This calls the
|
||||
callback one last time to read all the available outgoing data, putting it in the slot's m_OutgoingData
|
||||
buffer. Then it marks the slot as having no callback. The socket is kept alive until its outgoing data
|
||||
queue is empty, then shutdown is called on it and finally the socket is closed after a timeout.
|
||||
If at any time within this the remote end closes the socket, then the socket is closed directly.
|
||||
As soon as the socket is closed, the slot is finally removed from the SocketThread.
|
||||
The graph in $/docs/SocketThreads States.gv shows the state-machine transitions of the slot.
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/** How many clients should one thread handle? (must be less than FD_SETSIZE for your platform) */
|
||||
#define MAX_SLOTS 63
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#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:
|
||||
// Force a virtual destructor in all subclasses:
|
||||
virtual ~cCallback() {}
|
||||
|
||||
/** Called when data is received from the remote party.
|
||||
SocketThreads does not care about the return value, others can use it for their specific purpose -
|
||||
for example HTTPServer uses it to signal if the connection was terminated as a result of the data received. */
|
||||
virtual bool DataReceived(const char * a_Data, size_t a_Size) = 0;
|
||||
|
||||
/** Called when data can be sent to remote party
|
||||
The function is supposed to *set* outgoing data to a_Data (overwrite) */
|
||||
virtual void GetOutgoingData(AString & a_Data) = 0;
|
||||
|
||||
/** Called when the socket has been closed for any reason */
|
||||
virtual void SocketClosed(void) = 0;
|
||||
} ;
|
||||
|
||||
|
||||
cSocketThreads(void);
|
||||
~cSocketThreads();
|
||||
|
||||
/** Add a (socket, client) pair for processing, data from a_Socket is to be sent to a_Client; returns true if successful */
|
||||
bool AddClient(const cSocket & a_Socket, cCallback * a_Client);
|
||||
|
||||
/** Remove the associated socket and the client from processing.
|
||||
The socket is left to send its last outgoing data and is removed only after all its m_Outgoing is sent
|
||||
and after the socket is properly shutdown (unless the remote disconnects before that)
|
||||
*/
|
||||
void RemoveClient(const cCallback * a_Client);
|
||||
|
||||
/** Notify the thread responsible for a_Client that the client has something to write */
|
||||
void NotifyWrite(const cCallback * a_Client);
|
||||
|
||||
/** Puts a_Data into outgoing data queue for a_Client */
|
||||
void Write(const cCallback * a_Client, const AString & a_Data);
|
||||
|
||||
private:
|
||||
|
||||
class cSocketThread :
|
||||
public cIsThread
|
||||
{
|
||||
typedef cIsThread super;
|
||||
|
||||
public:
|
||||
|
||||
cSocketThread(cSocketThreads * a_Parent);
|
||||
virtual ~cSocketThread();
|
||||
|
||||
// All these methods assume parent's m_CS is locked
|
||||
bool HasEmptySlot(void) const {return m_NumSlots < MAX_SLOTS; }
|
||||
bool IsEmpty (void) const {return m_NumSlots == 0; }
|
||||
|
||||
void AddClient (const cSocket & a_Socket, cCallback * a_Client); // Takes ownership of the socket
|
||||
bool RemoveClient(const cCallback * a_Client); // Returns true if removed, false if not found
|
||||
bool HasClient (const cCallback * a_Client) const;
|
||||
bool HasSocket (const cSocket * a_Socket) const;
|
||||
bool NotifyWrite (const cCallback * a_Client); // Returns true if client handled by this thread
|
||||
bool Write (const cCallback * a_Client, const AString & a_Data); // Returns true if client handled by this thread
|
||||
|
||||
bool 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-dataqueues-state quadruplets.
|
||||
// Manipulation with these assumes that the parent's m_CS is locked
|
||||
struct sSlot
|
||||
{
|
||||
/** The socket is primarily owned by this object */
|
||||
cSocket m_Socket;
|
||||
|
||||
/** The callback to call for events. May be nullptr */
|
||||
cCallback * m_Client;
|
||||
|
||||
/** If sending writes only partial data, the rest is stored here for another send.
|
||||
Also used when the slot is being removed to store the last batch of outgoing data. */
|
||||
AString m_Outgoing;
|
||||
|
||||
enum eState
|
||||
{
|
||||
ssNormal, ///< Normal read / write operations
|
||||
ssWritingRestOut, ///< The client callback was removed, continue to send outgoing data
|
||||
ssShuttingDown, ///< The last outgoing data has been sent, the socket has called shutdown()
|
||||
ssShuttingDown2, ///< The shutdown has been done at least 1 thread loop ago (timeout detection)
|
||||
ssRemoteClosed, ///< The remote end has closed the connection (and we still have a client callback)
|
||||
} m_State;
|
||||
} ;
|
||||
|
||||
sSlot m_Slots[MAX_SLOTS];
|
||||
int m_NumSlots; // Number of slots actually used
|
||||
|
||||
virtual void Execute(void) override;
|
||||
|
||||
/** Prepares the Read and Write socket sets for select()
|
||||
Puts all sockets into the read set, along with m_ControlSocket1.
|
||||
Only sockets that have outgoing data queued on them are put in the write set.*/
|
||||
void PrepareSets(fd_set * a_ReadSet, fd_set * a_WriteSet, cSocket::xSocket & a_Highest);
|
||||
|
||||
/** Reads from sockets indicated in a_Read */
|
||||
void ReadFromSockets(fd_set * a_Read);
|
||||
|
||||
/** Writes to sockets indicated in a_Write */
|
||||
void WriteToSockets (fd_set * a_Write);
|
||||
|
||||
/** Sends data through the specified socket, trying to fill the OS send buffer in chunks.
|
||||
Returns true if there was no error while sending, false if an error has occured.
|
||||
Modifies a_Data to contain only the unsent data. */
|
||||
bool SendDataThroughSocket(cSocket & a_Socket, AString & a_Data);
|
||||
|
||||
/** Removes those slots in ssShuttingDown2 state, sets those with ssShuttingDown state to ssShuttingDown2 */
|
||||
void CleanUpShutSockets(void);
|
||||
|
||||
/** Calls each client's callback to retrieve outgoing data for that client. */
|
||||
void QueueOutgoingData(void);
|
||||
} ;
|
||||
|
||||
typedef std::list<cSocketThread *> cSocketThreadList;
|
||||
|
||||
|
||||
cCriticalSection m_CS;
|
||||
cSocketThreadList m_Threads;
|
||||
} ;
|
||||
|
||||
|
||||
|
||||
|
@ -17,8 +17,9 @@
|
||||
|
||||
cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks):
|
||||
super(a_LinkCallbacks),
|
||||
m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), -1, BEV_OPT_CLOSE_ON_FREE))
|
||||
m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), -1, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE))
|
||||
{
|
||||
LOGD("Created new cTCPLinkImpl at %p with BufferEvent at %p", this, m_BufferEvent);
|
||||
}
|
||||
|
||||
|
||||
@ -27,9 +28,11 @@ cTCPLinkImpl::cTCPLinkImpl(cTCPLink::cCallbacksPtr a_LinkCallbacks):
|
||||
|
||||
cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_LinkCallbacks, cServerHandleImplPtr a_Server, const sockaddr * a_Address, socklen_t a_AddrLen):
|
||||
super(a_LinkCallbacks),
|
||||
m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), a_Socket, BEV_OPT_CLOSE_ON_FREE)),
|
||||
m_BufferEvent(bufferevent_socket_new(cNetworkSingleton::Get().GetEventBase(), a_Socket, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_THREADSAFE)),
|
||||
m_Server(a_Server)
|
||||
{
|
||||
LOGD("Created new cTCPLinkImpl at %p with BufferEvent at %p", this, m_BufferEvent);
|
||||
|
||||
// Update the endpoint addresses:
|
||||
UpdateLocalAddress();
|
||||
UpdateAddress(a_Address, a_AddrLen, m_RemoteIP, m_RemotePort);
|
||||
@ -41,6 +44,7 @@ cTCPLinkImpl::cTCPLinkImpl(evutil_socket_t a_Socket, cTCPLink::cCallbacksPtr a_L
|
||||
|
||||
cTCPLinkImpl::~cTCPLinkImpl()
|
||||
{
|
||||
LOGD("Deleting cTCPLinkImpl at %p with BufferEvent at %p", this, m_BufferEvent);
|
||||
bufferevent_free(m_BufferEvent);
|
||||
}
|
||||
|
||||
@ -179,6 +183,8 @@ void cTCPLinkImpl::ReadCallback(bufferevent * a_BufferEvent, void * a_Self)
|
||||
|
||||
void cTCPLinkImpl::EventCallback(bufferevent * a_BufferEvent, short a_What, void * a_Self)
|
||||
{
|
||||
LOGD("cTCPLink event callback for link %p, BEV %p; what = 0x%02x", a_Self, a_BufferEvent, a_What);
|
||||
|
||||
ASSERT(a_Self != nullptr);
|
||||
cTCPLinkImplPtr Self = static_cast<cTCPLinkImpl *>(a_Self)->m_Self;
|
||||
|
||||
|
@ -10,6 +10,80 @@
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// cBlockingSslClientSocketConnectCallbacks:
|
||||
|
||||
class cBlockingSslClientSocketConnectCallbacks:
|
||||
public cNetwork::cConnectCallbacks
|
||||
{
|
||||
/** The socket object that is using this instance of the callbacks. */
|
||||
cBlockingSslClientSocket & m_Socket;
|
||||
|
||||
virtual void OnConnected(cTCPLink & a_Link) override
|
||||
{
|
||||
m_Socket.OnConnected();
|
||||
}
|
||||
|
||||
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override
|
||||
{
|
||||
m_Socket.OnConnectError(a_ErrorMsg);
|
||||
}
|
||||
|
||||
public:
|
||||
cBlockingSslClientSocketConnectCallbacks(cBlockingSslClientSocket & a_Socket):
|
||||
m_Socket(a_Socket)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// cBlockingSslClientSocketLinkCallbacks:
|
||||
|
||||
class cBlockingSslClientSocketLinkCallbacks:
|
||||
public cTCPLink::cCallbacks
|
||||
{
|
||||
cBlockingSslClientSocket & m_Socket;
|
||||
|
||||
virtual void OnLinkCreated(cTCPLinkPtr a_Link) override
|
||||
{
|
||||
m_Socket.SetLink(a_Link);
|
||||
}
|
||||
|
||||
|
||||
virtual void OnReceivedData(const char * a_Data, size_t a_Length)
|
||||
{
|
||||
m_Socket.OnReceivedData(a_Data, a_Length);
|
||||
}
|
||||
|
||||
|
||||
virtual void OnRemoteClosed(void)
|
||||
{
|
||||
m_Socket.OnDisconnected();
|
||||
}
|
||||
|
||||
|
||||
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg)
|
||||
{
|
||||
m_Socket.OnDisconnected();
|
||||
}
|
||||
public:
|
||||
cBlockingSslClientSocketLinkCallbacks(cBlockingSslClientSocket & a_Socket):
|
||||
m_Socket(a_Socket)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// cBlockingSslClientSocket:
|
||||
|
||||
cBlockingSslClientSocket::cBlockingSslClientSocket(void) :
|
||||
m_Ssl(*this),
|
||||
m_IsConnected(false)
|
||||
@ -32,10 +106,19 @@ bool cBlockingSslClientSocket::Connect(const AString & a_ServerName, UInt16 a_Po
|
||||
}
|
||||
|
||||
// Connect the underlying socket:
|
||||
m_Socket.CreateSocket(cSocket::IPv4);
|
||||
if (!m_Socket.ConnectIPv4(a_ServerName.c_str(), a_Port))
|
||||
m_ServerName = a_ServerName;
|
||||
if (!cNetwork::Connect(a_ServerName, a_Port,
|
||||
std::make_shared<cBlockingSslClientSocketConnectCallbacks>(*this),
|
||||
std::make_shared<cBlockingSslClientSocketLinkCallbacks>(*this))
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Wait for the connection to succeed or fail:
|
||||
m_Event.Wait();
|
||||
if (!m_IsConnected)
|
||||
{
|
||||
Printf(m_LastErrorText, "Socket connect failed: %s", m_Socket.GetLastErrorString().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -102,7 +185,7 @@ bool cBlockingSslClientSocket::Send(const void * a_Data, size_t a_NumBytes)
|
||||
ASSERT(m_IsConnected);
|
||||
|
||||
// Keep sending the data until all of it is sent:
|
||||
const char * Data = (const char *)a_Data;
|
||||
const char * Data = reinterpret_cast<const char *>(a_Data);
|
||||
size_t NumBytes = a_NumBytes;
|
||||
for (;;)
|
||||
{
|
||||
@ -156,7 +239,8 @@ void cBlockingSslClientSocket::Disconnect(void)
|
||||
}
|
||||
|
||||
m_Ssl.NotifyClose();
|
||||
m_Socket.CloseSocket();
|
||||
m_Socket->Close();
|
||||
m_Socket.reset();
|
||||
m_IsConnected = false;
|
||||
}
|
||||
|
||||
@ -166,13 +250,25 @@ void cBlockingSslClientSocket::Disconnect(void)
|
||||
|
||||
int cBlockingSslClientSocket::ReceiveEncrypted(unsigned char * a_Buffer, size_t a_NumBytes)
|
||||
{
|
||||
int res = m_Socket.Receive((char *)a_Buffer, a_NumBytes, 0);
|
||||
if (res < 0)
|
||||
// Wait for any incoming data, if there is none:
|
||||
cCSLock Lock(m_CSIncomingData);
|
||||
while (m_IsConnected && m_IncomingData.empty())
|
||||
{
|
||||
cCSUnlock Unlock(Lock);
|
||||
m_Event.Wait();
|
||||
}
|
||||
|
||||
// If we got disconnected, report an error after processing all data:
|
||||
if (!m_IsConnected && m_IncomingData.empty())
|
||||
{
|
||||
// PolarSSL's net routines distinguish between connection reset and general failure, we don't need to
|
||||
return POLARSSL_ERR_NET_RECV_FAILED;
|
||||
}
|
||||
return res;
|
||||
|
||||
// Copy the data from the incoming buffer into the specified space:
|
||||
size_t NumToCopy = std::min(a_NumBytes, m_IncomingData.size());
|
||||
memcpy(a_Buffer, m_IncomingData.data(), NumToCopy);
|
||||
m_IncomingData.erase(0, NumToCopy);
|
||||
return static_cast<int>(NumToCopy);
|
||||
}
|
||||
|
||||
|
||||
@ -181,13 +277,69 @@ int cBlockingSslClientSocket::ReceiveEncrypted(unsigned char * a_Buffer, size_t
|
||||
|
||||
int cBlockingSslClientSocket::SendEncrypted(const unsigned char * a_Buffer, size_t a_NumBytes)
|
||||
{
|
||||
int res = m_Socket.Send((const char *)a_Buffer, a_NumBytes);
|
||||
if (res < 0)
|
||||
cTCPLinkPtr Socket(m_Socket); // Make a copy so that multiple threads don't race on deleting the socket.
|
||||
if (Socket == nullptr)
|
||||
{
|
||||
return POLARSSL_ERR_NET_SEND_FAILED;
|
||||
}
|
||||
if (!Socket->Send(a_Buffer, a_NumBytes))
|
||||
{
|
||||
// PolarSSL's net routines distinguish between connection reset and general failure, we don't need to
|
||||
return POLARSSL_ERR_NET_SEND_FAILED;
|
||||
}
|
||||
return res;
|
||||
return static_cast<int>(a_NumBytes);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void cBlockingSslClientSocket::OnConnected(void)
|
||||
{
|
||||
m_IsConnected = true;
|
||||
m_Event.Set();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cBlockingSslClientSocket::OnConnectError(const AString & a_ErrorMsg)
|
||||
{
|
||||
LOG("Cannot connect to %s: %s", m_ServerName.c_str(), a_ErrorMsg.c_str());
|
||||
m_Event.Set();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cBlockingSslClientSocket::OnReceivedData(const char * a_Data, size_t a_Size)
|
||||
{
|
||||
{
|
||||
cCSLock Lock(m_CSIncomingData);
|
||||
m_IncomingData.append(a_Data, a_Size);
|
||||
}
|
||||
m_Event.Set();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cBlockingSslClientSocket::SetLink(cTCPLinkPtr a_Link)
|
||||
{
|
||||
m_Socket = a_Link;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cBlockingSslClientSocket::OnDisconnected(void)
|
||||
{
|
||||
m_Socket.reset();
|
||||
m_IsConnected = false;
|
||||
m_Event.Set();
|
||||
}
|
||||
|
||||
|
||||
|
@ -9,8 +9,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "OSSupport/Network.h"
|
||||
#include "CallbackSslContext.h"
|
||||
#include "../OSSupport/Socket.h"
|
||||
|
||||
|
||||
|
||||
@ -51,25 +51,56 @@ public:
|
||||
const AString & GetLastErrorText(void) const { return m_LastErrorText; }
|
||||
|
||||
protected:
|
||||
friend class cBlockingSslClientSocketConnectCallbacks;
|
||||
friend class cBlockingSslClientSocketLinkCallbacks;
|
||||
|
||||
/** The SSL context used for the socket */
|
||||
cCallbackSslContext m_Ssl;
|
||||
|
||||
/** The underlying socket to the SSL server */
|
||||
cSocket m_Socket;
|
||||
cTCPLinkPtr m_Socket;
|
||||
|
||||
/** The object used to signal state changes in the socket (the cause of the blocking). */
|
||||
cEvent m_Event;
|
||||
|
||||
/** The trusted CA root cert store, if we are to verify the cert strictly. Set by SetTrustedRootCertsFromString(). */
|
||||
cX509CertPtr m_CACerts;
|
||||
|
||||
/** The expected SSL peer's name, if we are to verify the cert strictly. Set by SetTrustedRootCertsFromString(). */
|
||||
AString m_ExpectedPeerName;
|
||||
|
||||
/** The hostname to which the socket is connecting (stored for error reporting). */
|
||||
AString m_ServerName;
|
||||
|
||||
/** Text of the last error that has occurred. */
|
||||
AString m_LastErrorText;
|
||||
|
||||
/** Set to true if the connection established successfully. */
|
||||
bool m_IsConnected;
|
||||
|
||||
/** Protects m_IncomingData against multithreaded access. */
|
||||
cCriticalSection m_CSIncomingData;
|
||||
|
||||
/** Buffer for the data incoming on the network socket.
|
||||
Protected by m_CSIncomingData. */
|
||||
AString m_IncomingData;
|
||||
|
||||
|
||||
/** Called when the connection is established successfully. */
|
||||
void OnConnected(void);
|
||||
|
||||
/** Called when an error occurs while connecting the socket. */
|
||||
void OnConnectError(const AString & a_ErrorMsg);
|
||||
|
||||
/** Called when there's incoming data from the socket. */
|
||||
void OnReceivedData(const char * a_Data, size_t a_Size);
|
||||
|
||||
/** Called when the link for the connection is created. */
|
||||
void SetLink(cTCPLinkPtr a_Link);
|
||||
|
||||
/** Called when the link is disconnected, either gracefully or by an error. */
|
||||
void OnDisconnected(void);
|
||||
|
||||
// cCallbackSslContext::cDataCallbacks overrides:
|
||||
virtual int ReceiveEncrypted(unsigned char * a_Buffer, size_t a_NumBytes) override;
|
||||
virtual int SendEncrypted(const unsigned char * a_Buffer, size_t a_NumBytes) override;
|
||||
|
@ -70,7 +70,7 @@ int cSslContext::Initialize(bool a_IsClient, const SharedPtr<cCtrDrbgContext> &
|
||||
// so they're disabled until someone needs them
|
||||
ssl_set_dbg(&m_Ssl, &SSLDebugMessage, this);
|
||||
ssl_set_verify(&m_Ssl, &SSLVerifyCert, this);
|
||||
*/
|
||||
//*/
|
||||
|
||||
/*
|
||||
// Set ciphersuite to the easiest one to decode, so that the connection can be wireshark-decoded:
|
||||
|
@ -108,8 +108,17 @@ cProtocol180::cProtocol180(cClientHandle * a_Client, const AString & a_ServerAdd
|
||||
{
|
||||
static int sCounter = 0;
|
||||
cFile::CreateFolder("CommLogs");
|
||||
AString FileName = Printf("CommLogs/%x_%d__%s.log", (unsigned)time(nullptr), sCounter++, a_Client->GetIPString().c_str());
|
||||
m_CommLogFile.Open(FileName, cFile::fmWrite);
|
||||
AString IP(a_Client->GetIPString());
|
||||
ReplaceString(IP, ":", "_");
|
||||
AString FileName = Printf("CommLogs/%x_%d__%s.log",
|
||||
static_cast<unsigned>(time(nullptr)),
|
||||
sCounter++,
|
||||
IP.c_str()
|
||||
);
|
||||
if (!m_CommLogFile.Open(FileName, cFile::fmWrite))
|
||||
{
|
||||
LOG("Cannot log communication to file, the log file \"%s\" cannot be opened for writing.", FileName.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1659,7 +1668,7 @@ void cProtocol180::FixItemFramePositions(int a_ObjectData, double & a_PosX, doub
|
||||
void cProtocol180::AddReceivedData(const char * a_Data, size_t a_Size)
|
||||
{
|
||||
// Write the incoming data into the comm log file:
|
||||
if (g_ShouldLogCommIn)
|
||||
if (g_ShouldLogCommIn && m_CommLogFile.IsOpen())
|
||||
{
|
||||
if (m_ReceivedData.GetReadableSpace() > 0)
|
||||
{
|
||||
@ -1764,7 +1773,7 @@ void cProtocol180::AddReceivedData(const char * a_Data, size_t a_Size)
|
||||
bb.Write("\0", 1);
|
||||
|
||||
// Log the packet info into the comm log file:
|
||||
if (g_ShouldLogCommIn)
|
||||
if (g_ShouldLogCommIn && m_CommLogFile.IsOpen())
|
||||
{
|
||||
AString PacketData;
|
||||
bb.ReadAll(PacketData);
|
||||
@ -1796,7 +1805,7 @@ void cProtocol180::AddReceivedData(const char * a_Data, size_t a_Size)
|
||||
#endif // _DEBUG
|
||||
|
||||
// Put a message in the comm log:
|
||||
if (g_ShouldLogCommIn)
|
||||
if (g_ShouldLogCommIn && m_CommLogFile.IsOpen())
|
||||
{
|
||||
m_CommLogFile.Printf("^^^^^^ Unhandled packet ^^^^^^\n\n\n");
|
||||
}
|
||||
@ -1813,7 +1822,7 @@ void cProtocol180::AddReceivedData(const char * a_Data, size_t a_Size)
|
||||
);
|
||||
|
||||
// Put a message in the comm log:
|
||||
if (g_ShouldLogCommIn)
|
||||
if (g_ShouldLogCommIn && m_CommLogFile.IsOpen())
|
||||
{
|
||||
m_CommLogFile.Printf("^^^^^^ Wrong number of bytes read for this packet (exp %d left, got " SIZE_T_FMT " left) ^^^^^^\n\n\n",
|
||||
1, bb.GetReadableSpace()
|
||||
@ -1827,7 +1836,7 @@ void cProtocol180::AddReceivedData(const char * a_Data, size_t a_Size)
|
||||
} // for (ever)
|
||||
|
||||
// Log any leftover bytes into the logfile:
|
||||
if (g_ShouldLogCommIn && (m_ReceivedData.GetReadableSpace() > 0))
|
||||
if (g_ShouldLogCommIn && (m_ReceivedData.GetReadableSpace() > 0) && m_CommLogFile.IsOpen())
|
||||
{
|
||||
AString AllData;
|
||||
size_t OldReadableSpace = m_ReceivedData.GetReadableSpace();
|
||||
@ -2798,7 +2807,7 @@ cProtocol180::cPacketizer::~cPacketizer()
|
||||
}
|
||||
|
||||
// Log the comm into logfile:
|
||||
if (g_ShouldLogCommOut)
|
||||
if (g_ShouldLogCommOut && m_Protocol.m_CommLogFile.IsOpen())
|
||||
{
|
||||
AString Hex;
|
||||
ASSERT(PacketData.size() > 0);
|
||||
|
@ -38,6 +38,43 @@ enum
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// cRCONListenCallbacks:
|
||||
|
||||
class cRCONListenCallbacks:
|
||||
public cNetwork::cListenCallbacks
|
||||
{
|
||||
public:
|
||||
cRCONListenCallbacks(cRCONServer & a_RCONServer, UInt16 a_Port):
|
||||
m_RCONServer(a_RCONServer),
|
||||
m_Port(a_Port)
|
||||
{
|
||||
}
|
||||
|
||||
protected:
|
||||
/** The RCON server instance that we're attached to. */
|
||||
cRCONServer & m_RCONServer;
|
||||
|
||||
/** The port for which this instance is responsible. */
|
||||
UInt16 m_Port;
|
||||
|
||||
// cNetwork::cListenCallbacks overrides:
|
||||
virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) override
|
||||
{
|
||||
LOG("RCON Client \"%s\" connected!", a_RemoteIPAddress.c_str());
|
||||
return std::make_shared<cRCONServer::cConnection>(m_RCONServer, a_RemoteIPAddress);
|
||||
}
|
||||
virtual void OnAccepted(cTCPLink & a_Link) override {}
|
||||
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override
|
||||
{
|
||||
LOGWARNING("RCON server error on port %d: %d (%s)", m_Port, a_ErrorCode, a_ErrorMsg.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// cRCONCommandOutput:
|
||||
|
||||
@ -45,7 +82,7 @@ class cRCONCommandOutput :
|
||||
public cCommandOutputCallback
|
||||
{
|
||||
public:
|
||||
cRCONCommandOutput(cRCONServer::cConnection & a_Connection, int a_RequestID) :
|
||||
cRCONCommandOutput(cRCONServer::cConnection & a_Connection, UInt32 a_RequestID) :
|
||||
m_Connection(a_Connection),
|
||||
m_RequestID(a_RequestID)
|
||||
{
|
||||
@ -59,13 +96,13 @@ public:
|
||||
|
||||
virtual void Finished(void) override
|
||||
{
|
||||
m_Connection.SendResponse(m_RequestID, RCON_PACKET_RESPONSE, (int)m_Buffer.size(), m_Buffer.c_str());
|
||||
m_Connection.SendResponse(m_RequestID, RCON_PACKET_RESPONSE, static_cast<UInt32>(m_Buffer.size()), m_Buffer.c_str());
|
||||
delete this;
|
||||
}
|
||||
|
||||
protected:
|
||||
cRCONServer::cConnection & m_Connection;
|
||||
int m_RequestID;
|
||||
UInt32 m_RequestID;
|
||||
AString m_Buffer;
|
||||
} ;
|
||||
|
||||
@ -77,9 +114,7 @@ protected:
|
||||
// cRCONServer:
|
||||
|
||||
cRCONServer::cRCONServer(cServer & a_Server) :
|
||||
m_Server(a_Server),
|
||||
m_ListenThread4(*this, cSocket::IPv4, "RCON"),
|
||||
m_ListenThread6(*this, cSocket::IPv6, "RCON")
|
||||
m_Server(a_Server)
|
||||
{
|
||||
}
|
||||
|
||||
@ -89,8 +124,10 @@ cRCONServer::cRCONServer(cServer & a_Server) :
|
||||
|
||||
cRCONServer::~cRCONServer()
|
||||
{
|
||||
m_ListenThread4.Stop();
|
||||
m_ListenThread6.Stop();
|
||||
for (auto srv: m_ListenServers)
|
||||
{
|
||||
srv->Close();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -112,42 +149,29 @@ void cRCONServer::Initialize(cIniFile & a_IniFile)
|
||||
return;
|
||||
}
|
||||
|
||||
// Read and initialize both IPv4 and IPv6 ports for RCON
|
||||
bool HasAnyPorts = false;
|
||||
AString Ports4 = a_IniFile.GetValueSet("RCON", "PortsIPv4", "25575");
|
||||
if (m_ListenThread4.Initialize(Ports4))
|
||||
// Read the listening ports for RCON from config:
|
||||
AStringVector Ports = ReadUpgradeIniPorts(a_IniFile, "RCON", "Ports", "PortsIPv4", "PortsIPv6", "25575");
|
||||
|
||||
// Start listening on each specified port:
|
||||
for (auto port: Ports)
|
||||
{
|
||||
HasAnyPorts = true;
|
||||
m_ListenThread4.Start();
|
||||
}
|
||||
AString Ports6 = a_IniFile.GetValueSet("RCON", "PortsIPv6", "25575");
|
||||
if (m_ListenThread6.Initialize(Ports6))
|
||||
{
|
||||
HasAnyPorts = true;
|
||||
m_ListenThread6.Start();
|
||||
}
|
||||
if (!HasAnyPorts)
|
||||
{
|
||||
LOGWARNING("RCON is requested, but no ports are specified. Specify at least one port in PortsIPv4 or PortsIPv6. RCON is now disabled.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cRCONServer::OnConnectionAccepted(cSocket & a_Socket)
|
||||
{
|
||||
if (!a_Socket.IsValid())
|
||||
{
|
||||
return;
|
||||
UInt16 PortNum;
|
||||
if (!StringToInteger(port, PortNum))
|
||||
{
|
||||
LOGINFO("Invalid RCON port value: \"%s\". Ignoring.", port.c_str());
|
||||
continue;
|
||||
}
|
||||
auto Handle = cNetwork::Listen(PortNum, std::make_shared<cRCONListenCallbacks>(*this, PortNum));
|
||||
if (Handle->IsListening())
|
||||
{
|
||||
m_ListenServers.push_back(Handle);
|
||||
}
|
||||
}
|
||||
|
||||
LOG("RCON Client \"%s\" connected!", a_Socket.GetIPString().c_str());
|
||||
|
||||
// Create a new cConnection object, it will be deleted when the connection is closed
|
||||
m_SocketThreads.AddClient(a_Socket, new cConnection(*this, a_Socket));
|
||||
if (m_ListenServers.empty())
|
||||
{
|
||||
LOGWARNING("RCON is enabled but no valid ports were found. RCON is not accessible.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -157,11 +181,10 @@ void cRCONServer::OnConnectionAccepted(cSocket & a_Socket)
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// cRCONServer::cConnection:
|
||||
|
||||
cRCONServer::cConnection::cConnection(cRCONServer & a_RCONServer, cSocket & a_Socket) :
|
||||
cRCONServer::cConnection::cConnection(cRCONServer & a_RCONServer, const AString & a_IPAddress) :
|
||||
m_IsAuthenticated(false),
|
||||
m_RCONServer(a_RCONServer),
|
||||
m_Socket(a_Socket),
|
||||
m_IPAddress(a_Socket.GetIPString())
|
||||
m_IPAddress(a_IPAddress)
|
||||
{
|
||||
}
|
||||
|
||||
@ -169,71 +192,78 @@ cRCONServer::cConnection::cConnection(cRCONServer & a_RCONServer, cSocket & a_So
|
||||
|
||||
|
||||
|
||||
bool cRCONServer::cConnection::DataReceived(const char * a_Data, size_t a_Size)
|
||||
void cRCONServer::cConnection::OnLinkCreated(cTCPLinkPtr a_Link)
|
||||
{
|
||||
m_Link = a_Link;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cRCONServer::cConnection::OnReceivedData(const char * a_Data, size_t a_Size)
|
||||
{
|
||||
ASSERT(m_Link != nullptr);
|
||||
|
||||
// Append data to the buffer:
|
||||
m_Buffer.append(a_Data, a_Size);
|
||||
|
||||
// Process the packets in the buffer:
|
||||
while (m_Buffer.size() >= 14)
|
||||
{
|
||||
int Length = IntFromBuffer(m_Buffer.data());
|
||||
UInt32 Length = UIntFromBuffer(m_Buffer.data());
|
||||
if (Length > 1500)
|
||||
{
|
||||
// Too long, drop the connection
|
||||
LOGWARNING("Received an invalid RCON packet length (%d), dropping RCON connection to %s.",
|
||||
Length, m_IPAddress.c_str()
|
||||
);
|
||||
m_RCONServer.m_SocketThreads.RemoveClient(this);
|
||||
m_Socket.CloseSocket();
|
||||
delete this;
|
||||
return false;
|
||||
m_Link->Close();
|
||||
m_Link.reset();
|
||||
return;
|
||||
}
|
||||
if (Length > (int)(m_Buffer.size() + 4))
|
||||
if (Length > static_cast<UInt32>(m_Buffer.size() + 4))
|
||||
{
|
||||
// Incomplete packet yet, wait for more data to come
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
int RequestID = IntFromBuffer(m_Buffer.data() + 4);
|
||||
int PacketType = IntFromBuffer(m_Buffer.data() + 8);
|
||||
UInt32 RequestID = UIntFromBuffer(m_Buffer.data() + 4);
|
||||
UInt32 PacketType = UIntFromBuffer(m_Buffer.data() + 8);
|
||||
if (!ProcessPacket(RequestID, PacketType, Length - 10, m_Buffer.data() + 12))
|
||||
{
|
||||
m_RCONServer.m_SocketThreads.RemoveClient(this);
|
||||
m_Socket.CloseSocket();
|
||||
delete this;
|
||||
return false;
|
||||
m_Link->Close();
|
||||
m_Link.reset();
|
||||
return;
|
||||
}
|
||||
m_Buffer.erase(0, Length + 4);
|
||||
} // while (m_Buffer.size() >= 14)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cRCONServer::cConnection::GetOutgoingData(AString & a_Data)
|
||||
void cRCONServer::cConnection::OnRemoteClosed(void)
|
||||
{
|
||||
a_Data.assign(m_Outgoing);
|
||||
m_Outgoing.clear();
|
||||
m_Link.reset();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cRCONServer::cConnection::SocketClosed(void)
|
||||
void cRCONServer::cConnection::OnError(int a_ErrorCode, const AString & a_ErrorMsg)
|
||||
{
|
||||
m_RCONServer.m_SocketThreads.RemoveClient(this);
|
||||
delete this;
|
||||
LOGD("Error in RCON connection %s: %d (%s)", m_IPAddress.c_str(), a_ErrorCode, a_ErrorMsg.c_str());
|
||||
m_Link.reset();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cRCONServer::cConnection::ProcessPacket(int a_RequestID, int a_PacketType, int a_PayloadLength, const char * a_Payload)
|
||||
bool cRCONServer::cConnection::ProcessPacket(UInt32 a_RequestID, UInt32 a_PacketType, UInt32 a_PayloadLength, const char * a_Payload)
|
||||
{
|
||||
switch (a_PacketType)
|
||||
{
|
||||
@ -242,7 +272,7 @@ bool cRCONServer::cConnection::ProcessPacket(int a_RequestID, int a_PacketType,
|
||||
if (strncmp(a_Payload, m_RCONServer.m_Password.c_str(), a_PayloadLength) != 0)
|
||||
{
|
||||
LOGINFO("RCON: Invalid password from client %s, dropping connection.", m_IPAddress.c_str());
|
||||
SendResponse(-1, RCON_PACKET_RESPONSE, 0, nullptr);
|
||||
SendResponse(0xffffffffU, RCON_PACKET_RESPONSE, 0, nullptr);
|
||||
return false;
|
||||
}
|
||||
m_IsAuthenticated = true;
|
||||
@ -284,23 +314,22 @@ bool cRCONServer::cConnection::ProcessPacket(int a_RequestID, int a_PacketType,
|
||||
|
||||
|
||||
|
||||
/// Reads 4 bytes from a_Buffer and returns the int they represent
|
||||
int cRCONServer::cConnection::IntFromBuffer(const char * a_Buffer)
|
||||
UInt32 cRCONServer::cConnection::UIntFromBuffer(const char * a_Buffer)
|
||||
{
|
||||
return ((unsigned char)a_Buffer[3] << 24) | ((unsigned char)a_Buffer[2] << 16) | ((unsigned char)a_Buffer[1] << 8) | (unsigned char)a_Buffer[0];
|
||||
const Byte * Buffer = reinterpret_cast<const Byte *>(a_Buffer);
|
||||
return (Buffer[3] << 24) | (Buffer[2] << 16) | (Buffer[1] << 8) | Buffer[0];
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Puts 4 bytes representing the int into the buffer
|
||||
void cRCONServer::cConnection::IntToBuffer(int a_Value, char * a_Buffer)
|
||||
void cRCONServer::cConnection::UIntToBuffer(UInt32 a_Value, char * a_Buffer)
|
||||
{
|
||||
a_Buffer[0] = a_Value & 0xff;
|
||||
a_Buffer[1] = (a_Value >> 8) & 0xff;
|
||||
a_Buffer[2] = (a_Value >> 16) & 0xff;
|
||||
a_Buffer[3] = (a_Value >> 24) & 0xff;
|
||||
a_Buffer[0] = static_cast<char>(a_Value & 0xff);
|
||||
a_Buffer[1] = static_cast<char>((a_Value >> 8) & 0xff);
|
||||
a_Buffer[2] = static_cast<char>((a_Value >> 16) & 0xff);
|
||||
a_Buffer[3] = static_cast<char>((a_Value >> 24) & 0xff);
|
||||
}
|
||||
|
||||
|
||||
@ -308,25 +337,22 @@ void cRCONServer::cConnection::IntToBuffer(int a_Value, char * a_Buffer)
|
||||
|
||||
|
||||
/// Sends a RCON packet back to the client
|
||||
void cRCONServer::cConnection::SendResponse(int a_RequestID, int a_PacketType, int a_PayloadLength, const char * a_Payload)
|
||||
void cRCONServer::cConnection::SendResponse(UInt32 a_RequestID, UInt32 a_PacketType, UInt32 a_PayloadLength, const char * a_Payload)
|
||||
{
|
||||
ASSERT((a_PayloadLength == 0) || (a_Payload != nullptr)); // Either zero data to send, or a valid payload ptr
|
||||
ASSERT(m_Link != nullptr);
|
||||
|
||||
char Buffer[4];
|
||||
int Length = a_PayloadLength + 10;
|
||||
IntToBuffer(Length, Buffer);
|
||||
m_Outgoing.append(Buffer, 4);
|
||||
IntToBuffer(a_RequestID, Buffer);
|
||||
m_Outgoing.append(Buffer, 4);
|
||||
IntToBuffer(a_PacketType, Buffer);
|
||||
m_Outgoing.append(Buffer, 4);
|
||||
char Buffer[12];
|
||||
UInt32 Length = a_PayloadLength + 10;
|
||||
UIntToBuffer(Length, Buffer);
|
||||
UIntToBuffer(a_RequestID, Buffer + 4);
|
||||
UIntToBuffer(a_PacketType, Buffer + 8);
|
||||
m_Link->Send(Buffer, 12);
|
||||
if (a_PayloadLength > 0)
|
||||
{
|
||||
m_Outgoing.append(a_Payload, a_PayloadLength);
|
||||
m_Link->Send(a_Payload, a_PayloadLength);
|
||||
}
|
||||
m_Outgoing.push_back(0);
|
||||
m_Outgoing.push_back(0);
|
||||
m_RCONServer.m_SocketThreads.NotifyWrite(this);
|
||||
m_Link->Send("\0", 2); // Send two zero chars as the padding
|
||||
}
|
||||
|
||||
|
||||
|
@ -9,8 +9,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "OSSupport/SocketThreads.h"
|
||||
#include "OSSupport/ListenThread.h"
|
||||
#include "OSSupport/Network.h"
|
||||
|
||||
|
||||
|
||||
@ -24,8 +23,7 @@ class cIniFile;
|
||||
|
||||
|
||||
|
||||
class cRCONServer :
|
||||
public cListenThread::cCallback
|
||||
class cRCONServer
|
||||
{
|
||||
public:
|
||||
cRCONServer(cServer & a_Server);
|
||||
@ -35,72 +33,61 @@ public:
|
||||
|
||||
protected:
|
||||
friend class cRCONCommandOutput;
|
||||
friend class cRCONListenCallbacks;
|
||||
|
||||
class cConnection :
|
||||
public cSocketThreads::cCallback
|
||||
public cTCPLink::cCallbacks
|
||||
{
|
||||
public:
|
||||
cConnection(cRCONServer & a_RCONServer, cSocket & a_Socket);
|
||||
cConnection(cRCONServer & a_RCONServer, const AString & a_IPAddress);
|
||||
|
||||
protected:
|
||||
friend class cRCONCommandOutput;
|
||||
|
||||
/// Set to true if the client has successfully authenticated
|
||||
/** Set to true if the client has successfully authenticated */
|
||||
bool m_IsAuthenticated;
|
||||
|
||||
/// Buffer for the incoming data
|
||||
/** Buffer for the incoming data */
|
||||
AString m_Buffer;
|
||||
|
||||
/// Buffer for the outgoing data
|
||||
AString m_Outgoing;
|
||||
|
||||
/// Server that owns this connection and processes requests
|
||||
/** Server that owns this connection and processes requests */
|
||||
cRCONServer & m_RCONServer;
|
||||
|
||||
/// The socket belonging to the client
|
||||
cSocket & m_Socket;
|
||||
/** The TCP link to the client */
|
||||
cTCPLinkPtr m_Link;
|
||||
|
||||
/// Address of the client
|
||||
/** Address of the client */
|
||||
AString m_IPAddress;
|
||||
|
||||
|
||||
// cSocketThreads::cCallback overrides:
|
||||
virtual bool DataReceived(const char * a_Data, size_t a_Size) override;
|
||||
virtual void GetOutgoingData(AString & a_Data) override;
|
||||
virtual void SocketClosed(void) override;
|
||||
// cTCPLink::cCallbacks overrides:
|
||||
virtual void OnLinkCreated(cTCPLinkPtr a_Link);
|
||||
virtual void OnReceivedData(const char * a_Data, size_t a_Length) override;
|
||||
virtual void OnRemoteClosed(void) override;
|
||||
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg) override;
|
||||
|
||||
/// Processes the given packet and sends the response; returns true if successful, false if the connection is to be dropped
|
||||
bool ProcessPacket(int a_RequestID, int a_PacketType, int a_PayloadLength, const char * a_Payload);
|
||||
/** Processes the given packet and sends the response; returns true if successful, false if the connection is to be dropped */
|
||||
bool ProcessPacket(UInt32 a_RequestID, UInt32 a_PacketType, UInt32 a_PayloadLength, const char * a_Payload);
|
||||
|
||||
/// Reads 4 bytes from a_Buffer and returns the int they represent
|
||||
int IntFromBuffer(const char * a_Buffer);
|
||||
/** Reads 4 bytes from a_Buffer and returns the LE UInt32 they represent */
|
||||
UInt32 UIntFromBuffer(const char * a_Buffer);
|
||||
|
||||
/// Puts 4 bytes representing the int into the buffer
|
||||
void IntToBuffer(int a_Value, char * a_Buffer);
|
||||
/** Puts 4 bytes representing the int into the buffer */
|
||||
void UIntToBuffer(UInt32 a_Value, char * a_Buffer);
|
||||
|
||||
/// Sends a RCON packet back to the client
|
||||
void SendResponse(int a_RequestID, int a_PacketType, int a_PayloadLength, const char * a_Payload);
|
||||
/** Sends a RCON packet back to the client */
|
||||
void SendResponse(UInt32 a_RequestID, UInt32 a_PacketType, UInt32 a_PayloadLength, const char * a_Payload);
|
||||
} ;
|
||||
|
||||
|
||||
/// The server object that will process the commands received
|
||||
/** The server object that will process the commands received */
|
||||
cServer & m_Server;
|
||||
|
||||
/// The thread(s) that take care of all the traffic on the RCON ports
|
||||
cSocketThreads m_SocketThreads;
|
||||
/** The sockets for accepting RCON connections (one socket per port). */
|
||||
cServerHandlePtrs m_ListenServers;
|
||||
|
||||
/// The thread for accepting IPv4 RCON connections
|
||||
cListenThread m_ListenThread4;
|
||||
|
||||
/// The thread for accepting IPv6 RCON connections
|
||||
cListenThread m_ListenThread6;
|
||||
|
||||
/// Password for authentication
|
||||
/** Password for authentication */
|
||||
AString m_Password;
|
||||
|
||||
|
||||
// cListenThread::cCallback overrides:
|
||||
virtual void OnConnectionAccepted(cSocket & a_Socket) override;
|
||||
} ;
|
||||
|
||||
|
||||
|
64
src/Root.cpp
64
src/Root.cpp
@ -181,43 +181,49 @@ void cRoot::Start(void)
|
||||
IniFile.WriteFile("settings.ini");
|
||||
|
||||
LOGD("Finalising startup...");
|
||||
m_Server->Start();
|
||||
|
||||
m_WebAdmin->Start();
|
||||
|
||||
#if !defined(ANDROID_NDK)
|
||||
LOGD("Starting InputThread...");
|
||||
try
|
||||
if (m_Server->Start())
|
||||
{
|
||||
m_InputThread = std::thread(InputThread, std::ref(*this));
|
||||
m_InputThread.detach();
|
||||
}
|
||||
catch (std::system_error & a_Exception)
|
||||
{
|
||||
LOGERROR("cRoot::Start (std::thread) error %i: could not construct input thread; %s", a_Exception.code().value(), a_Exception.what());
|
||||
}
|
||||
#endif
|
||||
m_WebAdmin->Start();
|
||||
|
||||
LOG("Startup complete, took %ldms!", static_cast<long int>(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - BeginTime).count()));
|
||||
#ifdef _WIN32
|
||||
EnableMenuItem(hmenu, SC_CLOSE, MF_ENABLED); // Re-enable close button
|
||||
#endif
|
||||
#if !defined(ANDROID_NDK)
|
||||
LOGD("Starting InputThread...");
|
||||
try
|
||||
{
|
||||
m_InputThread = std::thread(InputThread, std::ref(*this));
|
||||
m_InputThread.detach();
|
||||
}
|
||||
catch (std::system_error & a_Exception)
|
||||
{
|
||||
LOGERROR("cRoot::Start (std::thread) error %i: could not construct input thread; %s", a_Exception.code().value(), a_Exception.what());
|
||||
}
|
||||
#endif
|
||||
|
||||
while (!m_bStop && !m_bRestart && !m_TerminateEventRaised) // These are modified by external threads
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
LOG("Startup complete, took %ldms!", static_cast<long int>(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - BeginTime).count()));
|
||||
#ifdef _WIN32
|
||||
EnableMenuItem(hmenu, SC_CLOSE, MF_ENABLED); // Re-enable close button
|
||||
#endif
|
||||
|
||||
if (m_TerminateEventRaised)
|
||||
while (!m_bStop && !m_bRestart && !m_TerminateEventRaised) // These are modified by external threads
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
|
||||
if (m_TerminateEventRaised)
|
||||
{
|
||||
m_bStop = true;
|
||||
}
|
||||
|
||||
// Stop the server:
|
||||
m_WebAdmin->Stop();
|
||||
|
||||
LOG("Shutting down server...");
|
||||
m_Server->Shutdown();
|
||||
} // if (m_Server->Start())
|
||||
else
|
||||
{
|
||||
m_bStop = true;
|
||||
}
|
||||
|
||||
// Stop the server:
|
||||
m_WebAdmin->Stop();
|
||||
|
||||
LOG("Shutting down server...");
|
||||
m_Server->Shutdown();
|
||||
delete m_MojangAPI; m_MojangAPI = nullptr;
|
||||
|
||||
LOGD("Shutting down deadlock detector...");
|
||||
|
271
src/Server.cpp
271
src/Server.cpp
@ -5,7 +5,6 @@
|
||||
#include "Server.h"
|
||||
#include "ClientHandle.h"
|
||||
#include "Mobs/Monster.h"
|
||||
#include "OSSupport/Socket.h"
|
||||
#include "Root.h"
|
||||
#include "World.h"
|
||||
#include "ChunkDef.h"
|
||||
@ -57,6 +56,39 @@ typedef std::list< cClientHandle* > ClientList;
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// cServerListenCallbacks:
|
||||
|
||||
class cServerListenCallbacks:
|
||||
public cNetwork::cListenCallbacks
|
||||
{
|
||||
cServer & m_Server;
|
||||
UInt16 m_Port;
|
||||
|
||||
virtual cTCPLink::cCallbacksPtr OnIncomingConnection(const AString & a_RemoteIPAddress, UInt16 a_RemotePort) override
|
||||
{
|
||||
return m_Server.OnConnectionAccepted(a_RemoteIPAddress);
|
||||
}
|
||||
|
||||
virtual void OnAccepted(cTCPLink & a_Link) override {}
|
||||
|
||||
virtual void OnError(int a_ErrorCode, const AString & a_ErrorMsg)
|
||||
{
|
||||
LOGWARNING("Cannot listen on port %d: %d (%s).", m_Port, a_ErrorCode, a_ErrorMsg.c_str());
|
||||
}
|
||||
|
||||
public:
|
||||
cServerListenCallbacks(cServer & a_Server, UInt16 a_Port):
|
||||
m_Server(a_Server),
|
||||
m_Port(a_Port)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// cServer::cTickThread:
|
||||
|
||||
@ -100,8 +132,6 @@ void cServer::cTickThread::Execute(void)
|
||||
// cServer:
|
||||
|
||||
cServer::cServer(void) :
|
||||
m_ListenThreadIPv4(*this, cSocket::IPv4, "Client"),
|
||||
m_ListenThreadIPv6(*this, cSocket::IPv6, "Client"),
|
||||
m_PlayerCount(0),
|
||||
m_PlayerCountDiff(0),
|
||||
m_ClientViewDistance(0),
|
||||
@ -121,42 +151,6 @@ cServer::cServer(void) :
|
||||
|
||||
|
||||
|
||||
void cServer::ClientDestroying(const cClientHandle * a_Client)
|
||||
{
|
||||
m_SocketThreads.RemoveClient(a_Client);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cServer::NotifyClientWrite(const cClientHandle * a_Client)
|
||||
{
|
||||
m_NotifyWriteThread.NotifyClientWrite(a_Client);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cServer::WriteToClient(const cClientHandle * a_Client, const AString & a_Data)
|
||||
{
|
||||
m_SocketThreads.Write(a_Client, a_Data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cServer::RemoveClient(const cClientHandle * a_Client)
|
||||
{
|
||||
m_SocketThreads.RemoveClient(a_Client);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cServer::ClientMovedToWorld(const cClientHandle * a_Client)
|
||||
{
|
||||
cCSLock Lock(m_CSClients);
|
||||
@ -211,33 +205,8 @@ bool cServer::InitServer(cIniFile & a_SettingsIni, bool a_ShouldAuth)
|
||||
LOGINFO("Compatible clients: %s", MCS_CLIENT_VERSIONS);
|
||||
LOGINFO("Compatible protocol versions %s", MCS_PROTOCOL_VERSIONS);
|
||||
|
||||
if (cSocket::WSAStartup() != 0) // Only does anything on Windows, but whatever
|
||||
{
|
||||
LOGERROR("WSAStartup() != 0");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool HasAnyPorts = false;
|
||||
AString Ports = a_SettingsIni.GetValueSet("Server", "Port", "25565");
|
||||
m_ListenThreadIPv4.SetReuseAddr(true);
|
||||
if (m_ListenThreadIPv4.Initialize(Ports))
|
||||
{
|
||||
HasAnyPorts = true;
|
||||
}
|
||||
|
||||
Ports = a_SettingsIni.GetValueSet("Server", "PortsIPv6", "25565");
|
||||
m_ListenThreadIPv6.SetReuseAddr(true);
|
||||
if (m_ListenThreadIPv6.Initialize(Ports))
|
||||
{
|
||||
HasAnyPorts = true;
|
||||
}
|
||||
m_Ports = ReadUpgradeIniPorts(a_SettingsIni, "Server", "Ports", "Port", "PortsIPv6", "25565");
|
||||
|
||||
if (!HasAnyPorts)
|
||||
{
|
||||
LOGERROR("Couldn't open any ports. Aborting the server");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_RCONServer.Initialize(a_SettingsIni);
|
||||
|
||||
m_bIsConnected = true;
|
||||
@ -278,8 +247,6 @@ bool cServer::InitServer(cIniFile & a_SettingsIni, bool a_ShouldAuth)
|
||||
LOGINFO("Setting default viewdistance to the maximum of %d", m_ClientViewDistance);
|
||||
}
|
||||
|
||||
m_NotifyWriteThread.Start(this);
|
||||
|
||||
PrepareKeys();
|
||||
|
||||
return true;
|
||||
@ -327,36 +294,14 @@ void cServer::PrepareKeys(void)
|
||||
|
||||
|
||||
|
||||
void cServer::OnConnectionAccepted(cSocket & a_Socket)
|
||||
cTCPLink::cCallbacksPtr cServer::OnConnectionAccepted(const AString & a_RemoteIPAddress)
|
||||
{
|
||||
if (!a_Socket.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const AString & ClientIP = a_Socket.GetIPString();
|
||||
if (ClientIP.empty())
|
||||
{
|
||||
LOGWARN("cServer: A client connected, but didn't present its IP, disconnecting.");
|
||||
a_Socket.CloseSocket();
|
||||
return;
|
||||
}
|
||||
|
||||
LOGD("Client \"%s\" connected!", ClientIP.c_str());
|
||||
|
||||
cClientHandle * NewHandle = new cClientHandle(&a_Socket, m_ClientViewDistance);
|
||||
if (!m_SocketThreads.AddClient(a_Socket, NewHandle))
|
||||
{
|
||||
// For some reason SocketThreads have rejected the handle, clean it up
|
||||
LOGERROR("Client \"%s\" cannot be handled, server probably unstable", ClientIP.c_str());
|
||||
a_Socket.CloseSocket();
|
||||
delete NewHandle;
|
||||
NewHandle = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
LOGD("Client \"%s\" connected!", a_RemoteIPAddress.c_str());
|
||||
cClientHandlePtr NewHandle = std::make_shared<cClientHandle>(a_RemoteIPAddress, m_ClientViewDistance);
|
||||
NewHandle->SetSelf(NewHandle);
|
||||
cCSLock Lock(m_CSClients);
|
||||
m_Clients.push_back(NewHandle);
|
||||
return NewHandle;
|
||||
}
|
||||
|
||||
|
||||
@ -403,23 +348,30 @@ bool cServer::Tick(float a_Dt)
|
||||
|
||||
void cServer::TickClients(float a_Dt)
|
||||
{
|
||||
cClientHandleList RemoveClients;
|
||||
cClientHandlePtrs RemoveClients;
|
||||
{
|
||||
cCSLock Lock(m_CSClients);
|
||||
|
||||
// Remove clients that have moved to a world (the world will be ticking them from now on)
|
||||
for (cClientHandleList::const_iterator itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr)
|
||||
for (auto itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr)
|
||||
{
|
||||
m_Clients.remove(*itr);
|
||||
for (auto itrC = m_Clients.begin(), endC = m_Clients.end(); itrC != endC; ++itrC)
|
||||
{
|
||||
if (itrC->get() == *itr)
|
||||
{
|
||||
m_Clients.erase(itrC);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // for itr - m_ClientsToRemove[]
|
||||
m_ClientsToRemove.clear();
|
||||
|
||||
// Tick the remaining clients, take out those that have been destroyed into RemoveClients
|
||||
for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end();)
|
||||
for (auto itr = m_Clients.begin(); itr != m_Clients.end();)
|
||||
{
|
||||
if ((*itr)->IsDestroyed())
|
||||
{
|
||||
// Remove the client later, when CS is not held, to avoid deadlock: http://forum.mc-server.org/showthread.php?tid=374
|
||||
// Delete the client later, when CS is not held, to avoid deadlock: http://forum.mc-server.org/showthread.php?tid=374
|
||||
RemoveClients.push_back(*itr);
|
||||
itr = m_Clients.erase(itr);
|
||||
continue;
|
||||
@ -430,10 +382,7 @@ void cServer::TickClients(float a_Dt)
|
||||
}
|
||||
|
||||
// Delete the clients that have been destroyed
|
||||
for (cClientHandleList::iterator itr = RemoveClients.begin(); itr != RemoveClients.end(); ++itr)
|
||||
{
|
||||
delete *itr;
|
||||
} // for itr - RemoveClients[]
|
||||
RemoveClients.clear();
|
||||
}
|
||||
|
||||
|
||||
@ -442,12 +391,23 @@ void cServer::TickClients(float a_Dt)
|
||||
|
||||
bool cServer::Start(void)
|
||||
{
|
||||
if (!m_ListenThreadIPv4.Start())
|
||||
for (auto port: m_Ports)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!m_ListenThreadIPv6.Start())
|
||||
UInt16 PortNum;
|
||||
if (!StringToInteger(port, PortNum))
|
||||
{
|
||||
LOGWARNING("Invalid port specified for server: \"%s\". Ignoring.", port.c_str());
|
||||
continue;
|
||||
}
|
||||
auto Handle = cNetwork::Listen(PortNum, std::make_shared<cServerListenCallbacks>(*this, PortNum));
|
||||
if (Handle->IsListening())
|
||||
{
|
||||
m_ServerHandles.push_back(Handle);
|
||||
}
|
||||
} // for port - Ports[]
|
||||
if (m_ServerHandles.empty())
|
||||
{
|
||||
LOGERROR("Couldn't open any ports. Aborting the server");
|
||||
return false;
|
||||
}
|
||||
if (!m_TickThread.Start())
|
||||
@ -640,7 +600,6 @@ void cServer::PrintHelp(const AStringVector & a_Split, cCommandOutputCallback &
|
||||
const AStringPair & cmd = *itr;
|
||||
a_Output.Out(Printf("%-*s%s\n", static_cast<int>(Callback.m_MaxLen), cmd.first.c_str(), cmd.second.c_str()));
|
||||
} // for itr - Callback.m_Commands[]
|
||||
a_Output.Finished();
|
||||
}
|
||||
|
||||
|
||||
@ -670,19 +629,24 @@ void cServer::BindBuiltInConsoleCommands(void)
|
||||
|
||||
void cServer::Shutdown(void)
|
||||
{
|
||||
m_ListenThreadIPv4.Stop();
|
||||
m_ListenThreadIPv6.Stop();
|
||||
// Stop listening on all sockets:
|
||||
for (auto srv: m_ServerHandles)
|
||||
{
|
||||
srv->Close();
|
||||
}
|
||||
m_ServerHandles.clear();
|
||||
|
||||
// Notify the tick thread and wait for it to terminate:
|
||||
m_bRestarting = true;
|
||||
m_RestartEvent.Wait();
|
||||
|
||||
cRoot::Get()->SaveAllChunks();
|
||||
|
||||
// Remove all clients:
|
||||
cCSLock Lock(m_CSClients);
|
||||
for (ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
|
||||
for (auto itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
|
||||
{
|
||||
(*itr)->Destroy();
|
||||
delete *itr;
|
||||
}
|
||||
m_Clients.clear();
|
||||
}
|
||||
@ -694,7 +658,7 @@ void cServer::Shutdown(void)
|
||||
void cServer::KickUser(int a_ClientID, const AString & a_Reason)
|
||||
{
|
||||
cCSLock Lock(m_CSClients);
|
||||
for (ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
|
||||
for (auto itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
|
||||
{
|
||||
if ((*itr)->GetUniqueID() == a_ClientID)
|
||||
{
|
||||
@ -710,7 +674,7 @@ void cServer::KickUser(int a_ClientID, const AString & a_Reason)
|
||||
void cServer::AuthenticateUser(int a_ClientID, const AString & a_Name, const AString & a_UUID, const Json::Value & a_Properties)
|
||||
{
|
||||
cCSLock Lock(m_CSClients);
|
||||
for (ClientList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
|
||||
for (auto itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
|
||||
{
|
||||
if ((*itr)->GetUniqueID() == a_ClientID)
|
||||
{
|
||||
@ -724,82 +688,3 @@ void cServer::AuthenticateUser(int a_ClientID, const AString & a_Name, const ASt
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// cServer::cNotifyWriteThread:
|
||||
|
||||
cServer::cNotifyWriteThread::cNotifyWriteThread(void) :
|
||||
super("ClientPacketThread"),
|
||||
m_Server(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cServer::cNotifyWriteThread::~cNotifyWriteThread()
|
||||
{
|
||||
m_ShouldTerminate = true;
|
||||
m_Event.Set();
|
||||
Wait();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool cServer::cNotifyWriteThread::Start(cServer * a_Server)
|
||||
{
|
||||
m_Server = a_Server;
|
||||
return super::Start();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cServer::cNotifyWriteThread::Execute(void)
|
||||
{
|
||||
cClientHandleList Clients;
|
||||
while (!m_ShouldTerminate)
|
||||
{
|
||||
cCSLock Lock(m_CS);
|
||||
while (m_Clients.empty())
|
||||
{
|
||||
cCSUnlock Unlock(Lock);
|
||||
m_Event.Wait();
|
||||
if (m_ShouldTerminate)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the clients to notify and unlock the CS:
|
||||
Clients.splice(Clients.begin(), m_Clients);
|
||||
Lock.Unlock();
|
||||
|
||||
for (cClientHandleList::iterator itr = Clients.begin(); itr != Clients.end(); ++itr)
|
||||
{
|
||||
m_Server->m_SocketThreads.NotifyWrite(*itr);
|
||||
} // for itr - Clients[]
|
||||
Clients.clear();
|
||||
} // while (!mShouldTerminate)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
void cServer::cNotifyWriteThread::NotifyClientWrite(const cClientHandle * a_Client)
|
||||
{
|
||||
{
|
||||
cCSLock Lock(m_CS);
|
||||
m_Clients.remove(const_cast<cClientHandle *>(a_Client)); // Put it there only once
|
||||
m_Clients.push_back(const_cast<cClientHandle *>(a_Client));
|
||||
}
|
||||
m_Event.Set();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
97
src/Server.h
97
src/Server.h
@ -9,10 +9,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "OSSupport/SocketThreads.h"
|
||||
#include "OSSupport/ListenThread.h"
|
||||
|
||||
#include "RCONServer.h"
|
||||
#include "OSSupport/IsThread.h"
|
||||
#include "OSSupport/Network.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(push)
|
||||
@ -36,10 +35,12 @@
|
||||
// fwd:
|
||||
class cPlayer;
|
||||
class cClientHandle;
|
||||
typedef SharedPtr<cClientHandle> cClientHandlePtr;
|
||||
typedef std::list<cClientHandlePtr> cClientHandlePtrs;
|
||||
typedef std::list<cClientHandle *> cClientHandles;
|
||||
class cIniFile;
|
||||
class cCommandOutputCallback;
|
||||
|
||||
typedef std::list<cClientHandle *> cClientHandleList;
|
||||
|
||||
namespace Json
|
||||
{
|
||||
@ -50,10 +51,11 @@ namespace Json
|
||||
|
||||
|
||||
|
||||
class cServer // tolua_export
|
||||
: public cListenThread::cCallback
|
||||
{ // tolua_export
|
||||
public: // tolua_export
|
||||
// tolua_begin
|
||||
class cServer
|
||||
{
|
||||
public:
|
||||
// tolua_end
|
||||
|
||||
virtual ~cServer() {}
|
||||
bool InitServer(cIniFile & a_SettingsIni, bool a_ShouldAuth);
|
||||
@ -105,13 +107,6 @@ public: // tolua_export
|
||||
/** Called by cClientHandle's destructor; stop m_SocketThreads from calling back into a_Client */
|
||||
void ClientDestroying(const cClientHandle * a_Client);
|
||||
|
||||
/** Notifies m_SocketThreads that client has something to be written */
|
||||
void NotifyClientWrite(const cClientHandle * a_Client);
|
||||
|
||||
void WriteToClient(const cClientHandle * a_Client, const AString & a_Data); // Queues outgoing data for the client through m_SocketThreads
|
||||
|
||||
void RemoveClient(const cClientHandle * a_Client); // Removes the clienthandle from m_SocketThreads
|
||||
|
||||
/** Don't tick a_Client anymore, it will be ticked from its cPlayer instead */
|
||||
void ClientMovedToWorld(const cClientHandle * a_Client);
|
||||
|
||||
@ -147,30 +142,7 @@ public: // tolua_export
|
||||
private:
|
||||
|
||||
friend class cRoot; // so cRoot can create and destroy cServer
|
||||
|
||||
/** When NotifyClientWrite() is called, it is queued for this thread to process (to avoid deadlocks between cSocketThreads, cClientHandle and cChunkMap) */
|
||||
class cNotifyWriteThread :
|
||||
public cIsThread
|
||||
{
|
||||
typedef cIsThread super;
|
||||
|
||||
cEvent m_Event; // Set when m_Clients gets appended
|
||||
cServer * m_Server;
|
||||
|
||||
cCriticalSection m_CS;
|
||||
cClientHandleList m_Clients;
|
||||
|
||||
virtual void Execute(void);
|
||||
|
||||
public:
|
||||
|
||||
cNotifyWriteThread(void);
|
||||
~cNotifyWriteThread();
|
||||
|
||||
bool Start(cServer * a_Server);
|
||||
|
||||
void NotifyClientWrite(const cClientHandle * a_Client);
|
||||
} ;
|
||||
friend class cServerListenCallbacks; // Accessing OnConnectionAccepted()
|
||||
|
||||
/** The server tick thread takes care of the players who aren't yet spawned in a world */
|
||||
class cTickThread :
|
||||
@ -189,21 +161,29 @@ private:
|
||||
} ;
|
||||
|
||||
|
||||
cNotifyWriteThread m_NotifyWriteThread;
|
||||
/** The network sockets listening for client connections. */
|
||||
cServerHandlePtrs m_ServerHandles;
|
||||
|
||||
/** Protects m_Clients and m_ClientsToRemove against multithreaded access. */
|
||||
cCriticalSection m_CSClients;
|
||||
|
||||
/** Clients that are connected to the server and not yet assigned to a cWorld. */
|
||||
cClientHandlePtrs m_Clients;
|
||||
|
||||
/** Clients that have just been moved into a world and are to be removed from m_Clients in the next Tick(). */
|
||||
cClientHandles m_ClientsToRemove;
|
||||
|
||||
cListenThread m_ListenThreadIPv4;
|
||||
cListenThread m_ListenThreadIPv6;
|
||||
|
||||
cCriticalSection m_CSClients; ///< Locks client lists
|
||||
cClientHandleList m_Clients; ///< Clients that are connected to the server and not yet assigned to a cWorld
|
||||
cClientHandleList m_ClientsToRemove; ///< Clients that have just been moved into a world and are to be removed from m_Clients in the next Tick()
|
||||
|
||||
mutable cCriticalSection m_CSPlayerCount; ///< Locks the m_PlayerCount
|
||||
int m_PlayerCount; ///< Number of players currently playing in the server
|
||||
cCriticalSection m_CSPlayerCountDiff; ///< Locks the m_PlayerCountDiff
|
||||
int m_PlayerCountDiff; ///< Adjustment to m_PlayerCount to be applied in the Tick thread
|
||||
|
||||
cSocketThreads m_SocketThreads;
|
||||
/** Protects m_PlayerCount against multithreaded access. */
|
||||
mutable cCriticalSection m_CSPlayerCount;
|
||||
|
||||
/** Number of players currently playing in the server. */
|
||||
int m_PlayerCount;
|
||||
|
||||
/** Protects m_PlayerCountDiff against multithreaded access. */
|
||||
cCriticalSection m_CSPlayerCountDiff;
|
||||
|
||||
/** Adjustment to m_PlayerCount to be applied in the Tick thread. */
|
||||
int m_PlayerCountDiff;
|
||||
|
||||
int m_ClientViewDistance; // The default view distance for clients; settable in Settings.ini
|
||||
|
||||
@ -250,19 +230,24 @@ private:
|
||||
/** True if BungeeCord handshake packets (with player UUID) should be accepted. */
|
||||
bool m_ShouldAllowBungeeCord;
|
||||
|
||||
/** The list of ports on which the server should listen for connections.
|
||||
Initialized in InitServer(), used in Start(). */
|
||||
AStringVector m_Ports;
|
||||
|
||||
|
||||
cServer(void);
|
||||
|
||||
/** Loads, or generates, if missing, RSA keys for protocol encryption */
|
||||
void PrepareKeys(void);
|
||||
|
||||
/** Creates a new cClientHandle instance and adds it to the list of clients.
|
||||
Returns the cClientHandle reinterpreted as cTCPLink callbacks. */
|
||||
cTCPLink::cCallbacksPtr OnConnectionAccepted(const AString & a_RemoteIPAddress);
|
||||
|
||||
bool Tick(float a_Dt);
|
||||
|
||||
/** Ticks the clients in m_Clients, manages the list in respect to removing clients */
|
||||
void TickClients(float a_Dt);
|
||||
|
||||
// cListenThread::cCallback overrides:
|
||||
virtual void OnConnectionAccepted(cSocket & a_Socket) override;
|
||||
}; // tolua_export
|
||||
|
||||
|
||||
|
@ -905,3 +905,47 @@ bool SplitZeroTerminatedStrings(const AString & a_Strings, AStringVector & a_Out
|
||||
|
||||
|
||||
|
||||
|
||||
AStringVector MergeStringVectors(const AStringVector & a_Strings1, const AStringVector & a_Strings2)
|
||||
{
|
||||
// Initialize the resulting vector by the first vector:
|
||||
AStringVector res = a_Strings1;
|
||||
|
||||
// Add each item from strings2 that is not already present:
|
||||
for (auto item : a_Strings2)
|
||||
{
|
||||
if (std::find(res.begin(), res.end(), item) == res.end())
|
||||
{
|
||||
res.push_back(item);
|
||||
}
|
||||
} // for item - a_Strings2[]
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
AString StringsConcat(const AStringVector & a_Strings, char a_Separator)
|
||||
{
|
||||
// If the vector is empty, return an empty string:
|
||||
if (a_Strings.empty())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
// Concatenate the strings in the vector:
|
||||
AString res;
|
||||
res.append(a_Strings[0]);
|
||||
for (auto itr = a_Strings.cbegin() + 1, end = a_Strings.cend(); itr != end; ++itr)
|
||||
{
|
||||
res.push_back(a_Separator);
|
||||
res.append(*itr);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -115,7 +115,16 @@ a_Output is first cleared and then each separate string is pushed back into a_Ou
|
||||
Returns true if there are at least two strings in a_Output (there was at least one \0 separator). */
|
||||
extern bool SplitZeroTerminatedStrings(const AString & a_Strings, AStringVector & a_Output);
|
||||
|
||||
/// Parses any integer type. Checks bounds and returns errors out of band.
|
||||
/** Merges the two vectors of strings, removing duplicate entries from the second vector.
|
||||
The resulting vector contains items from a_Strings1 first, then from a_Strings2.
|
||||
The order of items doesn't change, only the duplicates are removed.
|
||||
If a_Strings1 contains duplicates, the result will still contain those duplicates. */
|
||||
extern AStringVector MergeStringVectors(const AStringVector & a_Strings1, const AStringVector & a_Strings2);
|
||||
|
||||
/** Concatenates the specified strings into a single string, separated by the specified separator. */
|
||||
extern AString StringsConcat(const AStringVector & a_Strings, char a_Separator);
|
||||
|
||||
/** Parses any integer type. Checks bounds and returns errors out of band. */
|
||||
template <class T>
|
||||
bool StringToInteger(const AString & a_str, T & a_Num)
|
||||
{
|
||||
|
@ -19,7 +19,16 @@
|
||||
|
||||
|
||||
|
||||
/// Helper class - appends all player names together in a HTML list
|
||||
static const char DEFAULT_WEBADMIN_PORTS[] = "8080";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// cPlayerAccum:
|
||||
|
||||
/** Helper class - appends all player names together in an HTML list */
|
||||
class cPlayerAccum :
|
||||
public cPlayerListCallback
|
||||
{
|
||||
@ -40,11 +49,12 @@ public:
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// cWebAdmin:
|
||||
|
||||
cWebAdmin::cWebAdmin(void) :
|
||||
m_IsInitialized(false),
|
||||
m_IsRunning(false),
|
||||
m_PortsIPv4("8080"),
|
||||
m_PortsIPv6(""),
|
||||
m_TemplateScript("<webadmin_template>")
|
||||
{
|
||||
}
|
||||
@ -91,8 +101,7 @@ bool cWebAdmin::Init(void)
|
||||
m_IniFile.AddHeaderComment(" Password format: Password=*password*; for example:");
|
||||
m_IniFile.AddHeaderComment(" [User:admin]");
|
||||
m_IniFile.AddHeaderComment(" Password=admin");
|
||||
m_IniFile.SetValue("WebAdmin", "Port", m_PortsIPv4);
|
||||
m_IniFile.SetValue("WebAdmin", "PortsIPv6", m_PortsIPv6);
|
||||
m_IniFile.SetValue("WebAdmin", "Ports", DEFAULT_WEBADMIN_PORTS);
|
||||
m_IniFile.WriteFile("webadmin.ini");
|
||||
}
|
||||
|
||||
@ -104,10 +113,37 @@ bool cWebAdmin::Init(void)
|
||||
|
||||
LOGD("Initialising WebAdmin...");
|
||||
|
||||
m_PortsIPv4 = m_IniFile.GetValueSet("WebAdmin", "Port", m_PortsIPv4);
|
||||
m_PortsIPv6 = m_IniFile.GetValueSet("WebAdmin", "PortsIPv6", m_PortsIPv6);
|
||||
// Initialize the WebAdmin template script and load the file
|
||||
m_TemplateScript.Create();
|
||||
m_TemplateScript.RegisterAPILibs();
|
||||
if (!m_TemplateScript.LoadFile(FILE_IO_PREFIX "webadmin/template.lua"))
|
||||
{
|
||||
LOGWARN("Could not load WebAdmin template \"%s\". WebAdmin disabled!", FILE_IO_PREFIX "webadmin/template.lua");
|
||||
m_TemplateScript.Close();
|
||||
m_HTTPServer.Stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_HTTPServer.Initialize(m_PortsIPv4, m_PortsIPv6))
|
||||
// Load the login template, provide a fallback default if not found:
|
||||
if (!LoadLoginTemplate())
|
||||
{
|
||||
LOGWARN("Could not load WebAdmin login template \"%s\", using fallback template.", FILE_IO_PREFIX "webadmin/login_template.html");
|
||||
|
||||
// Sets the fallback template:
|
||||
m_LoginTemplate = \
|
||||
"<h1>MCServer WebAdmin</h1>" \
|
||||
"<center>" \
|
||||
"<form method='get' action='webadmin/'>" \
|
||||
"<input type='submit' value='Log in'>" \
|
||||
"</form>" \
|
||||
"</center>";
|
||||
}
|
||||
|
||||
// Read the ports to be used:
|
||||
// Note that historically the ports were stored in the "Port" and "PortsIPv6" values
|
||||
m_Ports = ReadUpgradeIniPorts(m_IniFile, "WebAdmin", "Ports", "Port", "PortsIPv6", DEFAULT_WEBADMIN_PORTS);
|
||||
|
||||
if (!m_HTTPServer.Initialize())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@ -130,32 +166,7 @@ bool cWebAdmin::Start(void)
|
||||
|
||||
LOGD("Starting WebAdmin...");
|
||||
|
||||
// Initialize the WebAdmin template script and load the file
|
||||
m_TemplateScript.Create();
|
||||
m_TemplateScript.RegisterAPILibs();
|
||||
if (!m_TemplateScript.LoadFile(FILE_IO_PREFIX "webadmin/template.lua"))
|
||||
{
|
||||
LOGWARN("Could not load WebAdmin template \"%s\". WebAdmin disabled!", FILE_IO_PREFIX "webadmin/template.lua");
|
||||
m_TemplateScript.Close();
|
||||
m_HTTPServer.Stop();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!LoadLoginTemplate())
|
||||
{
|
||||
LOGWARN("Could not load WebAdmin login template \"%s\", using fallback template.", FILE_IO_PREFIX "webadmin/login_template.html");
|
||||
|
||||
// Sets the fallback template:
|
||||
m_LoginTemplate = \
|
||||
"<h1>MCServer WebAdmin</h1>" \
|
||||
"<center>" \
|
||||
"<form method='get' action='webadmin/'>" \
|
||||
"<input type='submit' value='Log in'>" \
|
||||
"</form>" \
|
||||
"</center>";
|
||||
}
|
||||
|
||||
m_IsRunning = m_HTTPServer.Start(*this);
|
||||
m_IsRunning = m_HTTPServer.Start(*this, m_Ports);
|
||||
return m_IsRunning;
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "OSSupport/Socket.h"
|
||||
#include "Bindings/LuaState.h"
|
||||
#include "IniFile.h"
|
||||
#include "HTTPServer/HTTPServer.h"
|
||||
@ -135,8 +134,16 @@ public:
|
||||
/** Returns the prefix needed for making a link point to the webadmin root from the given URL ("../../../webadmin"-style) */
|
||||
AString GetBaseURL(const AString & a_URL);
|
||||
|
||||
AString GetIPv4Ports(void) const { return m_PortsIPv4; }
|
||||
AString GetIPv6Ports(void) const { return m_PortsIPv6; }
|
||||
/** Returns the list of ports used for the webadmin. */
|
||||
AString GetPorts(void) const { return StringsConcat(m_Ports, ','); }
|
||||
|
||||
/** OBSOLETE: Returns the list of IPv4 ports used for the webadmin.
|
||||
Currently there is no distinction between IPv4 and IPv6; use GetPorts() instead. */
|
||||
AString GetIPv4Ports(void) const { return GetPorts(); }
|
||||
|
||||
/** OBSOLETE: Returns the list of IPv6 ports used for the webadmin.
|
||||
Currently there is no distinction between IPv4 and IPv6; use GetPorts() instead. */
|
||||
AString GetIPv6Ports(void) const { return GetPorts(); }
|
||||
|
||||
// tolua_end
|
||||
|
||||
@ -205,8 +212,8 @@ protected:
|
||||
|
||||
PluginList m_Plugins;
|
||||
|
||||
AString m_PortsIPv4;
|
||||
AString m_PortsIPv6;
|
||||
/** The ports on which the webadmin is running. */
|
||||
AStringVector m_Ports;
|
||||
|
||||
/** The Lua template script to provide templates: */
|
||||
cLuaState m_TemplateScript;
|
||||
|
@ -815,10 +815,9 @@ void cWorld::Stop(void)
|
||||
// Delete the clients that have been in this world:
|
||||
{
|
||||
cCSLock Lock(m_CSClients);
|
||||
for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
|
||||
for (auto itr = m_Clients.begin(); itr != m_Clients.end(); ++itr)
|
||||
{
|
||||
(*itr)->Destroy();
|
||||
delete *itr;
|
||||
} // for itr - m_Clients[]
|
||||
m_Clients.clear();
|
||||
}
|
||||
@ -1093,19 +1092,26 @@ void cWorld::TickScheduledTasks(void)
|
||||
|
||||
void cWorld::TickClients(float a_Dt)
|
||||
{
|
||||
cClientHandleList RemoveClients;
|
||||
cClientHandlePtrs RemoveClients;
|
||||
{
|
||||
cCSLock Lock(m_CSClients);
|
||||
|
||||
// Remove clients scheduled for removal:
|
||||
for (cClientHandleList::iterator itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr)
|
||||
for (auto itr = m_ClientsToRemove.begin(), end = m_ClientsToRemove.end(); itr != end; ++itr)
|
||||
{
|
||||
m_Clients.remove(*itr);
|
||||
for (auto itrC = m_Clients.begin(), endC = m_Clients.end(); itrC != endC; ++itrC)
|
||||
{
|
||||
if (itrC->get() == *itr)
|
||||
{
|
||||
m_Clients.erase(itrC);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // for itr - m_ClientsToRemove[]
|
||||
m_ClientsToRemove.clear();
|
||||
|
||||
// Add clients scheduled for adding:
|
||||
for (cClientHandleList::iterator itr = m_ClientsToAdd.begin(), end = m_ClientsToAdd.end(); itr != end; ++itr)
|
||||
for (auto itr = m_ClientsToAdd.begin(), end = m_ClientsToAdd.end(); itr != end; ++itr)
|
||||
{
|
||||
ASSERT(std::find(m_Clients.begin(), m_Clients.end(), *itr) == m_Clients.end());
|
||||
m_Clients.push_back(*itr);
|
||||
@ -1113,7 +1119,7 @@ void cWorld::TickClients(float a_Dt)
|
||||
m_ClientsToAdd.clear();
|
||||
|
||||
// Tick the clients, take out those that have been destroyed into RemoveClients
|
||||
for (cClientHandleList::iterator itr = m_Clients.begin(); itr != m_Clients.end();)
|
||||
for (auto itr = m_Clients.begin(); itr != m_Clients.end();)
|
||||
{
|
||||
if ((*itr)->IsDestroyed())
|
||||
{
|
||||
@ -1126,12 +1132,9 @@ void cWorld::TickClients(float a_Dt)
|
||||
++itr;
|
||||
} // for itr - m_Clients[]
|
||||
}
|
||||
|
||||
// Delete the clients that have been destroyed
|
||||
for (cClientHandleList::iterator itr = RemoveClients.begin(); itr != RemoveClients.end(); ++itr)
|
||||
{
|
||||
delete *itr;
|
||||
} // for itr - RemoveClients[]
|
||||
|
||||
// Delete the clients queued for removal:
|
||||
RemoveClients.clear();
|
||||
}
|
||||
|
||||
|
||||
@ -3525,7 +3528,7 @@ void cWorld::AddQueuedPlayers(void)
|
||||
cCSLock Lock(m_CSClients);
|
||||
for (cPlayerList::iterator itr = PlayersToAdd.begin(), end = PlayersToAdd.end(); itr != end; ++itr)
|
||||
{
|
||||
cClientHandle * Client = (*itr)->GetClientHandle();
|
||||
cClientHandlePtr Client = (*itr)->GetClientHandlePtr();
|
||||
if (Client != nullptr)
|
||||
{
|
||||
m_Clients.push_back(Client);
|
||||
|
@ -38,6 +38,9 @@ class cRedstoneSimulator;
|
||||
class cItem;
|
||||
class cPlayer;
|
||||
class cClientHandle;
|
||||
typedef SharedPtr<cClientHandle> cClientHandlePtr;
|
||||
typedef std::list<cClientHandlePtr> cClientHandlePtrs;
|
||||
typedef std::list<cClientHandle *> cClientHandles;
|
||||
class cEntity;
|
||||
class cBlockEntity;
|
||||
class cWorldGenerator; // The generator that actually generates the chunks for a single world
|
||||
@ -1019,13 +1022,13 @@ private:
|
||||
cCriticalSection m_CSClients;
|
||||
|
||||
/** List of clients in this world, these will be ticked by this world */
|
||||
cClientHandleList m_Clients;
|
||||
cClientHandlePtrs m_Clients;
|
||||
|
||||
/** Clients that are scheduled for removal (ticked in another world), waiting for TickClients() to remove them */
|
||||
cClientHandleList m_ClientsToRemove;
|
||||
cClientHandles m_ClientsToRemove;
|
||||
|
||||
/** Clients that are scheduled for adding, waiting for TickClients to add them */
|
||||
cClientHandleList m_ClientsToAdd;
|
||||
cClientHandlePtrs m_ClientsToAdd;
|
||||
|
||||
/** Guards m_EntitiesToAdd */
|
||||
cCriticalSection m_CSEntitiesToAdd;
|
||||
|
13
src/main.cpp
13
src/main.cpp
@ -11,14 +11,18 @@
|
||||
#include <dbghelp.h>
|
||||
#endif // _MSC_VER
|
||||
|
||||
|
||||
bool cRoot::m_TerminateEventRaised = false; // If something has told the server to stop; checked periodically in cRoot
|
||||
static bool g_ServerTerminated = false; // Set to true when the server terminates, so our CTRL handler can then tell the OS to close the console
|
||||
#include "OSSupport/NetworkSingleton.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/** If something has told the server to stop; checked periodically in cRoot */
|
||||
bool cRoot::m_TerminateEventRaised = false;
|
||||
|
||||
/** Set to true when the server terminates, so our CTRL handler can then tell the OS to close the console. */
|
||||
static bool g_ServerTerminated = false;
|
||||
|
||||
/** If set to true, the protocols will log each player's incoming (C->S) communication to a per-connection logfile */
|
||||
bool g_ShouldLogCommIn;
|
||||
|
||||
@ -305,6 +309,9 @@ int main( int argc, char **argv)
|
||||
|
||||
g_ServerTerminated = true;
|
||||
|
||||
// Shutdown all of LibEvent:
|
||||
cNetworkSingleton::Get().Terminate();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include "OSSupport/Network.h"
|
||||
#include "OSSupport/NetworkSingleton.h"
|
||||
|
||||
|
||||
|
||||
@ -98,7 +99,7 @@ class cEchoServerCallbacks:
|
||||
|
||||
|
||||
|
||||
int main()
|
||||
void DoTest(void)
|
||||
{
|
||||
LOGD("EchoServer: starting up");
|
||||
cServerHandlePtr Server = cNetwork::Listen(9876, std::make_shared<cEchoServerCallbacks>());
|
||||
@ -119,9 +120,20 @@ int main()
|
||||
Server->Close();
|
||||
ASSERT(!Server->IsListening());
|
||||
LOGD("Server has been closed.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
DoTest();
|
||||
|
||||
printf("Press enter to exit test.\n");
|
||||
AString line;
|
||||
std::getline(std::cin, line);
|
||||
cNetworkSingleton::Get().Terminate();
|
||||
|
||||
LOG("Network test finished.");
|
||||
return 0;
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <thread>
|
||||
#include "OSSupport/Event.h"
|
||||
#include "OSSupport/Network.h"
|
||||
#include "OSSupport/NetworkSingleton.h"
|
||||
|
||||
|
||||
|
||||
@ -96,7 +97,7 @@ public:
|
||||
|
||||
|
||||
|
||||
int main()
|
||||
void DoTest(void)
|
||||
{
|
||||
cEvent evtFinish;
|
||||
|
||||
@ -109,7 +110,19 @@ int main()
|
||||
LOGD("Connect request has been queued.");
|
||||
|
||||
evtFinish.Wait();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
DoTest();
|
||||
|
||||
cNetworkSingleton::Get().Terminate();
|
||||
LOGD("Network test finished");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <thread>
|
||||
#include "OSSupport/Event.h"
|
||||
#include "OSSupport/Network.h"
|
||||
#include "OSSupport/NetworkSingleton.h"
|
||||
|
||||
|
||||
|
||||
@ -45,7 +46,7 @@ public:
|
||||
|
||||
|
||||
|
||||
int main()
|
||||
void DoTest(void)
|
||||
{
|
||||
cEvent evtFinish;
|
||||
|
||||
@ -70,7 +71,16 @@ int main()
|
||||
LOGD("IP lookup has been successfully queued");
|
||||
evtFinish.Wait();
|
||||
LOGD("IP lookup finished.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
DoTest();
|
||||
cNetworkSingleton::Get().Terminate();
|
||||
LOGD("Network test finished");
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user