Separate into own repository

This commit is contained in:
Sofia 2020-09-30 16:07:53 +03:00
commit 624face1ba
9 changed files with 1162 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.meta

235
Packets/ByteBuffer.cs Normal file
View File

@ -0,0 +1,235 @@
using System.Collections.Generic;
using System;
using System.Text;
namespace NeonTea.Quakeball.TeaNet.Packets {
/// <summary>Contains a stream of bytes for sending or receiving over the internet.</summary>
public class ByteBuffer {
public int Size => Bytes.Count;
private List<byte> Bytes;
private int pos = 0;
/// <summary>Creates a new empty ByteBuffer</summary>
public ByteBuffer() {
Bytes = new List<byte>();
}
/// <summary>Creates a new ByteBuffer from the given byte[]</summary>
public ByteBuffer(byte[] bytes) {
Bytes = new List<byte>(bytes);
}
/// <summary>Packs the byte buffer in to a byte[] array.</summary>
public byte[] Pack() {
return Bytes.ToArray();
}
/// <summary>is there a byte to read next?</summary>
public bool CanRead() {
return pos < Bytes.Count;
}
/// <summary>Reads a given object with Serializable implemented. Assumes there is one next.</summary>
public void ReadSerializable(Serializable s) {
s.Read(this);
}
/// <summary>Read a char on the buffer. Assumes there is one next.</summary>
public char ReadChar() {
return BitConverter.ToChar(Read(2), 0);
}
/// <summary>Read a boolean on the buffer. Assumes there is one next.</summary>
public bool ReadBool() {
return Read() == 1;
}
/// <summary>Read a double on the buffer. Assumes there is one next.</summary>
public double ReadDouble() {
return BitConverter.ToDouble(Read(8), 0);
}
/// <summary>Read a float on the buffer. Assumes there is one next.</summary>
public float ReadFloat() {
return BitConverter.ToSingle(Read(4), 0);
}
/// <summary>Read an unsigned long on the buffer. Assumes there is one next.</summary>
public ulong ReadULong() {
return BitConverter.ToUInt64(Read(8), 0);
}
/// <summary>Read an unsigned integer on the buffer. Assumes there is one next.</summary>
public uint ReadUInt() {
return BitConverter.ToUInt32(Read(4), 0);
}
/// <summary>Read an unsigned short on the buffer. Assumes there is one next.</summary>
public ushort ReadUShort() {
return BitConverter.ToUInt16(Read(2), 0);
}
/// <summary>Read a long on the buffer. Assumes there is one next.</summary>
public long ReadLong() {
return BitConverter.ToInt64(Read(8), 0);
}
/// <summary>Read an integer on the buffer. Assumes there is one next.</summary>
public int ReadInt() {
return BitConverter.ToInt32(Read(4), 0);
}
/// <summary>Read a short on the buffer. Assumes there is one next.</summary>
public short ReadShort() {
return BitConverter.ToInt16(Read(2), 0);
}
/// <summary>Read a string on the buffer. Assumes there is one next.</summary>
public string ReadString() {
int length = ReadInt();
string s = Encoding.UTF8.GetString(Read(length));
return s;
}
/// <summary>Read an integer on the buffer. Assumes there is one next.</summary>
public byte[] Read(int amount) {
byte[] bytes = Bytes.GetRange(pos, amount).ToArray();
pos += amount;
return bytes;
}
/// <summary>Read the next byte on the buffer. Assumes there is one next.</summary>
public byte Read() {
return Bytes[pos++];
}
/// <summary>Write something that implements Serializable.</summary>
public void Write(Serializable s) {
s.Write(this);
}
/// <summary>Write a char to the buffer.</summary>
public void Write(char c) {
Bytes.AddRange(BitConverter.GetBytes(c));
}
/// <summary>Write a boolean to the buffer.</summary>
public void Write(bool b) {
Write(b ? (byte)0b1 : (byte)0b0);
}
/// <summary>Write a double to the buffer.</summary>
public void Write(double d) {
Bytes.AddRange(BitConverter.GetBytes(d));
}
/// <summary>Write a float to the buffer.</summary>
public void Write(float f) {
Bytes.AddRange(BitConverter.GetBytes(f));
}
/// <summary>Write an unsigned long to the buffer.</summary>
public void Write(ulong l) {
Bytes.AddRange(BitConverter.GetBytes(l));
}
/// <summary>Write an unsigned integer to the buffer.</summary>
public void Write(uint i) {
Bytes.AddRange(BitConverter.GetBytes(i));
}
/// <summary>Write an unsigned short to the buffer.</summary>
public void Write(ushort s) {
Bytes.AddRange(BitConverter.GetBytes(s));
}
/// <summary>Write a long to the buffer.</summary>
public void Write(long l) {
Bytes.AddRange(BitConverter.GetBytes(l));
}
/// <summary>Write an integer to the buffer.</summary>
public void Write(int i) {
Bytes.AddRange(BitConverter.GetBytes(i));
}
/// <summary>Write a short to the buffer.</summary>
public void Write(short s) {
Bytes.AddRange(BitConverter.GetBytes(s));
}
/// <summary>Write a string to the buffer.</summary>
public void Write(string s) {
byte[] bytes = Encoding.UTF8.GetBytes(s);
Write(bytes.Length);
Bytes.AddRange(bytes);
}
/// <summary>Write a byte to the buffer.</summary>
public void Write(byte b) {
Bytes.Add(b);
}
/// <summary>Read weather the given fingerprint is next on the buffer.</summary>
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;
case 1:
reason = ClosingReason.IncorrectVersion;
break;
}
return reason;
}
/// <summary>Write an entire packet using the protocol to the buffer.</summary>
public void WritePacket(Protocol protocol, Packet p) {
int old = Bytes.Count;
Write(protocol.GetPacketTypeID(p));
p.Write(this);
p.Size = Bytes.Count - old;
}
/// <summary>Read an entire packet using the given protocol from the buffer.</summary>
public Packet ReadPacket(Protocol protocol) {
int old = pos;
int packetType = ReadInt();
Type t = protocol.GetPacketType(packetType);
Packet p = (Packet)Activator.CreateInstance(t);
p.Read(this);
p.Size = pos - old;
return p;
}
}
}

