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