#if DEBUG && !UNITY_WP_8_1 && !UNITY_WSA #if DEBUG #define STATS_ENABLED #endif using System; using System.Collections.Generic; using System.Text; using System.Threading; using FlyingWormConsole3.LiteNetLib.Utils; namespace FlyingWormConsole3.LiteNetLib { public sealed class NetManager { internal delegate void OnMessageReceived(byte[] data, int length, int errorCode, NetEndPoint remoteEndPoint); private struct FlowMode { public int PacketsPerSecond; public int StartRtt; } private enum NetEventType { Connect, Disconnect, Receive, ReceiveUnconnected, Error, ConnectionLatencyUpdated, DiscoveryRequest, DiscoveryResponse } private sealed class NetEvent { public NetPeer Peer; public readonly NetDataReader DataReader = new NetDataReader(); public NetEventType Type; public NetEndPoint RemoteEndPoint; public int AdditionalData; public DisconnectReason DisconnectReason; } #if DEBUG private struct IncomingData { public byte[] Data; public NetEndPoint EndPoint; public DateTime TimeWhenGet; } private readonly List _pingSimulationList = new List(); private readonly Random _randomGenerator = new Random(); private const int MinLatencyTreshold = 5; #endif private readonly NetSocket _socket; private readonly List _flowModes; private readonly NetThread _logicThread; private readonly Queue _netEventsQueue; private readonly Stack _netEventsPool; private readonly INetEventListener _netEventListener; private readonly NetPeerCollection _peers; private readonly int _maxConnections; private readonly string _connectKey; private readonly NetPacketPool _netPacketPool; //config section public bool UnconnectedMessagesEnabled = false; public bool NatPunchEnabled = false; public int UpdateTime { get { return _logicThread.SleepTime; } set { _logicThread.SleepTime = value; } } public int PingInterval = NetConstants.DefaultPingInterval; public long DisconnectTimeout = 5000; public bool SimulatePacketLoss = false; public bool SimulateLatency = false; public int SimulationPacketLossChance = 10; public int SimulationMinLatency = 30; public int SimulationMaxLatency = 100; public bool UnsyncedEvents = false; public bool DiscoveryEnabled = false; public bool MergeEnabled = false; public int ReconnectDelay = 500; public int MaxConnectAttempts = 10; public bool ReuseAddress = false; private const int DefaultUpdateTime = 15; //stats public ulong PacketsSent { get; private set; } public ulong PacketsReceived { get; private set; } public ulong BytesSent { get; private set; } public ulong BytesReceived { get; private set; } //modules public readonly NatPunchModule NatPunchModule; /// /// Returns true if socket listening and update thread is running /// public bool IsRunning { get { return _logicThread.IsRunning; } } /// /// Local EndPoint (host and port) /// public NetEndPoint LocalEndPoint { get { return _socket.LocalEndPoint; } } /// /// Connected peers count /// public int PeersCount { get { return _peers.Count; } } public string ConnectKey { get { return _connectKey; } } //Flow public void AddFlowMode(int startRtt, int packetsPerSecond) { var fm = new FlowMode {PacketsPerSecond = packetsPerSecond, StartRtt = startRtt}; if (_flowModes.Count > 0 && startRtt < _flowModes[0].StartRtt) { _flowModes.Insert(0, fm); } else { _flowModes.Add(fm); } } internal int GetPacketsPerSecond(int flowMode) { if (flowMode < 0 || _flowModes.Count == 0) return 0; return _flowModes[flowMode].PacketsPerSecond; } internal int GetMaxFlowMode() { return _flowModes.Count - 1; } internal int GetStartRtt(int flowMode) { if (flowMode < 0 || _flowModes.Count == 0) return 0; return _flowModes[flowMode].StartRtt; } internal NetPacketPool PacketPool { get { return _netPacketPool; } } /// /// NetManager constructor with maxConnections = 1 (usable for client) /// /// Network events listener /// Application key (must be same with remote host for establish connection) public NetManager(INetEventListener listener, string connectKey) : this(listener, 1, connectKey) { } /// /// NetManager constructor /// /// Network events listener /// Maximum connections (incoming and outcoming) /// Application key (must be same with remote host for establish connection) public NetManager(INetEventListener listener, int maxConnections, string connectKey) { _logicThread = new NetThread("LogicThread", DefaultUpdateTime, UpdateLogic); _socket = new NetSocket(ReceiveLogic); _netEventListener = listener; _flowModes = new List(); _netEventsQueue = new Queue(); _netEventsPool = new Stack(); _netPacketPool = new NetPacketPool(); NatPunchModule = new NatPunchModule(this); _connectKey = connectKey; _peers = new NetPeerCollection(maxConnections); _maxConnections = maxConnections; _connectKey = connectKey; } internal void ConnectionLatencyUpdated(NetPeer fromPeer, int latency) { var evt = CreateEvent(NetEventType.ConnectionLatencyUpdated); evt.Peer = fromPeer; evt.AdditionalData = latency; EnqueueEvent(evt); } internal bool SendRawAndRecycle(NetPacket packet, NetEndPoint remoteEndPoint) { var result = SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint); _netPacketPool.Recycle(packet); return result; } internal bool SendRaw(byte[] message, int start, int length, NetEndPoint remoteEndPoint) { if (!IsRunning) return false; int errorCode = 0; bool result = _socket.SendTo(message, start, length, remoteEndPoint, ref errorCode) > 0; //10040 message to long... need to check //10065 no route to host if (errorCode != 0 && errorCode != 10040 && errorCode != 10065) { //Send error NetPeer fromPeer; if (_peers.TryGetValue(remoteEndPoint, out fromPeer)) { DisconnectPeer(fromPeer, DisconnectReason.SocketSendError, errorCode, false, null, 0, 0); } var netEvent = CreateEvent(NetEventType.Error); netEvent.RemoteEndPoint = remoteEndPoint; netEvent.AdditionalData = errorCode; EnqueueEvent(netEvent); return false; } if (errorCode == 10040) { NetUtils.DebugWrite(ConsoleColor.Red, "[SRD] 10040, datalen: {0}", length); return false; } #if STATS_ENABLED PacketsSent++; BytesSent += (uint)length; #endif return result; } private void DisconnectPeer( NetPeer peer, DisconnectReason reason, int socketErrorCode, bool sendDisconnectPacket, byte[] data, int start, int count) { if (sendDisconnectPacket) { if (count + 8 >= peer.Mtu) { //Drop additional data data = null; count = 0; NetUtils.DebugWriteError("[NM] Disconnect additional data size more than MTU - 8!"); } var disconnectPacket = _netPacketPool.Get(PacketProperty.Disconnect, 8 + count); FastBitConverter.GetBytes(disconnectPacket.RawData, 1, peer.ConnectId); if (data != null) { Buffer.BlockCopy(data, start, disconnectPacket.RawData, 9, count); } SendRawAndRecycle(disconnectPacket, peer.EndPoint); } var netEvent = CreateEvent(NetEventType.Disconnect); netEvent.Peer = peer; netEvent.AdditionalData = socketErrorCode; netEvent.DisconnectReason = reason; EnqueueEvent(netEvent); RemovePeer(peer.EndPoint); } private void ClearPeers() { lock (_peers) { #if WINRT && !UNITY_EDITOR _socket.ClearPeers(); #endif _peers.Clear(); } } private void RemovePeer(NetEndPoint endPoint) { _peers.Remove(endPoint); #if WINRT && !UNITY_EDITOR _socket.RemovePeer(endPoint); #endif } private void RemovePeerAt(int idx) { #if WINRT && !UNITY_EDITOR var endPoint = _peers[idx].EndPoint; _socket.RemovePeer(endPoint); #endif _peers.RemoveAt(idx); } private NetEvent CreateEvent(NetEventType type) { NetEvent evt = null; lock (_netEventsPool) { if (_netEventsPool.Count > 0) { evt = _netEventsPool.Pop(); } } if(evt == null) { evt = new NetEvent(); } evt.Type = type; return evt; } private void EnqueueEvent(NetEvent evt) { if (UnsyncedEvents) { ProcessEvent(evt); } else { lock (_netEventsQueue) { _netEventsQueue.Enqueue(evt); } } } private void ProcessEvent(NetEvent evt) { switch (evt.Type) { case NetEventType.Connect: _netEventListener.OnPeerConnected(evt.Peer); break; case NetEventType.Disconnect: var info = new DisconnectInfo { Reason = evt.DisconnectReason, AdditionalData = evt.DataReader, SocketErrorCode = evt.AdditionalData }; _netEventListener.OnPeerDisconnected(evt.Peer, info); break; case NetEventType.Receive: _netEventListener.OnNetworkReceive(evt.Peer, evt.DataReader); break; case NetEventType.ReceiveUnconnected: _netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.Default); break; case NetEventType.DiscoveryRequest: _netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.DiscoveryRequest); break; case NetEventType.DiscoveryResponse: _netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.DiscoveryResponse); break; case NetEventType.Error: _netEventListener.OnNetworkError(evt.RemoteEndPoint, evt.AdditionalData); break; case NetEventType.ConnectionLatencyUpdated: _netEventListener.OnNetworkLatencyUpdate(evt.Peer, evt.AdditionalData); break; } //Recycle evt.DataReader.Clear(); evt.Peer = null; evt.AdditionalData = 0; evt.RemoteEndPoint = null; lock (_netEventsPool) { _netEventsPool.Push(evt); } } //Update function private void UpdateLogic() { #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 //Process acks lock (_peers) { int delta = _logicThread.SleepTime; for(int i = 0; i < _peers.Count; i++) { var netPeer = _peers[i]; if (netPeer.ConnectionState == ConnectionState.Connected && netPeer.TimeSinceLastPacket > DisconnectTimeout) { NetUtils.DebugWrite("[NM] Disconnect by timeout: {0} > {1}", netPeer.TimeSinceLastPacket, DisconnectTimeout); var netEvent = CreateEvent(NetEventType.Disconnect); netEvent.Peer = netPeer; netEvent.DisconnectReason = DisconnectReason.Timeout; EnqueueEvent(netEvent); RemovePeerAt(i); i--; } else if(netPeer.ConnectionState == ConnectionState.Disconnected) { var netEvent = CreateEvent(NetEventType.Disconnect); netEvent.Peer = netPeer; netEvent.DisconnectReason = DisconnectReason.ConnectionFailed; EnqueueEvent(netEvent); RemovePeerAt(i); i--; } else { netPeer.Update(delta); } } } } private void ReceiveLogic(byte[] data, int length, int errorCode, NetEndPoint remoteEndPoint) { //Receive some info if (errorCode == 0) { #if DEBUG bool receivePacket = true; if (SimulatePacketLoss && _randomGenerator.Next(100/SimulationPacketLossChance) == 0) { receivePacket = false; } else if (SimulateLatency) { int latency = _randomGenerator.Next(SimulationMinLatency, SimulationMaxLatency); if (latency > MinLatencyTreshold) { 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) }); } receivePacket = false; } } if (receivePacket) //DataReceived #endif //ProcessEvents DataReceived(data, length, remoteEndPoint); } else //Error on receive { ClearPeers(); var netEvent = CreateEvent(NetEventType.Error); netEvent.AdditionalData = errorCode; EnqueueEvent(netEvent); } } private void DataReceived(byte[] reusableBuffer, int count, NetEndPoint remoteEndPoint) { #if STATS_ENABLED PacketsReceived++; BytesReceived += (uint) count; #endif //Try read packet NetPacket packet = _netPacketPool.GetAndRead(reusableBuffer, 0, count); if (packet == null) { NetUtils.DebugWriteError("[NM] DataReceived: bad!"); return; } //Check unconnected switch (packet.Property) { case PacketProperty.DiscoveryRequest: if(DiscoveryEnabled) { var netEvent = CreateEvent(NetEventType.DiscoveryRequest); netEvent.RemoteEndPoint = remoteEndPoint; netEvent.DataReader.SetSource(packet.RawData, NetConstants.HeaderSize); EnqueueEvent(netEvent); } return; case PacketProperty.DiscoveryResponse: { var netEvent = CreateEvent(NetEventType.DiscoveryResponse); netEvent.RemoteEndPoint = remoteEndPoint; netEvent.DataReader.SetSource(packet.RawData, NetConstants.HeaderSize); EnqueueEvent(netEvent); } return; case PacketProperty.UnconnectedMessage: if (UnconnectedMessagesEnabled) { var netEvent = CreateEvent(NetEventType.ReceiveUnconnected); netEvent.RemoteEndPoint = remoteEndPoint; netEvent.DataReader.SetSource(packet.RawData, NetConstants.HeaderSize); EnqueueEvent(netEvent); } return; case PacketProperty.NatIntroduction: case PacketProperty.NatIntroductionRequest: case PacketProperty.NatPunchMessage: { if (NatPunchEnabled) NatPunchModule.ProcessMessage(remoteEndPoint, packet); return; } } //Check normal packets NetPeer netPeer; //Check peers Monitor.Enter(_peers); int peersCount = _peers.Count; if (_peers.TryGetValue(remoteEndPoint, out netPeer)) { Monitor.Exit(_peers); //Send if (packet.Property == PacketProperty.Disconnect) { if (BitConverter.ToInt64(packet.RawData, 1) != netPeer.ConnectId) { //Old or incorrect disconnect _netPacketPool.Recycle(packet); return; } var netEvent = CreateEvent(NetEventType.Disconnect); netEvent.Peer = netPeer; netEvent.DataReader.SetSource(packet.RawData, 5, packet.Size - 5); netEvent.DisconnectReason = DisconnectReason.RemoteConnectionClose; EnqueueEvent(netEvent); _peers.Remove(netPeer.EndPoint); //do not recycle because no sense) } else if (packet.Property == PacketProperty.ConnectAccept) { if (netPeer.ProcessConnectAccept(packet)) { var connectEvent = CreateEvent(NetEventType.Connect); connectEvent.Peer = netPeer; EnqueueEvent(connectEvent); } _netPacketPool.Recycle(packet); } else { netPeer.ProcessPacket(packet); } return; } try { if (peersCount < _maxConnections && packet.Property == PacketProperty.ConnectRequest) { int protoId = BitConverter.ToInt32(packet.RawData, 1); if (protoId != NetConstants.ProtocolId) { NetUtils.DebugWrite(ConsoleColor.Cyan, "[NM] Peer connect reject. Invalid protocol ID: " + protoId); return; } string peerKey = Encoding.UTF8.GetString(packet.RawData, 13, packet.Size - 13); if (peerKey != _connectKey) { NetUtils.DebugWrite(ConsoleColor.Cyan, "[NM] Peer connect reject. Invalid key: " + peerKey); return; } //Getting new id for peer long connectionId = BitConverter.ToInt64(packet.RawData, 5); //response with id netPeer = new NetPeer(this, remoteEndPoint, connectionId); NetUtils.DebugWrite(ConsoleColor.Cyan, "[NM] Received peer connect request Id: {0}, EP: {1}", netPeer.ConnectId, remoteEndPoint); //clean incoming packet _netPacketPool.Recycle(packet); _peers.Add(remoteEndPoint, netPeer); var netEvent = CreateEvent(NetEventType.Connect); netEvent.Peer = netPeer; EnqueueEvent(netEvent); } } finally { Monitor.Exit(_peers); } } internal void ReceiveFromPeer(NetPacket packet, NetEndPoint remoteEndPoint) { NetPeer fromPeer; if (_peers.TryGetValue(remoteEndPoint, out fromPeer)) { NetUtils.DebugWrite(ConsoleColor.Cyan, "[NM] Received message"); var netEvent = CreateEvent(NetEventType.Receive); netEvent.Peer = fromPeer; netEvent.RemoteEndPoint = fromPeer.EndPoint; netEvent.DataReader.SetSource(packet.GetPacketData()); EnqueueEvent(netEvent); } } /// /// Send data to all connected peers /// /// DataWriter with data /// Send options (reliable, unreliable, etc.) public void SendToAll(NetDataWriter writer, SendOptions options) { SendToAll(writer.Data, 0, writer.Length, options); } /// /// Send data to all connected peers /// /// Data /// Send options (reliable, unreliable, etc.) public void SendToAll(byte[] data, SendOptions options) { SendToAll(data, 0, data.Length, options); } /// /// Send data to all connected peers /// /// Data /// Start of data /// Length of data /// Send options (reliable, unreliable, etc.) public void SendToAll(byte[] data, int start, int length, SendOptions options) { lock (_peers) { for(int i = 0; i < _peers.Count; i++) { _peers[i].Send(data, start, length, options); } } } /// /// Send data to all connected peers /// /// DataWriter with data /// Send options (reliable, unreliable, etc.) /// Excluded peer public void SendToAll(NetDataWriter writer, SendOptions options, NetPeer excludePeer) { SendToAll(writer.Data, 0, writer.Length, options, excludePeer); } /// /// Send data to all connected peers /// /// Data /// Send options (reliable, unreliable, etc.) /// Excluded peer public void SendToAll(byte[] data, SendOptions options, NetPeer excludePeer) { SendToAll(data, 0, data.Length, options, excludePeer); } /// /// Send data to all connected peers /// /// Data /// Start of data /// Length of data /// Send options (reliable, unreliable, etc.) /// Excluded peer public void SendToAll(byte[] data, int start, int length, SendOptions options, NetPeer excludePeer) { lock (_peers) { for (int i = 0; i < _peers.Count; i++) { var netPeer = _peers[i]; if (netPeer != excludePeer) { netPeer.Send(data, start, length, options); } } } } /// /// Start logic thread and listening on available port /// public bool Start() { return Start(0); } /// /// Start logic thread and listening on selected port /// /// port to listen public bool Start(int port) { if (IsRunning) { return false; } _netEventsQueue.Clear(); if (!_socket.Bind(port, ReuseAddress)) return false; _logicThread.Start(); return true; } /// /// Send message without connection /// /// Raw data /// Packet destination /// Operation result public bool SendUnconnectedMessage(byte[] message, NetEndPoint remoteEndPoint) { return SendUnconnectedMessage(message, 0, message.Length, remoteEndPoint); } /// /// Send message without connection /// /// Data serializer /// Packet destination /// Operation result public bool SendUnconnectedMessage(NetDataWriter writer, NetEndPoint 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, NetEndPoint remoteEndPoint) { if (!IsRunning) return false; var packet = _netPacketPool.GetWithData(PacketProperty.UnconnectedMessage, message, start, length); bool result = SendRawAndRecycle(packet, remoteEndPoint); return result; } public bool SendDiscoveryRequest(NetDataWriter writer, int port) { return SendDiscoveryRequest(writer.Data, 0, writer.Length, port); } public bool SendDiscoveryRequest(byte[] data, int port) { return SendDiscoveryRequest(data, 0, data.Length, port); } public bool SendDiscoveryRequest(byte[] data, int start, int length, int port) { if (!IsRunning) return false; var packet = _netPacketPool.GetWithData(PacketProperty.DiscoveryRequest, data, start, length); bool result = _socket.SendBroadcast(packet.RawData, 0, packet.Size, port); _netPacketPool.Recycle(packet); return result; } public bool SendDiscoveryResponse(NetDataWriter writer, NetEndPoint remoteEndPoint) { return SendDiscoveryResponse(writer.Data, 0, writer.Length, remoteEndPoint); } public bool SendDiscoveryResponse(byte[] data, NetEndPoint remoteEndPoint) { return SendDiscoveryResponse(data, 0, data.Length, remoteEndPoint); } public bool SendDiscoveryResponse(byte[] data, int start, int length, NetEndPoint remoteEndPoint) { if (!IsRunning) return false; var packet = _netPacketPool.GetWithData(PacketProperty.DiscoveryResponse, data, start, length); bool result = SendRawAndRecycle(packet, remoteEndPoint); return result; } /// /// Flush all queued packets of all peers /// public void Flush() { lock (_peers) { for (int i = 0; i < _peers.Count; i++) { _peers[i].Flush(); } } } /// /// Receive all pending events. Call this in game update code /// public void PollEvents() { if (UnsyncedEvents) return; while (_netEventsQueue.Count > 0) { NetEvent evt; lock (_netEventsQueue) { evt = _netEventsQueue.Dequeue(); } ProcessEvent(evt); } } /// /// Connect to remote host /// /// Server IP or hostname /// Server Port public void Connect(string address, int port) { //Create target endpoint NetEndPoint ep = new NetEndPoint(address, port); Connect(ep); } /// /// Connect to remote host /// /// Server end point (ip and port) public void Connect(NetEndPoint target) { if (!IsRunning) { throw new Exception("Client is not running"); } lock (_peers) { if (_peers.ContainsAddress(target) || _peers.Count >= _maxConnections) { //Already connected return; } //Create reliable connection //And request connection var newPeer = new NetPeer(this, target, 0); _peers.Add(target, newPeer); } } /// /// Force closes connection and stop all threads. /// public void Stop() { //Send disconnect packets lock (_peers) { for (int i = 0; i < _peers.Count; i++) { var disconnectPacket = _netPacketPool.Get(PacketProperty.Disconnect, 8); FastBitConverter.GetBytes(disconnectPacket.RawData, 1, _peers[i].ConnectId); SendRawAndRecycle(disconnectPacket, _peers[i].EndPoint); } } //Clear ClearPeers(); //Stop if (IsRunning) { _logicThread.Stop(); _socket.Close(); } } /// /// Get first peer. Usefull for Client mode /// /// public NetPeer GetFirstPeer() { lock (_peers) { if (_peers.Count > 0) { return _peers[0]; } } return null; } /// /// Get copy of current connected peers /// /// Array with connected peers public NetPeer[] GetPeers() { NetPeer[] peers; lock (_peers) { peers = _peers.ToArray(); } return peers; } /// /// Get copy of current connected peers (without allocations) /// /// List that will contain result public void GetPeersNonAlloc(List peers) { peers.Clear(); lock (_peers) { for(int i = 0; i < _peers.Count; i++) { peers.Add(_peers[i]); } } } /// /// 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) { if (peer != null && _peers.ContainsAddress(peer.EndPoint)) { DisconnectPeer(peer, DisconnectReason.DisconnectPeerCalled, 0, true, data, start, count); } } } } #endif