using System; using System.Collections; 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 enum IPv6Mode { Disabled, SeparateSocket, DualMode } 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.NetPacketPool.Recycle(_packet); _packet = null; _manager.RecycleEvent(_evt); } public void Recycle() { if(_manager.AutoRecycle) throw new Exception("Recycle called with AutoRecycle enabled"); RecycleInternal(); } } internal sealed class NetEvent { public NetEvent Next; public enum EType { Connect, Disconnect, Receive, ReceiveUnconnected, Error, ConnectionLatencyUpdated, Broadcast, ConnectionRequest, MessageDelivered } 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 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 class NetManager : INetSocketListener, IEnumerable { private class IPEndPointComparer : IEqualityComparer { public bool Equals(IPEndPoint x, IPEndPoint y) { return x.Address.Equals(y.Address) && x.Port == y.Port; } public int GetHashCode(IPEndPoint obj) { return obj.GetHashCode(); } } 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 { get { return _p; } } object IEnumerator.Current { get { return _p; } } } #if DEBUG private struct IncomingData { public byte[] 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 readonly NetSocket _socket; private Thread _logicThread; private readonly Queue _netEventsQueue; private NetEvent _netEventPoolHead; private readonly INetEventListener _netEventListener; private readonly IDeliveryEventListener _deliveryEventListener; private readonly Dictionary _peersDict; private readonly Dictionary _requestsDict; private readonly ReaderWriterLockSlim _peersLock; private volatile NetPeer _headPeer; private volatile int _connectedPeersCount; private readonly List _connectedPeerListCache; private NetPeer[] _peersArray; private readonly PacketLayerBase _extraPacketLayer; private int _lastPeerId; private readonly Queue _peerIds; private byte _channelsCount = 1; internal readonly NetPacketPool NetPacketPool; //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 /// public int UpdateTime = 15; /// /// Interval for latency detection and checking connection /// public int PingInterval = 1000; /// /// If NetManager doesn't receive any packet from remote peer during this time 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 /// public int SimulationMinLatency = 30; /// /// Maximum simulated latency /// 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 /// 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; /// /// Statistics of all connections /// public readonly NetStatistics Statistics; /// /// 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 { return _socket.IsRunning; } } /// /// Local EndPoint (host and port) /// public int LocalPort { get { return _socket.LocalPort; } } /// /// Automatically recycle NetPacketReader after OnReceive event /// public bool AutoRecycle; /// /// IPv6 support /// public IPv6Mode IPv6Enabled = IPv6Mode.SeparateSocket; /// /// First peer. Useful for Client mode /// public NetPeer FirstPeer { get { return _headPeer; } } /// /// QoS channel count per message type (value must be between 1 and 64 channels) /// public byte ChannelsCount { get { return _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; } } /// /// Gets peer by peer id /// /// id of peer /// Peer if peer with id exist, otherwise null public NetPeer GetPeerById(int id) { return _peersArray[id]; } /// /// Returns connected peers count /// public int ConnectedPeersCount { get { return _connectedPeersCount; } } public int ExtraPacketSizeForLayer { get { return _extraPacketLayer != null ? _extraPacketLayer.ExtraPacketSizeForLayer : 0; } } private bool TryGetPeer(IPEndPoint endPoint, out NetPeer peer) { _peersLock.EnterReadLock(); bool result = _peersDict.TryGetValue(endPoint, out peer); _peersLock.ExitReadLock(); return result; } private void AddPeer(NetPeer peer) { _peersLock.EnterWriteLock(); if (_headPeer != null) { peer.NextPeer = _headPeer; _headPeer.PrevPeer = peer; } _headPeer = peer; _peersDict.Add(peer.EndPoint, peer); if (peer.Id >= _peersArray.Length) { int newSize = _peersArray.Length * 2; while (peer.Id >= newSize) newSize *= 2; Array.Resize(ref _peersArray, newSize); } _peersArray[peer.Id] = peer; _peersLock.ExitWriteLock(); } private void RemovePeer(NetPeer peer) { _peersLock.EnterWriteLock(); RemovePeerInternal(peer); _peersLock.ExitWriteLock(); } private void RemovePeerInternal(NetPeer peer) { if (!_peersDict.Remove(peer.EndPoint)) return; if (peer == _headPeer) _headPeer = peer.NextPeer; if (peer.PrevPeer != null) peer.PrevPeer.NextPeer = peer.NextPeer; if (peer.NextPeer != null) peer.NextPeer.PrevPeer = peer.PrevPeer; peer.PrevPeer = null; _peersArray[peer.Id] = null; lock (_peerIds) _peerIds.Enqueue(peer.Id); } /// /// 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) { _socket = new NetSocket(this); _netEventListener = listener; _deliveryEventListener = listener as IDeliveryEventListener; _netEventsQueue = new Queue(); NetPacketPool = new NetPacketPool(); NatPunchModule = new NatPunchModule(_socket); Statistics = new NetStatistics(); _connectedPeerListCache = new List(); _peersDict = new Dictionary(new IPEndPointComparer()); _requestsDict = new Dictionary(new IPEndPointComparer()); _peersLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion); _peerIds = new Queue(); _peersArray = new NetPeer[32]; _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 int SendRawAndRecycle(NetPacket packet, IPEndPoint remoteEndPoint) { var result = SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint); NetPacketPool.Recycle(packet); return result; } internal int SendRaw(NetPacket packet, IPEndPoint remoteEndPoint) { return SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint); } internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEndPoint) { if (!_socket.IsRunning) return 0; SocketError errorCode = 0; int result; if (_extraPacketLayer != null) { var expandedPacket = NetPacketPool.GetPacket(length + _extraPacketLayer.ExtraPacketSizeForLayer); Buffer.BlockCopy(message, start, expandedPacket.RawData, 0, length); int newStart = 0; _extraPacketLayer.ProcessOutBoundPacket(ref expandedPacket.RawData, ref newStart, ref length); result = _socket.SendTo(expandedPacket.RawData, newStart, length, remoteEndPoint, ref errorCode); NetPacketPool.Recycle(expandedPacket); } else { result = _socket.SendTo(message, start, length, remoteEndPoint, ref errorCode); } NetPeer fromPeer; switch (errorCode) { case SocketError.MessageSize: NetDebug.Write(NetLogLevel.Trace, "[SRD] 10040, datalen: {0}", length); return -1; case SocketError.HostUnreachable: if (TryGetPeer(remoteEndPoint, out fromPeer)) DisconnectPeerForce(fromPeer, DisconnectReason.HostUnreachable, errorCode, null); CreateEvent(NetEvent.EType.Error, remoteEndPoint: remoteEndPoint, errorCode: errorCode); return -1; case SocketError.NetworkUnreachable: if (TryGetPeer(remoteEndPoint, out fromPeer)) DisconnectPeerForce(fromPeer, DisconnectReason.NetworkUnreachable, errorCode, null); CreateEvent(NetEvent.EType.Error, remoteEndPoint: remoteEndPoint, errorCode: errorCode); return -1; } if (result <= 0) return 0; if (EnableStatistics) { Statistics.PacketsSent++; Statistics.BytesSent += (uint)length; } return result; } 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, 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; do { evt = _netEventPoolHead; if (evt == null) { evt = new NetEvent(this); break; } } while (evt != Interlocked.CompareExchange(ref _netEventPoolHead, evt.Next, evt)); evt.Type = type; evt.DataReader.SetSource(readerSource, readerSource == null ? 0 : readerSource.GetHeaderSize()); evt.Peer = peer; evt.RemoteEndPoint = remoteEndPoint; evt.Latency = latency; evt.ErrorCode = errorCode; evt.DisconnectReason = disconnectReason; evt.ConnectionRequest = connectionRequest; evt.DeliveryMethod = deliveryMethod; evt.UserData = userData; if (unsyncEvent) { ProcessEvent(evt); } else { lock (_netEventsQueue) _netEventsQueue.Enqueue(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.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; } //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; do { evt.Next = _netEventPoolHead; } while (evt.Next != Interlocked.CompareExchange(ref _netEventPoolHead, evt, evt.Next)); } //Update function private void UpdateLogic() { var peersToRemove = new List(); var stopwatch = new Stopwatch(); stopwatch.Start(); while (_socket.IsRunning) { #if DEBUG if (SimulateLatency) { var time = DateTime.UtcNow; lock (_pingSimulationList) { for (int i = 0; i < _pingSimulationList.Count; i++) { var incomingData = _pingSimulationList[i]; if (incomingData.TimeWhenGet <= time) { DataReceived(incomingData.Data, incomingData.Data.Length, incomingData.EndPoint); _pingSimulationList.RemoveAt(i); i--; } } } } #endif ulong totalPacketLoss = 0; int elapsed = (int)stopwatch.ElapsedMilliseconds; elapsed = elapsed <= 0 ? 1 : elapsed; stopwatch.Reset(); stopwatch.Start(); 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 (EnableStatistics) { totalPacketLoss += netPeer.Statistics.PacketLoss; } } } if (peersToRemove.Count > 0) { _peersLock.EnterWriteLock(); for (int i = 0; i < peersToRemove.Count; i++) RemovePeerInternal(peersToRemove[i]); _peersLock.ExitWriteLock(); peersToRemove.Clear(); } if (EnableStatistics) { Statistics.PacketLoss = totalPacketLoss; } int sleepTime = UpdateTime - (int)stopwatch.ElapsedMilliseconds; if (sleepTime > 0) Thread.Sleep(sleepTime); } stopwatch.Stop(); } void INetSocketListener.OnMessageReceived(byte[] data, int length, SocketError errorCode, IPEndPoint remoteEndPoint) { if (errorCode != 0) { CreateEvent(NetEvent.EType.Error, errorCode: errorCode); NetDebug.WriteError("[NM] Receive error: {0}", errorCode); return; } #if DEBUG if (SimulatePacketLoss && _randomGenerator.NextDouble() * 100 < SimulationPacketLossChance) { //drop packet return; } if (SimulateLatency) { int latency = _randomGenerator.Next(SimulationMinLatency, SimulationMaxLatency); if (latency > MinLatencyThreshold) { byte[] holdedData = new byte[length]; Buffer.BlockCopy(data, 0, holdedData, 0, length); lock (_pingSimulationList) { _pingSimulationList.Add(new IncomingData { Data = holdedData, EndPoint = remoteEndPoint, TimeWhenGet = DateTime.UtcNow.AddMilliseconds(latency) }); } //hold packet return; } } #endif try { //ProcessEvents DataReceived(data, length, remoteEndPoint); } catch(Exception e) { //protects socket receive thread NetDebug.WriteError("[NM] SocketReceiveThread error: " + e ); } } 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 = NetPacketPool.GetWithProperty(PacketProperty.Disconnect, length); shutdownPacket.ConnectionNumber = request.ConnectionNumber; FastBitConverter.GetBytes(shutdownPacket.RawData, 1, request.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); } } else { _peersLock.EnterUpgradeableReadLock(); if (_peersDict.TryGetValue(request.RemoteEndPoint, out netPeer)) { //already have peer _peersLock.ExitUpgradeableReadLock(); } else if (request.Result == ConnectionRequestResult.Reject) { netPeer = new NetPeer(this, request.RemoteEndPoint, GetNextPeerId()); netPeer.Reject(request.ConnectionTime, request.ConnectionNumber, rejectData, start, length); AddPeer(netPeer); _peersLock.ExitUpgradeableReadLock(); NetDebug.Write(NetLogLevel.Trace, "[NM] Peer connect reject."); } else //Accept { netPeer = new NetPeer(this, request.RemoteEndPoint, GetNextPeerId(), request.ConnectionTime, request.ConnectionNumber); AddPeer(netPeer); _peersLock.ExitUpgradeableReadLock(); CreateEvent(NetEvent.EType.Connect, netPeer); NetDebug.Write(NetLogLevel.Trace, "[NM] Received peer connection Id: {0}, EP: {1}", netPeer.ConnectTime, netPeer.EndPoint); } } lock(_requestsDict) _requestsDict.Remove(request.RemoteEndPoint); return netPeer; } private int GetNextPeerId() { lock (_peerIds) return _peerIds.Count == 0 ? _lastPeerId++ : _peerIds.Dequeue(); } private void ProcessConnectRequest( IPEndPoint remoteEndPoint, NetPeer netPeer, NetConnectRequestPacket connRequest) { byte connectionNumber = connRequest.ConnectionNumber; ConnectionRequest req; //if we have peer if (netPeer != null) { var processResult = netPeer.ProcessConnectRequest(connRequest); NetDebug.Write("ConnectRequest LastId: {0}, NewId: {1}, EP: {2}, Result: {3}", netPeer.ConnectTime, connRequest.ConnectionTime, remoteEndPoint, 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) connectionNumber = (byte)((netPeer.ConnectionNum + 1) % NetConstants.MaxConnectionNumber); //To reconnect peer } else { NetDebug.Write("ConnectRequest Id: {0}, EP: {1}", connRequest.ConnectionTime, remoteEndPoint); } lock (_requestsDict) { if (_requestsDict.TryGetValue(remoteEndPoint, out req)) { req.UpdateRequest(connRequest); return; } req = new ConnectionRequest( connRequest.ConnectionTime, connectionNumber, connRequest.Data, remoteEndPoint, this); _requestsDict.Add(remoteEndPoint, req); } NetDebug.Write("[NM] Creating request event: " + connRequest.ConnectionTime); CreateEvent(NetEvent.EType.ConnectionRequest, connectionRequest: req); } private void DataReceived(byte[] reusableBuffer, int count, IPEndPoint remoteEndPoint) { if (EnableStatistics) { Statistics.PacketsReceived++; Statistics.BytesReceived += (uint)count; } if (_extraPacketLayer != null) { _extraPacketLayer.ProcessInboundPacket(ref reusableBuffer, ref count); if (count == 0) return; } //empty packet if (reusableBuffer[0] == (byte) PacketProperty.Empty) return; //Try read packet NetPacket packet = NetPacketPool.GetPacket(count); if (!packet.FromBytes(reusableBuffer, 0, count)) { NetPacketPool.Recycle(packet); NetDebug.WriteError("[NM] DataReceived: bad!"); return; } switch (packet.Property) { //special case connect request case PacketProperty.ConnectRequest: if (NetConnectRequestPacket.GetProtocolId(packet) != NetConstants.ProtocolId) { SendRawAndRecycle(NetPacketPool.GetWithProperty(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 NetPeer netPeer; _peersLock.EnterReadLock(); bool peerFound = _peersDict.TryGetValue(remoteEndPoint, out netPeer); _peersLock.ExitReadLock(); switch (packet.Property) { case PacketProperty.ConnectRequest: var connRequest = NetConnectRequestPacket.FromData(packet); if (connRequest != null) ProcessConnectRequest(remoteEndPoint, netPeer, connRequest); break; case PacketProperty.PeerNotFound: if (peerFound) { if (netPeer.ConnectionState != ConnectionState.Connected) return; if (packet.Size == 1) { //first reply var p = NetPacketPool.GetWithProperty(PacketProperty.PeerNotFound, 9); p.RawData[1] = 0; FastBitConverter.GetBytes(p.RawData, 2, netPeer.ConnectTime); SendRawAndRecycle(p, remoteEndPoint); NetDebug.Write("PeerNotFound sending connectTime: {0}", netPeer.ConnectTime); } else if (packet.Size == 10 && packet.RawData[1] == 1 && BitConverter.ToInt64(packet.RawData, 2) == netPeer.ConnectTime) { //second reply NetDebug.Write("PeerNotFound received our connectTime: {0}", netPeer.ConnectTime); DisconnectPeerForce(netPeer, DisconnectReason.RemoteConnectionClose, 0, null); } } else if (packet.Size == 10 && packet.RawData[1] == 0) { //send reply back packet.RawData[1] = 1; SendRawAndRecycle(packet, 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) { NetPacketPool.Recycle(packet); return; } DisconnectPeerForce( netPeer, disconnectResult == DisconnectResult.Disconnect ? DisconnectReason.RemoteConnectionClose : DisconnectReason.ConnectionRejected, 0, packet); } else { NetPacketPool.Recycle(packet); } //Send shutdown SendRawAndRecycle(NetPacketPool.GetWithProperty(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(NetPacketPool.GetWithProperty(PacketProperty.PeerNotFound), remoteEndPoint); break; } } internal void CreateReceiveEvent(NetPacket packet, DeliveryMethod method, int headerSize, NetPeer fromPeer) { NetEvent evt; do { evt = _netEventPoolHead; if (evt == null) { evt = new NetEvent(this); break; } } while (evt != Interlocked.CompareExchange(ref _netEventPoolHead, evt.Next, evt)); evt.Type = NetEvent.EType.Receive; evt.DataReader.SetSource(packet, headerSize); evt.Peer = fromPeer; evt.DeliveryMethod = method; if (UnsyncedEvents || UnsyncedReceiveEvent) { ProcessEvent(evt); } else { lock (_netEventsQueue) _netEventsQueue.Enqueue(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(); } } /// /// 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) { if (!_socket.Bind(addressIPv4, addressIPv6, port, ReuseAddress, IPv6Enabled)) return false; _logicThread = new Thread(UpdateLogic) { Name = "LogicThread", IsBackground = true }; _logicThread.Start(); return true; } /// /// 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); } /// /// 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 /// /// 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 = NetPacketPool.GetWithData(PacketProperty.UnconnectedMessage, message, start, length); return SendRawAndRecycle(packet, remoteEndPoint) > 0; } public bool SendBroadcast(NetDataWriter writer, int port) { return SendBroadcast(writer.Data, 0, writer.Length, port); } public bool SendBroadcast(byte[] data, int port) { return SendBroadcast(data, 0, data.Length, port); } public bool SendBroadcast(byte[] data, int start, int length, int port) { NetPacket packet; if (_extraPacketLayer != null) { var headerSize = NetPacket.GetHeaderSize(PacketProperty.Broadcast); packet = NetPacketPool.GetPacket(headerSize + length + _extraPacketLayer.ExtraPacketSizeForLayer); packet.Property = PacketProperty.Broadcast; Buffer.BlockCopy(data, start, packet.RawData, headerSize, length); var checksumComputeStart = 0; int preCrcLength = length + headerSize; _extraPacketLayer.ProcessOutBoundPacket(ref packet.RawData, ref checksumComputeStart, ref preCrcLength); } else { packet = NetPacketPool.GetWithData(PacketProperty.Broadcast, data, start, length); } bool result = _socket.SendBroadcast(packet.RawData, 0, packet.Size, port); NetPacketPool.Recycle(packet); return result; } /// /// Flush all queued packets of all peers /// public void Flush() { for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) netPeer.Flush(); } /// /// Receive all pending events. Call this in game update code /// public void PollEvents() { if (UnsyncedEvents) return; int eventsCount = _netEventsQueue.Count; for(int i = 0; i < eventsCount; i++) { NetEvent evt; lock (_netEventsQueue) evt = _netEventsQueue.Dequeue(); ProcessEvent(evt); } } /// /// 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 (!_socket.IsRunning) throw new InvalidOperationException("Client is not running"); NetPeer peer; byte connectionNumber = 0; if (_requestsDict.ContainsKey(target)) return null; _peersLock.EnterUpgradeableReadLock(); if (_peersDict.TryGetValue(target, out peer)) { switch (peer.ConnectionState) { //just return already connected peer case ConnectionState.Connected: case ConnectionState.Outgoing: _peersLock.ExitUpgradeableReadLock(); 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); _peersLock.ExitUpgradeableReadLock(); 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 (!_socket.IsRunning) return; NetDebug.Write("[NM] Stop"); //Send last disconnect for(var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer) netPeer.Shutdown(null, 0, 0, !sendDisconnectMessages); //Stop _socket.Close(false); _logicThread.Join(); _logicThread = null; //clear peers _peersLock.EnterWriteLock(); _headPeer = null; _peersDict.Clear(); _peersArray = new NetPeer[32]; _peersLock.ExitWriteLock(); lock(_peerIds) _peerIds.Clear(); #if DEBUG lock (_pingSimulationList) _pingSimulationList.Clear(); #endif _connectedPeersCount = 0; lock(_netEventsQueue) _netEventsQueue.Clear(); } /// /// 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); } public NetPeerEnumerator GetEnumerator() { return new NetPeerEnumerator(_headPeer); } IEnumerator IEnumerable.GetEnumerator() { return new NetPeerEnumerator(_headPeer); } IEnumerator IEnumerable.GetEnumerator() { return new NetPeerEnumerator(_headPeer); } } }