mirror of
https://github.com/Sanae6/SmoOnlineServer.git
synced 2024-11-23 19:55:18 +00:00
Reformat solution
This commit is contained in:
parent
351304f99f
commit
7dbe2ecd55
25 changed files with 89 additions and 73 deletions
|
@ -1,28 +1,34 @@
|
|||
using System.Buffers;
|
||||
using System.Net.Sockets;
|
||||
using System.Net.Sockets;
|
||||
using Shared.Packet.Packets;
|
||||
|
||||
namespace Server;
|
||||
namespace Server;
|
||||
|
||||
public class Client : IDisposable {
|
||||
public Socket? Socket;
|
||||
public readonly Dictionary<string, object> Metadata = new Dictionary<string, object>(); // can be used to store any information about a player
|
||||
public bool Connected = false;
|
||||
public Guid Id;
|
||||
|
||||
public CostumePacket CurrentCostume = new CostumePacket {
|
||||
BodyName = "",
|
||||
CapName = ""
|
||||
};
|
||||
public readonly Dictionary<string, object> Metadata = new Dictionary<string, object>(); // can be used to store any information about a player
|
||||
|
||||
public Guid Id;
|
||||
public Socket? Socket;
|
||||
|
||||
public void Dispose() {
|
||||
Socket?.Disconnect(false);
|
||||
}
|
||||
|
||||
public async Task Send(Memory<byte> data) {
|
||||
if (!Connected) return;
|
||||
await Socket!.SendAsync(data, SocketFlags.None);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Socket?.Disconnect(false);
|
||||
public static bool operator ==(Client? left, Client? right) {
|
||||
return left is { } leftClient && right is { } rightClient && leftClient.Id == rightClient.Id;
|
||||
}
|
||||
|
||||
public static bool operator ==(Client? left, Client? right) => left is { } leftClient && right is { } rightClient && leftClient.Id == rightClient.Id;
|
||||
public static bool operator !=(Client? left, Client? right) => !(left == right);
|
||||
public static bool operator !=(Client? left, Client? right) {
|
||||
return !(left == right);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,3 @@
|
|||
using System.Buffers;
|
||||
using System.Net.Sockets;
|
||||
|
||||
Server.Server server = new Server.Server();
|
||||
Server.Server server = new Server.Server();
|
||||
|
||||
await server.Listen(1027);
|
|
@ -9,19 +9,20 @@ using Shared.Packet.Packets;
|
|||
namespace Server;
|
||||
|
||||
public class Server {
|
||||
private readonly MemoryPool<byte> memoryPool = MemoryPool<byte>.Shared;
|
||||
public readonly List<Client> Clients = new List<Client>();
|
||||
public readonly Logger Logger = new Logger("Server");
|
||||
private readonly MemoryPool<byte> memoryPool = MemoryPool<byte>.Shared;
|
||||
|
||||
public async Task Listen(ushort port) {
|
||||
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
serverSocket.Bind(new IPEndPoint(IPAddress.Any, port));
|
||||
serverSocket.Listen();
|
||||
|
||||
|
||||
Logger.Info($"Listening on port {port}");
|
||||
|
||||
while (true) {
|
||||
Socket socket = await serverSocket.AcceptAsync();
|
||||
|
||||
|
||||
Logger.Warn("ok");
|
||||
|
||||
if (Clients.Count > Constants.MaxClients) {
|
||||
|
@ -47,14 +48,14 @@ public class Server {
|
|||
|
||||
PacketHeader header = new PacketHeader {
|
||||
Id = sender?.Id ?? Guid.Empty,
|
||||
Type = Constants.Packets[typeof(T)].Type,
|
||||
Type = Constants.Packets[typeof(T)].Type
|
||||
};
|
||||
FillPacket(header, packet, memory.Memory);
|
||||
await Broadcast(memory, sender);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Takes ownership of data and disposes once done.
|
||||
/// Takes ownership of data and disposes once done.
|
||||
/// </summary>
|
||||
/// <param name="data">Memory owner to dispose once done</param>
|
||||
/// <param name="sender">Optional sender to not broadcast data to</param>
|
||||
|
@ -67,7 +68,7 @@ public class Server {
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Broadcasts memory whose memory shouldn't be disposed, should only be fired by server code.
|
||||
/// Broadcasts memory whose memory shouldn't be disposed, should only be fired by server code.
|
||||
/// </summary>
|
||||
/// <param name="data">Memory to send to the clients</param>
|
||||
/// <param name="sender">Optional sender to not broadcast data to</param>
|
||||
|
@ -81,14 +82,15 @@ public class Server {
|
|||
|
||||
|
||||
private async void HandleSocket(Socket socket) {
|
||||
Client client = new Client {Socket = socket};
|
||||
Client client = new Client { Socket = socket };
|
||||
IMemoryOwner<byte> memory = null!;
|
||||
bool first = true;
|
||||
try {
|
||||
while (true) {
|
||||
memory = memoryPool.Rent(Constants.MaxPacketSize);
|
||||
int size = await socket.ReceiveAsync(memory.Memory, SocketFlags.None);
|
||||
if (size == 0) { // treat it as a disconnect and exit
|
||||
if (size == 0) {
|
||||
// treat it as a disconnect and exit
|
||||
Logger.Info($"Socket {socket.RemoteEndPoint} disconnected.");
|
||||
await socket.DisconnectAsync(false);
|
||||
break;
|
||||
|
@ -99,9 +101,7 @@ public class Server {
|
|||
// connection initialization
|
||||
if (first) {
|
||||
first = false;
|
||||
if (header.Type != PacketType.Connect) {
|
||||
throw new Exception($"First packet was not init, instead it was {header.Type}");
|
||||
}
|
||||
if (header.Type != PacketType.Connect) throw new Exception($"First packet was not init, instead it was {header.Type}");
|
||||
|
||||
ConnectPacket connect = MemoryMarshal.Read<ConnectPacket>(memory.Memory.Span[Constants.HeaderSize..size]);
|
||||
lock (Clients) {
|
||||
|
@ -119,6 +119,7 @@ public class Server {
|
|||
} else {
|
||||
firstConn = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -130,7 +131,7 @@ public class Server {
|
|||
// do any cleanup required when it comes to new clients
|
||||
List<Client> toDisconnect = Clients.FindAll(c => c.Id == header.Id && c.Connected && c.Socket != null);
|
||||
Clients.RemoveAll(c => c.Id == header.Id);
|
||||
|
||||
|
||||
client.Id = header.Id;
|
||||
Clients.Add(client);
|
||||
|
||||
|
@ -138,33 +139,32 @@ public class Server {
|
|||
// done disconnecting and removing stale clients with the same id
|
||||
}
|
||||
}
|
||||
|
||||
List<Client> otherConnectedPlayers = Clients.FindAll(c => c.Id != header.Id && c.Connected && c.Socket != null);
|
||||
await Parallel.ForEachAsync(otherConnectedPlayers, async (other, _) => {
|
||||
IMemoryOwner<byte> connectBuffer = MemoryPool<byte>.Shared.Rent(256);
|
||||
PacketHeader connectHeader = new PacketHeader() {
|
||||
PacketHeader connectHeader = new PacketHeader {
|
||||
Id = other.Id,
|
||||
Type = PacketType.Connect
|
||||
};
|
||||
MemoryMarshal.Write(connectBuffer.Memory.Span, ref connectHeader);
|
||||
ConnectPacket connectPacket = new ConnectPacket() {
|
||||
ConnectPacket connectPacket = new ConnectPacket {
|
||||
ConnectionType = ConnectionTypes.FirstConnection // doesn't matter what it is :)
|
||||
};
|
||||
MemoryMarshal.Write(connectBuffer.Memory.Span, ref connectPacket);
|
||||
await client.Send(connectBuffer.Memory);
|
||||
connectBuffer.Dispose();
|
||||
});
|
||||
|
||||
|
||||
Logger.Info($"Client {socket.RemoteEndPoint} ({client.Id}) connected.");
|
||||
}
|
||||
|
||||
|
||||
// todo support variable length packets if they show up
|
||||
Logger.Warn($"broadcasting {header.Type} from {client.Id}");
|
||||
await Broadcast(memory, client);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (e is SocketException {SocketErrorCode: SocketError.ConnectionReset}) {
|
||||
} catch (Exception e) {
|
||||
if (e is SocketException { SocketErrorCode: SocketError.ConnectionReset }) {
|
||||
Logger.Info($"Client {socket.RemoteEndPoint} ({client.Id}) disconnected from the server");
|
||||
} else {
|
||||
Logger.Error($"Exception on socket {socket.RemoteEndPoint} ({client.Id}) and disconnecting for: {e}");
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Shared\Shared.csproj" />
|
||||
<ProjectReference Include="..\Shared\Shared.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -3,13 +3,11 @@ using System.Runtime.InteropServices;
|
|||
using Shared.Packet;
|
||||
using Shared.Packet.Packets;
|
||||
|
||||
namespace Shared;
|
||||
namespace Shared;
|
||||
|
||||
public static class Constants {
|
||||
public const int MaxPacketSize = 256;
|
||||
public const int MaxClients = 4;
|
||||
public static int HeaderSize { get; } = Marshal.SizeOf<PacketHeader>();
|
||||
public static int PacketDataSize { get; } = MaxPacketSize - HeaderSize;
|
||||
public const int CostumeNameSize = 0x20;
|
||||
|
||||
// dictionary of packet types to packet
|
||||
|
@ -18,4 +16,7 @@ public static class Constants {
|
|||
.GetTypes()
|
||||
.Where(type => type.IsAssignableTo(typeof(IPacket)))
|
||||
.ToDictionary(type => type, type => type.GetCustomAttribute<PacketAttribute>()!);
|
||||
|
||||
public static int HeaderSize { get; } = Marshal.SizeOf<PacketHeader>();
|
||||
public static int PacketDataSize { get; } = MaxPacketSize - HeaderSize;
|
||||
}
|
|
@ -1,16 +1,18 @@
|
|||
using System.Text;
|
||||
|
||||
namespace Shared;
|
||||
namespace Shared;
|
||||
|
||||
public static class Extensions {
|
||||
public static string Hex(this Span<byte> span) {
|
||||
return span.ToArray().Hex();
|
||||
}
|
||||
|
||||
public static string Hex(this IEnumerable<byte> array) => string.Join(' ', array.ToArray().Select(x => x.ToString("X2")));
|
||||
|
||||
public static string Hex(this IEnumerable<byte> array) {
|
||||
return string.Join(' ', array.ToArray().Select(x => x.ToString("X2")));
|
||||
}
|
||||
|
||||
public static unsafe byte* Ptr(this Span<byte> span) {
|
||||
fixed (byte* data = span) return data;
|
||||
fixed (byte* data = span) {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
public static string TrimNullTerm(this string text) {
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
namespace Shared;
|
||||
namespace Shared;
|
||||
|
||||
public class Logger {
|
||||
public string Name { get; }
|
||||
public Logger(string name) {
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public void Info(string text) {
|
||||
Console.ResetColor();
|
||||
Console.WriteLine($"Info [{Name}] {text}");
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
namespace Shared.Packet;
|
||||
namespace Shared.Packet;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Struct, AllowMultiple = true)]
|
||||
public class PacketAttribute : Attribute {
|
||||
public PacketType Type { get; }
|
||||
public PacketAttribute(PacketType type) {
|
||||
Type = type;
|
||||
}
|
||||
|
||||
public PacketType Type { get; }
|
||||
}
|
|
@ -1,13 +1,14 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Shared.Packet.Packets;
|
||||
|
||||
namespace Shared.Packet;
|
||||
namespace Shared.Packet;
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct PacketHeader : IPacket {
|
||||
// public int Length;
|
||||
public Guid Id;
|
||||
public PacketType Type;
|
||||
|
||||
public void Serialize(Span<byte> data) {
|
||||
// MemoryMarshal.Write(data, ref Length);
|
||||
MemoryMarshal.Write(data, ref Id);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace Shared.Packet;
|
||||
namespace Shared.Packet;
|
||||
|
||||
public enum PacketType {
|
||||
Unknown,
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Shared.Packet.Packets;
|
||||
|
||||
namespace Shared.Packet;
|
||||
namespace Shared.Packet;
|
||||
|
||||
public static class PacketUtils {
|
||||
public static void SerializeHeaded<T>(Span<byte> data, PacketHeader header, T t) where T : struct, IPacket {
|
||||
header.Serialize(data);
|
||||
t.Serialize(data[Constants.HeaderSize..]);
|
||||
}
|
||||
|
||||
public static T Deserialize<T>(Span<byte> data) where T : IPacket, new() {
|
||||
T packet = new T();
|
||||
packet.Deserialize(data);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Shared.Packet.Packets;
|
||||
namespace Shared.Packet.Packets;
|
||||
|
||||
[Packet(PacketType.Cap)]
|
||||
public struct CapPacket : IPacket {
|
||||
|
@ -10,6 +10,7 @@ public struct CapPacket : IPacket {
|
|||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
public string CapAnim;
|
||||
|
||||
public void Serialize(Span<byte> data) {
|
||||
MemoryMarshal.Write(data, ref Position);
|
||||
MemoryMarshal.Write(data[12..], ref Position);
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Shared.Packet.Packets;
|
||||
namespace Shared.Packet.Packets;
|
||||
|
||||
[Packet(PacketType.Capture)]
|
||||
public struct CapturePacket : IPacket {
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = Constants.CostumeNameSize)]
|
||||
public string ModelName;
|
||||
|
||||
public bool IsCaptured;
|
||||
|
||||
public void Serialize(Span<byte> data) {
|
||||
Encoding.UTF8.GetBytes(ModelName).CopyTo(data[..Constants.CostumeNameSize]);
|
||||
MemoryMarshal.Write(data[Constants.CostumeNameSize..], ref IsCaptured);
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Shared.Packet.Packets;
|
||||
namespace Shared.Packet.Packets;
|
||||
|
||||
[Packet(PacketType.Connect)]
|
||||
public struct ConnectPacket : IPacket {
|
||||
public ConnectionTypes ConnectionType;
|
||||
|
||||
public void Serialize(Span<byte> data) {
|
||||
MemoryMarshal.Write(data, ref ConnectionType);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
namespace Shared.Packet.Packets;
|
||||
namespace Shared.Packet.Packets;
|
||||
|
||||
public enum ConnectionTypes {
|
||||
FirstConnection,
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Shared.Packet.Packets;
|
||||
namespace Shared.Packet.Packets;
|
||||
|
||||
[Packet(PacketType.Costume)]
|
||||
public struct CostumePacket : IPacket {
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = Constants.CostumeNameSize)]
|
||||
public string BodyName;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = Constants.CostumeNameSize)]
|
||||
public string CapName;
|
||||
|
||||
public void Serialize(Span<byte> data) {
|
||||
Encoding.UTF8.GetBytes(BodyName).CopyTo(data[..Constants.CostumeNameSize]);
|
||||
Encoding.UTF8.GetBytes(CapName).CopyTo(data[Constants.CostumeNameSize..]);
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
namespace Shared.Packet.Packets;
|
||||
namespace Shared.Packet.Packets;
|
||||
|
||||
[Packet(PacketType.Disconnect)]
|
||||
public struct DisconnectPacket : IPacket {
|
||||
//empty packet
|
||||
public void Serialize(Span<byte> data) {
|
||||
|
||||
}
|
||||
public void Serialize(Span<byte> data) { }
|
||||
|
||||
public void Deserialize(Span<byte> data) {
|
||||
|
||||
}
|
||||
public void Deserialize(Span<byte> data) { }
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Shared.Packet.Packets;
|
||||
namespace Shared.Packet.Packets;
|
||||
|
||||
// Packet interface for type safety
|
||||
public interface IPacket {
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
|
@ -11,17 +10,22 @@ public struct PlayerPacket : IPacket {
|
|||
|
||||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
|
||||
public float[] AnimationBlendWeights;
|
||||
|
||||
public float AnimationRate;
|
||||
public bool Is2d;
|
||||
public bool ThrowingCap;
|
||||
public bool IsIt;
|
||||
public int ScenarioNum;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NameSize)]
|
||||
public string Stage;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NameSize)]
|
||||
public string Act;
|
||||
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NameSize)]
|
||||
public string SubAct;
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Shared.Packet.Packets;
|
||||
namespace Shared.Packet.Packets;
|
||||
|
||||
[Packet(PacketType.Shine)]
|
||||
public struct ShinePacket : IPacket {
|
||||
public int ShineId;
|
||||
public bool IsGrand;
|
||||
|
||||
public void Serialize(Span<byte> data) {
|
||||
MemoryMarshal.Write(data, ref ShineId);
|
||||
MemoryMarshal.Write(data, ref IsGrand);
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Shared.Packet.Packets;
|
||||
namespace Shared.Packet.Packets;
|
||||
|
||||
[Packet(PacketType.Tag)]
|
||||
public struct TagPacket : IPacket {
|
||||
public bool IsIt = false;
|
||||
public bool IsIt;
|
||||
|
||||
public void Serialize(Span<byte> data) {
|
||||
MemoryMarshal.Write(data, ref IsIt);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
namespace Shared.Packet.Packets;
|
||||
namespace Shared.Packet.Packets;
|
||||
|
||||
[Packet(PacketType.Unknown)] // empty like boss
|
||||
// [Packet(PacketType.Command)]
|
||||
public struct UnhandledPacket : IPacket {
|
||||
public byte[] Data = new byte[Constants.PacketDataSize];
|
||||
|
||||
public void Serialize(Span<byte> data) {
|
||||
Data.CopyTo(data);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
|
||||
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
using System.Buffers;
|
||||
using System.Net.Sockets;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Shared;
|
||||
using Shared.Packet;
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Shared\Shared.csproj" />
|
||||
<ProjectReference Include="..\Shared\Shared.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
Loading…
Reference in a new issue