#if UNITY_EDITOR || DEVELOPMENT_BUILD #define DEVELOPMENT #endif using FishNet.Connection; using FishNet.Managing.Timing; using FishNet.Object; using FishNet.Serializing; using FishNet.Transporting; using FishNet.Transporting.Multipass; using System; using System.Collections.Generic; using GameKit.Dependencies.Utilities; using UnityEngine; namespace FishNet.Managing.Transporting { /// /// Communicates with the Transport to send and receive data. /// [DisallowMultipleComponent] [AddComponentMenu("FishNet/Manager/TransportManager")] public sealed partial class TransportManager : MonoBehaviour { #region Types. private struct DisconnectingClient { public uint Tick; public NetworkConnection Connection; public DisconnectingClient(uint tick, NetworkConnection connection) { Tick = tick; Connection = connection; } } #endregion #region Public. /// /// Returns if an IntermediateLayer is in use. /// public bool HasIntermediateLayer => (_intermediateLayer != null); /// /// Called before IterateOutgoing has started. /// internal event Action OnIterateOutgoingStart; /// /// Called after IterateOutgoing has completed. /// internal event Action OnIterateOutgoingEnd; /// /// Called before IterateIncoming has started. True for on server, false for on client. /// internal event Action OnIterateIncomingStart; /// /// Called after IterateIncoming has completed. True for on server, false for on client. /// internal event Action OnIterateIncomingEnd; /// /// The current Transport being used. /// [Tooltip("The current Transport being used.")] public Transport Transport; #endregion #region Serialized. /// /// Layer used to modify data before it is sent or received. /// [Tooltip("Layer used to modify data before it is sent or received.")] [SerializeField] private IntermediateLayer _intermediateLayer; /// /// /// [Tooltip("Latency simulation settings.")] [SerializeField] private LatencySimulator _latencySimulator = new(); /// /// Latency simulation settings. /// public LatencySimulator LatencySimulator { get { //Shouldn't ever be null unless the user nullifies it. if (_latencySimulator == null) _latencySimulator = new(); return _latencySimulator; } } #endregion #region Private. /// /// NetworkConnections on the server which have to send data to clients. /// private List _dirtyToClients = new(); /// /// PacketBundles to send to the server. /// private List _toServerBundles = new(); /// /// NetworkManager handling this TransportManager. /// private NetworkManager _networkManager; /// /// Clients which are pending disconnects. /// private List _disconnectingClients = new(); /// /// Lowest MTU of all transports for channels. /// private int[] _lowestMtus; /// /// Lowest MTU of all transports of all channels. /// private int _lowestMtu = 0; /// /// Custom amount to reserve on the MTU. /// private int _customMtuReserve = MINIMUM_MTU_RESERVE; #endregion #region Consts. /// /// Number of bytes sent for PacketId. /// public const byte PACKETID_LENGTH = 2; /// /// Number of bytes sent for ObjectId. /// public const byte OBJECT_ID_LENGTH = 2; /// /// Number of bytes sent for ComponentIndex. /// public const byte COMPONENT_INDEX_LENGTH = 1; /// /// Number of bytes sent for Tick. /// public const byte UNPACKED_TICK_LENGTH = 4; /// /// Number of bytes sent for an unpacked size, such as a collection or array size. /// public const byte UNPACKED_SIZE_LENGTH = 4; /// /// Number of bytes sent to indicate split count. /// private const byte SPLIT_COUNT_LENGTH = 4; /// /// Number of bytes required for split data. /// //todo: This shouldn't have to include TickBytes but there is a parse error if it's not included. Figure out why. public const byte SPLIT_INDICATOR_LENGTH = (UNPACKED_TICK_LENGTH + PACKETID_LENGTH + SPLIT_COUNT_LENGTH); /// /// Number of channels supported. /// public const byte CHANNEL_COUNT = 2; /// /// MTU reserved for internal use. /// 1 byte is used to specify channel in packets for transports that do not include channel within their packet header. This is transport dependent. /// public const int MINIMUM_MTU_RESERVE = 1; /// /// Value to use when a MTU could not be found. /// public const int INVALID_MTU = -1; #endregion /// /// Initializes this script for use. /// internal void InitializeOnce_Internal(NetworkManager manager) { _networkManager = manager; TryAddDefaultTransport(); Transport.Initialize(_networkManager, 0); SetLowestMTUs(); InitializeToServerBundles(); manager.ServerManager.OnServerConnectionState += ServerManager_OnServerConnectionState; manager.ClientManager.OnClientConnectionState += ClientManager_OnClientConnectionState; if (_intermediateLayer != null) _intermediateLayer.InitializeOnce(this); #if DEVELOPMENT _latencySimulator.Initialize(manager, Transport); #endif } /// /// Sets the lowest MTU values. /// private void SetLowestMTUs() { //Already set. if (_lowestMtu != 0) return; /* At least one transport is required. * Try to add default. If a transport is already * specified the add method will just exit early. */ TryAddDefaultTransport(); int allLowest = int.MaxValue; //Cache lowest Mtus. _lowestMtus = new int[CHANNEL_COUNT]; for (byte i = 0; i < CHANNEL_COUNT; i++) { int channelLowest = int.MaxValue; if (Transport is Multipass mp) { foreach (Transport t in mp.Transports) { int mtu = t.GetMTU(i); if (mtu != INVALID_MTU) channelLowest = Mathf.Min(channelLowest, mtu); } } else { channelLowest = Transport.GetMTU(i); } _lowestMtus[i] = channelLowest; _lowestMtu = Mathf.Min(allLowest, channelLowest); } } /// /// Adds the default transport if a transport is not yet specified. /// private void TryAddDefaultTransport() { if (Transport == null && !gameObject.TryGetComponent(out Transport)) Transport = gameObject.AddComponent(); } /// /// Called when the local connection state changes for the client. /// private void ClientManager_OnClientConnectionState(ClientConnectionStateArgs obj) { //Not stopped. if (obj.ConnectionState != LocalConnectionState.Stopped) return; //Reset toServer data. foreach (PacketBundle pb in _toServerBundles) pb.Reset(resetSendLast: true); } /// /// Called when the local connection state changes for the server. /// private void ServerManager_OnServerConnectionState(ServerConnectionStateArgs obj) { //Not stopped. if (obj.ConnectionState != LocalConnectionState.Stopped) return; //If no server is started just clear all dirtyToClients. if (!_networkManager.ServerManager.IsAnyServerStarted()) { _dirtyToClients.Clear(); return; } //Only one server is stopped, remove connections for that server. int index = obj.TransportIndex; List clientsForIndex = CollectionCaches.RetrieveList(); foreach (NetworkConnection conn in _dirtyToClients) { if (conn.TransportIndex == index) clientsForIndex.Add(conn); } foreach (NetworkConnection conn in clientsForIndex) _dirtyToClients.Remove(conn); CollectionCaches.Store(clientsForIndex); } ///// ///// Gets port for the first transport, or client transport if using Multipass. ///// //private ushort GetPort(bool asServer) //{ // if (Transport is Multipass mp) // { // if (asServer) // return mp.Transports[0].GetPort(); // else // return mp.ClientTransport.GetPort(); // } // else // { // return Transport.GetPort(); // } //} ///// ///// Stops the local server or client connection. ///// //internal bool StopConnection(bool asServer) //{ // return Transport.StopConnection(asServer); //} ///// ///// Starts the local server or client connection. ///// //internal bool StartConnection(bool asServer) //{ // return Transport.StartConnection(asServer); //} ///// ///// Starts the local server or client connection. ///// //internal bool StartConnection(string address, bool asServer) //{ // return StartConnection(address, GetPort(asServer), asServer); //} ///// ///// Starts the local server or client connection on the first transport or ClientTransport if using Multipass and as client. ///// //internal bool StartConnection(string address, ushort port, bool asServer) //{ // Transport t; // if (Transport is Multipass mp) // { // if (asServer) // t = mp.Transports[0]; // else // t = mp.ClientTransport; // } // else // { // t = Transport; // } // /* SetServerBindAddress must be called explictly. Only // * set address if for client. */ // if (!asServer) // t.SetClientAddress(address); // t.SetPort(port); // return t.StartConnection(asServer); //} /// /// Sets a connection from server to client dirty. /// /// internal void ServerDirty(NetworkConnection conn) { _dirtyToClients.Add(conn); } /// /// Initializes ToServerBundles for use. /// private void InitializeToServerBundles() { /* For ease of use FishNet will always have * only two channels, reliable and unreliable. * Even if the transport only supports reliable * also setup for unreliable. */ for (byte i = 0; i < CHANNEL_COUNT; i++) { int mtu = GetLowestMTU(i); _toServerBundles.Add(new(_networkManager, mtu)); } } #region GetMTU. /// /// Returns MTU excluding reserve amount. /// private int GetMTUWithReserve(int mtu) { int value = (mtu - MINIMUM_MTU_RESERVE - _customMtuReserve); /* If MTU is extremely low then warn user. * The number choosen has no significant value. */ if (value <= 100) { string msg = $"Available MTU of {mtu} is significantly low; an invalid MTU will be returned. Check transport settings, or reduce MTU reserve if you set one using {nameof(TransportManager.SetMTUReserve)}"; _networkManager.LogWarning(msg); return INVALID_MTU; } return value; } /// /// Sets a custom value to reserve for the internal buffers. /// This value is also deducted from transport MTU when using GetMTU methods. /// /// Value to use. public void SetMTUReserve(int value) { if (_networkManager != null && _networkManager.IsClientStarted || _networkManager.IsServerStarted) { _networkManager.LogError($"A custom MTU reserve cannot be set after the server or client have been started or connected."); return; } if (value < MINIMUM_MTU_RESERVE) { _networkManager.Log($"MTU reserve {value} is below minimum value of {MINIMUM_MTU_RESERVE}. Value has been updated to {MINIMUM_MTU_RESERVE}."); value = MINIMUM_MTU_RESERVE; } _customMtuReserve = value; InitializeToServerBundles(); } /// /// Returns the current MTU reserve. /// /// public int GetMTUReserve() => _customMtuReserve; /// /// Returns the lowest MTU of all channels. When using multipass this will evaluate all transports within Multipass. /// /// /// public int GetLowestMTU() { SetLowestMTUs(); return GetMTUWithReserve(_lowestMtu); } /// /// Returns the lowest MTU for a channel. When using multipass this will evaluate all transports within Multipass. /// /// /// public int GetLowestMTU(byte channel) { SetLowestMTUs(); return GetMTUWithReserve(_lowestMtus[channel]); } /// /// Gets MTU on the current transport for channel. /// /// Channel to get MTU of. /// public int GetMTU(byte channel) { SetLowestMTUs(); int mtu = Transport.GetMTU(channel); if (mtu == INVALID_MTU) return mtu; return GetMTUWithReserve(mtu); } /// /// Gets MTU on the transportIndex for channel. This requires use of Multipass. /// /// Index of the transport to get the MTU on. /// Channel to get MTU of. /// public int GetMTU(int transportIndex, byte channel) { if (Transport is Multipass mp) { int mtu = mp.GetMTU(channel, transportIndex); if (mtu == INVALID_MTU) return INVALID_MTU; return GetMTUWithReserve(mtu); } //Using first/only transport. else if (transportIndex == 0) { return GetMTU(channel); } //Unhandled. else { _networkManager.LogWarning($"MTU cannot be returned with transportIndex because {typeof(Multipass).Name} is not in use."); return -1; } } /// /// Gets MTU on the transport type for channel. This requires use of Multipass. /// /// Tyep of transport to use. /// Channel to get MTU of. /// public int GetMTU(byte channel) where T : Transport { Transport transport = GetTransport(); if (transport != null) { int mtu = transport.GetMTU(channel); if (mtu == INVALID_MTU) return mtu; return GetMTUWithReserve(mtu); } //Fall through. return INVALID_MTU; } #endregion /// /// Passes received to the intermediate layer. /// internal ArraySegment ProcessIntermediateIncoming(ArraySegment src, bool fromServer) { return _intermediateLayer.HandleIncoming(src, fromServer); } /// /// Passes sent to the intermediate layer. /// private ArraySegment ProcessIntermediateOutgoing(ArraySegment src, bool toServer) { return _intermediateLayer.HandleOutgoing(src, toServer); } /// /// Sends data to a client. /// /// Channel to send on. /// Data to send. /// Connection to send to. Use null for all clients. /// True to split large packets which exceed MTU and send them in order on the reliable channel. internal void SendToClient(byte channelId, ArraySegment segment, NetworkConnection connection, bool splitLargeMessages = true, DataOrderType orderType = DataOrderType.Default) { SetSplitValues(channelId, segment, splitLargeMessages, out int requiredMessages, out int maxSplitMessageSize); SendToClient(channelId, segment, connection, requiredMessages, maxSplitMessageSize, orderType); } private void SendToClient(byte channelId, ArraySegment segment, NetworkConnection connection, int requiredSplitMessages, int maxSplitMessageSize, DataOrderType orderType = DataOrderType.Default) { if (connection == null) return; if (requiredSplitMessages > 1) SendSplitData(connection, ref segment, requiredSplitMessages, maxSplitMessageSize, orderType); else connection.SendToClient(channelId, segment, false, orderType); } /// /// Sends data to observers. /// internal void SendToClients(byte channelId, ArraySegment segment, HashSet observers, HashSet excludedConnections = null, bool splitLargeMessages = true, DataOrderType orderType = DataOrderType.Default) { SetSplitValues(channelId, segment, splitLargeMessages, out int requiredMessages, out int maxSplitMessageSize); SendToClients(channelId, segment, observers, excludedConnections, requiredMessages, maxSplitMessageSize, orderType); } private void SendToClients(byte channelId, ArraySegment segment, HashSet observers, HashSet excludedConnections, int requiredSplitMessages, int maxSplitMessageSize, DataOrderType orderType = DataOrderType.Default) { if (excludedConnections == null || excludedConnections.Count == 0) { foreach (NetworkConnection conn in observers) SendToClient(channelId, segment, conn, requiredSplitMessages, maxSplitMessageSize, orderType); } else { foreach (NetworkConnection conn in observers) { if (excludedConnections.Contains(conn)) continue; SendToClient(channelId, segment, conn, requiredSplitMessages, maxSplitMessageSize, orderType); } } } /// /// Sends data to all clients. /// /// Channel to send on. /// Data to send. /// True to split large packets which exceed MTU and send them in order on the reliable channel. internal void SendToClients(byte channelId, ArraySegment segment, bool splitLargeMessages = true) { SetSplitValues(channelId, segment, splitLargeMessages, out int requiredMessages, out int maxSplitMessageSize); SendToClients_Internal(channelId, segment, requiredMessages, maxSplitMessageSize); } private void SendToClients_Internal(byte channelId, ArraySegment segment, int requiredSplitMessages, int maxSplitMessageSize) { /* Rather than buffer the message once and send to every client * it must be queued into every client. This ensures clients * receive the message in order of other packets being * delivered to them. */ foreach (NetworkConnection conn in _networkManager.ServerManager.Clients.Values) SendToClient(channelId, segment, conn, requiredSplitMessages, maxSplitMessageSize); } /// /// Sends data to the server. /// /// Channel to send on. /// Data to send. /// True to split large packets which exceed MTU and send them in order on the reliable channel. internal void SendToServer(byte channelId, ArraySegment segment, bool splitLargeMessages = true, DataOrderType orderType = DataOrderType.Default) { SetSplitValues(channelId, segment, splitLargeMessages, out int requiredMessages, out int maxSplitMessageSize); SendToServer(channelId, segment, requiredMessages, maxSplitMessageSize, orderType); } private void SendToServer(byte channelId, ArraySegment segment, int requiredMessages, int maxSplitMessageSize, DataOrderType orderType) { if (channelId >= _toServerBundles.Count) channelId = (byte)Channel.Reliable; if (requiredMessages > 1) SendSplitData(null, ref segment, requiredMessages, maxSplitMessageSize, orderType); else _toServerBundles[channelId].Write(segment, false, orderType); } #region Splitting. /// /// Checks if a message can be split and outputs split information if so. /// private void SetSplitValues(byte channelId, ArraySegment segment, bool split, out int requiredMessages, out int maxSplitMessageSize) { if (!split) { requiredMessages = 0; maxSplitMessageSize = 0; } else { SplitRequired(channelId, segment.Count, out requiredMessages, out maxSplitMessageSize); } } /// /// Checks to set channel to reliable if dataLength is too long. /// internal void CheckSetReliableChannel(int dataLength, ref Channel channel) { if (channel == Channel.Reliable) return; bool requiresMultipleMessages = (GetRequiredMessageCount((byte)channel, dataLength, out _) > 1); if (requiresMultipleMessages) channel = Channel.Reliable; } /// /// Gets the required number of messages needed for segmentSize and channel. /// private int GetRequiredMessageCount(byte channelId, int segmentSize, out int maxMessageSize) { maxMessageSize = GetLowestMTU(channelId) - SPLIT_INDICATOR_LENGTH; return Mathf.CeilToInt((float)segmentSize / maxMessageSize); } /// /// True if data must be split. /// /// /// private bool SplitRequired(byte channelId, int segmentSize, out int requiredMessages, out int maxMessageSize) { requiredMessages = GetRequiredMessageCount(channelId, segmentSize, out maxMessageSize); bool splitRequired = (requiredMessages > 1); if (splitRequired && channelId != (byte)Channel.Reliable) _networkManager.LogError($"A message of length {segmentSize} requires the reliable channel but was sent on channel {(Channel)channelId}. Please file this stack trace as a bug report."); return splitRequired; } /// /// Splits data going to which is too large to fit within the transport MTU. /// /// Connection to send to. If null data will be sent to the server. /// True if data was sent split. private void SendSplitData(NetworkConnection conn, ref ArraySegment segment, int requiredMessages, int maxMessageSize, DataOrderType orderType) { if (requiredMessages <= 1) { _networkManager.LogError($"SendSplitData was called with {requiredMessages} required messages. This method should only be called if messages must be split into 2 pieces or more."); return; } byte channelId = (byte)Channel.Reliable; PooledWriter headerWriter = WriterPool.Retrieve(); headerWriter.WritePacketIdUnpacked(PacketId.Split); headerWriter.WriteInt32(requiredMessages); ArraySegment headerSegment = headerWriter.GetArraySegment(); int writeIndex = 0; bool firstWrite = true; //Send to connection until everything is written. while (writeIndex < segment.Count) { int headerReduction = 0; if (firstWrite) { headerReduction = headerSegment.Count; firstWrite = false; } int chunkSize = Mathf.Min(segment.Count - writeIndex - headerReduction, maxMessageSize); //Make a new array segment for the chunk that is getting split. ArraySegment splitSegment = new(segment.Array, segment.Offset + writeIndex, chunkSize); //If connection is specified then it's going to a client. if (conn != null) { conn.SendToClient(channelId, headerSegment, true); conn.SendToClient(channelId, splitSegment); } //Otherwise it's going to the server. else { _toServerBundles[channelId].Write(headerSegment, true, orderType); _toServerBundles[channelId].Write(splitSegment, false, orderType); } writeIndex += chunkSize; } headerWriter.Store(); } #endregion /// /// Processes data received by the socket. /// /// True to read data from clients, false to read data from the server. internal void IterateIncoming(bool asServer) { OnIterateIncomingStart?.Invoke(asServer); Transport.IterateIncoming(asServer); OnIterateIncomingEnd?.Invoke(asServer); } /// /// Processes data to be sent by the socket. /// /// True to send data from the local server to clients, false to send from the local client to server. internal void IterateOutgoing(bool asServer) { if (asServer && _networkManager.ServerManager.AreAllServersStopped()) return; OnIterateOutgoingStart?.Invoke(); int channelCount = CHANNEL_COUNT; ulong sentBytes = 0; #if DEVELOPMENT bool latencySimulatorEnabled = LatencySimulator.CanSimulate; #endif if (asServer) SendAsServer(); else SendAsClient(); //Sends data as server. void SendAsServer() { TimeManager tm = _networkManager.TimeManager; uint localTick = tm.LocalTick; //Write any dirty syncTypes. _networkManager.ServerManager.Objects.WriteDirtySyncTypes(); int dirtyCount = _dirtyToClients.Count; //Run through all dirty connections to send data to. for (int z = 0; z < dirtyCount; z++) { NetworkConnection conn = _dirtyToClients[z]; if (conn == null || !conn.IsValid) continue; //Get packets for every channel. for (byte channel = 0; channel < channelCount; channel++) { if (conn.GetPacketBundle(channel, out PacketBundle pb)) { ProcessPacketBundle(pb); ProcessPacketBundle(pb.GetSendLastBundle(), true); void ProcessPacketBundle(PacketBundle ppb, bool isLast = false) { for (int i = 0; i < ppb.WrittenBuffers; i++) { //Length should always be more than 0 but check to be safe. if (ppb.GetBuffer(i, out ByteBuffer bb)) { ArraySegment segment = new(bb.Data, 0, bb.Length); if (HasIntermediateLayer) segment = ProcessIntermediateOutgoing(segment, false); #if DEVELOPMENT if (latencySimulatorEnabled) _latencySimulator.AddOutgoing(channel, segment, false, conn.ClientId); else #endif Transport.SendToClient(channel, segment, conn.ClientId); sentBytes += (ulong)segment.Count; } } ppb.Reset(false); } } } /* When marked as disconnecting data will still be sent * this iteration but the connection will be marked as invalid. * This will prevent future data from going out/coming in. * Also the connection will be added to a disconnecting collection * so it will it disconnected briefly later to allow data from * this tick to send. */ if (conn.Disconnecting) { uint requiredTicks = tm.TimeToTicks(0.1d, TickRounding.RoundUp); /* Require 100ms or 2 ticks to pass * before disconnecting to allow for the * higher chance of success that remaining * data is sent. */ requiredTicks = Math.Max(requiredTicks, 2); _disconnectingClients.Add(new(requiredTicks + localTick, conn)); } conn.ResetServerDirty(); } //Iterate disconnects. for (int i = 0; i < _disconnectingClients.Count; i++) { DisconnectingClient dc = _disconnectingClients[i]; if (localTick >= dc.Tick) { _networkManager.TransportManager.Transport.StopConnection(dc.Connection.ClientId, true); _disconnectingClients.RemoveAt(i); i--; } } _networkManager.StatisticsManager.NetworkTraffic.LocalServerSentData(sentBytes); if (dirtyCount == _dirtyToClients.Count) _dirtyToClients.Clear(); else if (dirtyCount > 0) _dirtyToClients.RemoveRange(0, dirtyCount); } //Sends data as client. void SendAsClient() { for (byte channel = 0; channel < channelCount; channel++) { if (PacketBundle.GetPacketBundle(channel, _toServerBundles, out PacketBundle pb)) { ProcessPacketBundle(pb); ProcessPacketBundle(pb.GetSendLastBundle()); void ProcessPacketBundle(PacketBundle ppb) { for (int i = 0; i < ppb.WrittenBuffers; i++) { if (ppb.GetBuffer(i, out ByteBuffer bb)) { ArraySegment segment = new(bb.Data, 0, bb.Length); if (HasIntermediateLayer) segment = ProcessIntermediateOutgoing(segment, true); #if DEVELOPMENT if (latencySimulatorEnabled) _latencySimulator.AddOutgoing(channel, segment); else #endif Transport.SendToServer(channel, segment); sentBytes += (ulong)segment.Count; } } ppb.Reset(false); } } } _networkManager.StatisticsManager.NetworkTraffic.LocalClientSentData(sentBytes); } #if DEVELOPMENT if (latencySimulatorEnabled) _latencySimulator.IterateOutgoing(asServer); #endif Transport.IterateOutgoing(asServer); OnIterateOutgoingEnd?.Invoke(); } #region Editor. #if UNITY_EDITOR private void OnValidate() { if (Transport == null) Transport = GetComponent(); /* Update enabled state to force a reset if needed. * This may be required if the user checked the enabled * tick box at runtime. If enabled value didn't change * then the Get will be the same as the Set and nothing * will happen. */ _latencySimulator.SetEnabled(_latencySimulator.GetEnabled()); } #endif #endregion } }