800 lines
16 KiB
C#
800 lines
16 KiB
C#
using System;
|
|||
|
|
using System.Collections.Concurrent;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
using System.Net;
|
||
|
|
using System.Net.Sockets;
|
||
|
|
using System.Threading;
|
||
|
|
using System.Threading.Tasks;
|
||
|
|
|
||
|
|
namespace Crysome.Common.Network;
|
||
|
|
|
||
|
|
public sealed class RudpChannel : IDisposable
|
||
|
|
{
|
||
|
|
private sealed class Pending
|
||
|
|
{
|
||
|
|
public uint Seq;
|
||
|
|
|
||
|
|
public byte[][] Datagrams;
|
||
|
|
|
||
|
|
public DateTime SentAt;
|
||
|
|
|
||
|
|
public DateTime LastSent;
|
||
|
|
|
||
|
|
public int Retries;
|
||
|
|
}
|
||
|
|
|
||
|
|
private sealed class FragBuf
|
||
|
|
{
|
||
|
|
public byte[][] Frags;
|
||
|
|
|
||
|
|
public int RecvCount;
|
||
|
|
|
||
|
|
public DateTime Created = DateTime.UtcNow;
|
||
|
|
|
||
|
|
public bool IsComplete
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
if (Frags != null)
|
||
|
|
{
|
||
|
|
return RecvCount >= Frags.Length;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public const int HeaderSize = 19;
|
||
|
|
|
||
|
|
private const int WireMtuSafePayload = 1200;
|
||
|
|
|
||
|
|
public const byte F_RELIABLE = 1;
|
||
|
|
|
||
|
|
public const byte F_UNRELIABLE = 2;
|
||
|
|
|
||
|
|
public const byte F_ACK = 4;
|
||
|
|
|
||
|
|
public const byte F_SYN = 8;
|
||
|
|
|
||
|
|
public const byte F_SYNACK = 16;
|
||
|
|
|
||
|
|
public const byte F_FIN = 32;
|
||
|
|
|
||
|
|
public const byte F_KEEPALIVE = 64;
|
||
|
|
|
||
|
|
private const int InitRto = 500;
|
||
|
|
|
||
|
|
private const int MaxRto = 16000;
|
||
|
|
|
||
|
|
private const int MaxRetries = 15;
|
||
|
|
|
||
|
|
private const int KeepaliveMs = 5000;
|
||
|
|
|
||
|
|
private const int TimeoutMs = 30000;
|
||
|
|
|
||
|
|
private const int MaxInFlight = 64;
|
||
|
|
|
||
|
|
private readonly UdpClient _socket;
|
||
|
|
|
||
|
|
private readonly bool _ownsSocket;
|
||
|
|
|
||
|
|
private uint _sendSeq;
|
||
|
|
|
||
|
|
private uint _recvSeq;
|
||
|
|
|
||
|
|
private uint _unrelSendSeq;
|
||
|
|
|
||
|
|
private uint _unrelLastSeq;
|
||
|
|
|
||
|
|
private readonly object _seqLock = new object();
|
||
|
|
|
||
|
|
private readonly ConcurrentDictionary<uint, Pending> _pending = new ConcurrentDictionary<uint, Pending>();
|
||
|
|
|
||
|
|
private readonly SortedDictionary<uint, byte[]> _ooo = new SortedDictionary<uint, byte[]>();
|
||
|
|
|
||
|
|
private readonly object _oooLock = new object();
|
||
|
|
|
||
|
|
private readonly ConcurrentDictionary<uint, FragBuf> _fragBufs = new ConcurrentDictionary<uint, FragBuf>();
|
||
|
|
|
||
|
|
private readonly ConcurrentDictionary<uint, FragBuf> _relFragBufs = new ConcurrentDictionary<uint, FragBuf>();
|
||
|
|
|
||
|
|
private int _rtoMs = 500;
|
||
|
|
|
||
|
|
private bool _rtoDoubledThisCycle;
|
||
|
|
|
||
|
|
private DateTime _lastActivity = DateTime.UtcNow;
|
||
|
|
|
||
|
|
private CancellationTokenSource _cts;
|
||
|
|
|
||
|
|
private bool _disposed;
|
||
|
|
|
||
|
|
private readonly SemaphoreSlim _sendWindow = new SemaphoreSlim(64, 64);
|
||
|
|
|
||
|
|
private readonly object _statsLock = new object();
|
||
|
|
|
||
|
|
private double _srtt = 100.0;
|
||
|
|
|
||
|
|
private double _rttvar = 50.0;
|
||
|
|
|
||
|
|
public uint SessionId { get; private set; }
|
||
|
|
|
||
|
|
public IPEndPoint Remote { get; private set; }
|
||
|
|
|
||
|
|
public bool IsAlive
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
if (!_disposed)
|
||
|
|
{
|
||
|
|
return (DateTime.UtcNow - _lastActivity).TotalMilliseconds < 30000.0;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public double RttMs { get; private set; } = 100.0;
|
||
|
|
|
||
|
|
public double LossRate { get; private set; }
|
||
|
|
|
||
|
|
public long BytesSent { get; private set; }
|
||
|
|
|
||
|
|
public long BytesRecv { get; private set; }
|
||
|
|
|
||
|
|
public long PktsSent { get; private set; }
|
||
|
|
|
||
|
|
public long PktsLost { get; private set; }
|
||
|
|
|
||
|
|
public event Action<byte[]> ReliableReceived;
|
||
|
|
|
||
|
|
public event Action<byte, byte[]> UnreliableFrameReceived;
|
||
|
|
|
||
|
|
public event Action Disconnected;
|
||
|
|
|
||
|
|
public event Action<double> CongestionChanged;
|
||
|
|
|
||
|
|
public RudpChannel(uint sessionId, IPEndPoint remote, int localPort = 0)
|
||
|
|
{
|
||
|
|
SessionId = sessionId;
|
||
|
|
Remote = remote;
|
||
|
|
_socket = new UdpClient(localPort);
|
||
|
|
_socket.Client.ReceiveBufferSize = 8388608;
|
||
|
|
_socket.Client.SendBufferSize = 8388608;
|
||
|
|
_ownsSocket = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
public RudpChannel(uint sessionId, IPEndPoint remote, UdpClient sharedSocket)
|
||
|
|
{
|
||
|
|
SessionId = sessionId;
|
||
|
|
Remote = remote;
|
||
|
|
_socket = sharedSocket;
|
||
|
|
_ownsSocket = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Start()
|
||
|
|
{
|
||
|
|
_cts = new CancellationTokenSource();
|
||
|
|
if (_ownsSocket)
|
||
|
|
{
|
||
|
|
Task.Run(delegate
|
||
|
|
{
|
||
|
|
OwnedReceiveLoop(_cts.Token);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
Task.Run(delegate
|
||
|
|
{
|
||
|
|
RetransmitLoop(_cts.Token);
|
||
|
|
});
|
||
|
|
Task.Run(delegate
|
||
|
|
{
|
||
|
|
KeepaliveLoop(_cts.Token);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
public void Dispose()
|
||
|
|
{
|
||
|
|
if (_disposed)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
_disposed = true;
|
||
|
|
try
|
||
|
|
{
|
||
|
|
_cts?.Cancel();
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
}
|
||
|
|
if (_ownsSocket)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
_socket?.Close();
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
}
|
||
|
|
}
|
||
|
|
int count = _pending.Count;
|
||
|
|
_pending.Clear();
|
||
|
|
for (int i = 0; i < count + 64; i++)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
_sendWindow.Release();
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public void SendReliable(byte[] payload)
|
||
|
|
{
|
||
|
|
if (_disposed)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
_sendWindow.Wait();
|
||
|
|
if (_disposed)
|
||
|
|
{
|
||
|
|
_sendWindow.Release();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
int num = Math.Max(1, (payload.Length + 1200 - 1) / 1200);
|
||
|
|
uint num2;
|
||
|
|
lock (_seqLock)
|
||
|
|
{
|
||
|
|
num2 = ++_sendSeq;
|
||
|
|
}
|
||
|
|
byte[][] array = new byte[num][];
|
||
|
|
for (ushort num3 = 0; num3 < num; num3++)
|
||
|
|
{
|
||
|
|
int num4 = num3 * 1200;
|
||
|
|
int payLen = Math.Min(1200, payload.Length - num4);
|
||
|
|
array[num3] = BuildDatagram(1, num2, 0u, num3, (ushort)num, 0, payload, num4, payLen);
|
||
|
|
}
|
||
|
|
Pending value = new Pending
|
||
|
|
{
|
||
|
|
Seq = num2,
|
||
|
|
Datagrams = array,
|
||
|
|
SentAt = DateTime.UtcNow,
|
||
|
|
LastSent = DateTime.UtcNow
|
||
|
|
};
|
||
|
|
_pending[num2] = value;
|
||
|
|
bool flag = num > 4;
|
||
|
|
byte[][] array2 = array;
|
||
|
|
foreach (byte[] dg in array2)
|
||
|
|
{
|
||
|
|
RawSend(dg);
|
||
|
|
if (flag)
|
||
|
|
{
|
||
|
|
Thread.Sleep(2);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
PktsSent++;
|
||
|
|
}
|
||
|
|
|
||
|
|
public void SendUnreliable(byte packetTypeId, byte[] payload)
|
||
|
|
{
|
||
|
|
if (_disposed)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
uint seqNum;
|
||
|
|
lock (_seqLock)
|
||
|
|
{
|
||
|
|
seqNum = ++_unrelSendSeq;
|
||
|
|
}
|
||
|
|
int num = Math.Max(1, (payload.Length + 1200 - 1) / 1200);
|
||
|
|
for (ushort num2 = 0; num2 < num; num2++)
|
||
|
|
{
|
||
|
|
int num3 = num2 * 1200;
|
||
|
|
int payLen = Math.Min(1200, payload.Length - num3);
|
||
|
|
byte[] dg = BuildDatagram(2, seqNum, 0u, num2, (ushort)num, packetTypeId, payload, num3, payLen);
|
||
|
|
RawSend(dg);
|
||
|
|
if (num > 16 && (num2 & 0xF) == 15 && num2 < num - 1)
|
||
|
|
{
|
||
|
|
Thread.Sleep(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public void SendSyn()
|
||
|
|
{
|
||
|
|
byte[] dg = BuildDatagram(8, 0u, 0u, 0, 1, 0, new byte[0], 0, 0);
|
||
|
|
RawSend(dg);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void SendSynAck()
|
||
|
|
{
|
||
|
|
byte[] dg = BuildDatagram(16, SessionId, 0u, 0, 1, 0, new byte[0], 0, 0);
|
||
|
|
RawSend(dg);
|
||
|
|
}
|
||
|
|
|
||
|
|
public void SendFin()
|
||
|
|
{
|
||
|
|
byte[] dg = BuildDatagram(32, 0u, 0u, 0, 1, 0, new byte[0], 0, 0);
|
||
|
|
try
|
||
|
|
{
|
||
|
|
RawSend(dg);
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public void FeedDatagram(byte[] datagram)
|
||
|
|
{
|
||
|
|
ProcessDatagram(datagram);
|
||
|
|
}
|
||
|
|
|
||
|
|
private async void OwnedReceiveLoop(CancellationToken ct)
|
||
|
|
{
|
||
|
|
while (!ct.IsCancellationRequested && !_disposed)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
ProcessDatagram((await _socket.ReceiveAsync()).Buffer);
|
||
|
|
}
|
||
|
|
catch (ObjectDisposedException)
|
||
|
|
{
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void ProcessDatagram(byte[] dg)
|
||
|
|
{
|
||
|
|
if (dg == null || dg.Length < 19)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
_lastActivity = DateTime.UtcNow;
|
||
|
|
BytesRecv += dg.Length;
|
||
|
|
BitConverter.ToUInt32(dg, 0);
|
||
|
|
byte b = dg[4];
|
||
|
|
uint seq = BitConverter.ToUInt32(dg, 5);
|
||
|
|
uint ackSeq = BitConverter.ToUInt32(dg, 9);
|
||
|
|
ushort fragIdx = BitConverter.ToUInt16(dg, 13);
|
||
|
|
ushort fragCount = BitConverter.ToUInt16(dg, 15);
|
||
|
|
byte typeId = dg[17];
|
||
|
|
int payLen = dg.Length - 19;
|
||
|
|
if ((b & 4) != 0)
|
||
|
|
{
|
||
|
|
HandleAck(ackSeq);
|
||
|
|
}
|
||
|
|
else if ((b & 0x40) == 0)
|
||
|
|
{
|
||
|
|
if ((b & 0x20) != 0)
|
||
|
|
{
|
||
|
|
TriggerDisconnect();
|
||
|
|
}
|
||
|
|
else if ((b & 1) != 0)
|
||
|
|
{
|
||
|
|
HandleReliableData(seq, fragIdx, fragCount, dg, payLen);
|
||
|
|
}
|
||
|
|
else if ((b & 2) != 0)
|
||
|
|
{
|
||
|
|
HandleUnreliableData(seq, typeId, fragIdx, fragCount, dg, payLen);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void HandleAck(uint ackSeq)
|
||
|
|
{
|
||
|
|
if (_pending.TryRemove(ackSeq, out var value))
|
||
|
|
{
|
||
|
|
double totalMilliseconds = (DateTime.UtcNow - value.SentAt).TotalMilliseconds;
|
||
|
|
UpdateRtt(totalMilliseconds);
|
||
|
|
try
|
||
|
|
{
|
||
|
|
_sendWindow.Release();
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void HandleReliableData(uint seq, ushort fragIdx, ushort fragCount, byte[] dg, int payLen)
|
||
|
|
{
|
||
|
|
int num = 19;
|
||
|
|
if (payLen < 0 || num + payLen > dg.Length)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
byte[] array = new byte[payLen];
|
||
|
|
if (payLen > 0)
|
||
|
|
{
|
||
|
|
Buffer.BlockCopy(dg, num, array, 0, payLen);
|
||
|
|
}
|
||
|
|
if (fragCount > 1)
|
||
|
|
{
|
||
|
|
FragBuf orAdd = _relFragBufs.GetOrAdd(seq, (uint _) => new FragBuf
|
||
|
|
{
|
||
|
|
Frags = new byte[fragCount][]
|
||
|
|
});
|
||
|
|
if (fragIdx >= orAdd.Frags.Length || orAdd.Frags[fragIdx] != null)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
orAdd.Frags[fragIdx] = array;
|
||
|
|
Interlocked.Increment(ref orAdd.RecvCount);
|
||
|
|
if (!orAdd.IsComplete)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
_relFragBufs.TryRemove(seq, out var _);
|
||
|
|
SendAck(seq);
|
||
|
|
int num2 = 0;
|
||
|
|
byte[][] frags = orAdd.Frags;
|
||
|
|
foreach (byte[] array2 in frags)
|
||
|
|
{
|
||
|
|
if (array2 != null)
|
||
|
|
{
|
||
|
|
num2 += array2.Length;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
byte[] array3 = new byte[num2];
|
||
|
|
int num4 = 0;
|
||
|
|
frags = orAdd.Frags;
|
||
|
|
foreach (byte[] array4 in frags)
|
||
|
|
{
|
||
|
|
if (array4 != null)
|
||
|
|
{
|
||
|
|
Buffer.BlockCopy(array4, 0, array3, num4, array4.Length);
|
||
|
|
num4 += array4.Length;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
array = array3;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
SendAck(seq);
|
||
|
|
}
|
||
|
|
lock (_oooLock)
|
||
|
|
{
|
||
|
|
uint num5 = _recvSeq + 1;
|
||
|
|
if (seq == num5 || _recvSeq == 0)
|
||
|
|
{
|
||
|
|
_recvSeq = seq;
|
||
|
|
DispatchReliable(array);
|
||
|
|
while (true)
|
||
|
|
{
|
||
|
|
uint num6 = _recvSeq + 1;
|
||
|
|
if (_ooo.TryGetValue(num6, out var value2))
|
||
|
|
{
|
||
|
|
_ooo.Remove(num6);
|
||
|
|
_recvSeq = num6;
|
||
|
|
DispatchReliable(value2);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if ((int)(seq - num5) > 0 && !_ooo.ContainsKey(seq))
|
||
|
|
{
|
||
|
|
_ooo[seq] = array;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void HandleUnreliableData(uint seq, byte typeId, ushort fragIdx, ushort fragCount, byte[] dg, int payLen)
|
||
|
|
{
|
||
|
|
lock (_seqLock)
|
||
|
|
{
|
||
|
|
if (_unrelLastSeq != 0 && (int)(seq - _unrelLastSeq) <= 0)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
int num = 19;
|
||
|
|
if (payLen < 0 || num + payLen > dg.Length)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
byte[] array = new byte[payLen];
|
||
|
|
if (payLen > 0)
|
||
|
|
{
|
||
|
|
Buffer.BlockCopy(dg, num, array, 0, payLen);
|
||
|
|
}
|
||
|
|
if (fragCount <= 1)
|
||
|
|
{
|
||
|
|
lock (_seqLock)
|
||
|
|
{
|
||
|
|
_unrelLastSeq = seq;
|
||
|
|
}
|
||
|
|
DispatchUnreliable(typeId, array);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
FragBuf orAdd = _fragBufs.GetOrAdd(seq, (uint _) => new FragBuf
|
||
|
|
{
|
||
|
|
Frags = new byte[fragCount][]
|
||
|
|
});
|
||
|
|
if (fragIdx >= orAdd.Frags.Length || orAdd.Frags[fragIdx] != null)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
orAdd.Frags[fragIdx] = array;
|
||
|
|
Interlocked.Increment(ref orAdd.RecvCount);
|
||
|
|
if (!orAdd.IsComplete)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
_fragBufs.TryRemove(seq, out var _);
|
||
|
|
lock (_seqLock)
|
||
|
|
{
|
||
|
|
if (_unrelLastSeq != 0 && (int)(seq - _unrelLastSeq) <= 0)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
_unrelLastSeq = seq;
|
||
|
|
}
|
||
|
|
int num2 = 0;
|
||
|
|
byte[][] frags = orAdd.Frags;
|
||
|
|
foreach (byte[] array2 in frags)
|
||
|
|
{
|
||
|
|
if (array2 != null)
|
||
|
|
{
|
||
|
|
num2 += array2.Length;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
byte[] array3 = new byte[num2];
|
||
|
|
int num4 = 0;
|
||
|
|
frags = orAdd.Frags;
|
||
|
|
foreach (byte[] array4 in frags)
|
||
|
|
{
|
||
|
|
if (array4 != null)
|
||
|
|
{
|
||
|
|
Buffer.BlockCopy(array4, 0, array3, num4, array4.Length);
|
||
|
|
num4 += array4.Length;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
DispatchUnreliable(typeId, array3);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void DispatchReliable(byte[] data)
|
||
|
|
{
|
||
|
|
Task.Run(delegate
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
this.ReliableReceived?.Invoke(data);
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
private void DispatchUnreliable(byte typeId, byte[] data)
|
||
|
|
{
|
||
|
|
Task.Run(delegate
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
this.UnreliableFrameReceived?.Invoke(typeId, data);
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
private async void RetransmitLoop(CancellationToken ct)
|
||
|
|
{
|
||
|
|
while (!ct.IsCancellationRequested && !_disposed)
|
||
|
|
{
|
||
|
|
await Task.Delay(50, ct).ContinueWith(delegate
|
||
|
|
{
|
||
|
|
});
|
||
|
|
DateTime utcNow = DateTime.UtcNow;
|
||
|
|
FragBuf value;
|
||
|
|
foreach (KeyValuePair<uint, FragBuf> fragBuf in _fragBufs)
|
||
|
|
{
|
||
|
|
if ((utcNow - fragBuf.Value.Created).TotalSeconds > 10.0)
|
||
|
|
{
|
||
|
|
_fragBufs.TryRemove(fragBuf.Key, out value);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
foreach (KeyValuePair<uint, FragBuf> relFragBuf in _relFragBufs)
|
||
|
|
{
|
||
|
|
if ((utcNow - relFragBuf.Value.Created).TotalSeconds > 60.0)
|
||
|
|
{
|
||
|
|
_relFragBufs.TryRemove(relFragBuf.Key, out value);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
_rtoDoubledThisCycle = false;
|
||
|
|
foreach (KeyValuePair<uint, Pending> item in _pending)
|
||
|
|
{
|
||
|
|
Pending value2 = item.Value;
|
||
|
|
if ((utcNow - value2.LastSent).TotalMilliseconds < (double)_rtoMs)
|
||
|
|
{
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (value2.Retries >= 15)
|
||
|
|
{
|
||
|
|
if (_pending.TryRemove(value2.Seq, out var _))
|
||
|
|
{
|
||
|
|
PktsLost++;
|
||
|
|
UpdateLossRate();
|
||
|
|
try
|
||
|
|
{
|
||
|
|
_sendWindow.Release();
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
}
|
||
|
|
}
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
value2.Retries++;
|
||
|
|
value2.LastSent = utcNow;
|
||
|
|
if (!_rtoDoubledThisCycle)
|
||
|
|
{
|
||
|
|
_rtoMs = Math.Min(_rtoMs * 2, 16000);
|
||
|
|
_rtoDoubledThisCycle = true;
|
||
|
|
}
|
||
|
|
bool flag = value2.Datagrams.Length > 4;
|
||
|
|
byte[][] datagrams = value2.Datagrams;
|
||
|
|
foreach (byte[] dg in datagrams)
|
||
|
|
{
|
||
|
|
RawSend(dg);
|
||
|
|
if (flag)
|
||
|
|
{
|
||
|
|
Thread.Sleep(1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
UpdateCongestionMetric();
|
||
|
|
if ((utcNow - _lastActivity).TotalMilliseconds > 30000.0)
|
||
|
|
{
|
||
|
|
TriggerDisconnect();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private async void KeepaliveLoop(CancellationToken ct)
|
||
|
|
{
|
||
|
|
while (!ct.IsCancellationRequested && !_disposed)
|
||
|
|
{
|
||
|
|
await Task.Delay(5000, ct).ContinueWith(delegate
|
||
|
|
{
|
||
|
|
});
|
||
|
|
if (!_disposed)
|
||
|
|
{
|
||
|
|
try
|
||
|
|
{
|
||
|
|
byte[] dg = BuildDatagram(64, 0u, 0u, 0, 1, 0, new byte[0], 0, 0);
|
||
|
|
RawSend(dg);
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
}
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void SendAck(uint seq)
|
||
|
|
{
|
||
|
|
byte[] dg = BuildDatagram(4, 0u, seq, 0, 1, 0, new byte[0], 0, 0);
|
||
|
|
try
|
||
|
|
{
|
||
|
|
RawSend(dg);
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void UpdateRtt(double sample)
|
||
|
|
{
|
||
|
|
lock (_statsLock)
|
||
|
|
{
|
||
|
|
_rttvar = 0.75 * _rttvar + 0.25 * Math.Abs(_srtt - sample);
|
||
|
|
_srtt = 0.875 * _srtt + 0.125 * sample;
|
||
|
|
RttMs = _srtt;
|
||
|
|
_rtoMs = Math.Max(200, Math.Min(16000, (int)(_srtt + 4.0 * _rttvar)));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void UpdateLossRate()
|
||
|
|
{
|
||
|
|
double val = ((PktsSent > 0) ? ((double)PktsLost / (double)PktsSent) : 0.0);
|
||
|
|
LossRate = Math.Min(1.0, val);
|
||
|
|
}
|
||
|
|
|
||
|
|
private void UpdateCongestionMetric()
|
||
|
|
{
|
||
|
|
UpdateLossRate();
|
||
|
|
double num = Math.Min(1.0, RttMs / 2000.0);
|
||
|
|
double lossRate = LossRate;
|
||
|
|
double obj = Math.Min(1.0, num * 0.5 + lossRate * 0.5);
|
||
|
|
try
|
||
|
|
{
|
||
|
|
this.CongestionChanged?.Invoke(obj);
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void TriggerDisconnect()
|
||
|
|
{
|
||
|
|
if (_disposed)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
Dispose();
|
||
|
|
try
|
||
|
|
{
|
||
|
|
this.Disconnected?.Invoke();
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private byte[] BuildDatagram(byte flags, uint seqNum, uint ackNum, ushort fragIdx, ushort fragCount, byte packetTypeId, byte[] payload, int payOffset, int payLen)
|
||
|
|
{
|
||
|
|
byte[] array = new byte[19 + payLen];
|
||
|
|
Buffer.BlockCopy(BitConverter.GetBytes(SessionId), 0, array, 0, 4);
|
||
|
|
array[4] = flags;
|
||
|
|
Buffer.BlockCopy(BitConverter.GetBytes(seqNum), 0, array, 5, 4);
|
||
|
|
Buffer.BlockCopy(BitConverter.GetBytes(ackNum), 0, array, 9, 4);
|
||
|
|
Buffer.BlockCopy(BitConverter.GetBytes(fragIdx), 0, array, 13, 2);
|
||
|
|
Buffer.BlockCopy(BitConverter.GetBytes(fragCount), 0, array, 15, 2);
|
||
|
|
array[17] = packetTypeId;
|
||
|
|
array[18] = 0;
|
||
|
|
if (payLen > 0)
|
||
|
|
{
|
||
|
|
Buffer.BlockCopy(payload, payOffset, array, 19, payLen);
|
||
|
|
}
|
||
|
|
return array;
|
||
|
|
}
|
||
|
|
|
||
|
|
private void RawSend(byte[] dg)
|
||
|
|
{
|
||
|
|
if (_disposed || dg == null)
|
||
|
|
{
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
try
|
||
|
|
{
|
||
|
|
_socket.Send(dg, dg.Length, Remote);
|
||
|
|
BytesSent += dg.Length;
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public static bool ParseHeader(byte[] dg, int len, out uint sessionId, out byte flags, out uint seqNum, out uint ackNum, out ushort fragIdx, out ushort fragCount, out byte packetTypeId)
|
||
|
|
{
|
||
|
|
sessionId = 0u;
|
||
|
|
flags = 0;
|
||
|
|
seqNum = 0u;
|
||
|
|
ackNum = 0u;
|
||
|
|
fragIdx = 0;
|
||
|
|
fragCount = 0;
|
||
|
|
packetTypeId = 0;
|
||
|
|
if (dg == null || len < 19)
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
sessionId = BitConverter.ToUInt32(dg, 0);
|
||
|
|
flags = dg[4];
|
||
|
|
seqNum = BitConverter.ToUInt32(dg, 5);
|
||
|
|
ackNum = BitConverter.ToUInt32(dg, 9);
|
||
|
|
fragIdx = BitConverter.ToUInt16(dg, 13);
|
||
|
|
fragCount = BitConverter.ToUInt16(dg, 15);
|
||
|
|
packetTypeId = dg[17];
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|