42
Packets/Packet.cs Normal file
View File

@ -0,0 +1,42 @@
namespace NeonTea.Quakeball.TeaNet.Packets {
/// <summary>A packet for sending stuff over to connections.</summary>
public abstract class Packet {
/// <summary>Packet meta-information: Is this packet reliable. Set just before sending.</summary>
public bool PacketIsReliable = true;
/// <summary>Packet meta-information: Id of this packet. Set just before sending.</summary>
public int PacketId;
/// <summary>Size of this packet in bytes. Only available after the packet has been Read (when received) or Written (when sent).</summary>
public int Size;
/// <summary>Write any relevant information about this packet into the buffer.</summary>
public abstract void Write(ByteBuffer buffer);
/// <summary>Read and assign any relevant information about this packet from the buffer.</summary>
public abstract void Read(ByteBuffer buffer);
/// <summary>Make a shallow copy for this packet, copying any primitives but retaining any references to instances.</summary>
public Packet ShallowCopy() {
return (Packet)this.MemberwiseClone();
}
}
/// <summary>Defines something as writeable/readable by the buffer. Useful for creating abstractions within packets.</summary>
public interface Serializable {
void Write(ByteBuffer buffer);
void Read(ByteBuffer buffer);
}
public enum PacketStage {
Establishing = 0,
Rejected = 1,
Closed = 2,
Ready = 3,
}
public enum ClosingReason {
Unknown = 0,
IncorrectVersion = 1,
Timeout = 2,
Manual = 3,
}
}

71
Packets/Protocol.cs Normal file
View File

@ -0,0 +1,71 @@
using System.Collections.Generic;
using System;
using NeonTea.Quakeball.TeaNet.Peers;
namespace NeonTea.Quakeball.TeaNet.Packets {
/// <summary>Manages a single form of conversation between clients for the Peer. Don't forget to register your packets with <see cref="Protocol.RegisterPacket(Type)"/></summary>
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;
/// <summary>Refers to the peer it is registered to</summary>
public Peer Peer;
/// <summary>Unique identifier for the protocol. This should be different for every protocol.</summary>
public abstract byte Identifier { get; }
/// <summary>Version of the protocol, should be changed if existing packets are changed, new packets are registered or old packets are removed.</summary>
public abstract string Version { get; }
/// <summary>Called when the Peer receives a packet from a connection that uses this protocol</summary>
public abstract void Receive(Connection conn, Packet packet);
/// <summary>Called when a ConnectionStatus is changed for a connection that uses this protocol</summary>
public abstract void ConnectionStatusChanged(ConnectionStatus oldStatus, ConnectionStatus newStatus, Connection conn);
/// <summary>Called when a connection that uses this protocol is timed out suddenly.</summary>
public abstract void Timeout(Connection conn);
/// <summary>Register a packet for sending and receiving.</summary>
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, int firstId, bool reliable) {
ByteBuffer buffer = new ByteBuffer();
foreach (byte b in Peer.Fingerprint) {
buffer.Write(b);
}
buffer.Write(Identifier);
if (connection.Status == ConnectionStatus.Establishing) {
buffer.Write((byte)PacketStage.Establishing);
buffer.Write(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.Write(connection.Internal.LatestInwardReliable);
buffer.Write(firstId);
buffer.Write(reliable);
}
return buffer;
}
public int GetPacketTypeID(Packet packet) {
return PacketToId[packet.GetType()];
}
public Type GetPacketType(int id) {
return IdToPacket[id];
}
}
}

