#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 FishNet.Managing.Statistic; using GameKit.Dependencies.Utilities; using UnityEngine; using UnityEngine.Serialization; 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. /// /// The maximum amount of bytes of any combined packet that a client may send. /// public uint MaximumClientPacketSize => _maximumClientPacketSize; [Tooltip("The maximum amount of bytes of any combined packet that a client may send.")] [SerializeField] private uint _maximumClientPacketSize = 20480; /// /// 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; /// /// private NetworkTrafficStatistics _networkTrafficStatistics; /// /// Maximum size which each segment of a split message can be. /// private int _maximumSplitPacketSegmentLength => GetLowestMTU(SPLIT_PACKET_CHANNELID) - SPLIT_PACKET_HEADER_LENGTH - UNPACKED_TICK_LENGTH; #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. /// public const byte SPLIT_PACKET_HEADER_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; /// /// A split message was not required, the value can be sent normally. /// private const int SPLIT_NOT_REQUIRED_VALUE = 0; /// /// A message was sent split. /// private const int SPLIT_SENT_VALUE = 1; /// /// An error occurred while trying to split a message. /// private const int SPLIT_ERROR_VALUE = 2; /// /// ChannelId to use for split packets. /// private const byte SPLIT_PACKET_CHANNELID = (byte)Channel.Reliable; #endregion /// /// Initializes this script for use. /// internal void InitializeOnce_Internal(NetworkManager manager) { _networkManager = manager; TryAddDefaultTransport(); Transport.Initialize(_networkManager, 0); SetLowestMTUs(); InitializeToServerBundles(); manager.StatisticsManager.TryGetNetworkTrafficStatistics(out _networkTrafficStatistics); 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); } /// /// 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(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. if (transportIndex == 0) return GetMTU(channel); // Unhandled. _networkManager.LogWarning($"MTU cannot be returned with transportIndex because {typeof(Multipass).Name} is not in use."); return -1; } /// /// Returns Channel.Reliable if data length is over MTU for the provided channel. /// public Channel GetReliableChannelIfOverMTU(int dataLength, Channel currentChannel) => dataLength > GetMTU((byte)currentChannel) ? Channel.Reliable : currentChannel; /// /// 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, DataOrderType orderType = DataOrderType.Default) { channelId = GetFallbackChannelIdAsNeeded(channelId); if (SendSplitMessage(connection, channelId, segment, orderType) == SPLIT_NOT_REQUIRED_VALUE) connection.SendToClient(channelId, segment, forceNewBuffer: false, orderType); } /// /// Sends data to observers. /// internal void SendToClients(byte channelId, ArraySegment segment, HashSet observers, HashSet excludedConnections = null, DataOrderType orderType = DataOrderType.Default) { if (excludedConnections == null || excludedConnections.Count == 0) { foreach (NetworkConnection conn in observers) SendToClient(channelId, segment, conn, orderType); } else { foreach (NetworkConnection conn in observers) { if (excludedConnections.Contains(conn)) continue; SendToClient(channelId, segment, conn, 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) { /* 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); } /// /// 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, DataOrderType orderType = DataOrderType.Default) { channelId = GetFallbackChannelIdAsNeeded(channelId); if (SendSplitMessage(conn: null, channelId, segment, orderType) == SPLIT_NOT_REQUIRED_VALUE) _toServerBundles[channelId].Write(segment, forceNewBuffer: false, orderType); } /// /// Gets the channelId to use, returning a fallback Id if the provided channelId is not supported. /// private byte GetFallbackChannelIdAsNeeded(byte channelId) => channelId > _toServerBundles.Count ? (byte)Channel.Reliable : channelId; /// /// 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 int SendSplitMessage(NetworkConnection conn, byte channelId, ArraySegment segment, DataOrderType orderType) { int lowestMTU = GetLowestMTU(channelId); int segmentCount = segment.Count; //Splitting is not required. if (segmentCount <= lowestMTU) //0 indicates no split required. return SPLIT_NOT_REQUIRED_VALUE; int maximumSegmentLength = _maximumSplitPacketSegmentLength; int messageCount = (int)Math.Ceiling((double)segmentCount / maximumSegmentLength); /* If going to the server and value exceeds the * maximum segment size then the data cannot be sent. */ if (conn == null && messageCount * maximumSegmentLength > _maximumClientPacketSize) { _networkManager.LogError($"A packet of length {segmentCount} cannot be sent because it exceeds the maximum packet size allowed by a client of {_maximumClientPacketSize}."); return SPLIT_ERROR_VALUE; } //Writer used to write the header and segment of each split message. PooledWriter splitWriter = WriterPool.Retrieve(); //Channel is forced to reliable for split messages. channelId = SPLIT_PACKET_CHANNELID; for (int i = 0; i < messageCount; i++) { splitWriter.WritePacketIdUnpacked(PacketId.Split); splitWriter.WriteInt32(messageCount); int startPosition = i * maximumSegmentLength; int chunkSize = Mathf.Min(segment.Count - startPosition, maximumSegmentLength); ArraySegment splitSegment = new(segment.Array, segment.Offset + startPosition, chunkSize); splitWriter.WriteArraySegment(splitSegment); // If connection is specified then it's going to a client. if (conn != null) conn.SendToClient(channelId, splitWriter.GetArraySegment()); // Otherwise it's going to the server. else _toServerBundles[channelId].Write(splitWriter.GetArraySegment(), forceNewBuffer: false, orderType); splitWriter.Clear(); } WriterPool.Store(splitWriter); return SPLIT_SENT_VALUE; } /// /// 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--; } } if (_networkTrafficStatistics != null) _networkTrafficStatistics.AddOutboundSocketData(sentBytes, asServer: true); 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); } } } if (_networkTrafficStatistics != null) _networkTrafficStatistics.AddOutboundSocketData(sentBytes, asServer: false); } #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 } }