From 8d2a2cd5de40e2b94ef5007c32832ed9a063dc40 Mon Sep 17 00:00:00 2001 From: chai <215380520@qq.com> Date: Thu, 12 Oct 2023 22:09:49 +0800 Subject: +hazel-networking --- .../Hazel/Udp/UdpConnection.KeepAlive.cs | 167 +++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 Tools/Hazel-Networking/Hazel/Udp/UdpConnection.KeepAlive.cs (limited to 'Tools/Hazel-Networking/Hazel/Udp/UdpConnection.KeepAlive.cs') diff --git a/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.KeepAlive.cs b/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.KeepAlive.cs new file mode 100644 index 0000000..75b4f1d --- /dev/null +++ b/Tools/Hazel-Networking/Hazel/Udp/UdpConnection.KeepAlive.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Threading; + + +namespace Hazel.Udp +{ + partial class UdpConnection + { + + /// + /// Class to hold packet data + /// + public class PingPacket : IRecyclable + { + private static readonly ObjectPool PacketPool = new ObjectPool(() => new PingPacket()); + + public readonly Stopwatch Stopwatch = new Stopwatch(); + + internal static PingPacket GetObject() + { + return PacketPool.GetObject(); + } + + public void Recycle() + { + Stopwatch.Stop(); + PacketPool.PutObject(this); + } + } + + internal ConcurrentDictionary activePingPackets = new ConcurrentDictionary(); + + /// + /// The interval from data being received or transmitted to a keepalive packet being sent in milliseconds. + /// + /// + /// + /// Keepalive packets serve to close connections when an endpoint abruptly disconnects and to ensure than any + /// NAT devices do not close their translation for our argument. By ensuring there is regular contact the + /// connection can detect and prevent these issues. + /// + /// + /// The default value is 10 seconds, set to System.Threading.Timeout.Infinite to disable keepalive packets. + /// + /// + public int KeepAliveInterval + { + get + { + return keepAliveInterval; + } + + set + { + keepAliveInterval = value; + ResetKeepAliveTimer(); + } + } + private int keepAliveInterval = 1500; + + public int MissingPingsUntilDisconnect { get; set; } = 6; + private volatile int pingsSinceAck = 0; + + /// + /// The timer creating keepalive pulses. + /// + private Timer keepAliveTimer; + + /// + /// Starts the keepalive timer. + /// + protected void InitializeKeepAliveTimer() + { + keepAliveTimer = new Timer( + HandleKeepAlive, + null, + keepAliveInterval, + keepAliveInterval + ); + } + + private void HandleKeepAlive(object state) + { + if (this.State != ConnectionState.Connected) return; + + if (this.pingsSinceAck >= this.MissingPingsUntilDisconnect) + { + this.DisposeKeepAliveTimer(); + this.DisconnectInternal(HazelInternalErrors.PingsWithoutResponse, $"Sent {this.pingsSinceAck} pings that remote has not responded to."); + return; + } + + try + { + this.pingsSinceAck++; + SendPing(); + } + catch + { + } + } + + // Pings are special, quasi-reliable packets. + // We send them to trigger responses that validate our connection is alive + // An unacked ping should never be the sole cause of a disconnect. + // Rather, the responses will reset our pingsSinceAck, enough unacked + // pings should cause a disconnect. + private void SendPing() + { + ushort id = (ushort)Interlocked.Increment(ref lastIDAllocated); + + byte[] bytes = new byte[3]; + bytes[0] = (byte)UdpSendOption.Ping; + bytes[1] = (byte)(id >> 8); + bytes[2] = (byte)id; + + PingPacket pkt; + if (!this.activePingPackets.TryGetValue(id, out pkt)) + { + pkt = PingPacket.GetObject(); + if (!this.activePingPackets.TryAdd(id, pkt)) + { + throw new Exception("This shouldn't be possible"); + } + } + + pkt.Stopwatch.Restart(); + + WriteBytesToConnection(bytes, bytes.Length); + + Statistics.LogReliableSend(0); + } + + /// + /// Resets the keepalive timer to zero. + /// + protected void ResetKeepAliveTimer() + { + try + { + keepAliveTimer?.Change(keepAliveInterval, keepAliveInterval); + } + catch { } + } + + /// + /// Disposes of the keep alive timer. + /// + private void DisposeKeepAliveTimer() + { + if (this.keepAliveTimer != null) + { + this.keepAliveTimer.Dispose(); + } + + foreach (var kvp in activePingPackets) + { + if (this.activePingPackets.TryRemove(kvp.Key, out var pkt)) + { + pkt.Recycle(); + } + } + } + } +} \ No newline at end of file -- cgit v1.1-26-g67d0