83
Peers/Connection.cs Normal file
View File

@ -0,0 +1,83 @@
using System.Net;
using System;
using NeonTea.Quakeball.TeaNet.Packets;
namespace NeonTea.Quakeball.TeaNet.Peers {
/// <summary>Represents a connection to a remot host over the internet.</summary>
public class Connection {
/// <summary>The IP end point of the connection</summary>
public IPEndPoint Endpoint;
/// <summary>The unique identifier of the connection.</summary>
public ulong uid;
/// <summary>Connection status of the current connection.</summary>
public ConnectionStatus Status;
/// <summary>Reason why the connection closed. Null if no reason.</summary>
public ClosingReason ClosingReason;
/// <summary>Internal data for the connection. Do not touch, unless you know what you're doing.</summary>
public ConnectionInternalData Internal = new ConnectionInternalData();
public Connection(IPEndPoint endpoint, ConnectionStatus status = ConnectionStatus.Establishing) {
Endpoint = endpoint;
Status = status;
Internal.LastMessage = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
Internal.LatestInwardReliable = -1;
Internal.LatestInwardUnreliable = -1;
Internal.LatestOutwardReliable = -1;
Internal.LatestOutwardUnreliable = -1;
}
/// <summary>Is the connection ready and established for sending packets.</summary>
public bool IsReady() {
return Status == ConnectionStatus.Ready;
}
/// <summary>Is the connection disconnected. Shorthand for weather Status is Rejected, Closed, Stopped or Lost.</summary>
public bool IsDisconnected() {
return !(Status == ConnectionStatus.Ready
|| Status == ConnectionStatus.Awaiting
|| Status == ConnectionStatus.Establishing);
}
}
public struct ConnectionInternalData {
/// <summary>The protocol identifier, which this connection uses.</summary>
public byte AssignedProtocol;
/// <summary>Last unix timestamp in milliseconds, when this connection was last heard of.</summary>
public long LastMessage;
/// <summary>Last reliable Packet ID the connection has told us they have</summary>
public int LatestOutwardReliable;
/// <summary>Last unreliablePacket ID the connection has told us they have</summary>
public int LatestOutwardUnreliable;
/// <summary>Last reliable Packet ID we've received from the connection</summary>
public int LatestInwardReliable;
/// <summary>Last unreliable Packet ID we've received from the connection</summary>
public int LatestInwardUnreliable;
/// <summary>Reliable Packet ID counter for packets we're sending them</summary>
public int ReliablePacketIDCounter;
/// <summary>Unreliable Packet ID counter for packets we're sending them</summary>
public int UnreliablePacketIDCounter;
}
/// <summary>Initiali</summary>
public enum ConnectionStatus {
/// <summary>Awaiting the other endpoint to establish the connection.</summary>
Awaiting,
/// <summary>Attempting to establish the connection.</summary>
Establishing,
/// <summary>Ready for packet sending</summary>
Ready,
/// <summary>Rejected connection at endpoint, sending information that it was rejected.</summary>
Rejected,
/// <summary>Closed the endpoint, and informing the connection that it should stop.</summary>
Closed,
/// <summary>Connection is stopped and waiting for timeout.</summary>
Stopped,
/// <summary>Connection Lost</summary>
Lost,
}
}

403
Peers/ConnectionManager.cs Normal file
View File

