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 allClients { get; private set; } = new List(); 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 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() 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 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 GetTotalConnectionStats() { var dictionary = new Dictionary(); 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(); 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 } } }