quantum-space-buddies/QuantumUNET/QNetworkClient.cs
2021-12-02 19:19:07 -08:00

615 lines
15 KiB
C#

using QuantumUNET.Logging;
using QuantumUNET.Messages;
using QuantumUNET.Transport;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using UnityEngine;
using UnityEngine.Networking;
namespace QuantumUNET
{
public class QNetworkClient
{
public QNetworkClient()
{
m_MsgBuffer = new byte[65535];
m_MsgReader = new QNetworkReader(m_MsgBuffer);
AddClient(this);
}
public QNetworkClient(QNetworkConnection conn)
{
m_MsgBuffer = new byte[65535];
m_MsgReader = new QNetworkReader(m_MsgBuffer);
AddClient(this);
SetActive(true);
m_Connection = conn;
m_AsyncConnect = ConnectState.Connected;
conn.SetHandlers(m_MessageHandlers);
RegisterSystemHandlers(false);
}
public static List<QNetworkClient> allClients { get; private set; } = new List<QNetworkClient>();
public static bool active { get; private set; }
internal void SetHandlers(QNetworkConnection conn) => conn.SetHandlers(m_MessageHandlers);
public string serverIp { get; private set; } = "";
public int serverPort { get; private set; }
public QNetworkConnection connection => m_Connection;
internal int hostId { get; private set; } = -1;
public Dictionary<short, QNetworkMessageDelegate> handlers => m_MessageHandlers.GetHandlers();
public int numChannels => hostTopology.DefaultConfig.ChannelCount;
public HostTopology hostTopology { get; private set; }
private const int k_MaxEventsPerFrame = 500;
private int m_HostPort;
private int m_ClientConnectionId = -1;
private int m_StatResetTime;
private static readonly QCRCMessage s_CRCMessage = new();
private readonly QNetworkMessageHandlers m_MessageHandlers = new();
protected QNetworkConnection m_Connection;
private readonly byte[] m_MsgBuffer;
private readonly QNetworkReader m_MsgReader;
protected ConnectState m_AsyncConnect = ConnectState.None;
private string m_RequestedServerHost = "";
public int hostPort
{
get => m_HostPort;
set
{
if (value < 0)
{
throw new ArgumentException("Port must not be a negative number.");
}
if (value > 65535)
{
throw new ArgumentException("Port must not be greater than 65535.");
}
m_HostPort = value;
}
}
public bool isConnected => m_AsyncConnect == ConnectState.Connected;
public Type networkConnectionClass { get; private set; } = typeof(QNetworkConnection);
public void SetNetworkConnectionClass<T>() where T : QNetworkConnection => networkConnectionClass = typeof(T);
public bool Configure(ConnectionConfig config, int maxConnections)
{
var topology = new HostTopology(config, maxConnections);
return Configure(topology);
}
public bool Configure(HostTopology topology)
{
hostTopology = topology;
return true;
}
private static bool IsValidIpV6(string address) =>
address.All(c => c is ':' or (>= '0' and <= '9') or (>= 'a' and <= 'f') or (>= 'A' and <= 'F'));
public void Connect(string serverIp, int serverPort)
{
PrepareForConnect();
this.serverPort = serverPort;
if (Application.platform == RuntimePlatform.WebGLPlayer)
{
this.serverIp = serverIp;
m_AsyncConnect = ConnectState.Resolved;
}
else if (serverIp.Equals("127.0.0.1") || serverIp.Equals("localhost"))
{
this.serverIp = "127.0.0.1";
m_AsyncConnect = ConnectState.Resolved;
}
else if (serverIp.IndexOf(":") != -1 && IsValidIpV6(serverIp))
{
this.serverIp = serverIp;
m_AsyncConnect = ConnectState.Resolved;
}
else
{
QLog.Log($"Async DNS START:{serverIp}");
m_RequestedServerHost = serverIp;
m_AsyncConnect = ConnectState.Resolving;
Dns.BeginGetHostAddresses(serverIp, GetHostAddressesCallback, this);
}
}
private void PrepareForConnect()
{
SetActive(true);
RegisterSystemHandlers(false);
if (hostTopology == null)
{
var connectionConfig = new ConnectionConfig();
connectionConfig.AddChannel(QosType.ReliableSequenced);
connectionConfig.AddChannel(QosType.Unreliable);
connectionConfig.UsePlatformSpecificProtocols = false;
hostTopology = new HostTopology(connectionConfig, 8);
}
hostId = NetworkTransport.AddHost(hostTopology, m_HostPort);
}
internal static void GetHostAddressesCallback(IAsyncResult ar)
{
try
{
var array = Dns.EndGetHostAddresses(ar);
var networkClient = (QNetworkClient)ar.AsyncState;
if (array.Length == 0)
{
QLog.Error($"DNS lookup failed for:{networkClient.m_RequestedServerHost}");
networkClient.m_AsyncConnect = ConnectState.Failed;
}
else
{
networkClient.serverIp = array[0].ToString();
networkClient.m_AsyncConnect = ConnectState.Resolved;
QLog.Log(
$"Async DNS Result:{networkClient.serverIp} for {networkClient.m_RequestedServerHost}: {networkClient.serverIp}");
}
}
catch (SocketException ex)
{
var networkClient2 = (QNetworkClient)ar.AsyncState;
QLog.Error($"DNS resolution failed: {ex.GetErrorCode()}");
QLog.Error($"Exception:{ex}");
networkClient2.m_AsyncConnect = ConnectState.Failed;
}
}
internal void ContinueConnect()
{
m_ClientConnectionId = NetworkTransport.Connect(hostId, serverIp, serverPort, 0, out var b);
m_Connection = (QNetworkConnection)Activator.CreateInstance(networkConnectionClass);
m_Connection.SetHandlers(m_MessageHandlers);
m_Connection.Initialize(serverIp, hostId, m_ClientConnectionId, hostTopology);
}
public virtual void Disconnect()
{
m_AsyncConnect = ConnectState.Disconnected;
QClientScene.HandleClientDisconnect(m_Connection);
if (m_Connection != null)
{
m_Connection.Disconnect();
m_Connection.Dispose();
m_Connection = null;
if (hostId != -1)
{
NetworkTransport.RemoveHost(hostId);
hostId = -1;
}
}
}
public bool Send(short msgType, QMessageBase msg)
{
bool result;
if (m_Connection != null)
{
if (m_AsyncConnect != ConnectState.Connected)
{
QLog.Error("NetworkClient Send when not connected to a server");
result = false;
}
else
{
result = m_Connection.Send(msgType, msg);
}
}
else
{
QLog.Error("NetworkClient Send with no connection");
result = false;
}
return result;
}
public bool SendWriter(QNetworkWriter writer, int channelId)
{
bool result;
if (m_Connection != null)
{
if (m_AsyncConnect != ConnectState.Connected)
{
QLog.Error("NetworkClient SendWriter when not connected to a server");
result = false;
}
else
{
result = m_Connection.SendWriter(writer, channelId);
}
}
else
{
QLog.Error("NetworkClient SendWriter with no connection");
result = false;
}
return result;
}
public bool SendBytes(byte[] data, int numBytes, int channelId)
{
bool result;
if (m_Connection != null)
{
if (m_AsyncConnect != ConnectState.Connected)
{
QLog.Error("NetworkClient SendBytes when not connected to a server");
result = false;
}
else
{
result = m_Connection.SendBytes(data, numBytes, channelId);
}
}
else
{
QLog.Error("NetworkClient SendBytes with no connection");
result = false;
}
return result;
}
public bool SendUnreliable(short msgType, QMessageBase msg)
{
bool result;
if (m_Connection != null)
{
if (m_AsyncConnect != ConnectState.Connected)
{
QLog.Error("NetworkClient SendUnreliable when not connected to a server");
result = false;
}
else
{
result = m_Connection.SendUnreliable(msgType, msg);
}
}
else
{
QLog.Error("NetworkClient SendUnreliable with no connection");
result = false;
}
return result;
}
public bool SendByChannel(short msgType, QMessageBase msg, int channelId)
{
bool result;
if (m_Connection != null)
{
if (m_AsyncConnect != ConnectState.Connected)
{
QLog.Error("NetworkClient SendByChannel when not connected to a server");
result = false;
}
else
{
result = m_Connection.SendByChannel(msgType, msg, channelId);
}
}
else
{
QLog.Error("NetworkClient SendByChannel with no connection");
result = false;
}
return result;
}
public void SetMaxDelay(float seconds)
{
if (m_Connection == null)
{
QLog.Warning("SetMaxDelay failed, not connected.");
}
else
{
m_Connection.SetMaxDelay(seconds);
}
}
public void Shutdown()
{
QLog.Log($"Shutting down client {hostId}");
if (hostId != -1)
{
NetworkTransport.RemoveHost(hostId);
hostId = -1;
}
RemoveClient(this);
if (allClients.Count == 0)
{
SetActive(false);
}
}
internal virtual void Update()
{
if (hostId != -1)
{
switch (m_AsyncConnect)
{
case ConnectState.None:
case ConnectState.Resolving:
case ConnectState.Disconnected:
return;
case ConnectState.Resolved:
m_AsyncConnect = ConnectState.Connecting;
ContinueConnect();
return;
case ConnectState.Failed:
GenerateConnectError(11);
m_AsyncConnect = ConnectState.Disconnected;
return;
}
if (m_Connection != null)
{
if ((int)Time.time != m_StatResetTime)
{
m_Connection.ResetStats();
m_StatResetTime = (int)Time.time;
}
}
var num = 0;
byte b;
for (; ; )
{
var networkEventType = NetworkTransport.ReceiveFromHost(hostId, out var num2, out var channelId, m_MsgBuffer, (ushort)m_MsgBuffer.Length, out var numBytes, out b);
if (m_Connection != null)
{
m_Connection.LastError = (NetworkError)b;
}
switch (networkEventType)
{
case NetworkEventType.DataEvent:
if (b != 0)
{
goto Block_11;
}
m_MsgReader.SeekZero();
m_Connection.TransportReceive(m_MsgBuffer, numBytes, channelId);
break;
case NetworkEventType.ConnectEvent:
QLog.Log("Client connected");
if (b != 0)
{
goto Block_10;
}
m_AsyncConnect = ConnectState.Connected;
m_Connection.InvokeHandlerNoData(QMsgType.Connect);
break;
case NetworkEventType.DisconnectEvent:
QLog.Log("Client disconnected");
m_AsyncConnect = ConnectState.Disconnected;
if (b != 0)
{
if (b != 6)
{
GenerateDisconnectError(b);
}
}
QClientScene.HandleClientDisconnect(m_Connection);
m_Connection?.InvokeHandlerNoData(QMsgType.Disconnect);
break;
case NetworkEventType.Nothing:
break;
default:
QLog.Error($"Unknown network message type received: {networkEventType}");
break;
}
if (++num >= 500)
{
goto Block_17;
}
if (hostId == -1)
{
goto Block_19;
}
if (networkEventType == NetworkEventType.Nothing)
{
goto IL_2C6;
}
}
Block_10:
GenerateConnectError(b);
return;
Block_11:
GenerateDataError(b);
return;
Block_17:
QLog.Log($"MaxEventsPerFrame hit ({500})");
Block_19:
IL_2C6:
if (m_Connection != null && m_AsyncConnect == ConnectState.Connected)
{
m_Connection.FlushChannels();
}
}
}
private void GenerateConnectError(int error)
{
QLog.Error($"UNet Client Error Connect Error: {error}");
GenerateError(error);
}
private void GenerateDataError(int error)
{
QLog.Error($"UNet Client Data Error: {(NetworkError)error}");
GenerateError(error);
}
private void GenerateDisconnectError(int error)
{
QLog.Error($"UNet Client Disconnect Error: {(NetworkError)error}");
GenerateError(error);
}
private void GenerateError(int error)
{
var handler = m_MessageHandlers.GetHandler(QMsgType.Error)
?? m_MessageHandlers.GetHandler(QMsgType.Error);
if (handler != null)
{
var errorMessage = new QErrorMessage
{
errorCode = error
};
var buffer = new byte[200];
var writer = new QNetworkWriter(buffer);
errorMessage.Serialize(writer);
var reader = new QNetworkReader(buffer);
handler(new QNetworkMessage
{
MsgType = QMsgType.Error,
Reader = reader,
Connection = m_Connection,
ChannelId = 0
});
}
}
public Dictionary<short, QNetworkConnection.PacketStat> GetConnectionStats() =>
m_Connection?.PacketStats;
public void ResetConnectionStats() => m_Connection?.ResetStats();
public int GetRTT() =>
hostId == -1 ? 0 : NetworkTransport.GetCurrentRTT(hostId, m_ClientConnectionId, out var b);
internal void RegisterSystemHandlers(bool localClient)
{
QClientScene.RegisterSystemHandlers(this, localClient);
RegisterHandlerSafe(14, OnCRC);
}
private void OnCRC(QNetworkMessage netMsg)
{
netMsg.ReadMessage(s_CRCMessage);
QNetworkCRC.Validate(s_CRCMessage.scripts, numChannels);
}
public void RegisterHandler(short msgType, QNetworkMessageDelegate handler) => m_MessageHandlers.RegisterHandler(msgType, handler);
public void RegisterHandlerSafe(short msgType, QNetworkMessageDelegate handler) => m_MessageHandlers.RegisterHandlerSafe(msgType, handler);
public void UnregisterHandler(short msgType) => m_MessageHandlers.UnregisterHandler(msgType);
public static Dictionary<short, QNetworkConnection.PacketStat> GetTotalConnectionStats()
{
var dictionary = new Dictionary<short, QNetworkConnection.PacketStat>();
foreach (var networkClient in allClients)
{
var connectionStats = networkClient.GetConnectionStats();
foreach (var key in connectionStats.Keys)
{
if (dictionary.ContainsKey(key))
{
var packetStat = dictionary[key];
packetStat.count += connectionStats[key].count;
packetStat.bytes += connectionStats[key].bytes;
dictionary[key] = packetStat;
}
else
{
dictionary[key] = new QNetworkConnection.PacketStat(connectionStats[key]);
}
}
}
return dictionary;
}
internal static void AddClient(QNetworkClient client) => allClients.Add(client);
internal static bool RemoveClient(QNetworkClient client) => allClients.Remove(client);
internal static void UpdateClients()
{
for (var i = 0; i < allClients.Count; i++)
{
if (allClients[i] != null)
{
allClients[i].Update();
}
else
{
allClients.RemoveAt(i);
}
}
}
public static void ShutdownAll()
{
while (allClients.Count != 0)
{
allClients[0].Shutdown();
}
allClients = new List<QNetworkClient>();
active = false;
QClientScene.Shutdown();
}
internal static void SetActive(bool state)
{
if (!active && state)
{
NetworkTransport.Init();
}
active = state;
}
protected enum ConnectState
{
None,
Resolving,
Resolved,
Connecting,
Connected,
Disconnected,
Failed
}
}
}