@ -0,0 +1,403 @@
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Net;
using System.Threading;
using System;
using NeonTea.Quakeball.TeaNet.Packets;
using UnityEngine;
namespace NeonTea.Quakeball.TeaNet.Peers {
/// <summary>Manages connections for Peer, sends them keepalives and sends and handles incoming messages.</summary>
public class ConnectionManager {
private ulong ConnectionCounter;
private Dictionary<ulong, Connection> Connections = new Dictionary<ulong, Connection>();
private Dictionary<IPEndPoint, ulong> IPtoID = new Dictionary<IPEndPoint, ulong>();
private Dictionary<ulong, ConcurrentQueue<Packet>> PacketQueue = new Dictionary<ulong, ConcurrentQueue<Packet>>();
private Peer Peer;
public Dictionary<ulong, Queue<ProtocolAction>> ProtocolActionQueues = new Dictionary<ulong, Queue<ProtocolAction>>();
private Queue<Connection> ConnectionsToRemove = new Queue<Connection>();
private Thread UpdateThread;
public long Timeout = 8000;
public long Interval = 100;
public ConnectionManagerTrafficData TrafficData = new ConnectionManagerTrafficData();
public ConnectionManager(Peer peer) {
Peer = peer;
UpdateThread = new Thread(new ThreadStart(UpdateThreadMethod));
UpdateThread.Start();
}
public void StopThread() {
UpdateThread.Abort();
}
/// <summary>Find a given Connection. Should not be used, unless expecting to establish a connection with the endpoint.</summary>
public Connection Find(IPEndPoint endpoint) {
if (IPtoID.ContainsKey(endpoint)) {
return Connections[IPtoID[endpoint]];
}
Connection conn = new Connection(endpoint, ConnectionStatus.Awaiting);
AddConnection(conn);
return conn;
}
/// <summary>Start establishing a connection to a given endpoint with the given protocol</summary>
public bool StartConnection(IPEndPoint endpoint, byte protocolIdent) {
if (IPtoID.ContainsKey(endpoint)) {
return false;
}
Connection conn = new Connection(endpoint);
conn.Internal.AssignedProtocol = protocolIdent;
AddConnection(conn);
return true;
}
/// <summary>Get the connection instance from the given uid, if such exists. Null otherwise.</summary>
public Connection GetConnection(ulong uid) {
Connection conn;
Connections.TryGetValue(uid, out conn);
return conn;
}
/// <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;
}
}
/// <summary>Add a reliable packet to the packet queue, to be sent on the next update, or when SendPacketQueue is called.</summary>
public void AddPacketToQueue(ulong uid, Packet p) {
if (!Connections.ContainsKey(uid)) {
return;
}
p = p.ShallowCopy();
p.PacketId = Connections[uid].Internal.ReliablePacketIDCounter++;
PacketQueue[uid].Enqueue(p);
}
/// <summary>Send the current packet queue instantly.</summary>
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()) {
Packet[] list = PacketQueue[uid].ToArray();
int firstId = Int32.MaxValue;
if (list.Length > 0) {
firstId = list[0].PacketId;
}
ByteBuffer buffer = protocol.BuildMessage(conn, firstId, true);
buffer.Write(list.Length);
foreach (Packet p in list) {
buffer.WritePacket(protocol, p);
// Do the analytics dance!
PerPacketData OldSentByPacket;
TrafficData.SentByPacket.TryGetValue(p.GetType(), out OldSentByPacket);
OldSentByPacket.Bytes += p.Size;
OldSentByPacket.Packets += 1;
TrafficData.SentByPacket[p.GetType()] = OldSentByPacket;
TrafficData.ReliablePacketsSent++;
}
Send(conn, buffer);
}
}
/// <summary>Send a single unreliable packet.</summary>
public void SendSingleUnreliable(ulong uid, Packet p) {
if (!Connections.ContainsKey(uid)) {
return;
}
Connection conn = Connections[uid];
p = p.ShallowCopy();
p.PacketId = conn.Internal.UnreliablePacketIDCounter++;
p.PacketIsReliable = false;
Protocol protocol = Peer.GetProtocol(conn.Internal.AssignedProtocol);
if (protocol != null && conn.IsReady()) {
ByteBuffer buffer = protocol.BuildMessage(conn, p.PacketId, false);
buffer.Write(1);
buffer.WritePacket(protocol, p);
Send(conn, buffer);
TrafficData.UnreliablePacketsSent++;
// Do the analytics dance!
PerPacketData OldSentByPacket;
TrafficData.SentByPacket.TryGetValue(p.GetType(), out OldSentByPacket);
OldSentByPacket.Bytes += p.Size;
OldSentByPacket.Packets += 1;
TrafficData.SentByPacket[p.GetType()] = OldSentByPacket;
}
}
/// <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);
}
}
}
while (ConnectionsToRemove.Count > 0) {
Connection conn = ConnectionsToRemove.Dequeue();
RemoveConnection(conn);
}
}
private void AddConnection(Connection conn) {
conn.uid = ConnectionCounter++;
Connections.Add(conn.uid, conn);
IPtoID.Add(conn.Endpoint, conn.uid);
PacketQueue.Add(conn.uid, new ConcurrentQueue<Packet>());
}
private void RemoveConnection(Connection conn) {
Connections.Remove(conn.uid);
IPtoID.Remove(conn.Endpoint);
PacketQueue.Remove(conn.uid);
}
private void SendPlain(Connection conn) {
Protocol protocol = Peer.GetProtocol(conn.Internal.AssignedProtocol);
if (protocol != null) {
ByteBuffer buffer = protocol.BuildMessage(conn, Int32.MaxValue, false);
Send(conn, buffer);
}
}
private void Send(Connection conn, ByteBuffer buffer) {
if (conn.Status == ConnectionStatus.Lost) {
return;
}
TrafficData.BytesSent += buffer.Size;
TrafficData.TotalMessagesSent++;
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.Internal.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.Internal.AssignedProtocol = protocolId;
string version = buffer.ReadString();
if (protocol == null || !version.Equals(protocol.Version)) {
conn.Status = ConnectionStatus.Rejected;
conn.ClosingReason = ClosingReason.IncorrectVersion;
} else {
conn.Status = ConnectionStatus.Ready;
}
if (protocol != null) {
ProtocolActionQueues[protocol.Identifier].Enqueue(new ConnectionChangedAction(oldStatus, conn.Status, conn));
}
}
break;
case PacketStage.Rejected:
conn.Status = ConnectionStatus.Closed;
conn.ClosingReason = buffer.ReadClosingReason();
if (protocol != null) {
ProtocolActionQueues[protocol.Identifier].Enqueue(new ConnectionChangedAction(oldStatus, conn.Status, conn));
}
break;
case PacketStage.Closed:
if (conn.Status == ConnectionStatus.Stopped) {
break;
}
conn.Status = ConnectionStatus.Stopped;
if (protocol != null) {
ProtocolActionQueues[protocol.Identifier].Enqueue(new ConnectionChangedAction(oldStatus, conn.Status, conn));
}
break;
case PacketStage.Ready:
if (conn.Internal.AssignedProtocol != protocolId || protocol == null) {
break;
}
if (oldStatus == ConnectionStatus.Establishing) { // Update connection status
conn.Status = ConnectionStatus.Ready;
ProtocolActionQueues[protocol.Identifier].Enqueue(new ConnectionChangedAction(oldStatus, conn.Status, conn));
}
if (!(oldStatus == ConnectionStatus.Establishing || oldStatus == ConnectionStatus.Ready)) {
break; // No cheating at this table! For realsies this time!
}
conn.Internal.LatestOutwardReliable = buffer.ReadInt();
int FirstPacketId = buffer.ReadInt();
bool Reliable = buffer.ReadBool();
ConcurrentQueue<Packet> queue = PacketQueue[conn.uid];
Packet peeked;
while (queue.TryPeek(out peeked)) {
if (peeked.PacketId > conn.Internal.LatestOutwardReliable) {
break;
} else {
Packet LastRemoved;
queue.TryDequeue(out LastRemoved);
}
}
PacketQueue[conn.uid] = queue;
int PacketAmount = buffer.ReadInt();
if (Reliable) {
TrafficData.ReliablePacketsReceived += PacketAmount;
} else {
TrafficData.UnreliablePacketsreceived += PacketAmount;
}
for (int i = 0; i < PacketAmount; i++) {
Packet p = buffer.ReadPacket(protocol);
p.PacketId = FirstPacketId + i;
p.PacketIsReliable = Reliable;
if (p.PacketIsReliable) {
if (p.PacketId > conn.Internal.LatestInwardReliable) {
conn.Internal.LatestInwardReliable = p.PacketId;
ProtocolActionQueues[protocol.Identifier].Enqueue(new ReceiveAction(conn, p));
}
} else if (p.PacketId > conn.Internal.LatestInwardUnreliable) {
conn.Internal.LatestInwardUnreliable = p.PacketId;
ProtocolActionQueues[protocol.Identifier].Enqueue(new ReceiveAction(conn, p));
}
// Do some analytics!
PerPacketData OldReceivedByPacket;
TrafficData.ReceivedByPacket.TryGetValue(p.GetType(), out OldReceivedByPacket);
OldReceivedByPacket.Bytes += p.Size;
OldReceivedByPacket.Packets += 1;
TrafficData.ReceivedByPacket[p.GetType()] = OldReceivedByPacket;
}
break;
}
}
private void UpdateThreadMethod() {
try {
while (Thread.CurrentThread.IsAlive) {
long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
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);
}
if (conn.Status != ConnectionStatus.Awaiting || conn.Status != ConnectionStatus.Stopped) {
if (conn.Status == ConnectionStatus.Ready) {
SendPacketQueue(uid);
} else {
SendPlain(conn);
}
}
}
foreach (ulong uid in timedOut) {
Connection conn = Connections[uid];
ConnectionsToRemove.Enqueue(conn);
if (conn.Status == ConnectionStatus.Ready
|| conn.Status == ConnectionStatus.Establishing
|| conn.Status == ConnectionStatus.Awaiting
|| conn.Status == ConnectionStatus.Lost) {
Protocol protocol = Peer.GetProtocol(conn.Internal.AssignedProtocol);
if (protocol != null) {
conn.ClosingReason = ClosingReason.Timeout;
ProtocolActionQueues[conn.Internal.AssignedProtocol].Enqueue(new TimeoutAction(conn));
}
}
}
Thread.Sleep((int)Interval);
}
} catch (ThreadAbortException) {
Peer.MessageListener.Message("Connection Thread Stopped");
}
}
}
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;
}
}
public class ConnectionManagerTrafficData {
/// <summary>The amount of bytes sent since the last clear interval from Peer</summary>
public int BytesSent;
/// <summary>The amount of total IP messages sent since the last clear interval from Peer</summary>
public int TotalMessagesSent;
/// <summary>The amount of reliable packets sent since the last clear interval from Peer</summary>
public int ReliablePacketsSent;
/// <summary>The amount of unreliable packets sent since the last clear interval from Peer</summary>
public int UnreliablePacketsSent;
/// <summary>The amount of reliable packets received since the last clear interval from Peer</summary>
public int ReliablePacketsReceived;
/// <summary>The amount of unreliable packets received since the last clear interval from Peer</summary>
public int UnreliablePacketsreceived;
/// <summary>Data relating to outward packet specific traffic</summary>
public ConcurrentDictionary<Type, PerPacketData> SentByPacket = new ConcurrentDictionary<Type, PerPacketData>();
/// <summary>Data relating to inward packet specific traffic</summary>
public ConcurrentDictionary<Type, PerPacketData> ReceivedByPacket = new ConcurrentDictionary<Type, PerPacketData>();
public void Clear() {
BytesSent = 0;
TotalMessagesSent = 0;
ReliablePacketsSent = 0;
UnreliablePacketsSent = 0;
ReliablePacketsReceived = 0;
UnreliablePacketsreceived = 0;
}
}
public struct PerPacketData {
public int Bytes;
public int Packets;
}
}

