760 lines
33 KiB
C#
760 lines
33 KiB
C#
using FishNet.Authenticating;
|
|
using FishNet.Component.Observing;
|
|
using FishNet.Connection;
|
|
using FishNet.Managing.Debugging;
|
|
using FishNet.Managing.Logging;
|
|
using FishNet.Managing.Predicting;
|
|
using FishNet.Managing.Transporting;
|
|
using FishNet.Object;
|
|
using FishNet.Serializing;
|
|
using FishNet.Transporting;
|
|
using FishNet.Utility.Extension;
|
|
using FishNet.Utility.Performance;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using UnityEngine;
|
|
|
|
namespace FishNet.Managing.Server
|
|
{
|
|
/// <summary>
|
|
/// A container for server data and actions.
|
|
/// </summary>
|
|
[DisallowMultipleComponent]
|
|
[AddComponentMenu("FishNet/Manager/ServerManager")]
|
|
public sealed partial class ServerManager : MonoBehaviour
|
|
{
|
|
#region Public.
|
|
/// <summary>
|
|
/// Called after the local server connection state changes.
|
|
/// </summary>
|
|
public event Action<ServerConnectionStateArgs> OnServerConnectionState;
|
|
/// <summary>
|
|
/// Called when authenticator has concluded a result for a connection. Boolean is true if authentication passed, false if failed.
|
|
/// </summary>
|
|
public event Action<NetworkConnection, bool> OnAuthenticationResult;
|
|
/// <summary>
|
|
/// Called when a remote client state changes with the server.
|
|
/// </summary>
|
|
public event Action<NetworkConnection, RemoteConnectionStateArgs> OnRemoteConnectionState;
|
|
/// <summary>
|
|
/// True if the server connection has started.
|
|
/// </summary>
|
|
public bool Started { get; private set; }
|
|
/// <summary>
|
|
/// Handling and information for objects on the server.
|
|
/// </summary>
|
|
public ServerObjects Objects { get; private set; }
|
|
/// <summary>
|
|
/// Authenticated and non-authenticated connected clients.
|
|
/// </summary>
|
|
[HideInInspector]
|
|
public Dictionary<int, NetworkConnection> Clients = new Dictionary<int, NetworkConnection>();
|
|
/// <summary>
|
|
/// NetworkManager for server.
|
|
/// </summary>
|
|
[HideInInspector]
|
|
public NetworkManager NetworkManager { get; private set; }
|
|
#endregion
|
|
|
|
#region Serialized.
|
|
/// <summary>
|
|
/// Authenticator for this ServerManager. May be null if not using authentication.
|
|
/// </summary>
|
|
[Obsolete("Use GetAuthenticator and SetAuthenticator.")] //Remove on 2023/06/01
|
|
public Authenticator Authenticator
|
|
{
|
|
get => GetAuthenticator();
|
|
set => SetAuthenticator(value);
|
|
}
|
|
/// <summary>
|
|
/// Gets the Authenticator for this manager.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public Authenticator GetAuthenticator() => _authenticator;
|
|
/// <summary>
|
|
/// Gets the Authenticator for this manager, and initializes it.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public void SetAuthenticator(Authenticator value)
|
|
{
|
|
_authenticator = value;
|
|
InitializeAuthenticator();
|
|
}
|
|
[Tooltip("Authenticator for this ServerManager. May be null if not using authentication.")]
|
|
[SerializeField]
|
|
private Authenticator _authenticator;
|
|
/// <summary>
|
|
/// Default send rate for SyncTypes. A value of 0f will send changed values every tick.
|
|
/// SyncTypeRate cannot yet be changed at runtime because this would require recalculating rates on SyncBase, which is not yet implemented.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
internal float GetSynctypeRate() => _syncTypeRate;
|
|
[Tooltip("Default send rate for SyncTypes. A value of 0f will send changed values every tick.")]
|
|
[Range(0f, 60f)]
|
|
[SerializeField]
|
|
private float _syncTypeRate = 0.1f;
|
|
/// <summary>
|
|
/// How to pack object spawns.
|
|
/// </summary>
|
|
[Tooltip("How to pack object spawns.")]
|
|
[SerializeField]
|
|
internal TransformPackingData SpawnPacking = new TransformPackingData()
|
|
{
|
|
Position = AutoPackType.Unpacked,
|
|
Rotation = AutoPackType.PackedLess,
|
|
Scale = AutoPackType.PackedLess
|
|
};
|
|
/// <summary>
|
|
/// True to automatically set the frame rate when the client connects.
|
|
/// </summary>
|
|
[Tooltip("True to automatically set the frame rate when the client connects.")]
|
|
[SerializeField]
|
|
private bool _changeFrameRate = true;
|
|
/// <summary>
|
|
/// Maximum frame rate the server may run at. When as host this value runs at whichever is higher between client and server.
|
|
/// </summary>
|
|
internal ushort FrameRate => (_changeFrameRate) ? _frameRate : (ushort)0;
|
|
[Tooltip("Maximum frame rate the server may run at. When as host this value runs at whichever is higher between client and server.")]
|
|
[Range(1, NetworkManager.MAXIMUM_FRAMERATE)]
|
|
[SerializeField]
|
|
private ushort _frameRate = NetworkManager.MAXIMUM_FRAMERATE;
|
|
/// <summary>
|
|
/// True to share the Ids of clients and the objects they own with other clients. No sensitive information is shared.
|
|
/// </summary>
|
|
internal bool ShareIds => _shareIds;
|
|
[Tooltip("True to share the Ids of clients and the objects they own with other clients. No sensitive information is shared.")]
|
|
[SerializeField]
|
|
private bool _shareIds = true;
|
|
/// <summary>
|
|
/// Gets StartOnHeadless value.
|
|
/// </summary>
|
|
public bool GetStartOnHeadless() => _startOnHeadless;
|
|
/// <summary>
|
|
/// Sets StartOnHeadless value.
|
|
/// </summary>
|
|
/// <param name="value">New value to use.</param>
|
|
public void SetStartOnHeadless(bool value) => _startOnHeadless = value;
|
|
[Tooltip("True to automatically start the server connection when running as headless.")]
|
|
[SerializeField]
|
|
private bool _startOnHeadless = true;
|
|
/// <summary>
|
|
/// True to kick clients which send data larger than the MTU.
|
|
/// </summary>
|
|
internal bool LimitClientMTU => _limitClientMTU;
|
|
[Tooltip("True to kick clients which send data larger than the MTU.")]
|
|
[SerializeField]
|
|
private bool _limitClientMTU = true;
|
|
#endregion
|
|
|
|
#region Private.
|
|
/// <summary>
|
|
/// Used to read splits.
|
|
/// </summary>
|
|
private SplitReader _splitReader = new SplitReader();
|
|
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
|
/// <summary>
|
|
/// Logs data about parser to help debug.
|
|
/// </summary>
|
|
private ParseLogger _parseLogger = new ParseLogger();
|
|
#endif
|
|
#endregion
|
|
|
|
private void OnDestroy()
|
|
{
|
|
Objects?.SubscribeToSceneLoaded(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes this script for use.
|
|
/// </summary>
|
|
/// <param name="manager"></param>
|
|
internal void InitializeOnce_Internal(NetworkManager manager)
|
|
{
|
|
NetworkManager = manager;
|
|
Objects = new ServerObjects(manager);
|
|
Objects.SubscribeToSceneLoaded(true);
|
|
InitializeRpcLinks();
|
|
//Unsubscribe first incase already subscribed.
|
|
SubscribeToTransport(false);
|
|
SubscribeToTransport(true);
|
|
NetworkManager.ClientManager.OnClientConnectionState += ClientManager_OnClientConnectionState;
|
|
NetworkManager.SceneManager.OnClientLoadedStartScenes += SceneManager_OnClientLoadedStartScenes;
|
|
|
|
if (_authenticator == null)
|
|
_authenticator = GetComponent<Authenticator>();
|
|
if (_authenticator != null)
|
|
InitializeAuthenticator();
|
|
|
|
_cachedLevelOfDetailInterval = NetworkManager.ClientManager.LevelOfDetailInterval;
|
|
_cachedUseLod = NetworkManager.ObserverManager.GetUseNetworkLod();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes the authenticator to this manager.
|
|
/// </summary>
|
|
private void InitializeAuthenticator()
|
|
{
|
|
Authenticator auth = GetAuthenticator();
|
|
if (auth == null || auth.Initialized)
|
|
return;
|
|
if (NetworkManager == null)
|
|
return;
|
|
|
|
auth.InitializeOnce(NetworkManager);
|
|
auth.OnAuthenticationResult += _authenticator_OnAuthenticationResult;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts the server if configured to for headless.
|
|
/// </summary>
|
|
internal void StartForHeadless()
|
|
{
|
|
if (GetStartOnHeadless())
|
|
{
|
|
//Wrapping logic in check instead of everything so _startOnHeadless doesnt warn as unused in editor.
|
|
#if UNITY_SERVER
|
|
StartConnection();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops the local server connection.
|
|
/// </summary>
|
|
/// <param name="sendDisconnectMessage">True to send a disconnect message to all clients first.</param>
|
|
public bool StopConnection(bool sendDisconnectMessage)
|
|
{
|
|
if (sendDisconnectMessage)
|
|
SendDisconnectMessages(Clients.Values.ToList(), true);
|
|
|
|
//Return stop connection result.
|
|
return NetworkManager.TransportManager.Transport.StopConnection(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends a disconnect messge to connectionIds.
|
|
/// This does not iterate outgoing automatically.
|
|
/// </summary>
|
|
/// <param name="connectionIds"></param>
|
|
internal void SendDisconnectMessages(int[] connectionIds)
|
|
{
|
|
List<NetworkConnection> conns = new List<NetworkConnection>();
|
|
foreach (int item in connectionIds)
|
|
{
|
|
if (Clients.TryGetValueIL2CPP(item, out NetworkConnection c))
|
|
conns.Add(c);
|
|
}
|
|
|
|
if (conns.Count > 0)
|
|
SendDisconnectMessages(conns, false);
|
|
}
|
|
/// <summary>
|
|
/// Sends a disconnect message to all clients and immediately iterates outgoing.
|
|
/// </summary>
|
|
private void SendDisconnectMessages(List<NetworkConnection> conns, bool iterate)
|
|
{
|
|
PooledWriter writer = WriterPool.GetWriter();
|
|
writer.WritePacketId(PacketId.Disconnect);
|
|
ArraySegment<byte> segment = writer.GetArraySegment();
|
|
//Send segment to each client, authenticated or not.
|
|
foreach (NetworkConnection c in conns)
|
|
c.SendToClient((byte)Channel.Reliable, segment);
|
|
//Recycle writer.
|
|
writer.Dispose();
|
|
|
|
if (iterate)
|
|
NetworkManager.TransportManager.IterateOutgoing(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts the local server connection.
|
|
/// </summary>
|
|
public bool StartConnection()
|
|
{
|
|
return NetworkManager.TransportManager.Transport.StartConnection(true);
|
|
}
|
|
/// <summary>
|
|
/// Starts the local server using port.
|
|
/// </summary>
|
|
/// <param name="port">Port to start on.</param>
|
|
/// <returns></returns>
|
|
public bool StartConnection(ushort port)
|
|
{
|
|
Transport t = NetworkManager.TransportManager.Transport;
|
|
t.SetPort(port);
|
|
return t.StartConnection(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called after the local client connection state changes.
|
|
/// </summary>
|
|
private void ClientManager_OnClientConnectionState(ClientConnectionStateArgs obj)
|
|
{
|
|
/* If client is doing anything but started destroy pending.
|
|
* Pending is only used for host mode. */
|
|
if (obj.ConnectionState != LocalConnectionState.Started)
|
|
Objects.DestroyPending();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when a client loads initial scenes after connecting.
|
|
/// </summary>
|
|
private void SceneManager_OnClientLoadedStartScenes(NetworkConnection conn, bool asServer)
|
|
{
|
|
if (asServer)
|
|
{
|
|
Objects.RebuildObservers(conn);
|
|
/* If connection is host then renderers must be hidden
|
|
* for all objects not visible to the host. The observer system
|
|
* does handle this but only after an initial state is set.
|
|
* If the clientHost joins without observation of an object
|
|
* then the initial state will never be set. */
|
|
if (conn.IsLocalClient)
|
|
{
|
|
foreach (NetworkObject nob in Objects.Spawned.Values)
|
|
{
|
|
if (!nob.Observers.Contains(conn))
|
|
nob.SetRenderersVisible(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changes subscription status to transport.
|
|
/// </summary>
|
|
/// <param name="subscribe"></param>
|
|
private void SubscribeToTransport(bool subscribe)
|
|
{
|
|
if (NetworkManager == null || NetworkManager.TransportManager == null || NetworkManager.TransportManager.Transport == null)
|
|
return;
|
|
|
|
if (subscribe)
|
|
{
|
|
NetworkManager.TransportManager.Transport.OnServerReceivedData += Transport_OnServerReceivedData;
|
|
NetworkManager.TransportManager.Transport.OnServerConnectionState += Transport_OnServerConnectionState;
|
|
NetworkManager.TransportManager.Transport.OnRemoteConnectionState += Transport_OnRemoteConnectionState;
|
|
}
|
|
else
|
|
{
|
|
NetworkManager.TransportManager.Transport.OnServerReceivedData -= Transport_OnServerReceivedData;
|
|
NetworkManager.TransportManager.Transport.OnServerConnectionState -= Transport_OnServerConnectionState;
|
|
NetworkManager.TransportManager.Transport.OnRemoteConnectionState -= Transport_OnRemoteConnectionState;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when authenticator has concluded a result for a connection. Boolean is true if authentication passed, false if failed.
|
|
/// Server listens for this event automatically.
|
|
/// </summary>
|
|
private void _authenticator_OnAuthenticationResult(NetworkConnection conn, bool authenticated)
|
|
{
|
|
if (!authenticated)
|
|
conn.Disconnect(false);
|
|
else
|
|
ClientAuthenticated(conn);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when a connection state changes for the local server.
|
|
/// </summary>
|
|
private void Transport_OnServerConnectionState(ServerConnectionStateArgs args)
|
|
{
|
|
/* Let the client manager know the server state is changing first.
|
|
* This gives the client an opportunity to clean-up or prepare
|
|
* before the server completes it's actions. */
|
|
Started = AnyServerStarted();
|
|
NetworkManager.ClientManager.Objects.OnServerConnectionState(args);
|
|
//If no servers are started then reset match conditions.
|
|
if (!Started)
|
|
{
|
|
MatchCondition.ClearMatchesWithoutRebuilding();
|
|
//Despawn without synchronizing network objects.
|
|
Objects.DespawnWithoutSynchronization(true);
|
|
}
|
|
Objects.OnServerConnectionState(args);
|
|
|
|
LocalConnectionState state = args.ConnectionState;
|
|
|
|
if (NetworkManager.CanLog(LoggingType.Common))
|
|
{
|
|
Transport t = NetworkManager.TransportManager.GetTransport(args.TransportIndex);
|
|
string tName = (t == null) ? "Unknown" : t.GetType().Name;
|
|
Debug.Log($"Local server is {state.ToString().ToLower()} for {tName}.");
|
|
}
|
|
|
|
NetworkManager.UpdateFramerate();
|
|
OnServerConnectionState?.Invoke(args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when a connection state changes for a remote client.
|
|
/// </summary>
|
|
private void Transport_OnRemoteConnectionState(RemoteConnectionStateArgs args)
|
|
{
|
|
//Sanity check to make sure transports are following proper types/ranges.
|
|
int id = args.ConnectionId;
|
|
int maxIdValue = short.MaxValue;
|
|
if (id < 0 || id > maxIdValue)
|
|
{
|
|
Kick(args.ConnectionId, KickReason.UnexpectedProblem, LoggingType.Error, $"The transport you are using supplied an invalid connection Id of {id}. Connection Id values must range between 0 and {maxIdValue}. The client has been disconnected.");
|
|
return;
|
|
}
|
|
//Valid Id.
|
|
else
|
|
{
|
|
//If started then add to authenticated clients.
|
|
if (args.ConnectionState == RemoteConnectionState.Started)
|
|
{
|
|
NetworkManager.Log($"Remote connection started for Id {id}.");
|
|
NetworkConnection conn = new NetworkConnection(NetworkManager, id, true);
|
|
Clients.Add(args.ConnectionId, conn);
|
|
OnRemoteConnectionState?.Invoke(conn, args);
|
|
//Connection is no longer valid. This can occur if the user changes the state using the OnRemoteConnectionState event.
|
|
if (!conn.IsValid)
|
|
return;
|
|
/* If there is an authenticator
|
|
* and the transport is not a local transport. */
|
|
Authenticator auth = GetAuthenticator();
|
|
if (auth != null && !NetworkManager.TransportManager.IsLocalTransport(id))
|
|
auth.OnRemoteConnection(conn);
|
|
else
|
|
ClientAuthenticated(conn);
|
|
}
|
|
//If stopping.
|
|
else if (args.ConnectionState == RemoteConnectionState.Stopped)
|
|
{
|
|
/* If client's connection is found then clean
|
|
* them up from server. */
|
|
if (Clients.TryGetValueIL2CPP(id, out NetworkConnection conn))
|
|
{
|
|
conn.SetDisconnecting(true);
|
|
OnRemoteConnectionState?.Invoke(conn, args);
|
|
Clients.Remove(id);
|
|
MatchCondition.RemoveFromMatchWithoutRebuild(conn, NetworkManager);
|
|
Objects.ClientDisconnected(conn);
|
|
BroadcastClientConnectionChange(false, conn);
|
|
//Return predictedObjectIds.
|
|
Queue<int> pqId = conn.PredictedObjectIds;
|
|
while (pqId.Count > 0)
|
|
Objects.CacheObjectId(pqId.Dequeue());
|
|
|
|
conn.Reset();
|
|
NetworkManager.Log($"Remote connection stopped for Id {id}.");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends client their connectionId.
|
|
/// </summary>
|
|
/// <param name="connectionid"></param>
|
|
private void SendAuthenticated(NetworkConnection conn)
|
|
{
|
|
using (PooledWriter writer = WriterPool.GetWriter())
|
|
{
|
|
writer.WritePacketId(PacketId.Authenticated);
|
|
writer.WriteNetworkConnection(conn);
|
|
/* If predicted spawning is enabled then also send
|
|
* reserved objectIds. */
|
|
;
|
|
PredictionManager pm = NetworkManager.PredictionManager;
|
|
if (pm.GetAllowPredictedSpawning())
|
|
{
|
|
int count = Mathf.Min(Objects.GetObjectIdCache().Count, pm.GetReservedObjectIds());
|
|
writer.WriteByte((byte)count);
|
|
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
ushort val = (ushort)Objects.GetNextNetworkObjectId(false);
|
|
writer.WriteNetworkObjectId(val);
|
|
conn.PredictedObjectIds.Enqueue(val);
|
|
}
|
|
}
|
|
|
|
NetworkManager.TransportManager.SendToClient((byte)Channel.Reliable, writer.GetArraySegment(), conn);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Called when the server socket receives data.
|
|
/// </summary>
|
|
private void Transport_OnServerReceivedData(ServerReceivedDataArgs args)
|
|
{
|
|
args.Data = NetworkManager.TransportManager.ProcessIntermediateIncoming(args.Data, false);
|
|
ParseReceived(args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called when the server receives data.
|
|
/// </summary>
|
|
/// <param name="args"></param>
|
|
private void ParseReceived(ServerReceivedDataArgs args)
|
|
{
|
|
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
|
_parseLogger.Reset();
|
|
#endif
|
|
|
|
//Not from a valid connection.
|
|
if (args.ConnectionId < 0)
|
|
return;
|
|
ArraySegment<byte> segment = args.Data;
|
|
NetworkManager.StatisticsManager.NetworkTraffic.LocalServerReceivedData((ulong)segment.Count);
|
|
if (segment.Count <= TransportManager.TICK_BYTES)
|
|
return;
|
|
|
|
//FishNet internally splits packets so nothing should ever arrive over MTU.
|
|
int channelMtu = NetworkManager.TransportManager.GetMTU(args.TransportIndex, (byte)args.Channel);
|
|
//If over MTU kick client immediately.
|
|
if (segment.Count > channelMtu && !NetworkManager.TransportManager.IsLocalTransport(args.ConnectionId))
|
|
{
|
|
ExceededMTUKick();
|
|
return;
|
|
}
|
|
|
|
PacketId packetId = PacketId.Unset;
|
|
#if !UNITY_EDITOR && !DEVELOPMENT_BUILD
|
|
try
|
|
{
|
|
#endif
|
|
using (PooledReader reader = ReaderPool.GetReader(segment, NetworkManager))
|
|
{
|
|
uint tick = reader.ReadUInt32(AutoPackType.Unpacked);
|
|
NetworkManager.TimeManager.LastPacketTick = tick;
|
|
/* This is a special condition where a message may arrive split.
|
|
* When this occurs buffer each packet until all packets are
|
|
* received. */
|
|
if (reader.PeekPacketId() == PacketId.Split)
|
|
{
|
|
//Skip packetId.
|
|
reader.ReadPacketId();
|
|
|
|
int expectedMessages;
|
|
_splitReader.GetHeader(reader, out expectedMessages);
|
|
//If here split message can be written.
|
|
_splitReader.Write(NetworkManager.TimeManager.LastPacketTick, reader, expectedMessages);
|
|
|
|
/* If fullMessage returns 0 count then the split
|
|
* has not written fully yet. Otherwise, if there is
|
|
* data within then reinitialize reader with the
|
|
* full message. */
|
|
ArraySegment<byte> fullMessage = _splitReader.GetFullMessage();
|
|
if (fullMessage.Count == 0)
|
|
return;
|
|
|
|
/* If here then all data has been received.
|
|
* It's possible the client could have exceeded
|
|
* maximum MTU but not the maximum number of splits.
|
|
* This is because the length of each split
|
|
* is not written, so we don't know how much data of the
|
|
* final message actually belonged to the split vs
|
|
* unrelated data added afterwards. We're going to cut
|
|
* the client some slack in this situation for the sake
|
|
* of keeping things simple. */
|
|
//Initialize reader with full message.
|
|
reader.Initialize(fullMessage, NetworkManager);
|
|
}
|
|
|
|
//Parse reader.
|
|
while (reader.Remaining > 0)
|
|
{
|
|
packetId = reader.ReadPacketId();
|
|
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
|
_parseLogger.AddPacket(packetId);
|
|
#endif
|
|
NetworkConnection conn;
|
|
|
|
/* Connection isn't available. This should never happen.
|
|
* Force an immediate disconnect. */
|
|
if (!Clients.TryGetValueIL2CPP(args.ConnectionId, out conn))
|
|
{
|
|
Kick(args.ConnectionId, KickReason.UnexpectedProblem, LoggingType.Error, $"ConnectionId {conn.ClientId} not found within Clients. Connection will be kicked immediately.");
|
|
return;
|
|
}
|
|
conn.SetLastPacketTick(tick);
|
|
/* If connection isn't authenticated and isn't a broadcast
|
|
* then disconnect client. If a broadcast then process
|
|
* normally; client may still become disconnected if the broadcast
|
|
* does not allow to be called while not authenticated. */
|
|
if (!conn.Authenticated && packetId != PacketId.Broadcast)
|
|
{
|
|
conn.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"ConnectionId {conn.ClientId} sent a Broadcast without being authenticated. Connection will be kicked immediately.");
|
|
return;
|
|
}
|
|
|
|
//Only check if not developer build because users pay pause editor.
|
|
#if !DEVELOPMENT_BUILD && !UNITY_EDITOR
|
|
/* If hasn't sent LOD recently enough. LODs are sent every half a second, so
|
|
* by multiplaying interval by 60 this gives the client a 30 second window. */
|
|
if (_cachedUseLod && conn.IsLateForLevelOfDetail(_cachedLevelOfDetailInterval * 60))
|
|
{
|
|
conn.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"ConnectionId {conn.ClientId} has gone too long without sending a level of detail update. Connection will be kicked immediately.");
|
|
return;
|
|
}
|
|
#endif
|
|
if (packetId == PacketId.Replicate)
|
|
{
|
|
Objects.ParseReplicateRpc(reader, conn, args.Channel);
|
|
}
|
|
else if (packetId == PacketId.ServerRpc)
|
|
{
|
|
Objects.ParseServerRpc(reader, conn, args.Channel);
|
|
}
|
|
else if (packetId == PacketId.ObjectSpawn)
|
|
{
|
|
if (!NetworkManager.PredictionManager.GetAllowPredictedSpawning())
|
|
{
|
|
conn.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"ConnectionId {conn.ClientId} sent a predicted spawn while predicted spawning is not enabled. Connection will be kicked immediately.");
|
|
return;
|
|
}
|
|
Objects.ReadPredictedSpawn(reader, conn);
|
|
}
|
|
else if (packetId == PacketId.ObjectDespawn)
|
|
{
|
|
if (!NetworkManager.PredictionManager.GetAllowPredictedSpawning())
|
|
{
|
|
conn.Kick(KickReason.ExploitAttempt, LoggingType.Common, $"ConnectionId {conn.ClientId} sent a predicted spawn while predicted spawning is not enabled. Connection will be kicked immediately.");
|
|
return;
|
|
}
|
|
Objects.ReadPredictedDespawn(reader, conn);
|
|
}
|
|
else if (packetId == PacketId.NetworkLODUpdate)
|
|
{
|
|
ParseNetworkLODUpdate(reader, conn);
|
|
}
|
|
else if (packetId == PacketId.Broadcast)
|
|
{
|
|
ParseBroadcast(reader, conn, args.Channel);
|
|
}
|
|
else if (packetId == PacketId.PingPong)
|
|
{
|
|
ParsePingPong(reader, conn);
|
|
}
|
|
else
|
|
{
|
|
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
|
NetworkManager.LogError($"Server received an unhandled PacketId of {(ushort)packetId} from connectionId {args.ConnectionId}. Remaining data has been purged.");
|
|
_parseLogger.Print(NetworkManager);
|
|
#else
|
|
NetworkManager.LogError($"Server received an unhandled PacketId of {(ushort)packetId} from connectionId {args.ConnectionId}. Connection will be kicked immediately.");
|
|
NetworkManager.TransportManager.Transport.StopConnection(args.ConnectionId, true);
|
|
#endif
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
#if !UNITY_EDITOR && !DEVELOPMENT_BUILD
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Kick(args.ConnectionId, KickReason.MalformedData, LoggingType.Error, $"Server encountered an error while parsing data for packetId {packetId} from connectionId {args.ConnectionId}. Connection will be kicked immediately. Message: {e.Message}.");
|
|
}
|
|
#endif
|
|
|
|
//Kicks connection for exceeding MTU.
|
|
void ExceededMTUKick()
|
|
{
|
|
Kick(args.ConnectionId, KickReason.ExploitExcessiveData, LoggingType.Common, $"ConnectionId {args.ConnectionId} sent a message larger than allowed amount. Connection will be kicked immediately.");
|
|
}
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Parses a received PingPong.
|
|
/// </summary>
|
|
/// <param name="reader"></param>
|
|
/// <param name="conn"></param>
|
|
private void ParsePingPong(PooledReader reader, NetworkConnection conn)
|
|
{
|
|
/* //security limit how often clients can send pings.
|
|
* have clients use a stopwatch rather than frame time
|
|
* for checks to ensure it's not possible to send
|
|
* excessively should their game stutter then catch back up. */
|
|
uint clientTick = reader.ReadUInt32(AutoPackType.Unpacked);
|
|
if (conn.CanPingPong())
|
|
NetworkManager.TimeManager.SendPong(conn, clientTick);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Called when a remote client authenticates with the server.
|
|
/// </summary>
|
|
/// <param name="connectionId"></param>
|
|
private void ClientAuthenticated(NetworkConnection connection)
|
|
{
|
|
/* Immediately send connectionId to client. Some transports
|
|
* don't give clients their remoteId, therefor it has to be sent
|
|
* by the ServerManager. This packet is very simple and can be built
|
|
* on the spot. */
|
|
connection.ConnectionAuthenticated();
|
|
/* Send client Ids before telling the client
|
|
* they are authenticated. This is important because when the client becomes
|
|
* authenticated they set their LocalConnection using Clients field in ClientManager,
|
|
* which is set after getting Ids. */
|
|
BroadcastClientConnectionChange(true, connection);
|
|
SendAuthenticated(connection);
|
|
|
|
OnAuthenticationResult?.Invoke(connection, true);
|
|
NetworkManager.SceneManager.OnClientAuthenticated(connection);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sends a client connection state change to owner and other clients if applicable.
|
|
/// </summary>
|
|
private void BroadcastClientConnectionChange(bool connected, NetworkConnection conn)
|
|
{
|
|
//If sharing Ids then send all connected client Ids first if is a connected state.
|
|
if (ShareIds)
|
|
{
|
|
/* Send a broadcast to all authenticated clients with the clientId
|
|
* that just connected. The conn client will also get this. */
|
|
ClientConnectionChangeBroadcast changeMsg = new ClientConnectionChangeBroadcast()
|
|
{
|
|
Connected = connected,
|
|
Id = conn.ClientId
|
|
};
|
|
Broadcast(changeMsg);
|
|
|
|
/* If state is connected then the conn client
|
|
* must also receive all currently connected client ids. */
|
|
if (connected)
|
|
{
|
|
//Send already connected clients to the connection that just joined.
|
|
ListCache<int> lc = ListCaches.GetIntCache();
|
|
foreach (int key in Clients.Keys)
|
|
lc.AddValue(key);
|
|
|
|
ConnectedClientsBroadcast allMsg = new ConnectedClientsBroadcast()
|
|
{
|
|
ListCache = lc
|
|
};
|
|
conn.Broadcast(allMsg);
|
|
ListCaches.StoreCache(lc);
|
|
}
|
|
}
|
|
//If not sharing Ids then only send ConnectionChange to conn.
|
|
else
|
|
{
|
|
if (connected)
|
|
{
|
|
/* Send broadcast only to the client which just disconnected.
|
|
* Only send if connecting. If the client is disconnected there's no reason
|
|
* to send them a disconnect msg. */
|
|
ClientConnectionChangeBroadcast changeMsg = new ClientConnectionChangeBroadcast()
|
|
{
|
|
Connected = connected,
|
|
Id = conn.ClientId
|
|
};
|
|
Broadcast(conn, changeMsg, true, Channel.Reliable);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
}
|