diff --git a/OpenRA.Game/Network/FrameData.cs b/OpenRA.Game/Network/FrameData.cs index f15ca2a4b7..c2a8da14a9 100755 --- a/OpenRA.Game/Network/FrameData.cs +++ b/OpenRA.Game/Network/FrameData.cs @@ -34,6 +34,11 @@ namespace OpenRA.Network public void ClientQuit( int clientId, int lastClientFrame ) { + if (lastClientFrame == -1) + lastClientFrame = framePackets + .Where(x => x.Value.ContainsKey(clientId)) + .Select(x => x.Key).OrderBy(x => x).LastOrDefault(); + clientQuitTimes[clientId] = lastClientFrame; } diff --git a/OpenRA.Game/Server/Connection.cs b/OpenRA.Game/Server/Connection.cs index c6d2923848..79e4248380 100644 --- a/OpenRA.Game/Server/Connection.cs +++ b/OpenRA.Game/Server/Connection.cs @@ -25,6 +25,10 @@ namespace OpenRA.Server public int MostRecentFrame = 0; public const int MaxOrderLength = 131072; + public int TimeSinceLastResponse { get { return Game.RunTime - lastReceivedTime; } } + public bool TimeoutMessageShown = false; + int lastReceivedTime = 0; + /* client data */ public int PlayerIndex; @@ -69,6 +73,9 @@ namespace OpenRA.Server } } + lastReceivedTime = Game.RunTime; + TimeoutMessageShown = false; + return true; } @@ -101,7 +108,6 @@ namespace OpenRA.Server ExpectLength = 8; State = ReceiveState.Header; - server.UpdateInFlightFrames(this); } break; } } diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index bea54f5ba8..db72d096c8 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -46,8 +46,6 @@ namespace OpenRA.Server public List PreConns = new List(); TcpListener listener = null; - Dictionary> inFlightFrames - = new Dictionary>(); TypeDictionary serverTraits = new TypeDictionary(); public Session LobbyInfo; @@ -389,20 +387,6 @@ namespace OpenRA.Server SyncLobbyGlobalSettings(); } - public void UpdateInFlightFrames(Connection conn) - { - if (conn.Frame == 0) - return; - - if (!inFlightFrames.ContainsKey(conn.Frame)) - inFlightFrames[conn.Frame] = new List { conn }; - else - inFlightFrames[conn.Frame].Add(conn); - - if (Conns.All(c => inFlightFrames[conn.Frame].Contains(c))) - inFlightFrames.Remove(conn.Frame); - } - void DispatchOrdersToClient(Connection c, int client, int frame, byte[] data) { try @@ -527,6 +511,11 @@ namespace OpenRA.Server } public void DropClient(Connection toDrop) + { + DropClient(toDrop, toDrop.MostRecentFrame); + } + + public void DropClient(Connection toDrop, int frame) { if (PreConns.Contains(toDrop)) PreConns.Remove(toDrop); @@ -565,7 +554,7 @@ namespace OpenRA.Server } } - DispatchOrders(toDrop, toDrop.MostRecentFrame, new byte[] { 0xbf }); + DispatchOrders(toDrop, frame, new byte[] { 0xbf }); if (!Conns.Any()) { diff --git a/OpenRA.Mods.Common/ServerTraits/PlayerPinger.cs b/OpenRA.Mods.Common/ServerTraits/PlayerPinger.cs index 5dce33a719..8ac9f85ad2 100644 --- a/OpenRA.Mods.Common/ServerTraits/PlayerPinger.cs +++ b/OpenRA.Mods.Common/ServerTraits/PlayerPinger.cs @@ -9,6 +9,7 @@ #endregion using System; +using System.Linq; using OpenRA.Server; using S = OpenRA.Server.Server; @@ -17,20 +18,52 @@ namespace OpenRA.Mods.Common.Server public class PlayerPinger : ServerTrait, ITick { int PingInterval = 5000; // Ping every 5 seconds + int ConnReportInterval = 20000; // Report every 20 seconds + int ConnTimeout = 90000; // Drop unresponsive clients after 90 seconds // TickTimeout is in microseconds public int TickTimeout { get { return PingInterval * 100; } } int lastPing = 0; + int lastConnReport = 0; bool isInitialPing = true; + public void Tick(S server) { if ((Game.RunTime - lastPing > PingInterval) || isInitialPing) { isInitialPing = false; lastPing = Game.RunTime; - foreach (var p in server.Conns) - server.SendOrderTo(p, "Ping", Game.RunTime.ToString()); + foreach (var c in server.Conns.ToList()) + { + if (c.TimeSinceLastResponse < ConnTimeout) + { + server.SendOrderTo(c, "Ping", Game.RunTime.ToString()); + if (!c.TimeoutMessageShown && c.TimeSinceLastResponse > PingInterval * 2) + { + server.SendMessage(server.GetClient(c).Name + " is experiencing connection problems."); + c.TimeoutMessageShown = true; + } + } + else + { + server.SendMessage(server.GetClient(c).Name + " has been dropped after timing out."); + server.DropClient(c, -1); + } + } + } + + if (Game.RunTime - lastConnReport > ConnReportInterval) + { + lastConnReport = Game.RunTime; + + var timeouts = server.Conns + .Where(c => c.TimeSinceLastResponse > ConnReportInterval && c.TimeSinceLastResponse < ConnTimeout) + .OrderBy(c => c.TimeSinceLastResponse); + + foreach (var c in timeouts) + server.SendMessage("{0} will be dropped in {1} seconds.".F( + server.GetClient(c).Name, (ConnTimeout - c.TimeSinceLastResponse) / 1000)); } } }