79
Peers/ListenerThread.cs Normal file
View File

@ -0,0 +1,79 @@
using UnityEngine;
using System.Net;
using System;
using System.Net.Sockets;
using System.Threading;
using NeonTea.Quakeball.TeaNet.Packets;
namespace NeonTea.Quakeball.TeaNet.Peers {
/// <summary>Manager for the thread that listens from the given endpoint. Initiated with <see cref="Peer.StartListen(string, int)"/></summary>
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 };
/// <summary>The amount of bytes received since the last clear interval from Peer</summary>
public int BytesReceived;
/// <summary>The amount of bytes received since the last clear interval from Peer</summary>
public int MessagesReceived;
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;
}
Thread.Abort();
return true;
}
private void ListenThreadMethod() {
try {
while (Thread.CurrentThread.IsAlive) {
if (Peer.UdpClient.Available > 0) {
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.Err($"Connection lost to {LastSentConnection.Endpoint}: {e.ToString()}");
}
} else {
Peer.MessageListener.Err($"Listener error: {e.ToString()}");
}
}
if (Buffer.ReadFingerprint(Peer.Fingerprint)) {
Peer.ConnectionManager.Handle(Listened, Buffer);
BytesReceived += Buffer.Size;
MessagesReceived++;
}
}
}
} catch (ThreadAbortException) {
Peer.MessageListener.Message("Listener Thread stopped");
}
}
}
}

