diff --git a/.idea/.idea.Intersect/.idea/vcs.xml b/.idea/.idea.Intersect/.idea/vcs.xml index 94a25f7f4c..2dc6e4d60d 100644 --- a/.idea/.idea.Intersect/.idea/vcs.xml +++ b/.idea/.idea.Intersect/.idea/vcs.xml @@ -2,5 +2,7 @@ + + \ No newline at end of file diff --git a/Framework/Intersect.Framework.Core/Network/Packets/Unconnected/Client/ServerStatusRequestPacket.cs b/Framework/Intersect.Framework.Core/Network/Packets/Unconnected/Client/ServerStatusRequestPacket.cs index d1d5d061af..d4524f904e 100644 --- a/Framework/Intersect.Framework.Core/Network/Packets/Unconnected/Client/ServerStatusRequestPacket.cs +++ b/Framework/Intersect.Framework.Core/Network/Packets/Unconnected/Client/ServerStatusRequestPacket.cs @@ -13,4 +13,6 @@ public ServerStatusRequestPacket(byte[] responseKey) : base(responseKey) } [Key(2)] public byte[] VersionData { get; set; } -} \ No newline at end of file + + [Key(3)] public byte[] StateToken { get; set; } +} diff --git a/Framework/Intersect.Framework.Core/Network/Packets/Unconnected/Server/ServerStatusResponsePacket.cs b/Framework/Intersect.Framework.Core/Network/Packets/Unconnected/Server/ServerStatusResponsePacket.cs index d383c3dece..0635b4c395 100644 --- a/Framework/Intersect.Framework.Core/Network/Packets/Unconnected/Server/ServerStatusResponsePacket.cs +++ b/Framework/Intersect.Framework.Core/Network/Packets/Unconnected/Server/ServerStatusResponsePacket.cs @@ -7,4 +7,6 @@ public partial class ServerStatusResponsePacket : UnconnectedResponsePacket { [Key(1)] public NetworkStatus Status { get; set; } -} \ No newline at end of file + + [Key(2)] public byte[] StateToken { get; set; } +} diff --git a/Intersect.Client.Core/MonoGame/Network/MonoSocket.cs b/Intersect.Client.Core/MonoGame/Network/MonoSocket.cs index 05321c6a03..af97662154 100644 --- a/Intersect.Client.Core/MonoGame/Network/MonoSocket.cs +++ b/Intersect.Client.Core/MonoGame/Network/MonoSocket.cs @@ -17,6 +17,7 @@ using Intersect.Core; using Intersect.Framework.Core; using Intersect.Network.Packets.Unconnected.Client; +using Intersect.Network.Packets.Unconnected.Server; using Intersect.Rsa; using Microsoft.Extensions.Logging; @@ -97,6 +98,9 @@ public override IClient Network private int? _lastPort; private IPEndPoint? _lastEndpoint; private volatile bool _resolvingHost; + private int _ping; + + private ServerStatusRequestMetadata? _serverStatusRequestMetadata; public static MonoSocket Instance { get; private set; } = default!; @@ -108,7 +112,7 @@ internal MonoSocket(IClientContext context) public override bool IsConnected => Network.IsConnected; - public override int Ping => Network.Ping; + public override int Ping => _ping < 0 ? Network.Ping : _ping; private bool TryResolveEndPoint([NotNullWhen(true)] out IPEndPoint? endPoint) { @@ -210,60 +214,80 @@ public override void Update() OnDataReceived(dequeued.Value); } - // ReSharper disable once InvertIf - if (Globals.GameState == GameStates.Menu) + switch (Globals.GameState) { - var now = Timing.Global.MillisecondsUtc; - // ReSharper disable once InvertIf - if (_nextServerStatusPing <= now || MainMenu.LastNetworkStatusChangeTime < 0) + case GameStates.InGame: + case GameStates.Error: + case GameStates.Intro: + case GameStates.Loading: + _ping = -1; + break; + + case GameStates.Menu: { - if (!_resolvingHost) + var now = Timing.Global.MillisecondsUtc; + // ReSharper disable once InvertIf + if (_nextServerStatusPing <= now || MainMenu.LastNetworkStatusChangeTime < 0) { - _resolvingHost = true; - Task.Run( - () => - { - try + if (!_resolvingHost) + { + _resolvingHost = true; + Task.Run( + () => { - if (TryResolveEndPoint(out var serverEndpoint)) + try { - var network = Network; - if (network == default) + if (TryResolveEndPoint(out var serverEndpoint)) { - ApplicationContext.Context.Value?.Logger.LogInformation("No network created to poll for server status."); + var network = Network; + if (network == default) + { + ApplicationContext.Context.Value?.Logger.LogInformation("No network created to poll for server status."); + } + else + { + ServerStatusRequestMetadata requestMetadata = new() + { + RequestTime = DateTime.UtcNow, + StateToken = Guid.NewGuid(), + }; + + network.SendUnconnected( + serverEndpoint, + new ServerStatusRequestPacket + { + StateToken = requestMetadata.StateToken.ToByteArray(), + VersionData = SharedConstants.VersionData, + } + ); + + _serverStatusRequestMetadata = requestMetadata; + } } - else + else if (!ClientNetwork.UnresolvableHostNames.Contains(_lastHost)) { - network.SendUnconnected( - serverEndpoint, - new ServerStatusRequestPacket - { - VersionData = SharedConstants.VersionData, - } - ); + ApplicationContext.Context.Value?.Logger.LogInformation($"Unable to resolve '{_lastHost}:{_lastPort}'"); } } - else if (!ClientNetwork.UnresolvableHostNames.Contains(_lastHost)) + catch (Exception exception) { - ApplicationContext.Context.Value?.Logger.LogInformation($"Unable to resolve '{_lastHost}:{_lastPort}'"); + ApplicationContext.Context.Value?.Logger.LogError(exception, "Error resolving host"); } + + _resolvingHost = false; } - catch (Exception exception) - { - ApplicationContext.Context.Value?.Logger.LogError(exception, "Error resolving host"); - } + ); + } - _resolvingHost = false; - } - ); - } + if (MainMenu.LastNetworkStatusChangeTime + (int)(ServerStatusPingInterval * 1.5f) < now) + { + MainMenu.SetNetworkStatus(NetworkStatus.Offline); + } - if (MainMenu.LastNetworkStatusChangeTime + (int)(ServerStatusPingInterval * 1.5f) < now) - { - MainMenu.SetNetworkStatus(NetworkStatus.Offline); + _nextServerStatusPing = now + ServerStatusPingInterval; } - _nextServerStatusPing = now + ServerStatusPingInterval; + break; } } } @@ -279,4 +303,30 @@ public override void Dispose() _network?.Dispose(); _network = default; } + + public void NotifyServerStatusResponse(ServerStatusResponsePacket responsePacket) + { + var now = DateTime.UtcNow; + + if (_serverStatusRequestMetadata is not {} requestMetadata) + { + return; + } + + Guid responseStateToken = new(responsePacket.StateToken); + if (requestMetadata.StateToken != responseStateToken) + { + return; + } + + var elapsed = now - requestMetadata.RequestTime; + _ping = (int)elapsed.TotalMilliseconds; + _serverStatusRequestMetadata = null; + } + + private record struct ServerStatusRequestMetadata + { + public DateTime RequestTime { get; init; } + public Guid StateToken { get; init; } + } } diff --git a/Intersect.Client.Core/Networking/UnconnectedPacketHandlers/ServerStatusResponsePacketHandler.cs b/Intersect.Client.Core/Networking/UnconnectedPacketHandlers/ServerStatusResponsePacketHandler.cs index f14ba05909..68b00fbca9 100644 --- a/Intersect.Client.Core/Networking/UnconnectedPacketHandlers/ServerStatusResponsePacketHandler.cs +++ b/Intersect.Client.Core/Networking/UnconnectedPacketHandlers/ServerStatusResponsePacketHandler.cs @@ -1,4 +1,5 @@ using Intersect.Client.Interface.Menu; +using Intersect.Client.MonoGame.Network; using Intersect.Core; using Intersect.Network; using Intersect.Network.Packets.Unconnected.Server; @@ -13,6 +14,7 @@ public override bool Handle(IPacketSender packetSender, ServerStatusResponsePack { try { + MonoSocket.Instance?.NotifyServerStatusResponse(packet); MainMenu.SetNetworkStatus(packet.Status); return true; } @@ -22,4 +24,4 @@ public override bool Handle(IPacketSender packetSender, ServerStatusResponsePack return false; } } -} \ No newline at end of file +} diff --git a/Intersect.Network/ClientNetwork.cs b/Intersect.Network/ClientNetwork.cs index 76ac7425da..1b43ce855f 100644 --- a/Intersect.Network/ClientNetwork.cs +++ b/Intersect.Network/ClientNetwork.cs @@ -72,41 +72,8 @@ public int Ping if (Configuration.Host is not { } hostNameOrAddress || UnresolvableHostNames.Contains(hostNameOrAddress)) { - return -1; - } - - try - { - // TODO: Add feature-specific log filtering, this one gets annoying - // ApplicationContext.Logger.LogTrace("Sending ping to server"); - - // Send a ping to the server. Timeout: 5000ms (5 seconds). Packet size: 32 bytes. TTL: 64. Don't fragment. - var reply = _ping.Send(hostNameOrAddress, 5000, [], new PingOptions(64, true)); - if (reply is { Status: IPStatus.Success }) - { - // Return the roundtrip time in milliseconds (ms) as an integer value (no decimals). - return (int)reply.RoundtripTime; - } - } - catch (PingException pingException) - { - if (pingException.InnerException is SocketException { SocketErrorCode: SocketError.HostNotFound }) - { - UnresolvableHostNames.Add(hostNameOrAddress); - ApplicationContext.Logger.LogWarning( - pingException, - "Invalid hostname '{HostNameOrAddress}' will not be pinged again", - hostNameOrAddress - ); - } - else - { - ApplicationContext.Logger.LogWarning(pingException, "Error sending ping request"); - } - } - catch (Exception exception) - { - ApplicationContext.Logger.LogWarning(exception, "Unknown error sending ping request"); + // Return a distinct ping value for unresolved host addresses so that it's clear why it's not showing + return -2; } return -1; diff --git a/Intersect.Network/LiteNetLib/LiteNetLibInterface.cs b/Intersect.Network/LiteNetLib/LiteNetLibInterface.cs index 860c05e46d..45268247c3 100644 --- a/Intersect.Network/LiteNetLib/LiteNetLibInterface.cs +++ b/Intersect.Network/LiteNetLib/LiteNetLibInterface.cs @@ -483,14 +483,22 @@ public void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketRead public void OnNetworkLatencyUpdate(NetPeer peer, int latency) { -#if !DIAGNOSTIC - if (latency < 1) + +#if DIAGNOSTIC + var logLatencyUpdate = true; +#else + if (latency < 0) { return; } + + var logLatencyUpdate = latency > 0; #endif - ApplicationContext.CurrentContext.Logger.LogTrace("LATENCY {Peer} {Latency}ms", peer, latency); + if (logLatencyUpdate) + { + ApplicationContext.CurrentContext.Logger.LogTrace("LATENCY {Peer} {Latency}ms", peer, latency); + } if (!_connectionIdLookup.TryGetValue(peer.Id, out var connectionId)) { @@ -616,4 +624,4 @@ public void OnConnectionRequest(ConnectionRequest request) ApplicationContext.Context.Value?.Logger.LogDebug($"Approved {peer} ({connection.Guid})"); } -} \ No newline at end of file +} diff --git a/Intersect.Server/Networking/UnconnectedPacketHandlers/ServerStatusRequestPacketHandler.cs b/Intersect.Server/Networking/UnconnectedPacketHandlers/ServerStatusRequestPacketHandler.cs index 7bb400770c..90f66e2deb 100644 --- a/Intersect.Server/Networking/UnconnectedPacketHandlers/ServerStatusRequestPacketHandler.cs +++ b/Intersect.Server/Networking/UnconnectedPacketHandlers/ServerStatusRequestPacketHandler.cs @@ -16,6 +16,7 @@ public override bool Handle(IPacketSender packetSender, ServerStatusRequestPacke { ResponseKey = packet.ResponseKey, Status = NetworkStatus.VersionMismatch, + StateToken = packet.StateToken, } ); } @@ -27,6 +28,7 @@ public override bool Handle(IPacketSender packetSender, ServerStatusRequestPacke { ResponseKey = packet.ResponseKey, Status = NetworkStatus.ServerFull, + StateToken = packet.StateToken, } ); } @@ -36,7 +38,8 @@ public override bool Handle(IPacketSender packetSender, ServerStatusRequestPacke { ResponseKey = packet.ResponseKey, Status = NetworkStatus.Online, + StateToken = packet.StateToken, } ); } -} \ No newline at end of file +}