1
0
mirror of synced 2024-11-23 22:10:59 +01:00

Add relay server

This commit is contained in:
asesidaa 2022-08-01 21:39:24 +08:00
parent c185497502
commit 316fff969e
7 changed files with 335 additions and 0 deletions

View File

@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedProject", "SharedProj
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MudAdmin", "MudAdmin\MudAdmin.csproj", "{DC8E30E9-F81E-4E28-A4D2-F4576C77FFBE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GCRelayServer", "GCRelayServer\GCRelayServer.csproj", "{268178DF-6345-4D9E-A389-9E809CBF39C9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -27,6 +29,10 @@ Global
{DC8E30E9-F81E-4E28-A4D2-F4576C77FFBE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DC8E30E9-F81E-4E28-A4D2-F4576C77FFBE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DC8E30E9-F81E-4E28-A4D2-F4576C77FFBE}.Release|Any CPU.Build.0 = Release|Any CPU
{268178DF-6345-4D9E-A389-9E809CBF39C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{268178DF-6345-4D9E-A389-9E809CBF39C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{268178DF-6345-4D9E-A389-9E809CBF39C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{268178DF-6345-4D9E-A389-9E809CBF39C9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -0,0 +1,23 @@
using System.Net;
namespace GCRelayServer;
public class DictEntry
{
public List<EndPoint> EndPoints { get; set; } = new();
public DateTime LastAccessTime { get; set; } = DateTime.Now;
public void AddEndpoint(EndPoint endPoint, bool shouldClear = false)
{
if (shouldClear)
{
EndPoints.Clear();
}
if (EndPoints.Contains(endPoint))
{
return;
}
EndPoints.Add(endPoint);
}
}

View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BinarySerializer" Version="8.6.3-alpha" />
<PackageReference Include="NetCoreServer" Version="6.2.0" />
<PackageReference Include="Swan.Logging" Version="6.0.2-beta.69" />
</ItemGroup>
</Project>

59
GCRelayServer/Program.cs Normal file
View File

@ -0,0 +1,59 @@
using System.Net;
using Swan.Logging;
namespace GCRelayServer
{
public static class Program
{
public static void Main(string[] args)
{
#if DEBUG
ConsoleLogger.Instance.LogLevel = LogLevel.Debug;
#endif
// UDP server port
var port = 3333;
if (args.Length > 0)
{
port = int.Parse(args[0]);
}
$"UDP server port: {port}".Info();
// Create a new UDP echo server
var server = new RelayServer(IPAddress.Any, port);
// Start the server
"Server starting...".Info();
server.Start();
"Server started".Info();
"Press Enter to stop the server or '!' to restart the server...".Info();
// Perform text input
for (;;)
{
var line = Console.ReadLine();
if (string.IsNullOrEmpty(line))
{
break;
}
// Restart the server
if (line != "!")
{
continue;
}
"Server restarting...".Info();
server.Restart();
"Server restarted".Info();
}
// Stop the server
"Server stopping...".Info();
server.Stop();
"Server stopped, press any key to close".Info();
Console.ReadKey(true);
}
}
}

View File

@ -0,0 +1,57 @@
using BinarySerialization;
namespace GCRelayServer;
public class RelayPacket
{
[FieldOrder(0)]
[FieldEndianness(Endianness.Big)]
public ushort Magic;
[FieldOrder(1)]
[FieldEndianness(Endianness.Big)]
public ushort RemainingSize;
[FieldOrder(2)]
[FieldEndianness(Endianness.Big)]
public ushort RequestMainType;
[FieldOrder(3)]
[FieldCount(6)]
public byte[] Unknown0 = Array.Empty<byte>();
[FieldOrder(4)]
[FieldEndianness(Endianness.Big)]
public ushort RequestSubType;
[FieldOrder(5)]
[FieldEndianness(Endianness.Big)]
public ushort Unknown1;
[FieldOrder(6)]
[FieldEndianness(Endianness.Big)]
public ushort DataSize;
[FieldOrder(7)]
[FieldEndianness(Endianness.Big)]
public ushort Unknown2;
[FieldOrder(8)]
[FieldEndianness(Endianness.Big)]
public uint MatchingId;
[FieldOrder(9)]
[FieldEndianness(Endianness.Big)]
public uint EntryNo;
[FieldOrder(10)]
[FieldEndianness(Endianness.Big)]
public uint MachineId;
[FieldOrder(11)]
[FieldEndianness(Endianness.Big)]
public uint Unknown3;
[FieldOrder(12)]
public byte[] Data = Array.Empty<byte>();
}

View File

@ -0,0 +1,14 @@
namespace GCRelayServer;
public static class RelayPacketTypes
{
public const ushort HEART_BEAT = 0xB0;
public const ushort HEART_BEAT_RESPONSE = 0xB1;
public const ushort START_MATCHING = 0xA0;
public const ushort START_MATCHING_RESPONSE = 0xA1;
public const ushort REGISTER_MATCHING = 0xA6;
}

View File

@ -0,0 +1,160 @@
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using BinarySerialization;
using Swan.Logging;
namespace GCRelayServer;
public class RelayServer : NetCoreServer.UdpServer
{
private ConcurrentDictionary<uint, DictEntry> matchingDictionary = new();
public RelayServer(IPAddress address, int port) : base(address, port) {}
protected override void OnStarted()
{
// Start receive datagrams
ReceiveAsync();
}
protected override void OnReceived(EndPoint endpoint, byte[] buffer, long offset, long size)
{
var serializer = new BinarySerializer();
var inputStream = new MemoryStream(buffer, (int)offset, (int)size, false);
var packet = serializer.Deserialize<RelayPacket>(inputStream);
if (!IsValidPacket(packet))
{
"Received malformed packet!".Warn();
return;
}
$"Received packet from {endpoint}, type is 0x{packet.RequestSubType:X2}".Info();
switch (packet.RequestSubType)
{
case RelayPacketTypes.HEART_BEAT:
{
for (var i = 0; i < 2; i++)
{
SendPacketSingle(endpoint, packet, RelayPacketTypes.HEART_BEAT_RESPONSE);
}
break;
}
case RelayPacketTypes.START_MATCHING:
{
AddEntry(packet.MatchingId, endpoint);
for (var i = 0; i < 2; i++)
{
SendPacketSingle(endpoint, packet, RelayPacketTypes.START_MATCHING_RESPONSE);
}
break;
}
case RelayPacketTypes.REGISTER_MATCHING:
{
var entry = AddEntry(packet.MatchingId, endpoint);
SendPacketToOthers(entry.EndPoints, packet, endpoint);
break;
}
default:
{
var entry = GetEntry(packet.MatchingId);
if (entry is null)
{
break;
}
SendPacketToOthers(entry.EndPoints, packet, endpoint);
break;
}
}
ReceiveAsync();
}
private void SendPacketSingle(EndPoint endpoint, RelayPacket packet, ushort subType)
{
packet.RequestSubType = subType;
var serializer = new BinarySerializer();
var sendStream = new MemoryStream(1024);
serializer.Serialize(sendStream, packet);
$"Send packet to {endpoint}, type is 0x{packet.RequestSubType:X2}".Info();
SendAsync(endpoint, sendStream.GetBuffer(), 0, sendStream.Length);
}
private void SendPacketToOthers(IEnumerable<EndPoint> endPoints, RelayPacket packet, EndPoint owner)
{
if (owner is not IPEndPoint ipEndPoint)
{
"Endpoint is not IP endpoint! This should not happen!".Fatal();
throw new ApplicationException();
}
foreach (var endPoint in endPoints.Where(endPoint => !ipEndPoint.Equals(endPoint)))
{
SendPacketSingle(endPoint, packet, packet.RequestSubType);
}
}
protected override void OnError(SocketError error)
{
$"Relay server caught an error with code {error}".Error();
}
private static bool IsValidPacket(RelayPacket packet)
{
var totalSize = packet.Magic + packet.RemainingSize;
var actualSize = 36 + packet.Data.Length;
if (totalSize != actualSize)
{
return false;
}
return packet.Data.Length == packet.DataSize;
}
private DictEntry AddEntry(uint matchingId, EndPoint endPoint)
{
var now = DateTime.Now;
var entry = matchingDictionary.GetValueOrDefault(matchingId, new DictEntry());
var shouldClear = false;
if (entry.LastAccessTime <= now && now - entry.LastAccessTime >= TimeSpan.FromMinutes(10))
{
$"Entry for matching id {matchingId:X8} has expired! Clients will be cleared!".Info();
shouldClear = true;
}
if (entry.EndPoints.Count >= 4)
{
$"Entry for matching id {matchingId:X8} contains more than 4 clients! Clients will be cleared!".Warn();
shouldClear = true;
}
entry.AddEndpoint(endPoint, shouldClear);
entry.LastAccessTime = DateTime.Now;
matchingDictionary[matchingId] = entry;
return entry;
}
private DictEntry? GetEntry(uint matchingId)
{
var now = DateTime.Now;
if (!matchingDictionary.ContainsKey(matchingId))
{
$"Entry for matching id {matchingId:X8} does not exist!".Warn();
return null;
}
var entry = matchingDictionary[matchingId];
if (entry.LastAccessTime <= now && now - entry.LastAccessTime >= TimeSpan.FromMinutes(10))
{
$"Entry for matching id {matchingId:X8} has expired!".Warn();
return null;
}
entry.LastAccessTime = DateTime.Now;
return entry;
}
}