235
Peers/Peer.cs Normal file
View File

@ -0,0 +1,235 @@
using System.Collections.Generic;
using System.Collections.Concurrent;
using System;
using System.Net;
using System.Net.Sockets;
using NeonTea.Quakeball.TeaNet.Packets;
namespace NeonTea.Quakeball.TeaNet.Peers {
/// <summary>Main class for networking. Remember to register a protocol before using. Remember to call Update from a gameobject!</summary>
public class Peer : PeerMessageListener {
/// <summary>Underlying UdpClient. Do not touch unless you know what you are doing.</summary>
public UdpClient UdpClient { get; private set; }
/// <summary>The fingerprint for networking. Used to make sure incoming bytes are from a correct source.</summary>
public byte[] Fingerprint { get; private set; }
/// <summary>Shorthand for ConnectionManager.Timeout: The amount of milliseconds before a connection is timed out.</summary>
public long Timeout {
get {
return ConnectionManager.Timeout;
}
set {
ConnectionManager.Timeout = value;
}
}
/// <summary>Shorthand for ConnectionManager.Interval: The interval of updates and rate of re-sending reliable messages.</summary>
public long UpdateInterval {
get {
return ConnectionManager.Interval;
}
set {
ConnectionManager.Interval = value;
}
}
/// <summary>The interval of traffic analyzed before updating. By default 5000 (5 seconds)</summary>
public long TrafficDataInterval = 5000;
/// <summary>Traffic Data for this Peer</summary>
public TrafficData TrafficData { get; private set; } = new TrafficData();
/// <summary>Whether the Peer is currently doing anything or not.null</sumary>
public bool Running { get; private set; }
public ListenerThread ListenerThread;
public ConnectionManager ConnectionManager;
public Dictionary<byte, Protocol> RegisteredProtocols = new Dictionary<byte, Protocol>();
/// <summary>Listener for messages and errors from within the Peer.</summary>
public PeerMessageListener MessageListener;
private long LastTrafficData;
/// <summary>Creates a new Peer with the given fingerprint. The fingerprint can be anything, it just must be same on both peers.</summary>
public Peer(byte[] fingerprint) {
Fingerprint = fingerprint;
ConnectionManager = new ConnectionManager(this);
MessageListener = this;
LastTrafficData = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
}
/// <summary>Starts the UdpClient, but does no networking as is.</summary>
public void Start(int sending_port) {
if (Running) {
return;
}
UdpClient = new UdpClient(sending_port);
MessageListener.Message("UdpClient Started");
Running = true;
}
/// <summary>Abruptly stops the UdpClient and all relevant threads.</summary>
public void Stop() {
ConnectionManager.StopThread();
if (ListenerThread != null) {
ListenerThread.Stop();
}
UdpClient.Dispose();
UdpClient.Close();
Running = false;
}
/// <summary>Start listening to a given address and port. Usually 0.0.0.0, 0 for clients and 0.0.0.0, port for servers</summary>
public void StartListen(string address, int port) {
IPEndPoint endpoint = new IPEndPoint(FindAddress(address), port);
StartListen(endpoint);
}
private void StartListen(IPEndPoint endpoint) {
if (ListenerThread != null) {
return; // Cant listen twice
}
ListenerThread = new ListenerThread(this, endpoint);
ListenerThread.Start();
MessageListener.Message($"Started listening to {endpoint}");
}
/// <summary>Connect to a remote host. Will initialize a listener to 0.0.0.0:0 if no listener is started.</summary>
public void Connect(string address, int port, byte protocolIdent, bool startListening = true) {
if (startListening) {
IPEndPoint listenEndpoint = (IPEndPoint)UdpClient.Client.LocalEndPoint;
StartListen(listenEndpoint);
}
IPEndPoint endpoint = new IPEndPoint(FindAddress(address), port);
ConnectionManager.StartConnection(endpoint, protocolIdent);
MessageListener.Message($"Connecting to {endpoint}");
}
/// <summary>Soft-disconnects the connection with the given uid, meaning it will wait until they acknowledge, before timing out.abstract</summary>
public void Disconnect(ulong uid) {
ConnectionManager.CloseConnection(uid, ClosingReason.Manual);
}
/// <summary>Send a reliable packet, meaning it will reliably be delivered.</summary>
public void SendReliable(ulong uid, Packet packet) {
if (Running) {
ConnectionManager.AddPacketToQueue(uid, packet);
ConnectionManager.SendPacketQueue(uid);
}
}
/// <summary>Add reliable packet to queue, so that it will be sent on the next update.</summary>
public void SendReliableLater(ulong uid, Packet packet) {
if (Running) {
ConnectionManager.AddPacketToQueue(uid, packet);
}
}
/// <summary>Send an unreliable packet, meaning its delivery is not reliable.</summary>
public void SendUnreliable(ulong uid, Packet packet) {
if (Running) {
ConnectionManager.SendSingleUnreliable(uid, packet);
}
}
/// <summary>Get a Connection instance from the given uid, if such exists. Null otherwise.</summary>
public Connection GetConnection(ulong uid) {
return ConnectionManager.GetConnection(uid);
}
/// <summary>Register a given protocol. Returns protocol.Identifier if successful, 0 otherwise.</summary>
public byte RegisterProtocol(Protocol protocol) {
byte ident = protocol.Identifier;
if (RegisteredProtocols.ContainsKey(ident)) {
return 0;
}
RegisteredProtocols.Add(ident, protocol);
ConnectionManager.ProtocolActionQueues.Add(ident, new Queue<ProtocolAction>());
protocol.Peer = this;
return ident;
}
/// <summary>Get protocol instance from the given identifier, if such exists.</summary>
public Protocol GetProtocol(byte ident) {
if (RegisteredProtocols.ContainsKey(ident)) {
return RegisteredProtocols[ident];
}
return null;
}
/// <summary>Shorthand for Peer.ConnectionManager.Update(): Handles network stuff that was received since last update.</summary>
public void Update() {
ConnectionManager.Update();
long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
if (now - LastTrafficData > TrafficDataInterval) {
LastTrafficData = now;
if (ListenerThread != null) {
TrafficData.Received = ListenerThread.BytesReceived;
TrafficData.TotalMessagesReceived = ListenerThread.MessagesReceived;
ListenerThread.BytesReceived = 0;
ListenerThread.MessagesReceived = 0;
}
if (ConnectionManager != null) {
TrafficData.Sent = ConnectionManager.TrafficData.BytesSent;
TrafficData.TotalMessagesSent = ConnectionManager.TrafficData.TotalMessagesSent;
TrafficData.UnreliableReceived = ConnectionManager.TrafficData.UnreliablePacketsreceived;
TrafficData.ReliableReceived = ConnectionManager.TrafficData.ReliablePacketsReceived;
TrafficData.UnreliableSent = ConnectionManager.TrafficData.UnreliablePacketsSent;
TrafficData.ReliableSent = ConnectionManager.TrafficData.ReliablePacketsSent;
PerPacketData empty = new PerPacketData();
foreach (Type t in ConnectionManager.TrafficData.ReceivedByPacket.Keys) {
TrafficData.ReceivedByPacket[t] = ConnectionManager.TrafficData.ReceivedByPacket[t];
ConnectionManager.TrafficData.ReceivedByPacket[t] = empty;
}
foreach (Type t in ConnectionManager.TrafficData.SentByPacket.Keys) {
TrafficData.SentByPacket[t] = ConnectionManager.TrafficData.SentByPacket[t];
ConnectionManager.TrafficData.SentByPacket[t] = empty;
}
ConnectionManager.TrafficData.Clear();
}
}
}
public void Message(string msg) { }
public void Err(string msg) { }
private IPAddress FindAddress(string host) {
IPAddress addr;
try {
addr = Dns.GetHostAddresses(host)[0];
} catch (ArgumentException) {
addr = IPAddress.Parse(host);
}
return addr;
}
}
/// <summary>Listener for messages and errors from the Peer.</summary>
public interface PeerMessageListener {
void Message(string msg);
void Err(string msg);
}
public class TrafficData {
/// <summary>The amount of bytes received in the last TrafficDataIntervel</summary>
public int Received;
/// <summary>The amount of bytes sent in the last TrafficDataIntervel</summary>
public int Sent;
/// <summary>The amount of total messages received in the last TrafficDataIntervel</summary>
public int TotalMessagesReceived;
/// <summary>The amount of total messages sent in the last TrafficDataIntervel</summary>
public int TotalMessagesSent;
/// <summary>The amount of reliable messages received in the last TrafficDataIntervel</summary>
public int ReliableReceived;
/// <summary>The amount of reliable messages sent in the last TrafficDataIntervel</summary>
public int ReliableSent;
/// <summary>The amount of unreliable messages received in the last TrafficDataIntervel</summary>
public int UnreliableReceived;
/// <summary>The amount of unreliable messages sent in the last TrafficDataIntervel</summary>
public int UnreliableSent;
/// <summary>The amount of bytes sent in the last TrafficDataIntervel by Packet</summary>
public Dictionary<Type, PerPacketData> SentByPacket = new Dictionary<Type, PerPacketData>();
/// <summary>The amount of bytes received in the last TrafficDataIntervel by Packet</summary>
public Dictionary<Type, PerPacketData> ReceivedByPacket = new Dictionary<Type, PerPacketData>();
}
}

13
README.md Normal file
View File

@ -0,0 +1,13 @@
# TeaNet
TeaNet is a networking library written in **C#** using .NET Core 3.1 System.Net.Sockets
TeaNet works with a very low-level networking API to provide a highly performant networking library
that is primarily meant for use in games, but can be used for anything else as well.
TeaNet was originally written in [Unity][unity] for a game project called [quakeball][quakeball] by [neontea][neontea],
and while the entire TeaNet codebase is written by me, it's commit history can be found in quakeball's repository.
[unity]: https://unity.com/
[quakeball]: https://git.teascade.net/neontea/quakeball/
[neontea]: https://neontea.itch.io/