initial commit
This commit is contained in:
@@ -0,0 +1,430 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Net.Quic;
|
||||
using System.Net.Sockets;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Crysome.Common.Network;
|
||||
using Crysome.Common.Network.Packets;
|
||||
using Crysome.Common.Network.Transport;
|
||||
using Crysome.Server.Data;
|
||||
|
||||
namespace Crysome.Server.Network;
|
||||
|
||||
public class CrysomeServer
|
||||
{
|
||||
public readonly PacketChannel PacketChannel;
|
||||
|
||||
private QuicListener _listener;
|
||||
|
||||
private CancellationTokenSource _cts;
|
||||
|
||||
private Task _acceptTask;
|
||||
|
||||
private X509Certificate2 _serverCert;
|
||||
|
||||
private UdpClient _legacySock;
|
||||
|
||||
private Task _legacyTask;
|
||||
|
||||
private readonly ConcurrentDictionary<CrysomeClient, string> _clientKeys = new ConcurrentDictionary<CrysomeClient, string>();
|
||||
|
||||
private readonly ConcurrentDictionary<uint, CrysomeClient> _legacySessions = new ConcurrentDictionary<uint, CrysomeClient>();
|
||||
|
||||
private readonly ConcurrentDictionary<uint, string> _legacySessionKeys = new ConcurrentDictionary<uint, string>();
|
||||
|
||||
private int _nextUdpToken = 1;
|
||||
|
||||
private readonly ConcurrentDictionary<uint, CrysomeClient> _udpSessionMap = new ConcurrentDictionary<uint, CrysomeClient>();
|
||||
|
||||
public ConcurrentDictionary<string, CrysomeClient> ConnectedClients { get; private set; }
|
||||
|
||||
public bool Listening { get; private set; }
|
||||
|
||||
public int Port { get; set; } = 7777;
|
||||
|
||||
public int QuicPort { get; set; } = 7778;
|
||||
|
||||
public AppDataStore Store { get; set; }
|
||||
|
||||
public int ClientCount => ConnectedClients.Count;
|
||||
|
||||
public event EventHandler<PacketEventArgs> PacketReceived;
|
||||
|
||||
public event EventHandler<ClientEventArgs> ClientConnected;
|
||||
|
||||
public event EventHandler<ClientEventArgs> ClientDisconnected;
|
||||
|
||||
public event EventHandler<UdpFrameEventArgs> UdpFrameReceived;
|
||||
|
||||
public CrysomeServer()
|
||||
{
|
||||
PacketChannel = new PacketChannel();
|
||||
ConnectedClients = new ConcurrentDictionary<string, CrysomeClient>();
|
||||
}
|
||||
|
||||
public uint RegisterUdpSession(CrysomeClient client)
|
||||
{
|
||||
uint num = (uint)Interlocked.Increment(ref _nextUdpToken);
|
||||
_udpSessionMap[num] = client;
|
||||
client.UdpSessionToken = num;
|
||||
return num;
|
||||
}
|
||||
|
||||
public void UnregisterUdpSession(CrysomeClient client)
|
||||
{
|
||||
if (client != null && client.UdpSessionToken != 0)
|
||||
{
|
||||
_udpSessionMap.TryRemove(client.UdpSessionToken, out var _);
|
||||
}
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (Listening)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
bool num = QuicPort > 0;
|
||||
bool flag = Port > 0;
|
||||
if (!num && !flag)
|
||||
{
|
||||
throw new InvalidOperationException("At least one of Port (RUDP) or QuicPort (QUIC) must be greater than 0.");
|
||||
}
|
||||
_cts = new CancellationTokenSource();
|
||||
if (num)
|
||||
{
|
||||
if (!QuicListener.IsSupported)
|
||||
{
|
||||
if (!flag)
|
||||
{
|
||||
throw new InvalidOperationException("QUIC listener is not supported. Use Windows 11 / Server 2022 or newer with MsQuic enabled, or set QuicPort to 0 and use RUDP only on Port.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_serverCert = QuicCertificateHelper.CreateServerCertificate();
|
||||
_listener = QuicTransportSession.StartListenerAsync(QuicPort, _serverCert, _cts.Token).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
if (flag)
|
||||
{
|
||||
_legacySock = new UdpClient(Port);
|
||||
_legacySock.Client.ReceiveBufferSize = 8388608;
|
||||
_legacySock.Client.SendBufferSize = 8388608;
|
||||
_legacyTask = Task.Run(() => LegacyReceiveLoop(_cts.Token));
|
||||
}
|
||||
Listening = true;
|
||||
if (_listener != null)
|
||||
{
|
||||
_acceptTask = Task.Run(() => AcceptLoopAsync(_cts.Token));
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
Listening = false;
|
||||
try
|
||||
{
|
||||
_cts?.Cancel();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
try
|
||||
{
|
||||
_listener?.DisposeAsync().AsTask().Wait(3000);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
_listener = null;
|
||||
try
|
||||
{
|
||||
_legacySock?.Close();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
_legacySock = null;
|
||||
foreach (KeyValuePair<string, CrysomeClient> connectedClient in ConnectedClients)
|
||||
{
|
||||
try
|
||||
{
|
||||
connectedClient.Value.Disconnect();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
ConnectedClients.Clear();
|
||||
_clientKeys.Clear();
|
||||
_legacySessions.Clear();
|
||||
_legacySessionKeys.Clear();
|
||||
_udpSessionMap.Clear();
|
||||
}
|
||||
|
||||
private async Task LegacyReceiveLoop(CancellationToken ct)
|
||||
{
|
||||
uint sessionId = default(uint);
|
||||
byte b = default(byte);
|
||||
uint num = default(uint);
|
||||
uint num2 = default(uint);
|
||||
ushort num3 = default(ushort);
|
||||
ushort num4 = default(ushort);
|
||||
byte b2 = default(byte);
|
||||
while (!ct.IsCancellationRequested && Listening && _legacySock != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
UdpReceiveResult udpReceiveResult = await _legacySock.ReceiveAsync().ConfigureAwait(continueOnCapturedContext: false);
|
||||
byte[] buffer = udpReceiveResult.Buffer;
|
||||
if (!RudpChannel.ParseHeader(buffer, buffer.Length, out sessionId, out b, out num, out num2, out num3, out num4, out b2))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (_legacySessions.TryGetValue(sessionId, out var client))
|
||||
{
|
||||
goto IL_0275;
|
||||
}
|
||||
if ((b & 9) == 0 || sessionId == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
RudpChannel val = new RudpChannel(sessionId, udpReceiveResult.RemoteEndPoint, _legacySock);
|
||||
val.UnreliableFrameReceived += delegate(byte typeId, byte[] data)
|
||||
{
|
||||
if (_legacySessions.TryGetValue(sessionId, out var value))
|
||||
{
|
||||
try
|
||||
{
|
||||
this.UdpFrameReceived?.Invoke(this, new UdpFrameEventArgs(value, typeId, data));
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
};
|
||||
val.Disconnected += delegate
|
||||
{
|
||||
LegacyClientGone(sessionId);
|
||||
};
|
||||
val.Start();
|
||||
client = new CrysomeClient(val);
|
||||
string key = Guid.NewGuid().ToString("N");
|
||||
_legacySessions[sessionId] = client;
|
||||
_legacySessionKeys[sessionId] = key;
|
||||
ConnectedClients[key] = client;
|
||||
Task.Run(delegate
|
||||
{
|
||||
ClientReadLoop(key, client, ct);
|
||||
}, ct);
|
||||
OnClientConnected(client);
|
||||
goto IL_0275;
|
||||
IL_0275:
|
||||
CrysomeClient obj = client;
|
||||
if (obj != null)
|
||||
{
|
||||
RudpChannel rudpChannel = obj.GetRudpChannel();
|
||||
if (rudpChannel != null)
|
||||
{
|
||||
rudpChannel.FeedDatagram(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void LegacyClientGone(uint sessionId)
|
||||
{
|
||||
if (_legacySessions.TryRemove(sessionId, out var value))
|
||||
{
|
||||
if (_legacySessionKeys.TryRemove(sessionId, out var value2))
|
||||
{
|
||||
ConnectedClients.TryRemove(value2, out var _);
|
||||
}
|
||||
UnregisterUdpSession(value);
|
||||
OnClientDisconnected(value);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AcceptLoopAsync(CancellationToken ct)
|
||||
{
|
||||
while (!ct.IsCancellationRequested && Listening && _listener != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
QuicConnection conn = await _listener.AcceptConnectionAsync(ct).ConfigureAwait(continueOnCapturedContext: false);
|
||||
Task.Run(() => HandleConnectionAsync(conn, ct), ct);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
break;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleConnectionAsync(QuicConnection conn, CancellationToken ct)
|
||||
{
|
||||
QuicTransportSession session = null;
|
||||
CrysomeClient client = null;
|
||||
string key = null;
|
||||
try
|
||||
{
|
||||
session = await QuicTransportSession.AcceptServerAsync(conn, ct).ConfigureAwait(continueOnCapturedContext: false);
|
||||
client = new CrysomeClient((ITransportSession)(object)session, true);
|
||||
key = Guid.NewGuid().ToString("N");
|
||||
_clientKeys[client] = key;
|
||||
ConnectedClients[key] = client;
|
||||
session.UnreliableFrameReceived += delegate(byte typeId, byte[] data)
|
||||
{
|
||||
try
|
||||
{
|
||||
this.UdpFrameReceived?.Invoke(this, new UdpFrameEventArgs(client, typeId, data));
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
};
|
||||
Task.Run(delegate
|
||||
{
|
||||
ClientReadLoop(key, client, ct);
|
||||
}, ct);
|
||||
OnClientConnected(client);
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
QuicTransportSession obj2 = session;
|
||||
if (obj2 != null)
|
||||
{
|
||||
obj2.Dispose();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
if (client != null && key != null)
|
||||
{
|
||||
ClientGone(client, key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ClientGone(CrysomeClient client, string key)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
CrysomeClient value;
|
||||
string value2;
|
||||
foreach (KeyValuePair<uint, CrysomeClient> legacySession in _legacySessions)
|
||||
{
|
||||
if (legacySession.Value == client)
|
||||
{
|
||||
_legacySessions.TryRemove(legacySession.Key, out value);
|
||||
_legacySessionKeys.TryRemove(legacySession.Key, out value2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
_clientKeys.TryRemove(client, out value2);
|
||||
if (key != null)
|
||||
{
|
||||
ConnectedClients.TryRemove(key, out value);
|
||||
}
|
||||
UnregisterUdpSession(client);
|
||||
OnClientDisconnected(client);
|
||||
}
|
||||
|
||||
private void ClientReadLoop(string key, CrysomeClient client, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
while (!ct.IsCancellationRequested && client.IsConnected)
|
||||
{
|
||||
IPacket packet = client.ReadPacket();
|
||||
OnPacketReceived(client, packet);
|
||||
}
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
ClientGone(client, key);
|
||||
}
|
||||
}
|
||||
|
||||
public void DisconnectClient(CrysomeClient client)
|
||||
{
|
||||
if (client == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
client.Disconnect();
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public List<CrysomeClient> GetClientSnapshot()
|
||||
{
|
||||
return new List<CrysomeClient>(ConnectedClients.Values);
|
||||
}
|
||||
|
||||
private void OnClientConnected(CrysomeClient client)
|
||||
{
|
||||
this.ClientConnected?.Invoke(this, new ClientEventArgs(client));
|
||||
if (Store == null || !Store.Settings.TelegramEnabled || string.IsNullOrEmpty(Store.Settings.TelegramToken) || string.IsNullOrEmpty(Store.Settings.TelegramChatId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
string ip = client.RemoteAddress?.Address?.ToString() ?? "Unknown";
|
||||
Task.Run(async delegate
|
||||
{
|
||||
try
|
||||
{
|
||||
string stringToEscape = "\ud83d\udd14 *New Client Connected*\nIP: " + ip;
|
||||
string requestUri = $"https://api.telegram.org/bot{Store.Settings.TelegramToken}/sendMessage?chat_id={Store.Settings.TelegramChatId}&text={Uri.EscapeDataString(stringToEscape)}&parse_mode=Markdown";
|
||||
using HttpClient hc = new HttpClient();
|
||||
await hc.GetAsync(requestUri);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void OnClientDisconnected(CrysomeClient client)
|
||||
{
|
||||
this.ClientDisconnected?.Invoke(this, new ClientEventArgs(client));
|
||||
}
|
||||
|
||||
private void OnPacketReceived(CrysomeClient client, IPacket packet)
|
||||
{
|
||||
this.PacketReceived?.Invoke(this, new PacketEventArgs(client, packet));
|
||||
PacketChannel.HandlePacket(client, packet);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user