using System; using System.Collections.Generic; using UnityEngine; namespace Mirror { public enum ConnectState { None, Connecting, Connected, Disconnected } /// /// This is a network client class used by the networking system. It contains a NetworkConnection that is used to connect to a network server. /// The NetworkClient handle connection state, messages handlers, and connection configuration. There can be many NetworkClient instances in a process at a time, but only one that is connected to a game server (NetworkServer) that uses spawned objects. /// NetworkClient has an internal update function where it handles events from the transport layer. This includes asynchronous connect events, disconnect events and incoming data from a server. /// The NetworkManager has a NetworkClient instance that it uses for games that it starts, but the NetworkClient may be used by itself. /// public static class NetworkClient { static readonly ILogger logger = LogFactory.GetLogger(typeof(NetworkClient)); /// /// The registered network message handlers. /// static readonly Dictionary handlers = new Dictionary(); /// /// The NetworkConnection object this client is using. /// public static NetworkConnection connection { get; internal set; } internal static ConnectState connectState = ConnectState.None; /// /// The IP address of the server that this client is connected to. /// This will be empty if the client has not connected yet. /// public static string serverIp => connection.address; /// /// active is true while a client is connecting/connected /// (= while the network is active) /// public static bool active => connectState == ConnectState.Connecting || connectState == ConnectState.Connected; /// /// This gives the current connection status of the client. /// public static bool isConnected => connectState == ConnectState.Connected; /// /// NetworkClient can connect to local server in host mode too /// public static bool isLocalClient => connection is ULocalConnectionToServer; /// /// Connect client to a NetworkServer instance. /// /// public static void Connect(string address) { if (logger.LogEnabled()) logger.Log("Client Connect: " + address); logger.Assert(Transport.activeTransport != null, "There was no active transport when calling NetworkClient.Connect, If you are calling Connect manually then make sure to set 'Transport.activeTransport' first"); RegisterSystemHandlers(false); Transport.activeTransport.enabled = true; InitializeTransportHandlers(); connectState = ConnectState.Connecting; Transport.activeTransport.ClientConnect(address); // setup all the handlers connection = new NetworkConnectionToServer(); connection.SetHandlers(handlers); } /// /// Connect client to a NetworkServer instance. /// /// Address of the server to connect to public static void Connect(Uri uri) { if (logger.LogEnabled()) logger.Log("Client Connect: " + uri); logger.Assert(Transport.activeTransport != null, "There was no active transport when calling NetworkClient.Connect, If you are calling Connect manually then make sure to set 'Transport.activeTransport' first"); RegisterSystemHandlers(false); Transport.activeTransport.enabled = true; InitializeTransportHandlers(); connectState = ConnectState.Connecting; Transport.activeTransport.ClientConnect(uri); // setup all the handlers connection = new NetworkConnectionToServer(); connection.SetHandlers(handlers); } public static void ConnectHost() { logger.Log("Client Connect Host to Server"); RegisterSystemHandlers(true); connectState = ConnectState.Connected; // create local connection objects and connect them ULocalConnectionToServer connectionToServer = new ULocalConnectionToServer(); ULocalConnectionToClient connectionToClient = new ULocalConnectionToClient(); connectionToServer.connectionToClient = connectionToClient; connectionToClient.connectionToServer = connectionToServer; connection = connectionToServer; connection.SetHandlers(handlers); // create server connection to local client NetworkServer.SetLocalConnection(connectionToClient); } /// /// connect host mode /// public static void ConnectLocalServer() { NetworkServer.OnConnected(NetworkServer.localConnection); NetworkServer.localConnection.Send(new ConnectMessage()); } /// /// disconnect host mode. this is needed to call DisconnectMessage for /// the host client too. /// public static void DisconnectLocalServer() { // only if host connection is running if (NetworkServer.localConnection != null) { // TODO ConnectLocalServer manually sends a ConnectMessage to the // local connection. should we send a DisconnectMessage here too? // (if we do then we get an Unknown Message ID log) //NetworkServer.localConnection.Send(new DisconnectMessage()); NetworkServer.OnDisconnected(NetworkServer.localConnection.connectionId); } } static void InitializeTransportHandlers() { Transport.activeTransport.OnClientConnected.AddListener(OnConnected); Transport.activeTransport.OnClientDataReceived.AddListener(OnDataReceived); Transport.activeTransport.OnClientDisconnected.AddListener(OnDisconnected); Transport.activeTransport.OnClientError.AddListener(OnError); } static void OnError(Exception exception) { logger.LogException(exception); } static void OnDisconnected() { connectState = ConnectState.Disconnected; ClientScene.HandleClientDisconnect(connection); connection?.InvokeHandler(new DisconnectMessage(), -1); } internal static void OnDataReceived(ArraySegment data, int channelId) { if (connection != null) { connection.TransportReceive(data, channelId); } else logger.LogError("Skipped Data message handling because connection is null."); } static void OnConnected() { if (connection != null) { // reset network time stats NetworkTime.Reset(); // the handler may want to send messages to the client // thus we should set the connected state before calling the handler connectState = ConnectState.Connected; NetworkTime.UpdateClient(); connection.InvokeHandler(new ConnectMessage(), -1); } else logger.LogError("Skipped Connect message handling because connection is null."); } /// /// Disconnect from server. /// The disconnect message will be invoked. /// public static void Disconnect() { connectState = ConnectState.Disconnected; ClientScene.HandleClientDisconnect(connection); // local or remote connection? if (isLocalClient) { if (isConnected) { NetworkServer.localConnection.Send(new DisconnectMessage()); } NetworkServer.RemoveLocalConnection(); } else { if (connection != null) { connection.Disconnect(); connection = null; RemoveTransportHandlers(); } } } static void RemoveTransportHandlers() { // so that we don't register them more than once Transport.activeTransport.OnClientConnected.RemoveListener(OnConnected); Transport.activeTransport.OnClientDataReceived.RemoveListener(OnDataReceived); Transport.activeTransport.OnClientDisconnected.RemoveListener(OnDisconnected); Transport.activeTransport.OnClientError.RemoveListener(OnError); } /// /// This sends a network message with a message Id to the server. This message is sent on channel zero, which by default is the reliable channel. /// The message must be an instance of a class derived from MessageBase. /// The message id passed to Send() is used to identify the handler function to invoke on the server when the message is received. /// /// The message type to unregister. /// /// public static void Send(T message, int channelId = Channels.DefaultReliable) where T : NetworkMessage { if (connection != null) { if (connectState == ConnectState.Connected) { connection.Send(message, channelId); } else logger.LogError("NetworkClient Send when not connected to a server"); } else logger.LogError("NetworkClient Send with no connection"); } public static void Update() { // local connection? if (connection is ULocalConnectionToServer localConnection) { localConnection.Update(); } // remote connection? else { // only update things while connected if (active && connectState == ConnectState.Connected) { NetworkTime.UpdateClient(); } } } internal static void RegisterSystemHandlers(bool hostMode) { // host mode client / regular client react to some messages differently. // but we still need to add handlers for all of them to avoid // 'message id not found' errors. if (hostMode) { RegisterHandler(ClientScene.OnHostClientObjectDestroy); RegisterHandler(ClientScene.OnHostClientObjectHide); RegisterHandler((conn, msg) => { }, false); RegisterHandler(ClientScene.OnHostClientSpawn); // host mode doesn't need spawning RegisterHandler((conn, msg) => { }); // host mode doesn't need spawning RegisterHandler((conn, msg) => { }); RegisterHandler((conn, msg) => { }); } else { RegisterHandler(ClientScene.OnObjectDestroy); RegisterHandler(ClientScene.OnObjectHide); RegisterHandler(NetworkTime.OnClientPong, false); RegisterHandler(ClientScene.OnSpawn); RegisterHandler(ClientScene.OnObjectSpawnStarted); RegisterHandler(ClientScene.OnObjectSpawnFinished); RegisterHandler(ClientScene.OnUpdateVarsMessage); } RegisterHandler(ClientScene.OnRPCMessage); } /// /// Register a handler for a particular message type. /// There are several system message types which you can add handlers for. You can also add your own message types. /// /// Message type /// Function handler which will be invoked when this message type is received. /// True if the message requires an authenticated connection public static void RegisterHandler(Action handler, bool requireAuthentication = true) where T : NetworkMessage { int msgType = MessagePacker.GetId(); if (handlers.ContainsKey(msgType)) { logger.LogWarning($"NetworkClient.RegisterHandler replacing handler for {typeof(T).FullName}, id={msgType}. If replacement is intentional, use ReplaceHandler instead to avoid this warning."); } handlers[msgType] = MessagePacker.MessageHandler(handler, requireAuthentication); } /// /// Register a handler for a particular message type. /// There are several system message types which you can add handlers for. You can also add your own message types. /// /// Message type /// Function handler which will be invoked when this message type is received. /// True if the message requires an authenticated connection public static void RegisterHandler(Action handler, bool requireAuthentication = true) where T : NetworkMessage { RegisterHandler((NetworkConnection _, T value) => { handler(value); }, requireAuthentication); } /// /// Replaces a handler for a particular message type. /// See also RegisterHandler(T)(Action(NetworkConnection, T), bool) /// /// Message type /// Function handler which will be invoked when this message type is received. /// True if the message requires an authenticated connection public static void ReplaceHandler(Action handler, bool requireAuthentication = true) where T : NetworkMessage { int msgType = MessagePacker.GetId(); handlers[msgType] = MessagePacker.MessageHandler(handler, requireAuthentication); } /// /// Replaces a handler for a particular message type. /// See also RegisterHandler(T)(Action(NetworkConnection, T), bool) /// /// Message type /// Function handler which will be invoked when this message type is received. /// True if the message requires an authenticated connection public static void ReplaceHandler(Action handler, bool requireAuthentication = true) where T : NetworkMessage { ReplaceHandler((NetworkConnection _, T value) => { handler(value); }, requireAuthentication); } /// /// Unregisters a network message handler. /// /// The message type to unregister. public static bool UnregisterHandler() where T : NetworkMessage { // use int to minimize collisions int msgType = MessagePacker.GetId(); return handlers.Remove(msgType); } /// /// Shut down a client. /// This should be done when a client is no longer going to be used. /// public static void Shutdown() { logger.Log("Shutting down client."); ClientScene.Shutdown(); connectState = ConnectState.None; handlers.Clear(); // disconnect the client connection. // we do NOT call Transport.Shutdown, because someone only called // NetworkClient.Shutdown. we can't assume that the server is // supposed to be shut down too! Transport.activeTransport.ClientDisconnect(); } } }