2020-08-05 03:21:04 +02:00
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Net;
|
|
|
|
using System.Threading;
|
|
|
|
using System;
|
2020-08-05 18:50:28 +02:00
|
|
|
using NeonTea.Quakeball.TeaNet.Packets;
|
2020-08-05 03:21:04 +02:00
|
|
|
|
2020-08-05 18:50:28 +02:00
|
|
|
namespace NeonTea.Quakeball.TeaNet.Peers {
|
2020-08-06 20:49:51 +02:00
|
|
|
/// <summary>Manages connections for Peer, sends them keepalives and sends and handles incoming messages.</summary>
|
2020-08-05 03:21:04 +02:00
|
|
|
public class ConnectionManager {
|
2020-08-05 19:39:52 +02:00
|
|
|
private ulong ConnectionCounter;
|
|
|
|
private Dictionary<ulong, Connection> Connections = new Dictionary<ulong, Connection>();
|
|
|
|
private Dictionary<IPEndPoint, ulong> IPtoID = new Dictionary<IPEndPoint, ulong>();
|
|
|
|
private Dictionary<ulong, List<Packet>> PacketQueue = new Dictionary<ulong, List<Packet>>();
|
2020-08-05 03:21:04 +02:00
|
|
|
private Peer Peer;
|
|
|
|
|
2020-08-07 04:11:53 +02:00
|
|
|
public Dictionary<ulong, Queue<ProtocolAction>> ProtocolActionQueues = new Dictionary<ulong, Queue<ProtocolAction>>();
|
2020-08-08 21:41:57 +02:00
|
|
|
private Queue<Connection> ConnectionsToRemove = new Queue<Connection>();
|
2020-08-07 04:11:53 +02:00
|
|
|
|
2020-08-05 03:21:04 +02:00
|
|
|
private Thread UpdateThread;
|
|
|
|
|
|
|
|
public long Timeout = 8000;
|
2020-08-05 16:59:16 +02:00
|
|
|
public long Interval = 100;
|
2020-08-05 03:21:04 +02:00
|
|
|
|
2020-08-10 19:34:56 +02:00
|
|
|
/// <summary>The amount of bytes sent since the last clear interval from Peer</summary>
|
|
|
|
public int BytesSent;
|
2020-08-10 19:57:35 +02:00
|
|
|
public Dictionary<Type, int> SentByPacket = new Dictionary<Type, int>();
|
|
|
|
public Dictionary<Type, int> ReceivedByPacket = new Dictionary<Type, int>();
|
2020-08-10 19:34:56 +02:00
|
|
|
|
2020-08-05 03:21:04 +02:00
|
|
|
public ConnectionManager(Peer peer) {
|
|
|
|
Peer = peer;
|
|
|
|
UpdateThread = new Thread(new ThreadStart(UpdateThreadMethod));
|
|
|
|
UpdateThread.Start();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void StopThread() {
|
|
|
|
UpdateThread.Abort();
|
|
|
|
}
|
|
|
|
|
2020-08-06 20:49:51 +02:00
|
|
|
/// <summary>Find a given Connection. Should not be used, unless expecting to establish a connection with the endpoint.</summary>
|
2020-08-05 03:21:04 +02:00
|
|
|
public Connection Find(IPEndPoint endpoint) {
|
2020-08-05 19:39:52 +02:00
|
|
|
if (IPtoID.ContainsKey(endpoint)) {
|
|
|
|
return Connections[IPtoID[endpoint]];
|
2020-08-05 03:21:04 +02:00
|
|
|
}
|
|
|
|
Connection conn = new Connection(endpoint, ConnectionStatus.Awaiting);
|
2020-08-05 19:39:52 +02:00
|
|
|
AddConnection(conn);
|
2020-08-05 03:21:04 +02:00
|
|
|
return conn;
|
|
|
|
}
|
|
|
|
|
2020-08-06 20:49:51 +02:00
|
|
|
/// <summary>Start establishing a connection to a given endpoint with the given protocol</summary>
|
2020-08-05 03:21:04 +02:00
|
|
|
public bool StartConnection(IPEndPoint endpoint, byte protocolIdent) {
|
2020-08-05 19:39:52 +02:00
|
|
|
if (IPtoID.ContainsKey(endpoint)) {
|
2020-08-05 03:21:04 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
Connection conn = new Connection(endpoint);
|
2020-08-05 19:39:52 +02:00
|
|
|
conn.Internal.AssignedProtocol = protocolIdent;
|
|
|
|
AddConnection(conn);
|
2020-08-05 03:21:04 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-08-06 20:49:51 +02:00
|
|
|
/// <summary>Get the connection instance from the given uid, if such exists. Null otherwise.</summary>
|
2020-08-05 19:39:52 +02:00
|
|
|
public Connection GetConnection(ulong uid) {
|
|
|
|
Connection conn;
|
|
|
|
Connections.TryGetValue(uid, out conn);
|
|
|
|
return conn;
|
2020-08-05 03:21:04 +02:00
|
|
|
}
|
|
|
|
|
2020-08-07 03:46:09 +02:00
|
|
|
/// <summary>Soft-closes the connection with the given uid, meaning it will wait for them to acknowledge the closing</summary>
|
|
|
|
public void CloseConnection(ulong uid, ClosingReason reason) {
|
|
|
|
if (Connections.ContainsKey(uid)) {
|
|
|
|
Connections[uid].ClosingReason = reason;
|
|
|
|
Connections[uid].Status = ConnectionStatus.Rejected;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-06 20:49:51 +02:00
|
|
|
/// <summary>Add a reliable packet to the packet queue, to be sent on the next update, or when SendPacketQueue is called.</summary>
|
2020-08-05 19:39:52 +02:00
|
|
|
public void AddPacketToQueue(ulong uid, Packet p) {
|
|
|
|
if (!Connections.ContainsKey(uid)) {
|
|
|
|
return;
|
|
|
|
}
|
2020-08-05 21:02:56 +02:00
|
|
|
p = p.ShallowCopy();
|
2020-08-07 03:46:09 +02:00
|
|
|
p.PacketId = Connections[uid].Internal.ReliablePacketIDCounter++;
|
2020-08-05 19:39:52 +02:00
|
|
|
PacketQueue[uid].Add(p);
|
|
|
|
}
|
|
|
|
|
2020-08-06 20:49:51 +02:00
|
|
|
/// <summary>Send the current packet queue instantly.</summary>
|
2020-08-05 19:39:52 +02:00
|
|
|
public void SendPacketQueue(ulong uid) {
|
|
|
|
if (!Connections.ContainsKey(uid)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Connection conn = Connections[uid];
|
|
|
|
Protocol protocol = Peer.GetProtocol(conn.Internal.AssignedProtocol);
|
|
|
|
if (protocol != null && conn.IsReady()) {
|
2020-08-05 03:21:04 +02:00
|
|
|
ByteBuffer buffer = protocol.BuildMessage(conn);
|
2020-08-08 08:29:15 +02:00
|
|
|
Packet[] list = PacketQueue[uid].ToArray();
|
|
|
|
buffer.Write(list.Length);
|
2020-08-05 03:21:04 +02:00
|
|
|
foreach (Packet p in list) {
|
2020-08-05 18:50:28 +02:00
|
|
|
buffer.WritePacket(protocol, p);
|
2020-08-10 19:57:35 +02:00
|
|
|
|
|
|
|
// Do the analytics dance!
|
|
|
|
int OldSentByPacket;
|
|
|
|
SentByPacket.TryGetValue(p.GetType(), out OldSentByPacket);
|
|
|
|
SentByPacket[p.GetType()] = OldSentByPacket + p.Size;
|
2020-08-05 03:21:04 +02:00
|
|
|
}
|
|
|
|
Send(conn, buffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-06 20:49:51 +02:00
|
|
|
/// <summary>Send a single unreliable packet.</summary>
|
2020-08-05 19:39:52 +02:00
|
|
|
public void SendSingleUnreliable(ulong uid, Packet p) {
|
|
|
|
if (!Connections.ContainsKey(uid)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
Connection conn = Connections[uid];
|
2020-08-05 21:04:21 +02:00
|
|
|
p = p.ShallowCopy();
|
2020-08-07 03:46:09 +02:00
|
|
|
p.PacketId = conn.Internal.UnreliablePacketIDCounter++;
|
|
|
|
p.PacketIsReliable = false;
|
2020-08-05 19:39:52 +02:00
|
|
|
Protocol protocol = Peer.GetProtocol(conn.Internal.AssignedProtocol);
|
|
|
|
if (protocol != null && conn.IsReady()) {
|
2020-08-05 03:21:04 +02:00
|
|
|
ByteBuffer buffer = protocol.BuildMessage(conn);
|
2020-08-05 20:38:37 +02:00
|
|
|
buffer.Write(1);
|
2020-08-05 18:50:28 +02:00
|
|
|
buffer.WritePacket(protocol, p);
|
2020-08-05 03:21:04 +02:00
|
|
|
Send(conn, buffer);
|
2020-08-10 19:57:35 +02:00
|
|
|
|
|
|
|
// Do the analytics dance!
|
|
|
|
int OldSentByPacket;
|
|
|
|
SentByPacket.TryGetValue(p.GetType(), out OldSentByPacket);
|
|
|
|
SentByPacket[p.GetType()] = OldSentByPacket + p.Size;
|
2020-08-05 03:21:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-07 04:11:53 +02:00
|
|
|
/// <summary>Go through queue of networking actions that have happened since last update.</summary>
|
|
|
|
public void Update() {
|
|
|
|
foreach (byte id in ProtocolActionQueues.Keys) {
|
|
|
|
Protocol protocol = Peer.GetProtocol(id);
|
|
|
|
while (ProtocolActionQueues[id].Count > 0) {
|
|
|
|
ProtocolAction action = ProtocolActionQueues[id].Dequeue();
|
|
|
|
if (action is ReceiveAction) {
|
|
|
|
ReceiveAction receive = (ReceiveAction)action;
|
|
|
|
protocol.Receive(receive.Connection, receive.Packet);
|
|
|
|
} else if (action is ConnectionChangedAction) {
|
|
|
|
ConnectionChangedAction changed = (ConnectionChangedAction)action;
|
|
|
|
protocol.ConnectionStatusChanged(changed.OldStatus, changed.NewStatus, changed.Connection);
|
|
|
|
} else if (action is TimeoutAction) {
|
|
|
|
TimeoutAction changed = (TimeoutAction)action;
|
|
|
|
protocol.Timeout(changed.Connection);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-08 21:41:57 +02:00
|
|
|
while (ConnectionsToRemove.Count > 0) {
|
|
|
|
Connection conn = ConnectionsToRemove.Dequeue();
|
|
|
|
RemoveConnection(conn);
|
|
|
|
}
|
2020-08-07 04:11:53 +02:00
|
|
|
}
|
|
|
|
|
2020-08-05 19:39:52 +02:00
|
|
|
private void AddConnection(Connection conn) {
|
|
|
|
conn.uid = ConnectionCounter++;
|
|
|
|
Connections.Add(conn.uid, conn);
|
|
|
|
IPtoID.Add(conn.Endpoint, conn.uid);
|
|
|
|
PacketQueue.Add(conn.uid, new List<Packet>());
|
|
|
|
}
|
|
|
|
|
|
|
|
private void RemoveConnection(Connection conn) {
|
|
|
|
Connections.Remove(conn.uid);
|
|
|
|
IPtoID.Remove(conn.Endpoint);
|
|
|
|
PacketQueue.Remove(conn.uid);
|
|
|
|
}
|
|
|
|
|
2020-08-05 03:21:04 +02:00
|
|
|
private void SendPlain(Connection conn) {
|
2020-08-05 19:39:52 +02:00
|
|
|
Protocol protocol = Peer.GetProtocol(conn.Internal.AssignedProtocol);
|
2020-08-05 03:21:04 +02:00
|
|
|
if (protocol != null) {
|
|
|
|
ByteBuffer buffer = protocol.BuildMessage(conn);
|
|
|
|
Send(conn, buffer);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void Send(Connection conn, ByteBuffer buffer) {
|
|
|
|
if (conn.Status == ConnectionStatus.Lost) {
|
|
|
|
return;
|
|
|
|
}
|
2020-08-10 19:34:56 +02:00
|
|
|
BytesSent += buffer.Size;
|
2020-08-05 03:21:04 +02:00
|
|
|
byte[] bytes = buffer.Pack();
|
|
|
|
Peer.ListenerThread.LastSentConnection = conn;
|
|
|
|
Peer.UdpClient.Send(bytes, bytes.Length, conn.Endpoint);
|
|
|
|
}
|
|
|
|
|
|
|
|
public void Handle(IPEndPoint endpoint, ByteBuffer buffer) {
|
2020-08-10 19:57:35 +02:00
|
|
|
|
|
|
|
|
2020-08-05 03:21:04 +02:00
|
|
|
Connection conn = Find(endpoint);
|
2020-08-05 19:39:52 +02:00
|
|
|
conn.Internal.LastMessage = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
2020-08-05 03:21:04 +02:00
|
|
|
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) {
|
2020-08-05 19:39:52 +02:00
|
|
|
conn.Internal.AssignedProtocol = protocolId;
|
2020-08-05 03:21:04 +02:00
|
|
|
string version = buffer.ReadString();
|
|
|
|
if (protocol == null || !version.Equals(protocol.Version)) {
|
|
|
|
conn.Status = ConnectionStatus.Rejected;
|
2020-08-05 18:50:28 +02:00
|
|
|
conn.ClosingReason = ClosingReason.IncorrectVersion;
|
2020-08-05 03:21:04 +02:00
|
|
|
} else {
|
|
|
|
conn.Status = ConnectionStatus.Ready;
|
|
|
|
}
|
|
|
|
if (protocol != null) {
|
2020-08-07 04:11:53 +02:00
|
|
|
ProtocolActionQueues[protocol.Identifier].Enqueue(new ConnectionChangedAction(oldStatus, conn.Status, conn));
|
2020-08-05 03:21:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PacketStage.Rejected:
|
|
|
|
conn.Status = ConnectionStatus.Closed;
|
|
|
|
conn.ClosingReason = buffer.ReadClosingReason();
|
|
|
|
if (protocol != null) {
|
2020-08-07 04:11:53 +02:00
|
|
|
ProtocolActionQueues[protocol.Identifier].Enqueue(new ConnectionChangedAction(oldStatus, conn.Status, conn));
|
2020-08-05 03:21:04 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PacketStage.Closed:
|
2020-08-05 18:50:28 +02:00
|
|
|
if (conn.Status == ConnectionStatus.Stopped) {
|
|
|
|
break;
|
|
|
|
}
|
2020-08-05 03:21:04 +02:00
|
|
|
conn.Status = ConnectionStatus.Stopped;
|
|
|
|
if (protocol != null) {
|
2020-08-07 04:11:53 +02:00
|
|
|
ProtocolActionQueues[protocol.Identifier].Enqueue(new ConnectionChangedAction(oldStatus, conn.Status, conn));
|
2020-08-05 03:21:04 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case PacketStage.Ready:
|
2020-08-05 19:39:52 +02:00
|
|
|
if (conn.Internal.AssignedProtocol != protocolId || protocol == null) {
|
2020-08-05 03:21:04 +02:00
|
|
|
break;
|
|
|
|
}
|
2020-08-05 18:50:28 +02:00
|
|
|
if (oldStatus == ConnectionStatus.Establishing) { // Update connection status
|
2020-08-05 03:21:04 +02:00
|
|
|
conn.Status = ConnectionStatus.Ready;
|
2020-08-07 04:11:53 +02:00
|
|
|
ProtocolActionQueues[protocol.Identifier].Enqueue(new ConnectionChangedAction(oldStatus, conn.Status, conn));
|
2020-08-05 03:21:04 +02:00
|
|
|
}
|
2020-08-07 04:34:38 +02:00
|
|
|
if (!(oldStatus == ConnectionStatus.Establishing || oldStatus == ConnectionStatus.Ready)) {
|
|
|
|
break; // No cheating at this table! For realsies this time!
|
|
|
|
}
|
|
|
|
|
2020-08-05 19:39:52 +02:00
|
|
|
conn.Internal.LatestOutwardReliable = buffer.ReadInt();
|
2020-08-05 18:50:28 +02:00
|
|
|
|
2020-08-05 19:39:52 +02:00
|
|
|
List<Packet> list = PacketQueue[conn.uid];
|
2020-08-07 03:46:09 +02:00
|
|
|
list.RemoveAll(p => p.PacketId <= conn.Internal.LatestOutwardReliable);
|
2020-08-05 19:39:52 +02:00
|
|
|
PacketQueue[conn.uid] = list;
|
2020-08-05 03:21:04 +02:00
|
|
|
|
|
|
|
int PacketAmount = buffer.ReadInt();
|
|
|
|
for (int i = 0; i < PacketAmount; i++) {
|
2020-08-05 18:50:28 +02:00
|
|
|
Packet p = buffer.ReadPacket(protocol);
|
2020-08-07 03:46:09 +02:00
|
|
|
if (p.PacketIsReliable) {
|
|
|
|
if (p.PacketId > conn.Internal.LatestInwardReliable) {
|
|
|
|
conn.Internal.LatestInwardReliable = p.PacketId;
|
2020-08-07 04:11:53 +02:00
|
|
|
ProtocolActionQueues[protocol.Identifier].Enqueue(new ReceiveAction(conn, p));
|
2020-08-05 18:50:28 +02:00
|
|
|
}
|
2020-08-07 03:46:09 +02:00
|
|
|
} else if (p.PacketId > conn.Internal.LatestInwardUnreliable) {
|
|
|
|
conn.Internal.LatestInwardUnreliable = p.PacketId;
|
2020-08-07 04:11:53 +02:00
|
|
|
ProtocolActionQueues[protocol.Identifier].Enqueue(new ReceiveAction(conn, p));
|
2020-08-05 18:50:28 +02:00
|
|
|
}
|
2020-08-10 19:57:35 +02:00
|
|
|
|
|
|
|
// Do some analytics!
|
|
|
|
int OldReceivedByPacket;
|
|
|
|
ReceivedByPacket.TryGetValue(p.GetType(), out OldReceivedByPacket);
|
|
|
|
ReceivedByPacket[p.GetType()] = OldReceivedByPacket + p.Size;
|
2020-08-05 03:21:04 +02:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void UpdateThreadMethod() {
|
|
|
|
try {
|
2020-08-05 16:59:16 +02:00
|
|
|
while (Thread.CurrentThread.IsAlive) {
|
2020-08-05 03:21:04 +02:00
|
|
|
long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
2020-08-05 19:39:52 +02:00
|
|
|
List<ulong> timedOut = new List<ulong>();
|
|
|
|
foreach (ulong uid in Connections.Keys) {
|
|
|
|
Connection conn = Connections[uid];
|
|
|
|
if ((now - conn.Internal.LastMessage) > Timeout || conn.Status == ConnectionStatus.Lost) {
|
|
|
|
timedOut.Add(uid);
|
2020-08-05 16:59:16 +02:00
|
|
|
}
|
|
|
|
if (conn.Status != ConnectionStatus.Awaiting || conn.Status != ConnectionStatus.Stopped) {
|
|
|
|
if (conn.Status == ConnectionStatus.Ready) {
|
2020-08-05 19:39:52 +02:00
|
|
|
SendPacketQueue(uid);
|
2020-08-05 16:59:16 +02:00
|
|
|
} else {
|
|
|
|
SendPlain(conn);
|
2020-08-05 03:21:04 +02:00
|
|
|
}
|
|
|
|
}
|
2020-08-05 16:59:16 +02:00
|
|
|
}
|
2020-08-05 19:39:52 +02:00
|
|
|
foreach (ulong uid in timedOut) {
|
|
|
|
Connection conn = Connections[uid];
|
2020-08-08 21:41:57 +02:00
|
|
|
ConnectionsToRemove.Enqueue(conn);
|
2020-08-05 19:39:52 +02:00
|
|
|
if (conn.Status == ConnectionStatus.Ready
|
|
|
|
|| conn.Status == ConnectionStatus.Establishing
|
|
|
|
|| conn.Status == ConnectionStatus.Awaiting
|
|
|
|
|| conn.Status == ConnectionStatus.Lost) {
|
|
|
|
Protocol protocol = Peer.GetProtocol(conn.Internal.AssignedProtocol);
|
2020-08-05 16:59:16 +02:00
|
|
|
if (protocol != null) {
|
2020-08-07 03:46:09 +02:00
|
|
|
conn.ClosingReason = ClosingReason.Timeout;
|
2020-08-07 04:11:53 +02:00
|
|
|
ProtocolActionQueues[conn.Internal.AssignedProtocol].Enqueue(new TimeoutAction(conn));
|
2020-08-05 03:21:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-05 16:59:16 +02:00
|
|
|
Thread.Sleep((int)Interval);
|
2020-08-05 03:21:04 +02:00
|
|
|
}
|
2020-08-05 16:59:16 +02:00
|
|
|
} catch (ThreadAbortException) {
|
2020-08-05 18:50:28 +02:00
|
|
|
Peer.MessageListener.Message("Connection Thread Stopped");
|
2020-08-05 03:21:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-07 04:11:53 +02:00
|
|
|
|
|
|
|
public abstract class ProtocolAction { };
|
|
|
|
|
|
|
|
class ReceiveAction : ProtocolAction {
|
|
|
|
public Connection Connection;
|
|
|
|
public Packet Packet;
|
|
|
|
|
|
|
|
public ReceiveAction(Connection connection, Packet packet) {
|
|
|
|
Connection = connection;
|
|
|
|
Packet = packet;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class ConnectionChangedAction : ProtocolAction {
|
|
|
|
public ConnectionStatus OldStatus;
|
|
|
|
public ConnectionStatus NewStatus;
|
|
|
|
public Connection Connection;
|
|
|
|
|
|
|
|
public ConnectionChangedAction(ConnectionStatus old, ConnectionStatus newstatus, Connection connection) {
|
|
|
|
Connection = connection;
|
|
|
|
OldStatus = old;
|
|
|
|
NewStatus = newstatus;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class TimeoutAction : ProtocolAction {
|
|
|
|
public Connection Connection;
|
|
|
|
|
|
|
|
public TimeoutAction(Connection connection) {
|
|
|
|
Connection = connection;
|
|
|
|
}
|
|
|
|
}
|
2020-08-05 03:21:04 +02:00
|
|
|
}
|