summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSarah Bradley <git@sarahduck.ca>2023-12-16 19:43:58 -0800
committerSarah Bradley <git@sarahduck.ca>2023-12-16 19:43:58 -0800
commitdbba03fe210a80f7d89d3ad021ec901a9196a537 (patch)
tree836e884541434a39bbbc74e0b645ffc79edd9c7c
parent2793b94040a473538f01723d5ca5f53c4535e2af (diff)
Redid networking with LiteNetLib
-rw-r--r--Main.cs19
-rw-r--r--Net.cs76
-rw-r--r--NetClient.cs136
-rw-r--r--NetServer.cs170
-rw-r--r--Oneko.cs4
-rw-r--r--OnekoOnline.csproj1
6 files changed, 210 insertions, 196 deletions
diff --git a/Main.cs b/Main.cs
index 8b3e9be..85306ec 100644
--- a/Main.cs
+++ b/Main.cs
@@ -7,6 +7,9 @@ static class OnekoOnline
{
public static readonly ConfigFile Config = new("config.conf");
+ public static Net.Server? Server;
+ public static Net.Client? Client;
+
const ConfigFlags raylibConfFlags =
//ConfigFlags.FLAG_WINDOW_UNDECORATED |
//ConfigFlags.FLAG_WINDOW_TRANSPARENT |
@@ -20,22 +23,27 @@ static class OnekoOnline
Raylib.SetConfigFlags(raylibConfFlags);
Raylib.InitWindow(640, 480, "OnekoOnline");
Raylib.SetTargetFPS(30);
- //Raylib.MaximizeWindow();
Oneko LocalOneko = new();
RenderTexture2D RenderTexture = Raylib.LoadRenderTexture(320,240);
int port = Config.GetValue("ServerPort", 42069);
+ string serverPassword = Config.GetValue("ServerPassword", "");
+
if (Config.GetValue("HostServer", false)) {
- Net.Server.Init(port);
- Net.Client.Init("127.0.0.1", port);
+ Server = new(port, Config.GetValue("ServerMaxUsers", 32));
+ Client = new("127.0.0.1", port, serverPassword);
} else {
- Net.Client.Init(Config.GetValue("ServerIP", "pond.sarahduck.ca"), port);
+ Client = new(Config.GetValue("ServerIP", "pond.sarahduck.ca"), port, serverPassword);
}
while (!Raylib.WindowShouldClose())
{
+ //Poll networking
+ Client?.Poll();
+ Server?.Poll();
+
Raylib.BeginTextureMode(RenderTexture);
Raylib.ClearBackground(Color.GRAY);
@@ -50,6 +58,9 @@ static class OnekoOnline
Raylib.EndDrawing();
}
+ Client?.Disconnect();
+ Server?.Disconnect();
+
Drawable.DisposeAll();
Raylib.CloseWindow();
Config.SaveFile();
diff --git a/Net.cs b/Net.cs
index 497c66d..b99a0f6 100644
--- a/Net.cs
+++ b/Net.cs
@@ -1,85 +1,83 @@
using System.Buffers.Binary;
-using System.Net.Sockets;
+using LiteNetLib.Utils;
namespace OnekoOnline.Net;
-static class NetBase
+public static class NetExtensions
{
- public static async Task<Packet> GetReliableData(NetworkStream stream, CancellationToken token = default)
+ public static void Put(this NetDataWriter writer, PacketInfo info)
{
- byte[] infoBytes = new byte[PacketInfo.SizeOf];
- await stream.ReadExactlyAsync(infoBytes, token);
- PacketInfo info = PacketInfo.Deserialize(infoBytes);
- if (info.Type == PacketType.Ping || info.Type == PacketType.Disconnect) return new(info.Type, [], info.FromId);
-
- byte[] data = new byte[info.DataSize];
- await stream.ReadExactlyAsync(data, token);
+ writer.Put(info.Serialize());
+ }
- return new Packet(info.Type, data, info.FromId);
+ public static PacketInfo GetPacketInfo(this NetDataReader reader)
+ {
+ return PacketInfo.Deserialize(reader.GetBytesSegment(PacketInfo.SizeOf));
}
- public static async Task SendReliableData(Packet packet, NetworkStream stream, CancellationToken token = default)
+ public static PacketInfo PeekPacketInfo(this NetDataReader reader)
{
- byte[] info = new byte[PacketInfo.SizeOf];
- new PacketInfo(packet).Serialize(info);
+ if (reader.AvailableBytes < PacketInfo.SizeOf) return PacketInfo.InvalidPacket;
+ return PacketInfo.Deserialize(reader.RawData.AsSpan(0, PacketInfo.SizeOf));
+ }
- await stream.WriteAsync(info, token);
- await stream.WriteAsync(packet.Data, token);
+ public static void ResetWithInfo(this NetDataWriter writer, PacketInfo info)
+ {
+ writer.Reset();
+ writer.Put(info);
}
-}
-public readonly struct Packet(PacketType type, byte[] data, int id)
-{
- public readonly PacketType Type = type;
- public readonly byte[] Data = data;
- public readonly int FromId = id;
+ public static void ResetWithInfo(this NetDataWriter writer, PacketInfo info, int size)
+ {
+ writer.Reset(size+PacketInfo.SizeOf);
+ writer.Put(info);
+ }
}
public enum PacketType : byte
{
MousePosition,
OnekoState,
- Ping,
OnekoSpritesheet,
Username,
UserId,
- Disconnect
+ Disconnect,
+ Invalid
}
public struct PacketInfo
{
- public readonly int DataSize;
public readonly PacketType Type;
public readonly int FromId;
- public const int SizeOf = (sizeof(int)*2) + 1;
+ public const int SizeOf = sizeof(int) + 1;
+ public readonly bool IsValid => Type != PacketType.Invalid;
- public PacketInfo(Packet packet)
- {
- DataSize = packet.Data.Length;
- Type = packet.Type;
- FromId = packet.FromId;
- }
+ public static readonly PacketInfo InvalidPacket = new(PacketType.Invalid, -1);
- public PacketInfo(PacketType type, int size, int id)
+ public PacketInfo(PacketType type, int id)
{
Type = type;
- DataSize = size;
FromId = id;
}
public readonly void Serialize(Span<byte> span)
{
span[0] = (byte)Type;
- BinaryPrimitives.WriteInt32LittleEndian(span[1..], DataSize);
- BinaryPrimitives.WriteInt32LittleEndian(span[(1+sizeof(int))..], FromId);
+ BinaryPrimitives.WriteInt32LittleEndian(span[1..], FromId);
+ }
+
+ public readonly byte[] Serialize()
+ {
+ byte[] bytes = new byte[SizeOf];
+ Serialize(bytes);
+ return bytes;
}
public static PacketInfo Deserialize(ReadOnlySpan<byte> span)
{
PacketType type = (PacketType)span[0];
- int size = BinaryPrimitives.ReadInt32LittleEndian(span[1..]);
- int id = BinaryPrimitives.ReadInt32LittleEndian(span[(1+sizeof(int))..]);
- return new PacketInfo(type, size, id);
+ int id = BinaryPrimitives.ReadInt32LittleEndian(span[1..]);
+ return new PacketInfo(type, id);
}
}
diff --git a/NetClient.cs b/NetClient.cs
index 31ea258..65fd484 100644
--- a/NetClient.cs
+++ b/NetClient.cs
@@ -1,78 +1,80 @@
-using System.Buffers.Binary;
-using System.Net.Sockets;
-using System.Text;
+using System.Collections.ObjectModel;
+using LiteNetLib;
+using LiteNetLib.Utils;
namespace OnekoOnline.Net;
-static class Client
+class Client
{
- static readonly TcpClient Tcp = new();
- static readonly UdpClient Udp = new();
+ public readonly string UserName = OnekoOnline.Config.GetValue("UserName", "Oneko");
+ public int Id {get; private set;} = -1;
+ public bool Connected => NetClient?.ConnectedPeersCount > 0 && Id != -1;
- public static readonly string UserName = OnekoOnline.Config.GetValue("UserName", "Oneko");
- public static int Id {get; private set;} = 0;
- public static bool Connected => Tcp.Connected && Id != 0;
+ readonly EventBasedNetListener Listener;
+ readonly NetManager NetClient;
+ public NetPeer ConnectedServer => NetClient.FirstPeer;
- public static Dictionary<int, User> Users = [];
- static readonly Dictionary<int, User> allUsers = [];
+ readonly Dictionary<int, User> users = [];
+ public ReadOnlyDictionary<int, User> Users => users.AsReadOnly();
- public static async void Init(string ServerAddress, int port)
+ public Client(string ServerAddress, int port, string ServerPassword)
{
- if (ServerAddress == "") return;
-
- await Task.WhenAny(Tcp.ConnectAsync(ServerAddress, port), Task.Delay(3000));
-
- if (Tcp.Connected)
- {
- Console.WriteLine("Connected to Server!");
- Udp.Connect(ServerAddress, port);
-
- CancellationTokenSource tokenSource = new();
- tokenSource.CancelAfter(5000);
-
- //Send Username
- Packet packet = new(PacketType.Username, Encoding.UTF8.GetBytes(UserName), Id);
- await NetBase.SendReliableData(packet, Tcp.GetStream(), tokenSource.Token);
- //Send Oneko
- packet = new(PacketType.OnekoSpritesheet, Oneko.LocalOneko!.SpriteSheet.Serialize(), Id);
- await NetBase.SendReliableData(packet, Tcp.GetStream(), tokenSource.Token);
- //Get Id
- Packet idPacket = await NetBase.GetReliableData(Tcp.GetStream());
- Id = BinaryPrimitives.ReadInt32LittleEndian(idPacket.Data);
- Console.WriteLine($"My ID is {Id}! {Connected}");
-
- while (Connected)
- {
- if (Tcp.Available > 0) {
- packet = await NetBase.GetReliableData(Tcp.GetStream());
- if (packet.FromId == 0) continue; //From server, ill implement later
-
- if (!allUsers.ContainsKey(packet.FromId)) {
- allUsers.Add(packet.FromId, new User(packet.FromId));
- }
- User sentFrom = allUsers[packet.FromId];
-
- if (packet.Type == PacketType.OnekoSpritesheet) {
- sentFrom.SpriteSheet = packet.Data;
- } else if (packet.Type == PacketType.Username) {
- sentFrom.Username = Encoding.UTF8.GetString(packet.Data);
- }
-
- if (sentFrom.ExchangedData && !Users.ContainsKey(sentFrom.Id)) Users.Add(sentFrom.Id, sentFrom);
- }
-
- if (Udp.Available > 0) {
- }
-
- await Task.Delay(10);
+ if (ServerAddress == "") throw new Exception("Server Address invalid!");
+
+ Listener = new();
+ NetClient = new(Listener);
+
+ NetClient.Start();
+ NetClient.Connect(ServerAddress, port, ServerPassword);
+
+ Listener.PeerConnectedEvent += peer => {
+ Console.WriteLine("Connected to the Server!");
+ NetDataWriter writer = new();
+
+ writer.Put(new PacketInfo(PacketType.OnekoSpritesheet, Id));
+ writer.Put(Oneko.LocalOneko!.SpriteSheet.Serialize());
+ peer.Send(writer, DeliveryMethod.ReliableUnordered);
+
+ writer.ResetWithInfo(new PacketInfo(PacketType.Username, Id));
+ writer.Put(UserName);
+ peer.Send(writer, DeliveryMethod.ReliableUnordered);
+ };
+
+ Listener.NetworkReceiveEvent += (fromPeer, reader, channel, DeliveryMethod) => {
+ if (reader.AvailableBytes < PacketInfo.SizeOf) return;
+ PacketInfo info = reader.GetPacketInfo();
+
+ if (info.Type == PacketType.UserId) {
+ Id = reader.GetInt();
+ return;
+ }
+
+ User? from;
+ if (!users.TryGetValue(info.FromId, out from)) {
+ from = new(info.FromId);
+ users.Add(from.Id, from);
+ }
+
+ if (info.Type == PacketType.Disconnect) {
+ if (from.Username != null) Console.WriteLine($"User {from.Username} left.");
+ users.Remove(info.FromId);
+ }
+
+ if (info.Type == PacketType.OnekoSpritesheet) from.SpriteSheet = reader.GetRemainingBytes();
+ if (info.Type == PacketType.Username) {
+ from.Username = reader.GetString();
+ Console.WriteLine($"User {from.Username} joined!");
}
- }
- else
- {
- Console.WriteLine("Connection to server failed.");
- }
-
- Tcp.Dispose();
- Udp.Dispose();
+ };
+ }
+
+ public void Poll()
+ {
+ NetClient.PollEvents();
+ }
+
+ public void Disconnect()
+ {
+ NetClient.DisconnectAll();
}
} \ No newline at end of file
diff --git a/NetServer.cs b/NetServer.cs
index cab6a00..a0910ba 100644
--- a/NetServer.cs
+++ b/NetServer.cs
@@ -1,110 +1,112 @@
-using System.Buffers.Binary;
-using System.Collections.Concurrent;
-using System.Net;
-using System.Net.NetworkInformation;
-using System.Net.Sockets;
-using System.Text;
-using System.Threading.Channels;
+using LiteNetLib;
+using LiteNetLib.Utils;
namespace OnekoOnline.Net;
-static class Server
+class Server
{
public static bool ServerRunning = false;
- static readonly ConcurrentDictionary<int, ServerUser> users = [];
+ readonly Dictionary<int, ServerUser> users = [];
+ readonly EventBasedNetListener Listener;
+ readonly NetManager NetServer;
- public static async void Init(int port)
+ public Server(int Port, int MaxConnections, string ServerPassword = "")
{
- TcpListener listener = new(IPAddress.Any, port);
- listener.Start(10);
- ServerRunning = true;
-
- while (ServerRunning)
- {
- TcpClient tcpSocket = await listener.AcceptTcpClientAsync();
- UdpClient udpSocket = new((IPEndPoint)tcpSocket.Client.RemoteEndPoint!);
-
- ServerUser newUser = new(tcpSocket.GetHashCode(), tcpSocket, udpSocket);
-
- Console.WriteLine("Accepted a connection from: " + tcpSocket.Client.RemoteEndPoint);
-
- await Task.Factory.StartNew(() => SetupUser(newUser));
- }
- }
-
- static async Task SetupUser(ServerUser user)
- {
- await Task.WhenAny(ExchangeUserData(user), Task.Delay(7000));
-
- if (user.ExchangedData) {
- //Send current user data
- foreach (ServerUser otherUser in users.Values.Where(u => u.ExchangedData)) {
- //Send username and spritesheet
- await NetBase.SendReliableData(new Packet(PacketType.OnekoSpritesheet, otherUser.SpriteSheet!, otherUser.Id), user.Tcp.GetStream());
- await NetBase.SendReliableData(new Packet(PacketType.Username, Encoding.UTF8.GetBytes(otherUser.Username!), otherUser.Id), user.Tcp.GetStream());
- //Ask users to take your data
- await otherUser.ToSend.Writer.WriteAsync(new Packet(PacketType.Username, Encoding.UTF8.GetBytes(user.Username!), user.Id));
- await otherUser.ToSend.Writer.WriteAsync(new Packet(PacketType.OnekoSpritesheet, user.SpriteSheet!, user.Id));
- Console.WriteLine($"Sent {otherUser.Username}'s Data to {user.Username}");
+ Listener = new();
+ NetServer = new(Listener);
+ ServerRunning = NetServer.Start(Port);
+
+ if (!ServerRunning) return;
+
+ Listener.ConnectionRequestEvent += request => {
+ if (NetServer.ConnectedPeersCount < MaxConnections) request.AcceptIfKey(ServerPassword);
+ else request.Reject();
+ };
+
+ Listener.PeerConnectedEvent += peer => {
+ Console.WriteLine($"New connection from {peer.EndPoint}!");
+ users.Add(peer.Id, new ServerUser(peer.Id, peer));
+ };
+
+ Listener.PeerDisconnectedEvent += (peer, info) => {
+ Console.WriteLine($"{peer.EndPoint} Disconnected! Reason: {info.Reason}");
+ users.Remove(peer.Id);
+
+ NetDataWriter writer = new();
+ //Send all users a disconnect message
+ foreach (ServerUser user in users.Values) {
+ user.Peer.Send(new PacketInfo(PacketType.Disconnect, peer.Id).Serialize(), DeliveryMethod.ReliableUnordered);
}
+ };
+
+ Listener.NetworkReceiveEvent += (fromPeer, dataReader, channel, DeliveryMethod) => {
+ PacketInfo info = dataReader.GetPacketInfo();
+ ServerUser user = users[fromPeer.Id];
+
+ //Size limits for packet types
+ if (info.Type == PacketType.OnekoSpritesheet && dataReader.AvailableBytes > 30000) return;
+ else if (info.Type != PacketType.OnekoSpritesheet && dataReader.AvailableBytes > 500) return;
+
+ if (info.Type == PacketType.OnekoSpritesheet) user.SpriteSheet = dataReader.GetRemainingBytes();
+ else if (info.Type == PacketType.Username) user.Username = dataReader.GetString(64);
+
+ if (user.ExchangedData && !user.sentId) {
+ NetDataWriter writer = new();
+ //Send ID
+ writer.Put(user.Id);
+ fromPeer.Send(writer, DeliveryMethod.ReliableUnordered);
+ user.sentId = true;
+
+ Console.WriteLine($"{fromPeer.EndPoint} is {user.Username}!");
+
+ foreach (ServerUser toSend in users.Values)
+ {
+ if (!toSend.ExchangedData || toSend == user) continue;
+
+ //Send all current users spritesheets to this user.
+ writer.ResetWithInfo(new PacketInfo(PacketType.OnekoSpritesheet, toSend.Id), toSend.SpriteSheet!.Length);
+ writer.Put(toSend.SpriteSheet);
+ fromPeer.Send(writer, DeliveryMethod.ReliableUnordered);
+
+ writer.ResetWithInfo(new PacketInfo(PacketType.Username, toSend.Id));
+ writer.Put(toSend.Username);
+ fromPeer.Send(writer, DeliveryMethod.ReliableUnordered);
+
+ //Send all current users this users spritesheet
+ writer.ResetWithInfo(new PacketInfo(PacketType.OnekoSpritesheet, user.Id), user.SpriteSheet!.Length);
+ writer.Put(user.SpriteSheet);
+ toSend.Peer.Send(writer, DeliveryMethod.ReliableUnordered);
+
+ writer.ResetWithInfo(new PacketInfo(PacketType.Username, user.Id));
+ writer.Put(user.Username);
+ toSend.Peer.Send(writer, DeliveryMethod.ReliableUnordered);
+ }
+ }
+ };
- users.GetOrAdd(user.Id, user);
- await Task.Factory.StartNew(() => UpdateUser(user));
- } else {
- Console.WriteLine($"{user.Tcp.Client.RemoteEndPoint} failed to send required data, terminating connection.");
- user.Dispose();
- }
+ Console.WriteLine("Server Initialized!");
}
- static async Task ExchangeUserData(ServerUser user)
+ public void Poll()
{
- while (!user.ExchangedData)
- {
- Packet packet = await NetBase.GetReliableData(user.Tcp.GetStream());
-
- if (packet.Data.Length > 50000) break;
-
- if (packet.Type == PacketType.Username) {
- user.Username = Encoding.UTF8.GetString(packet.Data);
- } else if (packet.Type == PacketType.OnekoSpritesheet) {
- user.SpriteSheet = packet.Data;
- }
- }
-
- if (!user.ExchangedData) return;
-
- //Send ID
- byte[] idData = new byte[sizeof(int)];
- BinaryPrimitives.WriteInt32LittleEndian(idData, user.Id);
- await NetBase.SendReliableData(new Packet(PacketType.UserId, idData, 0), user.Tcp.GetStream());
-
- Console.WriteLine($"{user.Tcp.Client.RemoteEndPoint} is {user.Username}!");
+ NetServer.PollEvents();
}
- static async Task UpdateUser(ServerUser user)
+ public void Disconnect()
{
- while (user.Tcp.Connected) {
- Packet toSend = await user.ToSend.Reader.ReadAsync(user.UpdateCancel.Token);
- await NetBase.SendReliableData(toSend, user.Tcp.GetStream(), user.UpdateCancel.Token);
- }
- //users.TryRemove(user.Id, out _);
- //user.Dispose();
+ NetServer.DisconnectAll();
}
}
-class ServerUser(int id, TcpClient tcp, UdpClient udp) : User(id), IDisposable
+class ServerUser(int id, NetPeer peer) : User(id), IDisposable
{
//Network
- public readonly TcpClient Tcp = tcp;
- public readonly UdpClient Udp = udp;
- public readonly Channel<Packet> ToSend = Channel.CreateUnbounded<Packet>();
- public CancellationTokenSource UpdateCancel = new();
+ public readonly NetPeer Peer = peer;
+ public bool sentId = false;
public void Dispose()
{
- UpdateCancel.Cancel();
- Tcp.Dispose();
- Udp.Dispose();
+ Peer.Disconnect();
}
} \ No newline at end of file
diff --git a/Oneko.cs b/Oneko.cs
index 274782c..a3d0a1c 100644
--- a/Oneko.cs
+++ b/Oneko.cs
@@ -54,8 +54,8 @@ class Oneko : Drawable
public override void Update(float delta)
{
if (this == LocalOneko) {
- foreach (User user in Client.Users.Values) {
- if (!NetNekos.ContainsKey(user.Id)) {
+ foreach (User user in OnekoOnline.Client!.Users.Values) {
+ if (user.ExchangedData && !NetNekos.ContainsKey(user.Id)) {
Bitmap spriteSheet = Bitmap.Deserialize(user.SpriteSheet);
NetNekos.Add(user.Id, new Oneko(spriteSheet));
}
diff --git a/OnekoOnline.csproj b/OnekoOnline.csproj
index 57f77e6..8480878 100644
--- a/OnekoOnline.csproj
+++ b/OnekoOnline.csproj
@@ -13,6 +13,7 @@
</PropertyGroup>
<ItemGroup>
+ <PackageReference Include="LiteNetLib" Version="1.1.0" />
<PackageReference Include="Raylib-cs" Version="5.0.0" />
<EmbeddedResource Include="oneko.png" />