using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Net; using System.Net.Sockets; using System.Threading; using LiteNetLib.Layers; using LiteNetLib.Utils; namespace LiteNetLib { public sealed class NetPacketReader : NetDataReader { private NetPacket _packet; private readonly NetManager _manager; private readonly NetEvent _evt; internal NetPacketReader(NetManager manager, NetEvent evt) { _manager = manager; _evt = evt; } internal void SetSource(NetPacket packet, int headerSize) { if (packet == null) return; _packet = packet; SetSource(packet.RawData, headerSize, packet.Size); } internal void RecycleInternal() { Clear(); if (_packet != null) _manager.PoolRecycle(_packet); _packet = null; _manager.RecycleEvent(_evt); } public void Recycle() { if (_manager.AutoRecycle) return; RecycleInternal(); } } internal sealed class NetEvent { public NetEvent Next; public enum EType { Connect, Disconnect, Receive, ReceiveUnconnected, Error, ConnectionLatencyUpdated, Broadcast, ConnectionRequest, MessageDelivered, PeerAddressChanged } public EType Type; public NetPeer Peer; public IPEndPoint RemoteEndPoint; public object UserData; public int Latency; public SocketError ErrorCode; public DisconnectReason DisconnectReason; public ConnectionRequest ConnectionRequest; public DeliveryMethod DeliveryMethod; public byte ChannelNumber; public readonly NetPacketReader DataReader; public NetEvent(NetManager manager) { DataReader = new NetPacketReader(manager, this); } } /// /// Main class for all network operations. Can be used as client and/or server. /// public partial class NetManager : IEnumerable { public struct NetPeerEnumerator : IEnumerator { private readonly NetPeer _initialPeer; private NetPeer _p; public NetPeerEnumerator(NetPeer p) { _initialPeer = p; _p = null; } public void Dispose() { } public bool MoveNext() { _p = _p == null ? _initialPeer : _p.NextPeer; return _p != null; } public void Reset() { throw new NotSupportedException(); } public NetPeer Current => _p; object IEnumerator.Current => _p; } #if DEBUG private struct IncomingData { public NetPacket Data; public IPEndPoint EndPoint; public DateTime TimeWhenGet; } private readonly List _pingSimulationList = new List(); private readonly Random _randomGenerator = new Random(); private const int MinLatencyThreshold = 5; #endif private Thread _logicThread; private bool _manualMode; private readonly AutoResetEvent _updateTriggerEvent = new AutoResetEvent(true); private NetEvent _pendingEventHead; private NetEvent _pendingEventTail; private NetEvent _netEventPoolHead; private readonly INetEventListener _netEventListener; private readonly IDeliveryEventListener _deliveryEventListener; private readonly INtpEventListener _ntpEventListener; private readonly IPeerAddressChangedListener _peerAddressChangedListener; private readonly Dictionary _requestsDict = new Dictionary(); private readonly ConcurrentDictionary _ntpRequests = new ConcurrentDictionary(); private int _connectedPeersCount; private readonly List _connectedPeerListCache = new List(); private readonly PacketLayerBase _extraPacketLayer; private int _lastPeerId; private ConcurrentQueue _peerIds = new ConcurrentQueue(); private byte _channelsCount = 1; private readonly object _eventLock = new object(); //config section /// /// Enable messages receiving without connection. (with SendUnconnectedMessage method) /// public bool UnconnectedMessagesEnabled = false; /// /// Enable nat punch messages /// public bool NatPunchEnabled = false; /// /// Library logic update and send period in milliseconds /// Lowest values in Windows doesn't change much because of Thread.Sleep precision /// To more frequent sends (or sends tied to your game logic) use /// public int UpdateTime = 15; /// /// Interval for latency detection and checking connection (in milliseconds) /// public int PingInterval = 1000; /// /// If NetManager doesn't receive any packet from remote peer during this time (in milliseconds) then connection will be closed /// (including library internal keepalive packets) /// public int DisconnectTimeout = 5000; /// /// Simulate packet loss by dropping random amount of packets. (Works only in DEBUG mode) /// public bool SimulatePacketLoss = false; /// /// Simulate latency by holding packets for random time. (Works only in DEBUG mode) /// public bool SimulateLatency = false; /// /// Chance of packet loss when simulation enabled. value in percents (1 - 100). /// public int SimulationPacketLossChance = 10; /// /// Minimum simulated latency (in milliseconds) /// public int SimulationMinLatency = 30; /// /// Maximum simulated latency (in milliseconds) /// public int SimulationMaxLatency = 100; /// /// Events automatically will be called without PollEvents method from another thread /// public bool UnsyncedEvents = false; /// /// If true - receive event will be called from "receive" thread immediately otherwise on PollEvents call /// public bool UnsyncedReceiveEvent = false; /// /// If true - delivery event will be called from "receive" thread immediately otherwise on PollEvents call /// public bool UnsyncedDeliveryEvent = false; /// /// Allows receive broadcast packets /// public bool BroadcastReceiveEnabled = false; /// /// Delay between initial connection attempts (in milliseconds) /// public int ReconnectDelay = 500; /// /// Maximum connection attempts before client stops and call disconnect event. /// public int MaxConnectAttempts = 10; /// /// Enables socket option "ReuseAddress" for specific purposes /// public bool ReuseAddress = false; /// /// UDP Only Socket Option /// Normally IP sockets send packets of data through routers and gateways until they reach the final destination. /// If the DontRoute flag is set to True, then data will be delivered on the local subnet only. /// public bool DontRoute = false; /// /// Statistics of all connections /// public readonly NetStatistics Statistics = new NetStatistics(); /// /// Toggles the collection of network statistics for the instance and all known peers /// public bool EnableStatistics = false; /// /// NatPunchModule for NAT hole punching operations /// public readonly NatPunchModule NatPunchModule; /// /// Returns true if socket listening and update thread is running /// public bool IsRunning { get; private set; } /// /// Local EndPoint (host and port) /// public int LocalPort { get; private set; } /// /// Automatically recycle NetPacketReader after OnReceive event /// public bool AutoRecycle; /// /// IPv6 support /// public bool IPv6Enabled = true; /// /// Override MTU for all new peers registered in this NetManager, will ignores MTU Discovery! /// public int MtuOverride = 0; /// /// Sets initial MTU to lowest possible value according to RFC1191 (576 bytes) /// public bool UseSafeMtu = false; /// /// First peer. Useful for Client mode /// public NetPeer FirstPeer => _headPeer; /// /// Experimental feature mostly for servers. Only for Windows/Linux /// use direct socket calls for send/receive to drastically increase speed and reduce GC pressure /// public bool UseNativeSockets = false; /// /// Disconnect peers if HostUnreachable or NetworkUnreachable spawned (old behaviour 0.9.x was true) /// public bool DisconnectOnUnreachable = false; /// /// Allows peer change it's ip (lte to wifi, wifi to lte, etc). Use only on server /// public bool AllowPeerAddressChange = false; /// /// QoS channel count per message type (value must be between 1 and 64 channels) /// public byte ChannelsCount { get => _channelsCount; set { if (value < 1 || value > 64) throw new ArgumentException("Channels count must be between 1 and 64"); _channelsCount = value; } } /// /// Returns connected peers list (with internal cached list) /// public List ConnectedPeerList { get { GetPeersNonAlloc(_connectedPeerListCache, ConnectionState.Connected); return _connectedPeerListCache; } } /// /// Returns connected peers count /// public int ConnectedPeersCount => Interlocked.CompareExchange(ref _connectedPeersCount,0,0); public int ExtraPacketSizeForLayer => _extraPacketLayer?.ExtraPacketSizeForLayer ?? 0; /// /// NetManager constructor /// /// Network events listener (also can implement IDeliveryEventListener) /// Extra processing of packages, like CRC checksum or encryption. All connected NetManagers must have same layer. public NetManager(INetEventListener listener, PacketLayerBase extraPacketLayer = null) { _netEventListener = listener; _deliveryEventListener = listener as IDeliveryEventListener; _ntpEventListener = listener as INtpEventListener; _peerAddressChangedListener = listener as IPeerAddressChangedListener; NatPunchModule = new NatPunchModule(this); _extraPacketLayer = extraPacketLayer; } internal void ConnectionLatencyUpdated(NetPeer fromPeer, int latency) { CreateEvent(NetEvent.EType.ConnectionLatencyUpdated, fromPeer, latency: latency); } internal void MessageDelivered(NetPeer fromPeer, object userData) { if(_deliveryEventListener != null) CreateEvent(NetEvent.EType.MessageDelivered, fromPeer, userData: userData); } internal void DisconnectPeerForce(NetPeer peer, DisconnectReason reason, SocketError socketErrorCode, NetPacket eventData) { DisconnectPeer(peer, reason, socketErrorCode, true, null, 0, 0, eventData); } private void DisconnectPeer( NetPeer peer, DisconnectReason reason, SocketError socketErrorCode, bool force, byte[] data, int start, int count, NetPacket eventData) { var shutdownResult = peer.Shutdown(data, start, count, force); if (shutdownResult == ShutdownResult.None) return; if(shutdownResult == ShutdownResult.WasConnected) Interlocked.Decrement(ref _connectedPeersCount); CreateEvent( NetEvent.EType.Disconnect, peer, errorCode: socketErrorCode, disconnectReason: reason, readerSource: eventData); } private void CreateEvent( NetEvent.EType type, NetPeer peer = null, IPEndPoint remoteEndPoint = null, SocketError errorCode = 0, int latency = 0, DisconnectReason disconnectReason = DisconnectReason.ConnectionFailed, ConnectionRequest connectionRequest = null, DeliveryMethod deliveryMethod = DeliveryMethod.Unreliable, byte channelNumber = 0, NetPacket readerSource = null, object userData = null) { NetEvent evt; bool unsyncEvent = UnsyncedEvents; if (type == NetEvent.EType.Connect) Interlocked.Increment(ref _connectedPeersCount); else if (type == NetEvent.EType.MessageDelivered) unsyncEvent = UnsyncedDeliveryEvent; lock(_eventLock) { evt = _netEventPoolHead; if (evt == null) evt = new NetEvent(this); else _netEventPoolHead = evt.Next; } evt.Next = null; evt.Type = type; evt.DataReader.SetSource(readerSource, readerSource?.GetHeaderSize() ?? 0); evt.Peer = peer; evt.RemoteEndPoint = remoteEndPoint; evt.Latency = latency; evt.ErrorCode = errorCode; evt.DisconnectReason = disconnectReason; evt.ConnectionRequest = connectionRequest; evt.DeliveryMethod = deliveryMethod; evt.ChannelNumber = channelNumber; evt.UserData = userData; if (unsyncEvent || _manualMode) { ProcessEvent(evt); } else { lock (_eventLock) { if (_pendingEventTail == null) _pendingEventHead = evt; else _pendingEventTail.Next = evt; _pendingEventTail = evt; } } } private void ProcessEvent(NetEvent evt) { NetDebug.Write("[NM] Processing event: " + evt.Type); bool emptyData = evt.DataReader.IsNull; switch (evt.Type) { case NetEvent.EType.Connect: _netEventListener.OnPeerConnected(evt.Peer); break; case NetEvent.EType.Disconnect: var info = new DisconnectInfo { Reason = evt.DisconnectReason, AdditionalData = evt.DataReader, SocketErrorCode = evt.ErrorCode }; _netEventListener.OnPeerDisconnected(evt.Peer, info); break; case NetEvent.EType.Receive: _netEventListener.OnNetworkReceive(evt.Peer, evt.DataReader, evt.ChannelNumber, evt.DeliveryMethod); break; case NetEvent.EType.ReceiveUnconnected: _netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.BasicMessage); break; case NetEvent.EType.Broadcast: _netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.Broadcast); break; case NetEvent.EType.Error: _netEventListener.OnNetworkError(evt.RemoteEndPoint, evt.ErrorCode); break; case NetEvent.EType.ConnectionLatencyUpdated: _netEventListener.OnNetworkLatencyUpdate(evt.Peer, evt.Latency); break; case NetEvent.EType.ConnectionRequest: _netEventListener.OnConnectionRequest(evt.ConnectionRequest); break; case NetEvent.EType.MessageDelivered: _deliveryEventListener.OnMessageDelivered(evt.Peer, evt.UserData); break; case NetEvent.EType.PeerAddressChanged: _peersLock.EnterUpgradeableReadLock(); IPEndPoint previousAddress = null; if (ContainsPeer(evt.Peer)) { _peersLock.EnterWriteLock(); RemovePeerFromSet(evt.Peer); previousAddress = new IPEndPoint(evt.Peer.Address, evt.Peer.Port); evt.Peer.FinishEndPointChange(evt.RemoteEndPoint); AddPeerToSet(evt.Peer); _peersLock.ExitWriteLock(); } _peersLock.ExitUpgradeableReadLock(); if(previousAddress != null && _peerAddressChangedListener != null) _peerAddressChangedListener.OnPeerAddressChanged(evt.Peer, previousAddress); break; } //Recycle if not message if (emptyData) RecycleEvent(evt); else if (AutoRecycle) evt.DataReader.RecycleInternal(); } internal void RecycleEvent(NetEvent evt) { evt.Peer = null; evt.ErrorCode = 0; evt.RemoteEndPoint = null; evt.ConnectionRequest = null; lock(_eventLock) { evt.Next = _netEventPoolHead; _netEventPoolHead = evt; } } //Update function private void UpdateLogic() { var peersToRemove = new List(); var stopwatch = new Stopwatch(); stopwatch.Start(); while (IsRunning) { try { ProcessDelayedPackets(); int elapsed = (int)stopwatch.ElapsedMilliseconds; elapsed = elapsed <= 0 ? 1 : elapsed; stopwatch.Restart(); for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) { if (netPeer.ConnectionState == ConnectionState.Disconnected && netPeer.TimeSinceLastPacket > DisconnectTimeout) { peersToRemove.Add(netPeer); } else { netPeer.Update(elapsed); } } if (peersToRemove.Count > 0) { _peersLock.EnterWriteLock(); for (int i = 0; i < peersToRemove.Count; i++) RemovePeerInternal(peersToRemove[i]); _peersLock.ExitWriteLock(); peersToRemove.Clear(); } ProcessNtpRequests(elapsed); int sleepTime = UpdateTime - (int)stopwatch.ElapsedMilliseconds; if (sleepTime > 0) _updateTriggerEvent.WaitOne(sleepTime); } catch (ThreadAbortException) { return; } catch (Exception e) { NetDebug.WriteError("[NM] LogicThread error: " + e); } } stopwatch.Stop(); } [Conditional("DEBUG")] private void ProcessDelayedPackets() { #if DEBUG if (!SimulateLatency) return; var time = DateTime.UtcNow; lock (_pingSimulationList) { for (int i = 0; i < _pingSimulationList.Count; i++) { var incomingData = _pingSimulationList[i]; if (incomingData.TimeWhenGet <= time) { DebugMessageReceived(incomingData.Data, incomingData.EndPoint); _pingSimulationList.RemoveAt(i); i--; } } } #endif } private void ProcessNtpRequests(int elapsedMilliseconds) { List requestsToRemove = null; foreach (var ntpRequest in _ntpRequests) { ntpRequest.Value.Send(_udpSocketv4, elapsedMilliseconds); if(ntpRequest.Value.NeedToKill) { if (requestsToRemove == null) requestsToRemove = new List(); requestsToRemove.Add(ntpRequest.Key); } } if (requestsToRemove != null) { foreach (var ipEndPoint in requestsToRemove) { _ntpRequests.TryRemove(ipEndPoint, out _); } } } /// /// Update and send logic. Use this only when NetManager started in manual mode /// /// elapsed milliseconds since last update call public void ManualUpdate(int elapsedMilliseconds) { if (!_manualMode) return; for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) { if (netPeer.ConnectionState == ConnectionState.Disconnected && netPeer.TimeSinceLastPacket > DisconnectTimeout) { RemovePeerInternal(netPeer); } else { netPeer.Update(elapsedMilliseconds); } } ProcessNtpRequests(elapsedMilliseconds); } internal NetPeer OnConnectionSolved(ConnectionRequest request, byte[] rejectData, int start, int length) { NetPeer netPeer = null; if (request.Result == ConnectionRequestResult.RejectForce) { NetDebug.Write(NetLogLevel.Trace, "[NM] Peer connect reject force."); if (rejectData != null && length > 0) { var shutdownPacket = PoolGetWithProperty(PacketProperty.Disconnect, length); shutdownPacket.ConnectionNumber = request.InternalPacket.ConnectionNumber; FastBitConverter.GetBytes(shutdownPacket.RawData, 1, request.InternalPacket.ConnectionTime); if (shutdownPacket.Size >= NetConstants.PossibleMtu[0]) NetDebug.WriteError("[Peer] Disconnect additional data size more than MTU!"); else Buffer.BlockCopy(rejectData, start, shutdownPacket.RawData, 9, length); SendRawAndRecycle(shutdownPacket, request.RemoteEndPoint); } lock (_requestsDict) _requestsDict.Remove(request.RemoteEndPoint); } else lock (_requestsDict) { if (TryGetPeer(request.RemoteEndPoint, out netPeer)) { //already have peer } else if (request.Result == ConnectionRequestResult.Reject) { netPeer = new NetPeer(this, request.RemoteEndPoint, GetNextPeerId()); netPeer.Reject(request.InternalPacket, rejectData, start, length); AddPeer(netPeer); NetDebug.Write(NetLogLevel.Trace, "[NM] Peer connect reject."); } else //Accept { netPeer = new NetPeer(this, request, GetNextPeerId()); AddPeer(netPeer); CreateEvent(NetEvent.EType.Connect, netPeer); NetDebug.Write(NetLogLevel.Trace, $"[NM] Received peer connection Id: {netPeer.ConnectTime}, EP: {netPeer}"); } _requestsDict.Remove(request.RemoteEndPoint); } return netPeer; } private int GetNextPeerId() { return _peerIds.TryDequeue(out int id) ? id : _lastPeerId++; } private void ProcessConnectRequest( IPEndPoint remoteEndPoint, NetPeer netPeer, NetConnectRequestPacket connRequest) { //if we have peer if (netPeer != null) { var processResult = netPeer.ProcessConnectRequest(connRequest); NetDebug.Write($"ConnectRequest LastId: {netPeer.ConnectTime}, NewId: {connRequest.ConnectionTime}, EP: {remoteEndPoint}, Result: {processResult}"); switch (processResult) { case ConnectRequestResult.Reconnection: DisconnectPeerForce(netPeer, DisconnectReason.Reconnect, 0, null); RemovePeer(netPeer); //go to new connection break; case ConnectRequestResult.NewConnection: RemovePeer(netPeer); //go to new connection break; case ConnectRequestResult.P2PLose: DisconnectPeerForce(netPeer, DisconnectReason.PeerToPeerConnection, 0, null); RemovePeer(netPeer); //go to new connection break; default: //no operations needed return; } //ConnectRequestResult.NewConnection //Set next connection number if(processResult != ConnectRequestResult.P2PLose) connRequest.ConnectionNumber = (byte)((netPeer.ConnectionNum + 1) % NetConstants.MaxConnectionNumber); //To reconnect peer } else { NetDebug.Write($"ConnectRequest Id: {connRequest.ConnectionTime}, EP: {remoteEndPoint}"); } ConnectionRequest req; lock (_requestsDict) { if (_requestsDict.TryGetValue(remoteEndPoint, out req)) { req.UpdateRequest(connRequest); return; } req = new ConnectionRequest(remoteEndPoint, connRequest, this); _requestsDict.Add(remoteEndPoint, req); } NetDebug.Write($"[NM] Creating request event: {connRequest.ConnectionTime}"); CreateEvent(NetEvent.EType.ConnectionRequest, connectionRequest: req); } private void OnMessageReceived(NetPacket packet, IPEndPoint remoteEndPoint) { if (packet.Size == 0) { PoolRecycle(packet); return; } #if DEBUG if (SimulatePacketLoss && _randomGenerator.NextDouble() * 100 < SimulationPacketLossChance) { //drop packet return; } if (SimulateLatency) { int latency = _randomGenerator.Next(SimulationMinLatency, SimulationMaxLatency); if (latency > MinLatencyThreshold) { lock (_pingSimulationList) { _pingSimulationList.Add(new IncomingData { Data = packet, EndPoint = remoteEndPoint, TimeWhenGet = DateTime.UtcNow.AddMilliseconds(latency) }); } //hold packet return; } } //ProcessEvents DebugMessageReceived(packet, remoteEndPoint); } private void DebugMessageReceived(NetPacket packet, IPEndPoint remoteEndPoint) { #endif var originalPacketSize = packet.Size; if (EnableStatistics) { Statistics.IncrementPacketsReceived(); Statistics.AddBytesReceived(originalPacketSize); } if (_ntpRequests.Count > 0 && _ntpRequests.TryGetValue(remoteEndPoint, out var request)) { if (packet.Size < 48) { NetDebug.Write(NetLogLevel.Trace, $"NTP response too short: {packet.Size}"); return; } byte[] copiedData = new byte[packet.Size]; Buffer.BlockCopy(packet.RawData, 0, copiedData, 0, packet.Size); NtpPacket ntpPacket = NtpPacket.FromServerResponse(copiedData, DateTime.UtcNow); try { ntpPacket.ValidateReply(); } catch (InvalidOperationException ex) { NetDebug.Write(NetLogLevel.Trace, $"NTP response error: {ex.Message}"); ntpPacket = null; } if (ntpPacket != null) { _ntpRequests.TryRemove(remoteEndPoint, out _); _ntpEventListener?.OnNtpResponse(ntpPacket); } return; } if (_extraPacketLayer != null) { _extraPacketLayer.ProcessInboundPacket(ref remoteEndPoint, ref packet.RawData, ref packet.Size); if (packet.Size == 0) return; } if (!packet.Verify()) { NetDebug.WriteError("[NM] DataReceived: bad!"); PoolRecycle(packet); return; } switch (packet.Property) { //special case connect request case PacketProperty.ConnectRequest: if (NetConnectRequestPacket.GetProtocolId(packet) != NetConstants.ProtocolId) { SendRawAndRecycle(PoolGetWithProperty(PacketProperty.InvalidProtocol), remoteEndPoint); return; } break; //unconnected messages case PacketProperty.Broadcast: if (!BroadcastReceiveEnabled) return; CreateEvent(NetEvent.EType.Broadcast, remoteEndPoint: remoteEndPoint, readerSource: packet); return; case PacketProperty.UnconnectedMessage: if (!UnconnectedMessagesEnabled) return; CreateEvent(NetEvent.EType.ReceiveUnconnected, remoteEndPoint: remoteEndPoint, readerSource: packet); return; case PacketProperty.NatMessage: if (NatPunchEnabled) NatPunchModule.ProcessMessage(remoteEndPoint, packet); return; } //Check normal packets bool peerFound = remoteEndPoint is NetPeer netPeer || TryGetPeer(remoteEndPoint, out netPeer); if (peerFound && EnableStatistics) { netPeer.Statistics.IncrementPacketsReceived(); netPeer.Statistics.AddBytesReceived(originalPacketSize); } switch (packet.Property) { case PacketProperty.ConnectRequest: var connRequest = NetConnectRequestPacket.FromData(packet); if (connRequest != null) ProcessConnectRequest(remoteEndPoint, netPeer, connRequest); break; case PacketProperty.PeerNotFound: if (peerFound) //local { if (netPeer.ConnectionState != ConnectionState.Connected) return; if (packet.Size == 1) { //first reply //send NetworkChanged packet netPeer.ResetMtu(); SendRaw(NetConnectAcceptPacket.MakeNetworkChanged(netPeer), remoteEndPoint); NetDebug.Write($"PeerNotFound sending connection info: {remoteEndPoint}"); } else if (packet.Size == 2 && packet.RawData[1] == 1) { //second reply DisconnectPeerForce(netPeer, DisconnectReason.PeerNotFound, 0, null); } } else if (packet.Size > 1) //remote { //check if this is old peer bool isOldPeer = false; if (AllowPeerAddressChange) { NetDebug.Write($"[NM] Looks like address change: {packet.Size}"); var remoteData = NetConnectAcceptPacket.FromData(packet); if (remoteData != null && remoteData.PeerNetworkChanged && remoteData.PeerId < _peersArray.Length) { _peersLock.EnterUpgradeableReadLock(); var peer = _peersArray[remoteData.PeerId]; _peersLock.ExitUpgradeableReadLock(); if (peer != null && peer.ConnectTime == remoteData.ConnectionTime && peer.ConnectionNum == remoteData.ConnectionNumber) { if (peer.ConnectionState == ConnectionState.Connected) { peer.InitiateEndPointChange(); CreateEvent(NetEvent.EType.PeerAddressChanged, peer, remoteEndPoint); NetDebug.Write("[NM] PeerNotFound change address of remote peer"); } isOldPeer = true; } } } PoolRecycle(packet); //else peer really not found if (!isOldPeer) { var secondResponse = PoolGetWithProperty(PacketProperty.PeerNotFound, 1); secondResponse.RawData[1] = 1; SendRawAndRecycle(secondResponse, remoteEndPoint); } } break; case PacketProperty.InvalidProtocol: if (peerFound && netPeer.ConnectionState == ConnectionState.Outgoing) DisconnectPeerForce(netPeer, DisconnectReason.InvalidProtocol, 0, null); break; case PacketProperty.Disconnect: if (peerFound) { var disconnectResult = netPeer.ProcessDisconnect(packet); if (disconnectResult == DisconnectResult.None) { PoolRecycle(packet); return; } DisconnectPeerForce( netPeer, disconnectResult == DisconnectResult.Disconnect ? DisconnectReason.RemoteConnectionClose : DisconnectReason.ConnectionRejected, 0, packet); } else { PoolRecycle(packet); } //Send shutdown SendRawAndRecycle(PoolGetWithProperty(PacketProperty.ShutdownOk), remoteEndPoint); break; case PacketProperty.ConnectAccept: if (!peerFound) return; var connAccept = NetConnectAcceptPacket.FromData(packet); if (connAccept != null && netPeer.ProcessConnectAccept(connAccept)) CreateEvent(NetEvent.EType.Connect, netPeer); break; default: if(peerFound) netPeer.ProcessPacket(packet); else SendRawAndRecycle(PoolGetWithProperty(PacketProperty.PeerNotFound), remoteEndPoint); break; } } internal void CreateReceiveEvent(NetPacket packet, DeliveryMethod method, byte channelNumber, int headerSize, NetPeer fromPeer) { NetEvent evt; if (UnsyncedEvents || UnsyncedReceiveEvent || _manualMode) { lock (_eventLock) { evt = _netEventPoolHead; if (evt == null) evt = new NetEvent(this); else _netEventPoolHead = evt.Next; } evt.Next = null; evt.Type = NetEvent.EType.Receive; evt.DataReader.SetSource(packet, headerSize); evt.Peer = fromPeer; evt.DeliveryMethod = method; evt.ChannelNumber = channelNumber; ProcessEvent(evt); } else { lock (_eventLock) { evt = _netEventPoolHead; if (evt == null) evt = new NetEvent(this); else _netEventPoolHead = evt.Next; evt.Next = null; evt.Type = NetEvent.EType.Receive; evt.DataReader.SetSource(packet, headerSize); evt.Peer = fromPeer; evt.DeliveryMethod = method; evt.ChannelNumber = channelNumber; if (_pendingEventTail == null) _pendingEventHead = evt; else _pendingEventTail.Next = evt; _pendingEventTail = evt; } } } /// /// Send data to all connected peers (channel - 0) /// /// DataWriter with data /// Send options (reliable, unreliable, etc.) public void SendToAll(NetDataWriter writer, DeliveryMethod options) { SendToAll(writer.Data, 0, writer.Length, options); } /// /// Send data to all connected peers (channel - 0) /// /// Data /// Send options (reliable, unreliable, etc.) public void SendToAll(byte[] data, DeliveryMethod options) { SendToAll(data, 0, data.Length, options); } /// /// Send data to all connected peers (channel - 0) /// /// Data /// Start of data /// Length of data /// Send options (reliable, unreliable, etc.) public void SendToAll(byte[] data, int start, int length, DeliveryMethod options) { SendToAll(data, start, length, 0, options); } /// /// Send data to all connected peers /// /// DataWriter with data /// Number of channel (from 0 to channelsCount - 1) /// Send options (reliable, unreliable, etc.) public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options) { SendToAll(writer.Data, 0, writer.Length, channelNumber, options); } /// /// Send data to all connected peers /// /// Data /// Number of channel (from 0 to channelsCount - 1) /// Send options (reliable, unreliable, etc.) public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options) { SendToAll(data, 0, data.Length, channelNumber, options); } /// /// Send data to all connected peers /// /// Data /// Start of data /// Length of data /// Number of channel (from 0 to channelsCount - 1) /// Send options (reliable, unreliable, etc.) public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options) { try { _peersLock.EnterReadLock(); for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) netPeer.Send(data, start, length, channelNumber, options); } finally { _peersLock.ExitReadLock(); } } /// /// Send data to all connected peers (channel - 0) /// /// DataWriter with data /// Send options (reliable, unreliable, etc.) /// Excluded peer public void SendToAll(NetDataWriter writer, DeliveryMethod options, NetPeer excludePeer) { SendToAll(writer.Data, 0, writer.Length, 0, options, excludePeer); } /// /// Send data to all connected peers (channel - 0) /// /// Data /// Send options (reliable, unreliable, etc.) /// Excluded peer public void SendToAll(byte[] data, DeliveryMethod options, NetPeer excludePeer) { SendToAll(data, 0, data.Length, 0, options, excludePeer); } /// /// Send data to all connected peers (channel - 0) /// /// Data /// Start of data /// Length of data /// Send options (reliable, unreliable, etc.) /// Excluded peer public void SendToAll(byte[] data, int start, int length, DeliveryMethod options, NetPeer excludePeer) { SendToAll(data, start, length, 0, options, excludePeer); } /// /// Send data to all connected peers /// /// DataWriter with data /// Number of channel (from 0 to channelsCount - 1) /// Send options (reliable, unreliable, etc.) /// Excluded peer public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options, NetPeer excludePeer) { SendToAll(writer.Data, 0, writer.Length, channelNumber, options, excludePeer); } /// /// Send data to all connected peers /// /// Data /// Number of channel (from 0 to channelsCount - 1) /// Send options (reliable, unreliable, etc.) /// Excluded peer public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options, NetPeer excludePeer) { SendToAll(data, 0, data.Length, channelNumber, options, excludePeer); } /// /// Send data to all connected peers /// /// Data /// Start of data /// Length of data /// Number of channel (from 0 to channelsCount - 1) /// Send options (reliable, unreliable, etc.) /// Excluded peer public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options, NetPeer excludePeer) { try { _peersLock.EnterReadLock(); for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) { if (netPeer != excludePeer) netPeer.Send(data, start, length, channelNumber, options); } } finally { _peersLock.ExitReadLock(); } } #if LITENETLIB_SPANS || NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1 || NETCOREAPP3_1 || NET5_0 || NETSTANDARD2_1 /// /// Send data to all connected peers (channel - 0) /// /// Data /// Send options (reliable, unreliable, etc.) public void SendToAll(ReadOnlySpan data, DeliveryMethod options) { SendToAll(data, 0, options, null); } /// /// Send data to all connected peers (channel - 0) /// /// Data /// Send options (reliable, unreliable, etc.) /// Excluded peer public void SendToAll(ReadOnlySpan data, DeliveryMethod options, NetPeer excludePeer) { SendToAll(data, 0, options, excludePeer); } /// /// Send data to all connected peers /// /// Data /// Number of channel (from 0 to channelsCount - 1) /// Send options (reliable, unreliable, etc.) /// Excluded peer public void SendToAll(ReadOnlySpan data, byte channelNumber, DeliveryMethod options, NetPeer excludePeer) { try { _peersLock.EnterReadLock(); for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) { if (netPeer != excludePeer) netPeer.Send(data, channelNumber, options); } } finally { _peersLock.ExitReadLock(); } } #endif /// /// Start logic thread and listening on available port /// public bool Start() { return Start(0); } /// /// Start logic thread and listening on selected port /// /// bind to specific ipv4 address /// bind to specific ipv6 address /// port to listen public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port) { return Start(addressIPv4, addressIPv6, port, false); } /// /// Start logic thread and listening on selected port /// /// bind to specific ipv4 address /// bind to specific ipv6 address /// port to listen public bool Start(string addressIPv4, string addressIPv6, int port) { IPAddress ipv4 = NetUtils.ResolveAddress(addressIPv4); IPAddress ipv6 = NetUtils.ResolveAddress(addressIPv6); return Start(ipv4, ipv6, port); } /// /// Start logic thread and listening on selected port /// /// port to listen public bool Start(int port) { return Start(IPAddress.Any, IPAddress.IPv6Any, port); } /// /// Start in manual mode and listening on selected port /// In this mode you should use ManualReceive (without PollEvents) for receive packets /// and ManualUpdate(...) for update and send packets /// This mode useful mostly for single-threaded servers /// /// bind to specific ipv4 address /// bind to specific ipv6 address /// port to listen public bool StartInManualMode(IPAddress addressIPv4, IPAddress addressIPv6, int port) { return Start(addressIPv4, addressIPv6, port, true); } /// /// Start in manual mode and listening on selected port /// In this mode you should use ManualReceive (without PollEvents) for receive packets /// and ManualUpdate(...) for update and send packets /// This mode useful mostly for single-threaded servers /// /// bind to specific ipv4 address /// bind to specific ipv6 address /// port to listen public bool StartInManualMode(string addressIPv4, string addressIPv6, int port) { IPAddress ipv4 = NetUtils.ResolveAddress(addressIPv4); IPAddress ipv6 = NetUtils.ResolveAddress(addressIPv6); return StartInManualMode(ipv4, ipv6, port); } /// /// Start in manual mode and listening on selected port /// In this mode you should use ManualReceive (without PollEvents) for receive packets /// and ManualUpdate(...) for update and send packets /// This mode useful mostly for single-threaded servers /// /// port to listen public bool StartInManualMode(int port) { return StartInManualMode(IPAddress.Any, IPAddress.IPv6Any, port); } /// /// Send message without connection /// /// Raw data /// Packet destination /// Operation result public bool SendUnconnectedMessage(byte[] message, IPEndPoint remoteEndPoint) { return SendUnconnectedMessage(message, 0, message.Length, remoteEndPoint); } /// /// Send message without connection. WARNING This method allocates a new IPEndPoint object and /// synchronously makes a DNS request. If you're calling this method every frame it will be /// much faster to just cache the IPEndPoint. /// /// Data serializer /// Packet destination IP or hostname /// Packet destination port /// Operation result public bool SendUnconnectedMessage(NetDataWriter writer, string address, int port) { IPEndPoint remoteEndPoint = NetUtils.MakeEndPoint(address, port); return SendUnconnectedMessage(writer.Data, 0, writer.Length, remoteEndPoint); } /// /// Send message without connection /// /// Data serializer /// Packet destination /// Operation result public bool SendUnconnectedMessage(NetDataWriter writer, IPEndPoint remoteEndPoint) { return SendUnconnectedMessage(writer.Data, 0, writer.Length, remoteEndPoint); } /// /// Send message without connection /// /// Raw data /// data start /// data length /// Packet destination /// Operation result public bool SendUnconnectedMessage(byte[] message, int start, int length, IPEndPoint remoteEndPoint) { //No need for CRC here, SendRaw does that NetPacket packet = PoolGetWithData(PacketProperty.UnconnectedMessage, message, start, length); return SendRawAndRecycle(packet, remoteEndPoint) > 0; } /// /// Triggers update and send logic immediately (works asynchronously) /// public void TriggerUpdate() { _updateTriggerEvent.Set(); } /// /// Receive all pending events. Call this in game update code /// In Manual mode it will call also socket Receive (which can be slow) /// public void PollEvents() { if (_manualMode) { if (_udpSocketv4 != null) ManualReceive(_udpSocketv4, _bufferEndPointv4); if (_udpSocketv6 != null && _udpSocketv6 != _udpSocketv4) ManualReceive(_udpSocketv6, _bufferEndPointv6); ProcessDelayedPackets(); return; } if (UnsyncedEvents) return; NetEvent pendingEvent; lock (_eventLock) { pendingEvent = _pendingEventHead; _pendingEventHead = null; _pendingEventTail = null; } while (pendingEvent != null) { var next = pendingEvent.Next; ProcessEvent(pendingEvent); pendingEvent = next; } } /// /// Connect to remote host /// /// Server IP or hostname /// Server Port /// Connection key /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting /// Manager is not running. Call public NetPeer Connect(string address, int port, string key) { return Connect(address, port, NetDataWriter.FromString(key)); } /// /// Connect to remote host /// /// Server IP or hostname /// Server Port /// Additional data for remote peer /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting /// Manager is not running. Call public NetPeer Connect(string address, int port, NetDataWriter connectionData) { IPEndPoint ep; try { ep = NetUtils.MakeEndPoint(address, port); } catch { CreateEvent(NetEvent.EType.Disconnect, disconnectReason: DisconnectReason.UnknownHost); return null; } return Connect(ep, connectionData); } /// /// Connect to remote host /// /// Server end point (ip and port) /// Connection key /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting /// Manager is not running. Call public NetPeer Connect(IPEndPoint target, string key) { return Connect(target, NetDataWriter.FromString(key)); } /// /// Connect to remote host /// /// Server end point (ip and port) /// Additional data for remote peer /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting /// Manager is not running. Call public NetPeer Connect(IPEndPoint target, NetDataWriter connectionData) { if (!IsRunning) throw new InvalidOperationException("Client is not running"); lock(_requestsDict) { if (_requestsDict.ContainsKey(target)) return null; byte connectionNumber = 0; if (TryGetPeer(target, out var peer)) { switch (peer.ConnectionState) { //just return already connected peer case ConnectionState.Connected: case ConnectionState.Outgoing: return peer; } //else reconnect connectionNumber = (byte)((peer.ConnectionNum + 1) % NetConstants.MaxConnectionNumber); RemovePeer(peer); } //Create reliable connection //And send connection request peer = new NetPeer(this, target, GetNextPeerId(), connectionNumber, connectionData); AddPeer(peer); return peer; } } /// /// Force closes connection and stop all threads. /// public void Stop() { Stop(true); } /// /// Force closes connection and stop all threads. /// /// Send disconnect messages public void Stop(bool sendDisconnectMessages) { if (!IsRunning) return; NetDebug.Write("[NM] Stop"); #if UNITY_2018_3_OR_NEWER _pausedSocketFix.Deinitialize(); _pausedSocketFix = null; #endif //Send last disconnect for(var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) netPeer.Shutdown(null, 0, 0, !sendDisconnectMessages); //Stop CloseSocket(); _updateTriggerEvent.Set(); if (!_manualMode) { _logicThread.Join(); _logicThread = null; } //clear peers ClearPeerSet(); _peerIds = new ConcurrentQueue(); _lastPeerId = 0; #if DEBUG lock (_pingSimulationList) _pingSimulationList.Clear(); #endif _connectedPeersCount = 0; _pendingEventHead = null; _pendingEventTail = null; } /// /// Return peers count with connection state /// /// peer connection state (you can use as bit flags) /// peers count public int GetPeersCount(ConnectionState peerState) { int count = 0; _peersLock.EnterReadLock(); for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) { if ((netPeer.ConnectionState & peerState) != 0) count++; } _peersLock.ExitReadLock(); return count; } /// /// Get copy of peers (without allocations) /// /// List that will contain result /// State of peers public void GetPeersNonAlloc(List peers, ConnectionState peerState) { peers.Clear(); _peersLock.EnterReadLock(); for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) { if ((netPeer.ConnectionState & peerState) != 0) peers.Add(netPeer); } _peersLock.ExitReadLock(); } /// /// Disconnect all peers without any additional data /// public void DisconnectAll() { DisconnectAll(null, 0, 0); } /// /// Disconnect all peers with shutdown message /// /// Data to send (must be less or equal MTU) /// Data start /// Data count public void DisconnectAll(byte[] data, int start, int count) { //Send disconnect packets _peersLock.EnterReadLock(); for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) { DisconnectPeer( netPeer, DisconnectReason.DisconnectPeerCalled, 0, false, data, start, count, null); } _peersLock.ExitReadLock(); } /// /// Immediately disconnect peer from server without additional data /// /// peer to disconnect public void DisconnectPeerForce(NetPeer peer) { DisconnectPeerForce(peer, DisconnectReason.DisconnectPeerCalled, 0, null); } /// /// Disconnect peer from server /// /// peer to disconnect public void DisconnectPeer(NetPeer peer) { DisconnectPeer(peer, null, 0, 0); } /// /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8) /// /// peer to disconnect /// additional data public void DisconnectPeer(NetPeer peer, byte[] data) { DisconnectPeer(peer, data, 0, data.Length); } /// /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8) /// /// peer to disconnect /// additional data public void DisconnectPeer(NetPeer peer, NetDataWriter writer) { DisconnectPeer(peer, writer.Data, 0, writer.Length); } /// /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8) /// /// peer to disconnect /// additional data /// data start /// data length public void DisconnectPeer(NetPeer peer, byte[] data, int start, int count) { DisconnectPeer( peer, DisconnectReason.DisconnectPeerCalled, 0, false, data, start, count, null); } /// /// Create the requests for NTP server /// /// NTP Server address. public void CreateNtpRequest(IPEndPoint endPoint) { _ntpRequests.TryAdd(endPoint, new NtpRequest(endPoint)); } /// /// Create the requests for NTP server /// /// NTP Server address. /// port public void CreateNtpRequest(string ntpServerAddress, int port) { IPEndPoint endPoint = NetUtils.MakeEndPoint(ntpServerAddress, port); _ntpRequests.TryAdd(endPoint, new NtpRequest(endPoint)); } /// /// Create the requests for NTP server (default port) /// /// NTP Server address. public void CreateNtpRequest(string ntpServerAddress) { IPEndPoint endPoint = NetUtils.MakeEndPoint(ntpServerAddress, NtpRequest.DefaultPort); _ntpRequests.TryAdd(endPoint, new NtpRequest(endPoint)); } public NetPeerEnumerator GetEnumerator() { return new NetPeerEnumerator(_headPeer); } IEnumerator IEnumerable.GetEnumerator() { return new NetPeerEnumerator(_headPeer); } IEnumerator IEnumerable.GetEnumerator() { return new NetPeerEnumerator(_headPeer); } } }