initial commit
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using Crysome.Common.Network.Packets;
|
||||
|
||||
namespace Crysome.Common.Network;
|
||||
|
||||
public class CrysomeClient
|
||||
{
|
||||
private RudpChannel _rudp;
|
||||
|
||||
private CancellationTokenSource _readCts = new CancellationTokenSource();
|
||||
|
||||
private readonly BlockingCollection<IPacket> _inQueue = new BlockingCollection<IPacket>(2048);
|
||||
|
||||
private static int _clientIdGen = new Random().Next(1, 100000);
|
||||
|
||||
public bool IsConnected
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_rudp != null && _rudp.IsAlive)
|
||||
{
|
||||
return !_readCts.IsCancellationRequested;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public IPEndPoint RemoteAddress => _rudp?.Remote;
|
||||
|
||||
public IPEndPoint LocalAddress => null;
|
||||
|
||||
public int Port => 0;
|
||||
|
||||
public double RttMs => _rudp?.RttMs ?? 0.0;
|
||||
|
||||
public double LossRate => _rudp?.LossRate ?? 0.0;
|
||||
|
||||
public long BytesSent => _rudp?.BytesSent ?? 0;
|
||||
|
||||
public long BytesRecv => _rudp?.BytesRecv ?? 0;
|
||||
|
||||
public uint UdpSessionToken { get; set; }
|
||||
|
||||
public IPEndPoint UdpRemoteEndPoint { get; set; }
|
||||
|
||||
public uint UdpLastFrameSeq { get; set; }
|
||||
|
||||
public event EventHandler PacketReceived;
|
||||
|
||||
public event EventHandler PacketSent;
|
||||
|
||||
public void Connect(IPAddress address, int port)
|
||||
{
|
||||
IPEndPoint remote = new IPEndPoint(address, port);
|
||||
RudpChannel rudpChannel = new RudpChannel(NextSessionId(), remote);
|
||||
rudpChannel.Start();
|
||||
AttachChannel(rudpChannel);
|
||||
Thread.Sleep(50);
|
||||
}
|
||||
|
||||
private static uint NextSessionId()
|
||||
{
|
||||
uint num = (uint)Interlocked.Increment(ref _clientIdGen);
|
||||
if (num != 0)
|
||||
{
|
||||
return num;
|
||||
}
|
||||
return (uint)Interlocked.Increment(ref _clientIdGen);
|
||||
}
|
||||
|
||||
private void AttachChannel(RudpChannel channel)
|
||||
{
|
||||
_rudp = channel;
|
||||
_rudp.ReliableReceived += OnReliableData;
|
||||
_rudp.UnreliableFrameReceived += OnUnreliableControl;
|
||||
_rudp.Disconnected += OnDisconnected;
|
||||
}
|
||||
|
||||
public RudpChannel GetRudpChannel()
|
||||
{
|
||||
return _rudp;
|
||||
}
|
||||
|
||||
public void SendPacket(IPacket packet)
|
||||
{
|
||||
byte[] payload = PacketSerializer.Serialize(packet);
|
||||
_rudp.SendReliable(payload);
|
||||
this.PacketSent?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void SendPacketUnreliable(IPacket packet)
|
||||
{
|
||||
byte[] payload = PacketSerializer.Serialize(packet);
|
||||
_rudp.SendUnreliable(0, payload);
|
||||
this.PacketSent?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public void SendDesktopScreenFrame(byte[] jpegData)
|
||||
{
|
||||
if (jpegData != null && jpegData.Length != 0)
|
||||
{
|
||||
_rudp.SendUnreliable(36, jpegData);
|
||||
this.PacketSent?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public void SendHvncFrame(byte[] jpegData)
|
||||
{
|
||||
if (jpegData != null && jpegData.Length != 0)
|
||||
{
|
||||
_rudp.SendUnreliable(46, jpegData);
|
||||
this.PacketSent?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public IPacket ReadPacket()
|
||||
{
|
||||
try
|
||||
{
|
||||
IPacket result = _inQueue.Take(_readCts.Token);
|
||||
this.PacketReceived?.Invoke(this, EventArgs.Empty);
|
||||
return result;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw new IOException("Connection closed");
|
||||
}
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
_readCts.Cancel();
|
||||
try
|
||||
{
|
||||
_rudp?.SendFin();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
try
|
||||
{
|
||||
_rudp?.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void OnReliableData(byte[] payload)
|
||||
{
|
||||
try
|
||||
{
|
||||
IPacket packet = PacketSerializer.Deserialize(payload);
|
||||
if (packet != null && !_inQueue.IsAddingCompleted)
|
||||
{
|
||||
_inQueue.TryAdd(packet, 100);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUnreliableControl(byte typeId, byte[] data)
|
||||
{
|
||||
if (typeId != 0 || data == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
IPacket packet = PacketSerializer.Deserialize(data);
|
||||
if (packet != null && !_inQueue.IsAddingCompleted)
|
||||
{
|
||||
_inQueue.TryAdd(packet, 20);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisconnected()
|
||||
{
|
||||
_readCts.Cancel();
|
||||
_inQueue.CompleteAdding();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,799 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
|
||||
namespace Crysome.Common.Network;
|
||||
|
||||
public static class UdpTransport
|
||||
{
|
||||
public const int HeaderSize = 13;
|
||||
|
||||
public const int MaxFragmentSize = 60000;
|
||||
|
||||
public static byte[] BuildFragment(uint sessionToken, byte packetTypeId, uint frameSeq, ushort fragIdx, ushort fragCount, byte[] payloadData, int payloadOffset, int payloadLength)
|
||||
{
|
||||
byte[] array = new byte[13 + payloadLength];
|
||||
int num = 0;
|
||||
Buffer.BlockCopy(BitConverter.GetBytes(sessionToken), 0, array, num, 4);
|
||||
num += 4;
|
||||
array[num++] = packetTypeId;
|
||||
Buffer.BlockCopy(BitConverter.GetBytes(frameSeq), 0, array, num, 4);
|
||||
num += 4;
|
||||
Buffer.BlockCopy(BitConverter.GetBytes(fragIdx), 0, array, num, 2);
|
||||
num += 2;
|
||||
Buffer.BlockCopy(BitConverter.GetBytes(fragCount), 0, array, num, 2);
|
||||
num += 2;
|
||||
Buffer.BlockCopy(payloadData, payloadOffset, array, num, payloadLength);
|
||||
return array;
|
||||
}
|
||||
|
||||
public static bool ParseHeader(byte[] datagram, int length, out uint sessionToken, out byte packetTypeId, out uint frameSeq, out ushort fragIdx, out ushort fragCount, out int payloadOffset, out int payloadLength)
|
||||
{
|
||||
sessionToken = 0u;
|
||||
packetTypeId = 0;
|
||||
frameSeq = 0u;
|
||||
fragIdx = 0;
|
||||
fragCount = 0;
|
||||
payloadOffset = 0;
|
||||
payloadLength = 0;
|
||||
if (length < 13)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
int num = 0;
|
||||
sessionToken = BitConverter.ToUInt32(datagram, num);
|
||||
num += 4;
|
||||
packetTypeId = datagram[num++];
|
||||
frameSeq = BitConverter.ToUInt32(datagram, num);
|
||||
num += 4;
|
||||
fragIdx = BitConverter.ToUInt16(datagram, num);
|
||||
num += 2;
|
||||
fragCount = BitConverter.ToUInt16(datagram, num);
|
||||
payloadLength = length - (payloadOffset = num + 2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user