From 63eddfa43b8a22f69f63ea0391c31720071f9193 Mon Sep 17 00:00:00 2001 From: teascade Date: Wed, 5 Aug 2020 04:21:04 +0300 Subject: [PATCH] Make networking work --- Assets/Scripts/Net/CanvasInput.cs | 27 ++- Assets/Scripts/Net/Connection.cs | 28 ++- Assets/Scripts/Net/Net.cs | 48 ++++- .../Net/{Endpoint.meta => Packets.meta} | 2 +- Assets/Scripts/Net/Packets/Packet.cs | 118 +++++++++++ .../Server.cs.meta => Packets/Packet.cs.meta} | 2 +- Assets/Scripts/Net/Packets/Protocol.cs | 67 ++++++ .../Protocol.cs.meta} | 2 +- Assets/Scripts/Net/Peers/Client.cs | 27 --- Assets/Scripts/Net/Peers/ConnectionManager.cs | 200 ++++++++++++++++++ .../Net/Peers/ConnectionManager.cs.meta | 11 + Assets/Scripts/Net/Peers/ListenerThread.cs | 71 +++++++ .../Scripts/Net/Peers/ListenerThread.cs.meta | 11 + Assets/Scripts/Net/Peers/Peer.cs | 179 ++++++---------- Assets/Scripts/Net/Peers/Server.cs | 35 --- Assets/Scripts/Net/TestProtocol.cs | 27 +++ Assets/Scripts/Net/TestProtocol.cs.meta | 11 + ProjectSettings/ProjectSettings.asset | 1 + 18 files changed, 667 insertions(+), 200 deletions(-) rename Assets/Scripts/Net/{Endpoint.meta => Packets.meta} (77%) create mode 100644 Assets/Scripts/Net/Packets/Packet.cs rename Assets/Scripts/Net/{Peers/Server.cs.meta => Packets/Packet.cs.meta} (83%) create mode 100644 Assets/Scripts/Net/Packets/Protocol.cs rename Assets/Scripts/Net/{Peers/Client.cs.meta => Packets/Protocol.cs.meta} (83%) delete mode 100644 Assets/Scripts/Net/Peers/Client.cs create mode 100644 Assets/Scripts/Net/Peers/ConnectionManager.cs create mode 100644 Assets/Scripts/Net/Peers/ConnectionManager.cs.meta create mode 100644 Assets/Scripts/Net/Peers/ListenerThread.cs create mode 100644 Assets/Scripts/Net/Peers/ListenerThread.cs.meta delete mode 100644 Assets/Scripts/Net/Peers/Server.cs create mode 100644 Assets/Scripts/Net/TestProtocol.cs create mode 100644 Assets/Scripts/Net/TestProtocol.cs.meta diff --git a/Assets/Scripts/Net/CanvasInput.cs b/Assets/Scripts/Net/CanvasInput.cs index 3dff86c..49551ee 100644 --- a/Assets/Scripts/Net/CanvasInput.cs +++ b/Assets/Scripts/Net/CanvasInput.cs @@ -5,24 +5,35 @@ using UnityEngine.UI; using NeonTea.Quakeball.Net.Peers; namespace NeonTea.Quakeball.Net { - public class CanvasInput : MonoBehaviour { - + public class CanvasInput : MonoBehaviour, PeerMessageListener { public Button Host; public Button Join; + public Button Stop; public InputField HostAddr; public InputField Port; + public Text TextField; + + private static List Stuff = new List(); void Start() { Host.onClick.AddListener(() => { - //Destroy(Join.gameObject); - //Host.interactable = false; - Net.Singleton.Start(new Server(), "0.0.0.0", 8080); + Net.Singleton.StartServer("0.0.0.0", 8080, this); }); Join.onClick.AddListener(() => { - //Destroy(Host.gameObject); - //Join.interactable = false; - Net.Singleton.Start(new Client(), "127.0.0.1", 8080); + Net.Singleton.StartClient("127.0.0.1", 8080, this); }); + Stop.onClick.AddListener(() => { + Net.Singleton.Stop(); + }); + } + + void Update() { + TextField.text = string.Join("\n", Stuff.ToArray()); + } + + public void Message(string text) { + Stuff.Add(text); + Debug.Log(string.Join(", ", Stuff.ToArray())); } diff --git a/Assets/Scripts/Net/Connection.cs b/Assets/Scripts/Net/Connection.cs index d7a77e4..5471b8b 100644 --- a/Assets/Scripts/Net/Connection.cs +++ b/Assets/Scripts/Net/Connection.cs @@ -1,21 +1,37 @@ using System.Collections; using System.Collections.Generic; using System.Net; +using System; + +using NeonTea.Quakeball.Net.Packets; namespace NeonTea.Quakeball.Net { public class Connection { public IPEndPoint Endpoint; - public ConnectionStatus Status = ConnectionStatus.Establishing; + public ConnectionStatus Status; + public byte AssignedProtocol; + public ClosingReason ClosingReason; - public Connection(IPEndPoint endpoint) { - this.Endpoint = endpoint; + public long LastMessage; + public int LatestPacketSent; // Last Packet ID the connection has told us they have + public int LatestPacketReceived; // Last Packet ID we've received from the connection + public int PacketIDCounter; // Packet ID counter for packets we're sending them + + public Connection(IPEndPoint endpoint, ConnectionStatus status = ConnectionStatus.Establishing) { + Endpoint = endpoint; + Status = status; + LastMessage = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); } } public enum ConnectionStatus { - Establishing, - Ready, - Closed, + Awaiting, // Awaiting an establishing + Establishing, // Attempting to establish + Ready, // Ready for packet sending + Rejected, // Rejected connection at endpoint, sending Rejected + Closed, // Closed connection at endpoint, sending Closed + Stopped, // Not sending packages + Lost, // Connection Lost } } diff --git a/Assets/Scripts/Net/Net.cs b/Assets/Scripts/Net/Net.cs index 10e2c00..27a09d9 100644 --- a/Assets/Scripts/Net/Net.cs +++ b/Assets/Scripts/Net/Net.cs @@ -1,4 +1,6 @@ -using UnityEngine; +using System.Collections.Generic; +using System; +using UnityEngine; using NeonTea.Quakeball.Net.Peers; using System.Threading; @@ -6,23 +8,49 @@ namespace NeonTea.Quakeball.Net { public class Net { public static Net Singleton = new Net(); + private static byte[] FP = new byte[] { 0xFF, 0xF7 }; - private Peer Endpoint; + public Peer Peer; - public void Start(Peer endpoint, string host, int port) { - if (Endpoint != null) { + public void StartClient(string address, int port, PeerMessageListener listener) { + if (Peer != null) { Debug.Log("Can not start multiple endpoints at once! Use Server if multiple connections are required."); return; } - Endpoint = endpoint; - Endpoint.Start(host, port); + Peer = new Peer(FP); + Peer.MessageListener = listener; + Peer.Start(0); + byte ident = Peer.RegisterProtocol(new TestProtocol()); + Peer.Connect(address, port, ident); } - public void Stop(DisconnectReason reason) { - if (Endpoint != null) { - Endpoint.Stop(reason); - Endpoint = null; + public void StartServer(string address, int port, PeerMessageListener listener) { + if (Peer != null) { + Debug.Log("Can not start multiple endpoints at once! Use Server if multiple connections are required."); + return; } + Peer = new Peer(FP); + Peer.MessageListener = listener; + Peer.Start(port); + Peer.RegisterProtocol(new TestProtocol()); + Peer.StartListen(address, port); + } + + public void Stop() { + if (Peer != null) { + Peer.Stop(); + Peer.MessageListener.Message("Stopping"); + Peer = null; + } + } + + static void Quit() { + Singleton.Stop(); + } + + [RuntimeInitializeOnLoadMethod] + static void RunOnStart() { + Application.quitting += Quit; } } } diff --git a/Assets/Scripts/Net/Endpoint.meta b/Assets/Scripts/Net/Packets.meta similarity index 77% rename from Assets/Scripts/Net/Endpoint.meta rename to Assets/Scripts/Net/Packets.meta index 18331f8..f25d4d3 100644 --- a/Assets/Scripts/Net/Endpoint.meta +++ b/Assets/Scripts/Net/Packets.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 9d5b2ed44f40cc940bae2d8e4c868bf4 +guid: 195df611d888d7248a7d4d22f10dfd71 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/Scripts/Net/Packets/Packet.cs b/Assets/Scripts/Net/Packets/Packet.cs new file mode 100644 index 0000000..3842e82 --- /dev/null +++ b/Assets/Scripts/Net/Packets/Packet.cs @@ -0,0 +1,118 @@ +using System.Collections.Generic; +using System; +using System.Text; + +namespace NeonTea.Quakeball.Net.Packets { + public abstract class Packet { + public int id; + + public abstract void Write(ByteBuffer buffer); + public abstract void Read(ByteBuffer buffer); + } + + public enum PacketStage { + Establishing = 0, + Rejected = 1, + Closed = 2, + Ready = 3, + } + + public enum ClosingReason { + Unknown = 0, + } + + + public class ByteBuffer { + private List Bytes; + private int pos = 0; + + public ByteBuffer() { + Bytes = new List(); + } + + public ByteBuffer(byte[] bytes) { + Bytes = new List(bytes); + } + + public byte[] Pack() { + return Bytes.ToArray(); + } + + public bool CanRead() { + return pos < Bytes.Count; + } + + public int ReadInt() { + return BitConverter.ToInt32(Read(4), 0); + } + + public string ReadString() { + int length = ReadInt(); + string s = Encoding.UTF8.GetString(Read(length)); + return s; + } + + public byte[] Read(int amount) { + byte[] bytes = Bytes.GetRange(pos, amount).ToArray(); + pos += amount; + return bytes; + } + + public byte Read() { + return Bytes[pos++]; + } + + public void WriteInt(int i) { + Bytes.AddRange(BitConverter.GetBytes(i)); + } + + public void WriteString(string s) { + byte[] bytes = Encoding.UTF8.GetBytes(s); + WriteInt(bytes.Length); + Bytes.AddRange(bytes); + } + + public void Write(byte b) { + Bytes.Add(b); + } + + public bool ReadFingerprint(byte[] fingerprint) { + foreach (byte b in fingerprint) { + if (!(CanRead() && Read() == b)) { + return false; + } + } + return true; + } + + public PacketStage ReadStage() { + PacketStage stage = PacketStage.Closed; + switch (Read()) { + case 0: + stage = PacketStage.Establishing; + break; + case 1: + stage = PacketStage.Rejected; + break; + case 2: + stage = PacketStage.Closed; + break; + case 3: + stage = PacketStage.Ready; + break; + } + return stage; + } + + public ClosingReason ReadClosingReason() { + ClosingReason reason = ClosingReason.Unknown; + switch (Read()) { + case 0: + reason = ClosingReason.Unknown; + break; + } + return reason; + } + } + +} \ No newline at end of file diff --git a/Assets/Scripts/Net/Peers/Server.cs.meta b/Assets/Scripts/Net/Packets/Packet.cs.meta similarity index 83% rename from Assets/Scripts/Net/Peers/Server.cs.meta rename to Assets/Scripts/Net/Packets/Packet.cs.meta index 7eceb8b..5e674c6 100644 --- a/Assets/Scripts/Net/Peers/Server.cs.meta +++ b/Assets/Scripts/Net/Packets/Packet.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 08642b59bb65292409a46c688efbf134 +guid: 32ad4f04beca6dd4db5cdea5cc66e648 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Scripts/Net/Packets/Protocol.cs b/Assets/Scripts/Net/Packets/Protocol.cs new file mode 100644 index 0000000..0d84cf9 --- /dev/null +++ b/Assets/Scripts/Net/Packets/Protocol.cs @@ -0,0 +1,67 @@ +using System.Collections.Generic; +using System; + +using NeonTea.Quakeball.Net.Peers; + + +namespace NeonTea.Quakeball.Net.Packets { + public abstract class Protocol { + private Dictionary PacketToId = new Dictionary(); + private Dictionary IdToPacket = new Dictionary(); + private int PacketIdCounter; + + public Peer Peer; + + public abstract byte Identifier { get; } + public abstract string Version { get; } + + public abstract void Receive(Connection conn, Packet packet); + public abstract void ConnectionStatusChanged(ConnectionStatus oldStatus, ConnectionStatus newStatus, Connection conn); + public abstract void Timeout(Connection conn); + + public void SendPacket(Packet p, Connection conn) { + Peer.ConnectionManager.AddPacketToQueue(conn, p); + Peer.ConnectionManager.SendPacketQueue(conn); + } + + public int RegisterPacket(Type t) { + if (t.BaseType != typeof(Packet) || PacketToId.ContainsKey(t)) { + return -1; + } + int id = PacketIdCounter++; + PacketToId.Add(t, id); + IdToPacket.Add(id, t); + return id; + } + + public ByteBuffer BuildMessage(Connection connection) { + ByteBuffer buffer = new ByteBuffer(); + foreach (byte b in Peer.Fingerprint) { + buffer.Write(b); + } + buffer.Write(Identifier); + if (connection.Status == ConnectionStatus.Establishing) { + Peer.MessageListener.Message("Sending Establishing"); + buffer.Write((byte)PacketStage.Establishing); + buffer.WriteString(Version); + } else if (connection.Status == ConnectionStatus.Closed) { + buffer.Write((byte)PacketStage.Closed); + } else if (connection.Status == ConnectionStatus.Rejected) { + buffer.Write((byte)PacketStage.Rejected); + buffer.Write((byte)connection.ClosingReason); + } else if (connection.Status == ConnectionStatus.Ready) { + buffer.Write((byte)PacketStage.Ready); + buffer.WriteInt(connection.LatestPacketReceived); + } + return buffer; + } + + public int GetPacketID(Packet packet) { + return PacketToId[packet.GetType()]; + } + + public Type GetPacketType(int id) { + return IdToPacket[id]; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Net/Peers/Client.cs.meta b/Assets/Scripts/Net/Packets/Protocol.cs.meta similarity index 83% rename from Assets/Scripts/Net/Peers/Client.cs.meta rename to Assets/Scripts/Net/Packets/Protocol.cs.meta index b58c363..293b710 100644 --- a/Assets/Scripts/Net/Peers/Client.cs.meta +++ b/Assets/Scripts/Net/Packets/Protocol.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: ffeef21f11f5f864f9213c2822a05d38 +guid: 7767effb4937ff34aa9262202afa3a46 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Scripts/Net/Peers/Client.cs b/Assets/Scripts/Net/Peers/Client.cs deleted file mode 100644 index 5bd5f59..0000000 --- a/Assets/Scripts/Net/Peers/Client.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; -using System; -using System.Net.Sockets; -using System.Text; -using System.Net; - -namespace NeonTea.Quakeball.Net.Peers { - public class Client : Peer { - - public override void OnStart(string host, int port) { - SendBytes(Encoding.UTF8.GetBytes("Hello! This is testing."), MainConnection.Endpoint); - - Debug.Log($"Client started at {host}:{port}!"); - } - - public override void HandlePacket(IPEndPoint endpoint, ByteReader reader) { - if (endpoint.Equals(MainConnection.Endpoint)) { - Debug.Log("Got stuff!"); - } - } - - public override void OnStop(DisconnectReason reason) { - Debug.Log($"Client closed: {reason.Description}"); - } - } -} \ No newline at end of file diff --git a/Assets/Scripts/Net/Peers/ConnectionManager.cs b/Assets/Scripts/Net/Peers/ConnectionManager.cs new file mode 100644 index 0000000..9f7dcd4 --- /dev/null +++ b/Assets/Scripts/Net/Peers/ConnectionManager.cs @@ -0,0 +1,200 @@ +using UnityEngine; +using System.Collections.Generic; +using System.Net; +using System.Threading; +using System; +using NeonTea.Quakeball.Net.Packets; + +namespace NeonTea.Quakeball.Net.Peers { + public class ConnectionManager { + private Dictionary Connections = new Dictionary(); + private Dictionary> PacketQueue = new Dictionary>(); + private Peer Peer; + + private Thread UpdateThread; + private long LastUpdate; + + public long Timeout = 8000; + public long Frequency = 100; + + public ConnectionManager(Peer peer) { + Peer = peer; + UpdateThread = new Thread(new ThreadStart(UpdateThreadMethod)); + UpdateThread.Start(); + } + + public void StopThread() { + UpdateThread.Abort(); + } + + public Connection Find(IPEndPoint endpoint) { + if (Connections.ContainsKey(endpoint)) { + return Connections[endpoint]; + } + Connection conn = new Connection(endpoint, ConnectionStatus.Awaiting); + Connections.Add(endpoint, conn); + PacketQueue.Add(conn, new List()); + return conn; + } + + public bool StartConnection(IPEndPoint endpoint, byte protocolIdent) { + if (Connections.ContainsKey(endpoint)) { + return false; + } + Connection conn = new Connection(endpoint); + conn.AssignedProtocol = protocolIdent; + Connections.Add(endpoint, conn); + PacketQueue.Add(conn, new List()); + return true; + } + + public void AddPacketToQueue(Connection conn, Packet p) { + p.id = conn.PacketIDCounter++; + PacketQueue[conn].Add(p); + } + + public void SendPacketQueue(Connection conn) { + Protocol protocol = Peer.GetProtocol(conn.AssignedProtocol); + if (protocol != null) { + ByteBuffer buffer = protocol.BuildMessage(conn); + List list = PacketQueue[conn]; + buffer.WriteInt(list.Count); + foreach (Packet p in list) { + buffer.WriteInt(protocol.GetPacketID(p)); + p.Write(buffer); + } + Send(conn, buffer); + } + } + + public void SendSinglePacket(Connection conn, Packet p) { + Protocol protocol = Peer.GetProtocol(conn.AssignedProtocol); + if (protocol != null) { + ByteBuffer buffer = protocol.BuildMessage(conn); + buffer.WriteInt(1); + buffer.WriteInt(protocol.GetPacketID(p)); + p.Write(buffer); + Send(conn, buffer); + } + } + + private void SendPlain(Connection conn) { + Protocol protocol = Peer.GetProtocol(conn.AssignedProtocol); + if (protocol != null) { + ByteBuffer buffer = protocol.BuildMessage(conn); + Send(conn, buffer); + } + } + + private void Send(Connection conn, ByteBuffer buffer) { + if (conn.Status == ConnectionStatus.Lost) { + return; + } + byte[] bytes = buffer.Pack(); + Peer.ListenerThread.LastSentConnection = conn; + Peer.UdpClient.Send(bytes, bytes.Length, conn.Endpoint); + } + + public void Handle(IPEndPoint endpoint, ByteBuffer buffer) { + Connection conn = Find(endpoint); + conn.LastMessage = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + ConnectionStatus oldStatus = conn.Status; + byte protocolId = buffer.Read(); + Protocol protocol = Peer.GetProtocol(protocolId); + PacketStage stage = buffer.ReadStage(); + switch (stage) { + case PacketStage.Establishing: + if (conn.Status == ConnectionStatus.Awaiting) { + conn.AssignedProtocol = protocolId; + string version = buffer.ReadString(); + if (protocol == null || !version.Equals(protocol.Version)) { + conn.Status = ConnectionStatus.Rejected; + conn.ClosingReason = ClosingReason.Unknown; + } else { + conn.Status = ConnectionStatus.Ready; + } + if (protocol != null) { + protocol.ConnectionStatusChanged(oldStatus, conn.Status, conn); + } + } + break; + case PacketStage.Rejected: + conn.Status = ConnectionStatus.Closed; + conn.ClosingReason = buffer.ReadClosingReason(); + if (protocol != null) { + protocol.ConnectionStatusChanged(oldStatus, conn.Status, conn); + } + break; + case PacketStage.Closed: + conn.Status = ConnectionStatus.Stopped; + if (protocol != null) { + protocol.ConnectionStatusChanged(oldStatus, conn.Status, conn); + } + break; + case PacketStage.Ready: + if (conn.AssignedProtocol != protocolId || protocol == null) { + break; + } + if (oldStatus == ConnectionStatus.Establishing) { + conn.Status = ConnectionStatus.Ready; + protocol.ConnectionStatusChanged(oldStatus, conn.Status, conn); + } + conn.LatestPacketSent = buffer.ReadInt(); + List list = PacketQueue[conn]; + list.RemoveAll(p => p.id <= conn.LatestPacketSent); + PacketQueue[conn] = list; + + int PacketAmount = buffer.ReadInt(); + for (int i = 0; i < PacketAmount; i++) { + int id = buffer.ReadInt(); + conn.LatestPacketReceived = Math.Max(conn.LatestPacketReceived, id); + Type t = protocol.GetPacketType(id); + Packet p = (Packet)Activator.CreateInstance(t); + p.Read(buffer); + protocol.Receive(conn, p); + } + break; + } + } + + private void UpdateThreadMethod() { + try { + while (true) { + long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + if ((now - LastUpdate) > Frequency) { + LastUpdate = now; + List> timedOut = new List>(); + foreach (KeyValuePair pair in Connections) { + Connection conn = pair.Value; + if ((now - conn.LastMessage) > Timeout || conn.Status == ConnectionStatus.Lost) { + timedOut.Add(pair); + } + if (conn.Status != ConnectionStatus.Awaiting || conn.Status != ConnectionStatus.Stopped) { + if (conn.Status == ConnectionStatus.Ready) { + SendPacketQueue(conn); + } else { + SendPlain(conn); + } + } + } + foreach (KeyValuePair pair in timedOut) { + Connections.Remove(pair.Key); + PacketQueue.Remove(pair.Value); + if (pair.Value.Status == ConnectionStatus.Ready + || pair.Value.Status == ConnectionStatus.Establishing + || pair.Value.Status == ConnectionStatus.Awaiting + || pair.Value.Status == ConnectionStatus.Lost) { + Protocol protocol = Peer.GetProtocol(pair.Value.AssignedProtocol); + if (protocol != null) { + protocol.Timeout(pair.Value); + } + } + } + } + } + } catch (ThreadAbortException e) { + Debug.Log("Connection Thread Stopped"); + } + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Net/Peers/ConnectionManager.cs.meta b/Assets/Scripts/Net/Peers/ConnectionManager.cs.meta new file mode 100644 index 0000000..b7a86ee --- /dev/null +++ b/Assets/Scripts/Net/Peers/ConnectionManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7a3471929fd5c5944be0ca1a0c52ef4a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Net/Peers/ListenerThread.cs b/Assets/Scripts/Net/Peers/ListenerThread.cs new file mode 100644 index 0000000..cb6dd8b --- /dev/null +++ b/Assets/Scripts/Net/Peers/ListenerThread.cs @@ -0,0 +1,71 @@ +using UnityEngine; +using System.Net; +using System; +using System.Net.Sockets; +using System.Threading; + +using NeonTea.Quakeball.Net.Packets; + +namespace NeonTea.Quakeball.Net.Peers { + public class ListenerThread { + private IPEndPoint EndPoint; + private Thread Thread; + private Peer Peer; + + public Connection LastSentConnection; + + private static int[] CONN_LOST_CODES = new int[] { 10054, 10051 }; + + public ListenerThread(Peer peer, IPEndPoint endpoint) { + EndPoint = endpoint; + Peer = peer; + } + + + public bool Start() { + if (Thread != null) { + return false; + } + Thread t = new Thread(new ThreadStart(ListenThreadMethod)); + + t.Start(); + Thread = t; + return true; + } + + public bool Stop() { + if (Thread == null) { + return false; + } + Debug.Log("Stopping ListenerThread!"); + Thread.Abort(); + return true; + } + + private void ListenThreadMethod() { + try { + while (true) { + IPEndPoint Listened = new IPEndPoint(EndPoint.Address, EndPoint.Port); + ByteBuffer Buffer = new ByteBuffer(); + try { + Buffer = new ByteBuffer(Peer.UdpClient.Receive(ref Listened)); + } catch (SocketException e) { + if (Array.Exists(CONN_LOST_CODES, x => x == e.ErrorCode)) { + if (LastSentConnection != null) { + LastSentConnection.Status = ConnectionStatus.Lost; + Peer.MessageListener.Message($"Connection lost to {LastSentConnection.Endpoint}: {e.ToString()}"); + } + } else { + Peer.MessageListener.Message($"Listener error: {e.ToString()}"); + } + } + if (Buffer.ReadFingerprint(Peer.Fingerprint)) { + Peer.ConnectionManager.Handle(Listened, Buffer); + } + } + } catch (ThreadAbortException e) { + Debug.Log("Listener Thread stopped"); + } + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/Net/Peers/ListenerThread.cs.meta b/Assets/Scripts/Net/Peers/ListenerThread.cs.meta new file mode 100644 index 0000000..fc537f2 --- /dev/null +++ b/Assets/Scripts/Net/Peers/ListenerThread.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cb30bb5582d404341a6e14bae841e642 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Net/Peers/Peer.cs b/Assets/Scripts/Net/Peers/Peer.cs index 2f128ee..67caf9a 100644 --- a/Assets/Scripts/Net/Peers/Peer.cs +++ b/Assets/Scripts/Net/Peers/Peer.cs @@ -4,49 +4,82 @@ using System; using System.Net; using System.Net.Sockets; using System.Threading; +using NeonTea.Quakeball.Net.Packets; +using System.Threading.Tasks; +using System.Runtime.Remoting; namespace NeonTea.Quakeball.Net.Peers { - public abstract class Peer { - private Thread ListenThread; + public class Peer : PeerMessageListener { + public UdpClient UdpClient { get; private set; } + public byte[] Fingerprint { get; private set; } - public byte[] Fingerprint = new byte[] { 0xCA, 0x11, 0x7F, 0xF8 }; + public ListenerThread ListenerThread; + public ConnectionManager ConnectionManager; + public Dictionary RegisteredProtocols = new Dictionary(); - protected UdpClient UdpClient; - protected Connection MainConnection; + public PeerMessageListener MessageListener; - public abstract void OnStart(string orighost, int origport); - public abstract void OnStop(DisconnectReason reason); - public abstract void HandlePacket(IPEndPoint endpoint, ByteReader reader); - - public void Start(string host, int port) { - int own_port = port; - if (this.GetType() == typeof(Client)) { - own_port = 0; - } - UdpClient = new UdpClient(own_port); - - try { - MainConnection = new Connection(new IPEndPoint(FindAddress(host), port)); - } catch (Exception e) { - Debug.Log($"Failed to create initial connection: {e.ToString()}"); - Net.Singleton.Stop(new DisconnectReason(Reason.INITIAL_ERROR, e.ToString())); - return; - } - StartListen(MainConnection); - - OnStart(host, port); + public Peer(byte[] fingerprint) { + Fingerprint = fingerprint; + ConnectionManager = new ConnectionManager(this); + MessageListener = this; } - public void Stop(DisconnectReason reason) { + public void Start(int sending_port) { + UdpClient = new UdpClient(sending_port); + MessageListener.Message("UdpClient Started"); + } + + public void Stop() { + ConnectionManager.StopThread(); + if (ListenerThread != null) { + ListenerThread.Stop(); + } + UdpClient.Dispose(); UdpClient.Close(); - if (ListenThread != null) { - ListenThread.Abort(); - } - MainConnection.Status = ConnectionStatus.Closed; - OnStop(reason); } - protected IPAddress FindAddress(string host) { + public void StartListen(string address, int port) { + IPEndPoint endpoint = new IPEndPoint(FindAddress(address), port); + StartListen(endpoint); + } + + private void StartListen(IPEndPoint endpoint) { + ListenerThread = new ListenerThread(this, endpoint); + ListenerThread.Start(); + MessageListener.Message($"Started listening to {endpoint}"); + } + + public void Connect(string address, int port, byte protocolIdent) { + IPEndPoint listenEndpoint = (IPEndPoint)UdpClient.Client.LocalEndPoint; + StartListen(listenEndpoint); + IPEndPoint endpoint = new IPEndPoint(FindAddress(address), port); + ConnectionManager.StartConnection(endpoint, protocolIdent); + MessageListener.Message($"Connecting to {endpoint}"); + } + + public byte RegisterProtocol(Protocol protocol) { + byte ident = protocol.Identifier; + if (RegisteredProtocols.ContainsKey(ident)) { + return 0; + } + RegisteredProtocols.Add(ident, protocol); + protocol.Peer = this; + return ident; + } + + public Protocol GetProtocol(byte ident) { + if (RegisteredProtocols.ContainsKey(ident)) { + return RegisteredProtocols[ident]; + } + return null; + } + + public void Message(string msg) { + + } + + public IPAddress FindAddress(string host) { IPAddress addr; try { addr = Dns.GetHostAddresses(host)[0]; @@ -55,85 +88,9 @@ namespace NeonTea.Quakeball.Net.Peers { } return addr; } - - protected void SendBytes(byte[] bytes, IPEndPoint endpoint) { - if (bytes != null) { - List ByteList = new List(); - ByteList.AddRange(Fingerprint); - ByteList.AddRange(bytes); - byte[] sent = ByteList.ToArray(); - UdpClient.Send(sent, sent.Length, endpoint); - } - } - - private bool StartListen(Connection connection) { - if (ListenThread != null) { - return false; - } - Thread t = new Thread(ListenThreadMethod); - t.Start(connection); - ListenThread = t; - return true; - } - - private void ListenThreadMethod(object obj) { - Connection listened; - try { - listened = (Connection)obj; - } catch (InvalidCastException) { - Debug.Log($"Can not cast {obj} to a Connection"); - return; - } - while (listened.Status != ConnectionStatus.Closed) { - try { - IPEndPoint Listened = new IPEndPoint(listened.Endpoint.Address, listened.Endpoint.Port); - ByteReader Received = new ByteReader(UdpClient.Receive(ref Listened)); - foreach (byte b in Fingerprint) { - if (!(Received.hasNext() && Received.next() == b)) { - goto end_of_handle; - } - } - HandlePacket(Listened, Received); - } catch (Exception e) { - Net.Singleton.Stop(new DisconnectReason(Reason.LISTENING_ERROR, e.ToString())); - } - end_of_handle: { }; - } - } } - public class ByteReader { - private byte[] ByteList; - private uint pos = 0; - - public ByteReader(byte[] list) { - ByteList = list; - } - - public bool hasNext() { - return pos < ByteList.Length; - } - - public byte next() { - return ByteList[pos++]; - } - } - - public struct DisconnectReason { - public static DisconnectReason LOCAL_MANUAL = new DisconnectReason(Reason.LOCAL_MANUAL, ""); - - public Reason Reason; - public string Description; - - public DisconnectReason(Reason reason, string description) { - Reason = reason; - Description = description; - } - } - - public enum Reason { - LOCAL_MANUAL, - LISTENING_ERROR, - INITIAL_ERROR, + public interface PeerMessageListener { + void Message(string msg); } } diff --git a/Assets/Scripts/Net/Peers/Server.cs b/Assets/Scripts/Net/Peers/Server.cs deleted file mode 100644 index ee93088..0000000 --- a/Assets/Scripts/Net/Peers/Server.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; -using System.Net; - -namespace NeonTea.Quakeball.Net.Peers { - public class Server : Peer { - - private static uint ConnectionUUIDCounter; - private Dictionary Connections = new Dictionary(); - - public override void OnStart(string host, int port) { - MainConnection.Status = ConnectionStatus.Ready; - Debug.Log($"Server started at {host}:{port}!"); - } - - public override void OnStop(DisconnectReason reason) { - Debug.Log($"Server closed: {reason.Description}"); - } - - public override void HandlePacket(IPEndPoint endpoint, ByteReader reader) { - if (Connections.ContainsKey(endpoint)) { - Debug.Log("Got stuff from an existing connection!"); - } else { - Connections.Add(endpoint, new Connection(endpoint)); - Debug.Log($"Initialized new connection from {endpoint.ToString()}"); - } - } - - private Connection AddConnection(IPEndPoint endpoint) { - Connection conn = new Connection(endpoint); - Connections.Add(endpoint, conn); - return conn; - } - } -} \ No newline at end of file diff --git a/Assets/Scripts/Net/TestProtocol.cs b/Assets/Scripts/Net/TestProtocol.cs new file mode 100644 index 0000000..ba69641 --- /dev/null +++ b/Assets/Scripts/Net/TestProtocol.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using System; +using UnityEngine; +using NeonTea.Quakeball.Net.Peers; +using NeonTea.Quakeball.Net; +using NeonTea.Quakeball.Net.Packets; + +namespace NeonTea.Quakeball.Net { + + public class TestProtocol : Protocol { + public override byte Identifier => 0x7A; + + public override string Version => "0.0.1"; + + public override void ConnectionStatusChanged(ConnectionStatus oldStatus, ConnectionStatus newStatus, Connection conn) { + Peer.MessageListener.Message($"Connection Status Changed into {newStatus.ToString()} for {conn.Endpoint}"); + } + + public override void Receive(Connection conn, Packet packet) { + Peer.MessageListener.Message($"Received packet from {conn.Endpoint}"); + } + + public override void Timeout(Connection conn) { + Peer.MessageListener.Message($"Closed {conn.Endpoint} for Timeout"); + } + } +} diff --git a/Assets/Scripts/Net/TestProtocol.cs.meta b/Assets/Scripts/Net/TestProtocol.cs.meta new file mode 100644 index 0000000..dc6e654 --- /dev/null +++ b/Assets/Scripts/Net/TestProtocol.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: df23a50fdd8df89489adbb9e8b4f1a87 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 2696802..0c49b4f 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -128,6 +128,7 @@ PlayerSettings: bundleVersion: 0.1 preloadedAssets: - {fileID: 0} + - {fileID: 0} metroInputSource: 0 wsaTransparentSwapchain: 0 m_HolographicPauseOnTrackingLoss: 1