diff --git a/src/ClientHandle.cpp b/src/ClientHandle.cpp index 387cc4628..e90114d1b 100644 --- a/src/ClientHandle.cpp +++ b/src/ClientHandle.cpp @@ -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 = (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); + } + } +} + + + + + void cClientHandle::HandlePlayerAbilities(bool a_CanFly, bool a_IsFlying, float FlyingSpeed, float WalkingSpeed) { UNUSED(FlyingSpeed); // Ignore the client values for these @@ -1777,44 +1822,12 @@ 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); + if (m_Link != nullptr) { - 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); + m_Link->Send(a_Data, a_Size); + } } @@ -1873,8 +1886,8 @@ void cClientHandle::Tick(float a_Dt) } m_Protocol->DataReceived(IncomingData.data(), IncomingData.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(); @@ -1970,8 +1983,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 +2856,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 +2873,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(); } diff --git a/src/ClientHandle.h b/src/ClientHandle.h index 03ae38cfd..f5b7faede 100644 --- a/src/ClientHandle.h +++ b/src/ClientHandle.h @@ -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 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 cChannels; @@ -361,13 +368,16 @@ 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 data going out through m_Link against multi-threaded sending. */ cCriticalSection m_CSOutgoingData; - cByteBuffer m_OutgoingData; - AString m_OutgoingDataOverflow; ///< For data that didn't fit into the m_OutgoingData ringbuffer temporarily Vector3d m_ConfirmPosition; @@ -379,8 +389,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 +468,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 +500,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 - - - diff --git a/src/Entities/Player.cpp b/src/Entities/Player.cpp index 1ca131375..527380761 100644 --- a/src/Entities/Player.cpp +++ b/src/Entities/Player.cpp @@ -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); diff --git a/src/Entities/Player.h b/src/Entities/Player.h index d3ed46db6..fa9ac7cad 100644 --- a/src/Entities/Player.h +++ b/src/Entities/Player.h @@ -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; diff --git a/src/Server.cpp b/src/Server.cpp index df6e5ec8a..783b12947 100644 --- a/src/Server.cpp +++ b/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(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 = static_cast(atoi(port.c_str())); + if (PortNum == 0) + { + LOGWARNING("Invalid port specified for server: \"%s\". Ignoring.", port.c_str()); + continue; + } + auto Handle = cNetwork::Listen(PortNum, std::make_shared(*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()) @@ -669,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(); } @@ -693,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) { @@ -709,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) { @@ -723,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(a_Client)); // Put it there only once - m_Clients.push_back(const_cast(a_Client)); - } - m_Event.Set(); -} - - - - diff --git a/src/Server.h b/src/Server.h index aab47987f..1f30295b7 100644 --- a/src/Server.h +++ b/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 cClientHandlePtr; +typedef std::list cClientHandlePtrs; +typedef std::list cClientHandles; class cIniFile; class cCommandOutputCallback; -typedef std::list 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 diff --git a/src/World.cpp b/src/World.cpp index 24b1a9b40..474f77b81 100644 --- a/src/World.cpp +++ b/src/World.cpp @@ -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); diff --git a/src/World.h b/src/World.h index e7519dab8..3cac71a36 100644 --- a/src/World.h +++ b/src/World.h @@ -38,6 +38,9 @@ class cRedstoneSimulator; class cItem; class cPlayer; class cClientHandle; +typedef SharedPtr cClientHandlePtr; +typedef std::list cClientHandlePtrs; +typedef std::list 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;