您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
1641 行
61 KiB
1641 行
61 KiB
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Main class for all network operations. Can be used as client and/or server.
|
|
/// </summary>
|
|
public class NetManager : INetSocketListener, IEnumerable<NetPeer>
|
|
{
|
|
private class IPEndPointComparer : IEqualityComparer<IPEndPoint>
|
|
{
|
|
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<NetPeer>
|
|
{
|
|
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<IncomingData> _pingSimulationList = new List<IncomingData>();
|
|
private readonly Random _randomGenerator = new Random();
|
|
private const int MinLatencyThreshold = 5;
|
|
#endif
|
|
|
|
private readonly NetSocket _socket;
|
|
private Thread _logicThread;
|
|
|
|
private readonly Queue<NetEvent> _netEventsQueue;
|
|
private NetEvent _netEventPoolHead;
|
|
private readonly INetEventListener _netEventListener;
|
|
private readonly IDeliveryEventListener _deliveryEventListener;
|
|
|
|
private readonly Dictionary<IPEndPoint, NetPeer> _peersDict;
|
|
private readonly Dictionary<IPEndPoint, ConnectionRequest> _requestsDict;
|
|
private readonly ReaderWriterLockSlim _peersLock;
|
|
private volatile NetPeer _headPeer;
|
|
private volatile int _connectedPeersCount;
|
|
private readonly List<NetPeer> _connectedPeerListCache;
|
|
private NetPeer[] _peersArray;
|
|
private readonly PacketLayerBase _extraPacketLayer;
|
|
private int _lastPeerId;
|
|
private readonly Queue<int> _peerIds;
|
|
private byte _channelsCount = 1;
|
|
|
|
internal readonly NetPacketPool NetPacketPool;
|
|
|
|
//config section
|
|
/// <summary>
|
|
/// Enable messages receiving without connection. (with SendUnconnectedMessage method)
|
|
/// </summary>
|
|
public bool UnconnectedMessagesEnabled = false;
|
|
|
|
/// <summary>
|
|
/// Enable nat punch messages
|
|
/// </summary>
|
|
public bool NatPunchEnabled = false;
|
|
|
|
/// <summary>
|
|
/// Library logic update and send period in milliseconds
|
|
/// </summary>
|
|
public int UpdateTime = 15;
|
|
|
|
/// <summary>
|
|
/// Interval for latency detection and checking connection
|
|
/// </summary>
|
|
public int PingInterval = 1000;
|
|
|
|
/// <summary>
|
|
/// If NetManager doesn't receive any packet from remote peer during this time then connection will be closed
|
|
/// (including library internal keepalive packets)
|
|
/// </summary>
|
|
public int DisconnectTimeout = 5000;
|
|
|
|
/// <summary>
|
|
/// Simulate packet loss by dropping random amount of packets. (Works only in DEBUG mode)
|
|
/// </summary>
|
|
public bool SimulatePacketLoss = false;
|
|
|
|
/// <summary>
|
|
/// Simulate latency by holding packets for random time. (Works only in DEBUG mode)
|
|
/// </summary>
|
|
public bool SimulateLatency = false;
|
|
|
|
/// <summary>
|
|
/// Chance of packet loss when simulation enabled. value in percents (1 - 100).
|
|
/// </summary>
|
|
public int SimulationPacketLossChance = 10;
|
|
|
|
/// <summary>
|
|
/// Minimum simulated latency
|
|
/// </summary>
|
|
public int SimulationMinLatency = 30;
|
|
|
|
/// <summary>
|
|
/// Maximum simulated latency
|
|
/// </summary>
|
|
public int SimulationMaxLatency = 100;
|
|
|
|
/// <summary>
|
|
/// Events automatically will be called without PollEvents method from another thread
|
|
/// </summary>
|
|
public bool UnsyncedEvents = false;
|
|
|
|
/// <summary>
|
|
/// If true - receive event will be called from "receive" thread immediately otherwise on PollEvents call
|
|
/// </summary>
|
|
public bool UnsyncedReceiveEvent = false;
|
|
|
|
/// <summary>
|
|
/// If true - delivery event will be called from "receive" thread immediately otherwise on PollEvents call
|
|
/// </summary>
|
|
public bool UnsyncedDeliveryEvent = false;
|
|
|
|
/// <summary>
|
|
/// Allows receive broadcast packets
|
|
/// </summary>
|
|
public bool BroadcastReceiveEnabled = false;
|
|
|
|
/// <summary>
|
|
/// Delay between initial connection attempts
|
|
/// </summary>
|
|
public int ReconnectDelay = 500;
|
|
|
|
/// <summary>
|
|
/// Maximum connection attempts before client stops and call disconnect event.
|
|
/// </summary>
|
|
public int MaxConnectAttempts = 10;
|
|
|
|
/// <summary>
|
|
/// Enables socket option "ReuseAddress" for specific purposes
|
|
/// </summary>
|
|
public bool ReuseAddress = false;
|
|
|
|
/// <summary>
|
|
/// Statistics of all connections
|
|
/// </summary>
|
|
public readonly NetStatistics Statistics;
|
|
|
|
/// <summary>
|
|
/// Toggles the collection of network statistics for the instance and all known peers
|
|
/// </summary>
|
|
public bool EnableStatistics = false;
|
|
|
|
/// <summary>
|
|
/// NatPunchModule for NAT hole punching operations
|
|
/// </summary>
|
|
public readonly NatPunchModule NatPunchModule;
|
|
|
|
/// <summary>
|
|
/// Returns true if socket listening and update thread is running
|
|
/// </summary>
|
|
public bool IsRunning { get { return _socket.IsRunning; } }
|
|
|
|
/// <summary>
|
|
/// Local EndPoint (host and port)
|
|
/// </summary>
|
|
public int LocalPort { get { return _socket.LocalPort; } }
|
|
|
|
/// <summary>
|
|
/// Automatically recycle NetPacketReader after OnReceive event
|
|
/// </summary>
|
|
public bool AutoRecycle;
|
|
|
|
/// <summary>
|
|
/// IPv6 support
|
|
/// </summary>
|
|
public IPv6Mode IPv6Enabled = IPv6Mode.SeparateSocket;
|
|
|
|
/// <summary>
|
|
/// First peer. Useful for Client mode
|
|
/// </summary>
|
|
public NetPeer FirstPeer
|
|
{
|
|
get { return _headPeer; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// QoS channel count per message type (value must be between 1 and 64 channels)
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns connected peers list (with internal cached list)
|
|
/// </summary>
|
|
public List<NetPeer> ConnectedPeerList
|
|
{
|
|
get
|
|
{
|
|
GetPeersNonAlloc(_connectedPeerListCache, ConnectionState.Connected);
|
|
return _connectedPeerListCache;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets peer by peer id
|
|
/// </summary>
|
|
/// <param name="id">id of peer</param>
|
|
/// <returns>Peer if peer with id exist, otherwise null</returns>
|
|
public NetPeer GetPeerById(int id)
|
|
{
|
|
return _peersArray[id];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns connected peers count
|
|
/// </summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// NetManager constructor
|
|
/// </summary>
|
|
/// <param name="listener">Network events listener (also can implement IDeliveryEventListener)</param>
|
|
/// <param name="extraPacketLayer">Extra processing of packages, like CRC checksum or encryption. All connected NetManagers must have same layer.</param>
|
|
public NetManager(INetEventListener listener, PacketLayerBase extraPacketLayer = null)
|
|
{
|
|
_socket = new NetSocket(this);
|
|
_netEventListener = listener;
|
|
_deliveryEventListener = listener as IDeliveryEventListener;
|
|
_netEventsQueue = new Queue<NetEvent>();
|
|
NetPacketPool = new NetPacketPool();
|
|
NatPunchModule = new NatPunchModule(_socket);
|
|
Statistics = new NetStatistics();
|
|
_connectedPeerListCache = new List<NetPeer>();
|
|
_peersDict = new Dictionary<IPEndPoint, NetPeer>(new IPEndPointComparer());
|
|
_requestsDict = new Dictionary<IPEndPoint, ConnectionRequest>(new IPEndPointComparer());
|
|
_peersLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
|
|
_peerIds = new Queue<int>();
|
|
_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<NetPeer>();
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send data to all connected peers (channel - 0)
|
|
/// </summary>
|
|
/// <param name="writer">DataWriter with data</param>
|
|
/// <param name="options">Send options (reliable, unreliable, etc.)</param>
|
|
public void SendToAll(NetDataWriter writer, DeliveryMethod options)
|
|
{
|
|
SendToAll(writer.Data, 0, writer.Length, options);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send data to all connected peers (channel - 0)
|
|
/// </summary>
|
|
/// <param name="data">Data</param>
|
|
/// <param name="options">Send options (reliable, unreliable, etc.)</param>
|
|
public void SendToAll(byte[] data, DeliveryMethod options)
|
|
{
|
|
SendToAll(data, 0, data.Length, options);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send data to all connected peers (channel - 0)
|
|
/// </summary>
|
|
/// <param name="data">Data</param>
|
|
/// <param name="start">Start of data</param>
|
|
/// <param name="length">Length of data</param>
|
|
/// <param name="options">Send options (reliable, unreliable, etc.)</param>
|
|
public void SendToAll(byte[] data, int start, int length, DeliveryMethod options)
|
|
{
|
|
SendToAll(data, start, length, 0, options);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send data to all connected peers
|
|
/// </summary>
|
|
/// <param name="writer">DataWriter with data</param>
|
|
/// <param name="channelNumber">Number of channel (from 0 to channelsCount - 1)</param>
|
|
/// <param name="options">Send options (reliable, unreliable, etc.)</param>
|
|
public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options)
|
|
{
|
|
SendToAll(writer.Data, 0, writer.Length, channelNumber, options);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send data to all connected peers
|
|
/// </summary>
|
|
/// <param name="data">Data</param>
|
|
/// <param name="channelNumber">Number of channel (from 0 to channelsCount - 1)</param>
|
|
/// <param name="options">Send options (reliable, unreliable, etc.)</param>
|
|
public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options)
|
|
{
|
|
SendToAll(data, 0, data.Length, channelNumber, options);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send data to all connected peers
|
|
/// </summary>
|
|
/// <param name="data">Data</param>
|
|
/// <param name="start">Start of data</param>
|
|
/// <param name="length">Length of data</param>
|
|
/// <param name="channelNumber">Number of channel (from 0 to channelsCount - 1)</param>
|
|
/// <param name="options">Send options (reliable, unreliable, etc.)</param>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send data to all connected peers (channel - 0)
|
|
/// </summary>
|
|
/// <param name="writer">DataWriter with data</param>
|
|
/// <param name="options">Send options (reliable, unreliable, etc.)</param>
|
|
/// <param name="excludePeer">Excluded peer</param>
|
|
public void SendToAll(NetDataWriter writer, DeliveryMethod options, NetPeer excludePeer)
|
|
{
|
|
SendToAll(writer.Data, 0, writer.Length, 0, options, excludePeer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send data to all connected peers (channel - 0)
|
|
/// </summary>
|
|
/// <param name="data">Data</param>
|
|
/// <param name="options">Send options (reliable, unreliable, etc.)</param>
|
|
/// <param name="excludePeer">Excluded peer</param>
|
|
public void SendToAll(byte[] data, DeliveryMethod options, NetPeer excludePeer)
|
|
{
|
|
SendToAll(data, 0, data.Length, 0, options, excludePeer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send data to all connected peers (channel - 0)
|
|
/// </summary>
|
|
/// <param name="data">Data</param>
|
|
/// <param name="start">Start of data</param>
|
|
/// <param name="length">Length of data</param>
|
|
/// <param name="options">Send options (reliable, unreliable, etc.)</param>
|
|
/// <param name="excludePeer">Excluded peer</param>
|
|
public void SendToAll(byte[] data, int start, int length, DeliveryMethod options, NetPeer excludePeer)
|
|
{
|
|
SendToAll(data, start, length, 0, options, excludePeer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send data to all connected peers
|
|
/// </summary>
|
|
/// <param name="writer">DataWriter with data</param>
|
|
/// <param name="channelNumber">Number of channel (from 0 to channelsCount - 1)</param>
|
|
/// <param name="options">Send options (reliable, unreliable, etc.)</param>
|
|
/// <param name="excludePeer">Excluded peer</param>
|
|
public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options, NetPeer excludePeer)
|
|
{
|
|
SendToAll(writer.Data, 0, writer.Length, channelNumber, options, excludePeer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send data to all connected peers
|
|
/// </summary>
|
|
/// <param name="data">Data</param>
|
|
/// <param name="channelNumber">Number of channel (from 0 to channelsCount - 1)</param>
|
|
/// <param name="options">Send options (reliable, unreliable, etc.)</param>
|
|
/// <param name="excludePeer">Excluded peer</param>
|
|
public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options, NetPeer excludePeer)
|
|
{
|
|
SendToAll(data, 0, data.Length, channelNumber, options, excludePeer);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Send data to all connected peers
|
|
/// </summary>
|
|
/// <param name="data">Data</param>
|
|
/// <param name="start">Start of data</param>
|
|
/// <param name="length">Length of data</param>
|
|
/// <param name="channelNumber">Number of channel (from 0 to channelsCount - 1)</param>
|
|
/// <param name="options">Send options (reliable, unreliable, etc.)</param>
|
|
/// <param name="excludePeer">Excluded peer</param>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start logic thread and listening on available port
|
|
/// </summary>
|
|
public bool Start()
|
|
{
|
|
return Start(0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start logic thread and listening on selected port
|
|
/// </summary>
|
|
/// <param name="addressIPv4">bind to specific ipv4 address</param>
|
|
/// <param name="addressIPv6">bind to specific ipv6 address</param>
|
|
/// <param name="port">port to listen</param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start logic thread and listening on selected port
|
|
/// </summary>
|
|
/// <param name="addressIPv4">bind to specific ipv4 address</param>
|
|
/// <param name="addressIPv6">bind to specific ipv6 address</param>
|
|
/// <param name="port">port to listen</param>
|
|
public bool Start(string addressIPv4, string addressIPv6, int port)
|
|
{
|
|
IPAddress ipv4 = NetUtils.ResolveAddress(addressIPv4);
|
|
IPAddress ipv6 = NetUtils.ResolveAddress(addressIPv6);
|
|
return Start(ipv4, ipv6, port);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start logic thread and listening on selected port
|
|
/// </summary>
|
|
/// <param name="port">port to listen</param>
|
|
public bool Start(int port)
|
|
{
|
|
return Start(IPAddress.Any, IPAddress.IPv6Any, port);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send message without connection
|
|
/// </summary>
|
|
/// <param name="message">Raw data</param>
|
|
/// <param name="remoteEndPoint">Packet destination</param>
|
|
/// <returns>Operation result</returns>
|
|
public bool SendUnconnectedMessage(byte[] message, IPEndPoint remoteEndPoint)
|
|
{
|
|
return SendUnconnectedMessage(message, 0, message.Length, remoteEndPoint);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send message without connection
|
|
/// </summary>
|
|
/// <param name="writer">Data serializer</param>
|
|
/// <param name="remoteEndPoint">Packet destination</param>
|
|
/// <returns>Operation result</returns>
|
|
public bool SendUnconnectedMessage(NetDataWriter writer, IPEndPoint remoteEndPoint)
|
|
{
|
|
return SendUnconnectedMessage(writer.Data, 0, writer.Length, remoteEndPoint);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send message without connection
|
|
/// </summary>
|
|
/// <param name="message">Raw data</param>
|
|
/// <param name="start">data start</param>
|
|
/// <param name="length">data length</param>
|
|
/// <param name="remoteEndPoint">Packet destination</param>
|
|
/// <returns>Operation result</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Flush all queued packets of all peers
|
|
/// </summary>
|
|
public void Flush()
|
|
{
|
|
for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
|
|
netPeer.Flush();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Receive all pending events. Call this in game update code
|
|
/// </summary>
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Connect to remote host
|
|
/// </summary>
|
|
/// <param name="address">Server IP or hostname</param>
|
|
/// <param name="port">Server Port</param>
|
|
/// <param name="key">Connection key</param>
|
|
/// <returns>New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting</returns>
|
|
/// <exception cref="InvalidOperationException">Manager is not running. Call <see cref="Start()"/></exception>
|
|
public NetPeer Connect(string address, int port, string key)
|
|
{
|
|
return Connect(address, port, NetDataWriter.FromString(key));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Connect to remote host
|
|
/// </summary>
|
|
/// <param name="address">Server IP or hostname</param>
|
|
/// <param name="port">Server Port</param>
|
|
/// <param name="connectionData">Additional data for remote peer</param>
|
|
/// <returns>New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting</returns>
|
|
/// <exception cref="InvalidOperationException">Manager is not running. Call <see cref="Start()"/></exception>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Connect to remote host
|
|
/// </summary>
|
|
/// <param name="target">Server end point (ip and port)</param>
|
|
/// <param name="key">Connection key</param>
|
|
/// <returns>New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting</returns>
|
|
/// <exception cref="InvalidOperationException">Manager is not running. Call <see cref="Start()"/></exception>
|
|
public NetPeer Connect(IPEndPoint target, string key)
|
|
{
|
|
return Connect(target, NetDataWriter.FromString(key));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Connect to remote host
|
|
/// </summary>
|
|
/// <param name="target">Server end point (ip and port)</param>
|
|
/// <param name="connectionData">Additional data for remote peer</param>
|
|
/// <returns>New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting</returns>
|
|
/// <exception cref="InvalidOperationException">Manager is not running. Call <see cref="Start()"/></exception>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Force closes connection and stop all threads.
|
|
/// </summary>
|
|
public void Stop()
|
|
{
|
|
Stop(true);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Force closes connection and stop all threads.
|
|
/// </summary>
|
|
/// <param name="sendDisconnectMessages">Send disconnect messages</param>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return peers count with connection state
|
|
/// </summary>
|
|
/// <param name="peerState">peer connection state (you can use as bit flags)</param>
|
|
/// <returns>peers count</returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get copy of peers (without allocations)
|
|
/// </summary>
|
|
/// <param name="peers">List that will contain result</param>
|
|
/// <param name="peerState">State of peers</param>
|
|
public void GetPeersNonAlloc(List<NetPeer> 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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disconnect all peers without any additional data
|
|
/// </summary>
|
|
public void DisconnectAll()
|
|
{
|
|
DisconnectAll(null, 0, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disconnect all peers with shutdown message
|
|
/// </summary>
|
|
/// <param name="data">Data to send (must be less or equal MTU)</param>
|
|
/// <param name="start">Data start</param>
|
|
/// <param name="count">Data count</param>
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Immediately disconnect peer from server without additional data
|
|
/// </summary>
|
|
/// <param name="peer">peer to disconnect</param>
|
|
public void DisconnectPeerForce(NetPeer peer)
|
|
{
|
|
DisconnectPeerForce(peer, DisconnectReason.DisconnectPeerCalled, 0, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disconnect peer from server
|
|
/// </summary>
|
|
/// <param name="peer">peer to disconnect</param>
|
|
public void DisconnectPeer(NetPeer peer)
|
|
{
|
|
DisconnectPeer(peer, null, 0, 0);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8)
|
|
/// </summary>
|
|
/// <param name="peer">peer to disconnect</param>
|
|
/// <param name="data">additional data</param>
|
|
public void DisconnectPeer(NetPeer peer, byte[] data)
|
|
{
|
|
DisconnectPeer(peer, data, 0, data.Length);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8)
|
|
/// </summary>
|
|
/// <param name="peer">peer to disconnect</param>
|
|
/// <param name="writer">additional data</param>
|
|
public void DisconnectPeer(NetPeer peer, NetDataWriter writer)
|
|
{
|
|
DisconnectPeer(peer, writer.Data, 0, writer.Length);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8)
|
|
/// </summary>
|
|
/// <param name="peer">peer to disconnect</param>
|
|
/// <param name="data">additional data</param>
|
|
/// <param name="start">data start</param>
|
|
/// <param name="count">data length</param>
|
|
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<NetPeer> IEnumerable<NetPeer>.GetEnumerator()
|
|
{
|
|
return new NetPeerEnumerator(_headPeer);
|
|
}
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
{
|
|
return new NetPeerEnumerator(_headPeer);
|
|
}
|
|
}
|
|
}
|