Make networking work
This commit is contained in:
parent
db1d3bb671
commit
63eddfa43b
@ -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<string> Stuff = new List<string>();
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d5b2ed44f40cc940bae2d8e4c868bf4
|
||||
guid: 195df611d888d7248a7d4d22f10dfd71
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
118
Assets/Scripts/Net/Packets/Packet.cs
Normal file
118
Assets/Scripts/Net/Packets/Packet.cs
Normal file
@ -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<byte> Bytes;
|
||||
private int pos = 0;
|
||||
|
||||
public ByteBuffer() {
|
||||
Bytes = new List<byte>();
|
||||
}
|
||||
|
||||
public ByteBuffer(byte[] bytes) {
|
||||
Bytes = new List<byte>(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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 08642b59bb65292409a46c688efbf134
|
||||
guid: 32ad4f04beca6dd4db5cdea5cc66e648
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
67
Assets/Scripts/Net/Packets/Protocol.cs
Normal file
67
Assets/Scripts/Net/Packets/Protocol.cs
Normal file
@ -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<Type, int> PacketToId = new Dictionary<Type, int>();
|
||||
private Dictionary<int, Type> IdToPacket = new Dictionary<int, Type>();
|
||||
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];
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ffeef21f11f5f864f9213c2822a05d38
|
||||
guid: 7767effb4937ff34aa9262202afa3a46
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
200
Assets/Scripts/Net/Peers/ConnectionManager.cs
Normal file
200
Assets/Scripts/Net/Peers/ConnectionManager.cs
Normal file
@ -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<IPEndPoint, Connection> Connections = new Dictionary<IPEndPoint, Connection>();
|
||||
private Dictionary<Connection, List<Packet>> PacketQueue = new Dictionary<Connection, List<Packet>>();
|
||||
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<Packet>());
|
||||
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<Packet>());
|
||||
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<Packet> 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<Packet> 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<KeyValuePair<IPEndPoint, Connection>> timedOut = new List<KeyValuePair<IPEndPoint, Connection>>();
|
||||
foreach (KeyValuePair<IPEndPoint, Connection> 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<IPEndPoint, Connection> 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Scripts/Net/Peers/ConnectionManager.cs.meta
Normal file
11
Assets/Scripts/Net/Peers/ConnectionManager.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a3471929fd5c5944be0ca1a0c52ef4a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
71
Assets/Scripts/Net/Peers/ListenerThread.cs
Normal file
71
Assets/Scripts/Net/Peers/ListenerThread.cs
Normal file
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Scripts/Net/Peers/ListenerThread.cs.meta
Normal file
11
Assets/Scripts/Net/Peers/ListenerThread.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cb30bb5582d404341a6e14bae841e642
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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<byte, Protocol> RegisteredProtocols = new Dictionary<byte, Protocol>();
|
||||
|
||||
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<byte> ByteList = new List<byte>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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<IPEndPoint, Connection> Connections = new Dictionary<IPEndPoint, Connection>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
27
Assets/Scripts/Net/TestProtocol.cs
Normal file
27
Assets/Scripts/Net/TestProtocol.cs
Normal file
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Scripts/Net/TestProtocol.cs.meta
Normal file
11
Assets/Scripts/Net/TestProtocol.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df23a50fdd8df89489adbb9e8b4f1a87
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -128,6 +128,7 @@ PlayerSettings:
|
||||
bundleVersion: 0.1
|
||||
preloadedAssets:
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
metroInputSource: 0
|
||||
wsaTransparentSwapchain: 0
|
||||
m_HolographicPauseOnTrackingLoss: 1
|
||||
|
Loading…
Reference in New Issue
Block a user