using FishNet.Connection; using FishNet.Managing.Timing; using FishNet.Serializing; using FishNet.Transporting; using FishNet.Transporting.Multipass; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; 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. /// /// 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 LatencySimulator(); /// /// Latency simulation settings. /// public LatencySimulator LatencySimulator { get { //Shouldn't ever be null unless the user nullifies it. if (_latencySimulator == null) _latencySimulator = new LatencySimulator(); return _latencySimulator; } } #endregion #region Private. /// /// NetworkConnections on the server which have to send data to clients. /// private List _dirtyToClients = new List(); /// /// PacketBundles to send to the server. /// private List _toServerBundles = new List(); /// /// NetworkManager handling this TransportManager. /// private NetworkManager _networkManager; /// /// Clients which are pending disconnects. /// private List _disconnectingClients = new List(); /// /// Lowest MTU of all transports for channels. /// private int[] _lowestMtu; #endregion #region Consts. /// /// Number of bytes sent for PacketId. /// public const byte PACKET_ID_BYTES = 2; /// /// Number of bytes sent for ObjectId. /// public const byte OBJECT_ID_BYTES = 2; /// /// Number of bytes sent for ComponentIndex. /// public const byte COMPONENT_INDEX_BYTES = 1; /// /// Number of bytes sent for Tick. /// public const byte TICK_BYTES = 4; /// /// Number of bytes sent to indicate split count. /// private const byte SPLIT_COUNT_BYTES = 4; /// /// Number of bytes required for split data. /// public const byte SPLIT_INDICATOR_SIZE = (PACKET_ID_BYTES + SPLIT_COUNT_BYTES); /// /// Number of channels supported. /// public const byte CHANNEL_COUNT = 2; #endregion /// /// Initializes this script for use. /// internal void InitializeOnce_Internal(NetworkManager manager) { _networkManager = manager; /* If transport isn't specified then add default * transport. */ if (Transport == null && !gameObject.TryGetComponent(out Transport)) Transport = gameObject.AddComponent(); Transport.Initialize(_networkManager, 0); //Cache lowest Mtus. _lowestMtu = new int[CHANNEL_COUNT]; for (byte i = 0; i < CHANNEL_COUNT; i++) _lowestMtu[i] = GetLowestMTU(i); InitializeToServerBundles(); if (_intermediateLayer != null) _intermediateLayer.InitializeOnce(this); #if UNITY_EDITOR || DEVELOPMENT_BUILD _latencySimulator.Initialize(manager, Transport); #endif } /// /// 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 PacketBundle(_networkManager, mtu)); } } #region GetMTU. /* Returned MTUs are always -1 to allow an extra byte * to specify channel where certain transports do * not allow or provide channel information. */ /// /// Returns the lowest MTU for a channel. When using multipass this will evaluate all transports within Multipass. /// /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetLowestMTU(byte channel) { //Use cached if available. if (_lowestMtu[channel] > 0) return _lowestMtu[channel]; if (Transport is Multipass mp) { int? lowestMtu = null; foreach (Transport t in mp.Transports) { int thisMtu = t.GetMTU(channel); if (lowestMtu == null || thisMtu < lowestMtu.Value) lowestMtu = thisMtu; } //If lowest was not changed return unset. if (lowestMtu == null) { return -1; } else { int mtu = lowestMtu.Value; if (mtu >= 0) mtu -= 1; return mtu; } } else { return GetMTU(channel); } } /// /// Gets MTU on the current transport for channel. /// /// Channel to get MTU of. /// public int GetMTU(byte channel) { int mtu = Transport.GetMTU(channel); if (mtu >= 0) mtu -= 1; return 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. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetMTU(int transportIndex, byte channel) { if (Transport is Multipass mp) { int mtu = mp.GetMTU(channel, transportIndex); if (mtu >= 0) mtu -= 1; return 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. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public int GetMTU(byte channel) where T : Transport { Transport transport = GetTransport(); if (transport != null) { int mtu = transport.GetMTU(channel); if (mtu >= 0) mtu -= 1; return mtu; } //Fall through. return -1; } #endregion /// /// Passes received to the intermediate layer. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] internal ArraySegment ProcessIntermediateIncoming(ArraySegment src, bool fromServer) { return (_intermediateLayer == null) ? src : _intermediateLayer.HandleIncoming(src, fromServer); } /// /// Passes sent to the intermediate layer. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private ArraySegment ProcessIntermediateOutgoing(ArraySegment src, bool toServer) { return (_intermediateLayer == null) ? src : _intermediateLayer.HandleOutoing(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. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void SendToClient(byte channelId, ArraySegment segment, NetworkConnection connection, bool splitLargeMessages = true) { segment = ProcessIntermediateOutgoing(segment, false); SetSplitValues(channelId, segment, splitLargeMessages, out int requiredSplitMessages, out int maxSplitMessageSize); SendToClient_Internal(channelId, segment, connection, requiredSplitMessages, maxSplitMessageSize); } private void SendToClient_Internal(byte channelId, ArraySegment segment, NetworkConnection connection, int requiredSplitMessages, int maxSplitMessageSize) { if (connection == null) return; if (requiredSplitMessages > 1) SendSplitData(connection, ref segment, requiredSplitMessages, maxSplitMessageSize); else connection.SendToClient(channelId, segment); } /// /// Sends data to observers. /// /// /// /// /// True to split large packets which exceed MTU and send them in order on the reliable channel. internal void SendToClients(byte channelId, ArraySegment segment, HashSet observers, HashSet excludedConnections = null, bool splitLargeMessages = true) { segment = ProcessIntermediateOutgoing(segment, false); SetSplitValues(channelId, segment, splitLargeMessages, out int requiredSplitMessages, out int maxSplitMessageSize); SendToClients_Internal(channelId, segment, observers, excludedConnections, requiredSplitMessages, maxSplitMessageSize); } private void SendToClients_Internal(byte channelId, ArraySegment segment, HashSet observers, HashSet excludedConnections, int requiredSplitMessages, int maxSplitMessageSize) { if (excludedConnections == null || excludedConnections.Count == 0) { foreach (NetworkConnection conn in observers) SendToClient_Internal(channelId, segment, conn, requiredSplitMessages, maxSplitMessageSize); } else { foreach (NetworkConnection conn in observers) { if (excludedConnections.Contains(conn)) continue; SendToClient_Internal(channelId, segment, conn, requiredSplitMessages, maxSplitMessageSize); } } } /// /// 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. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void SendToClients(byte channelId, ArraySegment segment, bool splitLargeMessages = true) { segment = ProcessIntermediateOutgoing(segment, false); SetSplitValues(channelId, segment, splitLargeMessages, out int requiredSplitMessages, out int maxSplitMessageSize); SendToClients_Internal(channelId, segment, requiredSplitMessages, 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_Internal(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. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal void SendToServer(byte channelId, ArraySegment segment, bool splitLargeMessages = true) { segment = ProcessIntermediateOutgoing(segment, true); SetSplitValues(channelId, segment, splitLargeMessages, out int requiredSplitMessages, out int maxSplitMessageSize); SendToServer_Internal(channelId, segment, requiredSplitMessages, maxSplitMessageSize); } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void SendToServer_Internal(byte channelId, ArraySegment segment, int requiredSplitMessages, int maxSplitMessageSize) { if (channelId >= _toServerBundles.Count) channelId = (byte)Channel.Reliable; if (requiredSplitMessages > 1) SendSplitData(null, ref segment, requiredSplitMessages, maxSplitMessageSize); else _toServerBundles[channelId].Write(segment); } #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 requiredSplitMessages, out int maxSplitMessageSize) { if (!split) { requiredSplitMessages = 0; maxSplitMessageSize = 0; } else { SplitRequired(channelId, segment.Count, out requiredSplitMessages, out maxSplitMessageSize); } } /// /// True if data must be split. /// /// /// /// private bool SplitRequired(byte channelId, int segmentSize, out int requiredMessages, out int maxMessageSize) { maxMessageSize = GetLowestMTU(channelId) - (TransportManager.TICK_BYTES + SPLIT_INDICATOR_SIZE); requiredMessages = Mathf.CeilToInt((float)segmentSize / maxMessageSize); return (requiredMessages > 1); } /// /// 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) { 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.GetWriter(); headerWriter.WritePacketId(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 ArraySegment( 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); _toServerBundles[channelId].Write(splitSegment, false); } writeIndex += chunkSize; } headerWriter.Dispose(); } #endregion /// /// Processes data received by the socket. /// /// True to process data received on the server. internal void IterateIncoming(bool server) { OnIterateIncomingStart?.Invoke(server); Transport.IterateIncoming(server); OnIterateIncomingEnd?.Invoke(server); } /// /// Processes data to be sent by the socket. /// /// True to process data received on the server. internal void IterateOutgoing(bool toServer) { OnIterateOutgoingStart?.Invoke(); int channelCount = CHANNEL_COUNT; ulong sentBytes = 0; #if UNITY_EDITOR || DEVELOPMENT_BUILD bool latencySimulatorEnabled = LatencySimulator.CanSimulate; #endif /* If sending to the client. */ if (!toServer) { 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)) { for (int i = 0; i < pb.WrittenBuffers; i++) { //Length should always be more than 0 but check to be safe. if (pb.GetBuffer(i, out ByteBuffer bb)) { ArraySegment segment = new ArraySegment(bb.Data, 0, bb.Length); #if UNITY_EDITOR || DEVELOPMENT_BUILD if (latencySimulatorEnabled) _latencySimulator.AddOutgoing(channel, segment, false, conn.ClientId); else #endif Transport.SendToClient(channel, segment, conn.ClientId); sentBytes += (ulong)segment.Count; } } pb.Reset(); } } /* 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 DisconnectingClient(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); } /* If sending to the server. */ else { for (byte channel = 0; channel < channelCount; channel++) { if (PacketBundle.GetPacketBundle(channel, _toServerBundles, out PacketBundle pb)) { for (int i = 0; i < pb.WrittenBuffers; i++) { if (pb.GetBuffer(i, out ByteBuffer bb)) { ArraySegment segment = new ArraySegment(bb.Data, 0, bb.Length); #if UNITY_EDITOR || DEVELOPMENT_BUILD if (latencySimulatorEnabled) _latencySimulator.AddOutgoing(channel, segment); else #endif Transport.SendToServer(channel, segment); sentBytes += (ulong)segment.Count; } } pb.Reset(); } } _networkManager.StatisticsManager.NetworkTraffic.LocalClientSentData(sentBytes); } #if UNITY_EDITOR || DEVELOPMENT_BUILD if (latencySimulatorEnabled) _latencySimulator.IterateOutgoing(toServer); #endif Transport.IterateOutgoing(toServer); 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 } }