diff options
| author | Sarah Bradley <git@sarahduck.ca> | 2023-12-16 19:43:58 -0800 |
|---|---|---|
| committer | Sarah Bradley <git@sarahduck.ca> | 2023-12-16 19:43:58 -0800 |
| commit | dbba03fe210a80f7d89d3ad021ec901a9196a537 (patch) | |
| tree | 836e884541434a39bbbc74e0b645ffc79edd9c7c | |
| parent | 2793b94040a473538f01723d5ca5f53c4535e2af (diff) | |
Redid networking with LiteNetLib
| -rw-r--r-- | Main.cs | 19 | ||||
| -rw-r--r-- | Net.cs | 76 | ||||
| -rw-r--r-- | NetClient.cs | 136 | ||||
| -rw-r--r-- | NetServer.cs | 170 | ||||
| -rw-r--r-- | Oneko.cs | 4 | ||||
| -rw-r--r-- | OnekoOnline.csproj | 1 |
6 files changed, 210 insertions, 196 deletions
@@ -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(); @@ -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 @@ -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" /> |
