您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
1173 行
45 KiB
1173 行
45 KiB
#if DEBUG
|
|
#define STATS_ENABLED
|
|
#endif
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Net;
|
|
using System.Threading;
|
|
using LiteNetLib.Utils;
|
|
|
|
namespace LiteNetLib
|
|
{
|
|
/// <summary>
|
|
/// Peer connection state
|
|
/// </summary>
|
|
[Flags]
|
|
public enum ConnectionState : byte
|
|
{
|
|
Outgoing = 1 << 1,
|
|
Connected = 1 << 2,
|
|
ShutdownRequested = 1 << 3,
|
|
Disconnected = 1 << 4,
|
|
Any = Outgoing | Connected | ShutdownRequested
|
|
}
|
|
|
|
internal enum ConnectRequestResult
|
|
{
|
|
None,
|
|
P2PLose, //when peer connecting
|
|
Reconnection, //when peer was connected
|
|
NewConnection //when peer was disconnected
|
|
}
|
|
|
|
internal enum DisconnectResult
|
|
{
|
|
None,
|
|
Reject,
|
|
Disconnect
|
|
}
|
|
|
|
internal enum ShutdownResult
|
|
{
|
|
None,
|
|
Success,
|
|
WasConnected
|
|
}
|
|
|
|
/// <summary>
|
|
/// Network peer. Main purpose is sending messages to specific peer.
|
|
/// </summary>
|
|
public class NetPeer
|
|
{
|
|
//Ping and RTT
|
|
private int _rtt;
|
|
private int _avgRtt;
|
|
private int _rttCount;
|
|
private double _resendDelay = 27.0;
|
|
private int _pingSendTimer;
|
|
private int _rttResetTimer;
|
|
private readonly Stopwatch _pingTimer = new Stopwatch();
|
|
private int _timeSinceLastPacket;
|
|
private long _remoteDelta;
|
|
|
|
//Common
|
|
private readonly NetPacketPool _packetPool;
|
|
private readonly object _flushLock = new object();
|
|
private readonly object _sendLock = new object();
|
|
private readonly object _shutdownLock = new object();
|
|
|
|
internal volatile NetPeer NextPeer;
|
|
internal NetPeer PrevPeer;
|
|
|
|
internal byte ConnectionNum
|
|
{
|
|
get { return _connectNum; }
|
|
private set
|
|
{
|
|
_connectNum = value;
|
|
_mergeData.ConnectionNumber = value;
|
|
_pingPacket.ConnectionNumber = value;
|
|
_pongPacket.ConnectionNumber = value;
|
|
}
|
|
}
|
|
|
|
//Channels
|
|
private readonly Queue<NetPacket> _unreliableChannel;
|
|
private readonly BaseChannel[] _channels;
|
|
private BaseChannel _headChannel;
|
|
|
|
//MTU
|
|
private int _mtu;
|
|
private int _mtuIdx;
|
|
private bool _finishMtu;
|
|
private int _mtuCheckTimer;
|
|
private int _mtuCheckAttempts;
|
|
private const int MtuCheckDelay = 1000;
|
|
private const int MaxMtuCheckAttempts = 4;
|
|
private readonly object _mtuMutex = new object();
|
|
|
|
//Fragment
|
|
private class IncomingFragments
|
|
{
|
|
public NetPacket[] Fragments;
|
|
public int ReceivedCount;
|
|
public int TotalSize;
|
|
public byte ChannelId;
|
|
}
|
|
private ushort _fragmentId;
|
|
private readonly Dictionary<ushort, IncomingFragments> _holdedFragments;
|
|
private readonly Dictionary<ushort, ushort> _deliveredFramgnets;
|
|
|
|
//Merging
|
|
private readonly NetPacket _mergeData;
|
|
private int _mergePos;
|
|
private int _mergeCount;
|
|
|
|
//Connection
|
|
private int _connectAttempts;
|
|
private int _connectTimer;
|
|
private long _connectTime;
|
|
private byte _connectNum;
|
|
private ConnectionState _connectionState;
|
|
private NetPacket _shutdownPacket;
|
|
private const int ShutdownDelay = 300;
|
|
private int _shutdownTimer;
|
|
private readonly NetPacket _pingPacket;
|
|
private readonly NetPacket _pongPacket;
|
|
private readonly NetPacket _connectRequestPacket;
|
|
private readonly NetPacket _connectAcceptPacket;
|
|
|
|
/// <summary>
|
|
/// Peer ip address and port
|
|
/// </summary>
|
|
public readonly IPEndPoint EndPoint;
|
|
|
|
/// <summary>
|
|
/// Peer parent NetManager
|
|
/// </summary>
|
|
public readonly NetManager NetManager;
|
|
|
|
/// <summary>
|
|
/// Current connection state
|
|
/// </summary>
|
|
public ConnectionState ConnectionState { get { return _connectionState; } }
|
|
|
|
/// <summary>
|
|
/// Connection time for internal purposes
|
|
/// </summary>
|
|
internal long ConnectTime { get { return _connectTime; } }
|
|
|
|
/// <summary>
|
|
/// Peer id can be used as key in your dictionary of peers
|
|
/// </summary>
|
|
public readonly int Id;
|
|
|
|
/// <summary>
|
|
/// Current ping in milliseconds
|
|
/// </summary>
|
|
public int Ping { get { return _avgRtt/2; } }
|
|
|
|
/// <summary>
|
|
/// Current MTU - Maximum Transfer Unit ( maximum udp packet size without fragmentation )
|
|
/// </summary>
|
|
public int Mtu { get { return _mtu; } }
|
|
|
|
/// <summary>
|
|
/// Delta with remote time in ticks (not accurate)
|
|
/// positive - remote time > our time
|
|
/// </summary>
|
|
public long RemoteTimeDelta
|
|
{
|
|
get { return _remoteDelta; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remote UTC time (not accurate)
|
|
/// </summary>
|
|
public DateTime RemoteUtcTime
|
|
{
|
|
get { return new DateTime(DateTime.UtcNow.Ticks + _remoteDelta); }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Time since last packet received (including internal library packets)
|
|
/// </summary>
|
|
public int TimeSinceLastPacket { get { return _timeSinceLastPacket; } }
|
|
|
|
internal double ResendDelay { get { return _resendDelay; } }
|
|
|
|
/// <summary>
|
|
/// Application defined object containing data about the connection
|
|
/// </summary>
|
|
public object Tag;
|
|
|
|
/// <summary>
|
|
/// Statistics of peer connection
|
|
/// </summary>
|
|
public readonly NetStatistics Statistics;
|
|
|
|
//incoming connection constructor
|
|
internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id)
|
|
{
|
|
Id = id;
|
|
Statistics = new NetStatistics();
|
|
_packetPool = netManager.NetPacketPool;
|
|
NetManager = netManager;
|
|
SetMtu(0);
|
|
|
|
EndPoint = remoteEndPoint;
|
|
_connectionState = ConnectionState.Connected;
|
|
_mergeData = new NetPacket(PacketProperty.Merged, NetConstants.MaxPacketSize);
|
|
_pongPacket = new NetPacket(PacketProperty.Pong, 0);
|
|
_pingPacket = new NetPacket(PacketProperty.Ping, 0) {Sequence = 1};
|
|
|
|
_unreliableChannel = new Queue<NetPacket>(64);
|
|
_headChannel = null;
|
|
_holdedFragments = new Dictionary<ushort, IncomingFragments>();
|
|
_deliveredFramgnets = new Dictionary<ushort, ushort>();
|
|
|
|
_channels = new BaseChannel[netManager.ChannelsCount * 4];
|
|
}
|
|
|
|
private void SetMtu(int mtuIdx)
|
|
{
|
|
_mtu = NetConstants.PossibleMtu[mtuIdx] - NetManager.ExtraPacketSizeForLayer;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns packets count in queue for reliable channel
|
|
/// </summary>
|
|
/// <param name="channelNumber">number of channel 0-63</param>
|
|
/// <param name="ordered">type of channel ReliableOrdered or ReliableUnordered</param>
|
|
/// <returns>packets count in channel queue</returns>
|
|
public int GetPacketsCountInReliableQueue(byte channelNumber, bool ordered)
|
|
{
|
|
int idx = channelNumber * 4 +
|
|
(byte) (ordered ? DeliveryMethod.ReliableOrdered : DeliveryMethod.ReliableUnordered);
|
|
var channel = _channels[idx];
|
|
return channel != null ? ((ReliableChannel)channel).PacketsInQueue : 0;
|
|
}
|
|
|
|
private BaseChannel CreateChannel(byte idx)
|
|
{
|
|
BaseChannel newChannel = _channels[idx];
|
|
if (newChannel != null)
|
|
return newChannel;
|
|
switch ((DeliveryMethod)(idx % 4))
|
|
{
|
|
case DeliveryMethod.ReliableUnordered:
|
|
newChannel = new ReliableChannel(this, false, idx);
|
|
break;
|
|
case DeliveryMethod.Sequenced:
|
|
newChannel = new SequencedChannel(this, false, idx);
|
|
break;
|
|
case DeliveryMethod.ReliableOrdered:
|
|
newChannel = new ReliableChannel(this, true, idx);
|
|
break;
|
|
case DeliveryMethod.ReliableSequenced:
|
|
newChannel = new SequencedChannel(this, true, idx);
|
|
break;
|
|
}
|
|
_channels[idx] = newChannel;
|
|
newChannel.Next = _headChannel;
|
|
_headChannel = newChannel;
|
|
return newChannel;
|
|
}
|
|
|
|
//"Connect to" constructor
|
|
internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id, byte connectNum, NetDataWriter connectData)
|
|
: this(netManager, remoteEndPoint, id)
|
|
{
|
|
_connectTime = DateTime.UtcNow.Ticks;
|
|
_connectionState = ConnectionState.Outgoing;
|
|
ConnectionNum = connectNum;
|
|
|
|
//Make initial packet
|
|
_connectRequestPacket = NetConnectRequestPacket.Make(connectData, remoteEndPoint.Serialize(), _connectTime);
|
|
_connectRequestPacket.ConnectionNumber = connectNum;
|
|
|
|
//Send request
|
|
NetManager.SendRaw(_connectRequestPacket, EndPoint);
|
|
|
|
NetDebug.Write(NetLogLevel.Trace, "[CC] ConnectId: {0}, ConnectNum: {1}", _connectTime, connectNum);
|
|
}
|
|
|
|
//"Accept" incoming constructor
|
|
internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id, long connectId, byte connectNum)
|
|
: this(netManager, remoteEndPoint, id)
|
|
{
|
|
_connectTime = connectId;
|
|
_connectionState = ConnectionState.Connected;
|
|
ConnectionNum = connectNum;
|
|
|
|
//Make initial packet
|
|
_connectAcceptPacket = NetConnectAcceptPacket.Make(_connectTime, connectNum, false);
|
|
//Send
|
|
NetManager.SendRaw(_connectAcceptPacket, EndPoint);
|
|
|
|
NetDebug.Write(NetLogLevel.Trace, "[CC] ConnectId: {0}", _connectTime);
|
|
}
|
|
|
|
//Reject
|
|
internal void Reject(long connectionId, byte connectionNumber, byte[] data, int start, int length)
|
|
{
|
|
_connectTime = connectionId;
|
|
_connectNum = connectionNumber;
|
|
Shutdown(data, start, length, false);
|
|
}
|
|
|
|
internal bool ProcessConnectAccept(NetConnectAcceptPacket packet)
|
|
{
|
|
if (_connectionState != ConnectionState.Outgoing)
|
|
return false;
|
|
|
|
//check connection id
|
|
if (packet.ConnectionId != _connectTime)
|
|
{
|
|
NetDebug.Write(NetLogLevel.Trace, "[NC] Invalid connectId: {0}", _connectTime);
|
|
return false;
|
|
}
|
|
//check connect num
|
|
ConnectionNum = packet.ConnectionNumber;
|
|
|
|
NetDebug.Write(NetLogLevel.Trace, "[NC] Received connection accept");
|
|
Interlocked.Exchange(ref _timeSinceLastPacket, 0);
|
|
_connectionState = ConnectionState.Connected;
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets maximum size of packet that will be not fragmented.
|
|
/// </summary>
|
|
/// <param name="options">Type of packet that you want send</param>
|
|
/// <returns>size in bytes</returns>
|
|
public int GetMaxSinglePacketSize(DeliveryMethod options)
|
|
{
|
|
return _mtu - NetPacket.GetHeaderSize(options == DeliveryMethod.Unreliable ? PacketProperty.Unreliable : PacketProperty.Channeled);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send data to peer with delivery event called
|
|
/// </summary>
|
|
/// <param name="data">Data</param>
|
|
/// <param name="channelNumber">Number of channel (from 0 to channelsCount - 1)</param>
|
|
/// <param name="deliveryMethod">Delivery method (reliable, unreliable, etc.)</param>
|
|
/// <param name="userData">User data that will be received in DeliveryEvent</param>
|
|
/// <exception cref="ArgumentException">
|
|
/// If you trying to send unreliable packet type<para/>
|
|
/// </exception>
|
|
public void SendWithDeliveryEvent(byte[] data, byte channelNumber, DeliveryMethod deliveryMethod, object userData)
|
|
{
|
|
if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered)
|
|
throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets");
|
|
SendInternal(data, 0, data.Length, channelNumber, deliveryMethod, userData);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send data to peer with delivery event called
|
|
/// </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="deliveryMethod">Delivery method (reliable, unreliable, etc.)</param>
|
|
/// <param name="userData">User data that will be received in DeliveryEvent</param>
|
|
/// <exception cref="ArgumentException">
|
|
/// If you trying to send unreliable packet type<para/>
|
|
/// </exception>
|
|
public void SendWithDeliveryEvent(byte[] data, int start, int length, byte channelNumber, DeliveryMethod deliveryMethod, object userData)
|
|
{
|
|
if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered)
|
|
throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets");
|
|
SendInternal(data, start, length, channelNumber, deliveryMethod, userData);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send data to peer with delivery event called
|
|
/// </summary>
|
|
/// <param name="dataWriter">Data</param>
|
|
/// <param name="channelNumber">Number of channel (from 0 to channelsCount - 1)</param>
|
|
/// <param name="deliveryMethod">Delivery method (reliable, unreliable, etc.)</param>
|
|
/// <param name="userData">User data that will be received in DeliveryEvent</param>
|
|
/// <exception cref="ArgumentException">
|
|
/// If you trying to send unreliable packet type<para/>
|
|
/// </exception>
|
|
public void SendWithDeliveryEvent(NetDataWriter dataWriter, byte channelNumber, DeliveryMethod deliveryMethod, object userData)
|
|
{
|
|
if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered)
|
|
throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets");
|
|
SendInternal(dataWriter.Data, 0, dataWriter.Length, channelNumber, deliveryMethod, userData);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send data to peer (channel - 0)
|
|
/// </summary>
|
|
/// <param name="data">Data</param>
|
|
/// <param name="deliveryMethod">Send options (reliable, unreliable, etc.)</param>
|
|
/// <exception cref="TooBigPacketException">
|
|
/// If size exceeds maximum limit:<para/>
|
|
/// MTU - headerSize bytes for Unreliable<para/>
|
|
/// Fragment count exceeded ushort.MaxValue<para/>
|
|
/// </exception>
|
|
public void Send(byte[] data, DeliveryMethod deliveryMethod)
|
|
{
|
|
SendInternal(data, 0, data.Length, 0, deliveryMethod, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send data to peer (channel - 0)
|
|
/// </summary>
|
|
/// <param name="dataWriter">DataWriter with data</param>
|
|
/// <param name="deliveryMethod">Send options (reliable, unreliable, etc.)</param>
|
|
/// <exception cref="TooBigPacketException">
|
|
/// If size exceeds maximum limit:<para/>
|
|
/// MTU - headerSize bytes for Unreliable<para/>
|
|
/// Fragment count exceeded ushort.MaxValue<para/>
|
|
/// </exception>
|
|
public void Send(NetDataWriter dataWriter, DeliveryMethod deliveryMethod)
|
|
{
|
|
SendInternal(dataWriter.Data, 0, dataWriter.Length, 0, deliveryMethod, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send data to peer (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>
|
|
/// <exception cref="TooBigPacketException">
|
|
/// If size exceeds maximum limit:<para/>
|
|
/// MTU - headerSize bytes for Unreliable<para/>
|
|
/// Fragment count exceeded ushort.MaxValue<para/>
|
|
/// </exception>
|
|
public void Send(byte[] data, int start, int length, DeliveryMethod options)
|
|
{
|
|
SendInternal(data, start, length, 0, options, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send data to peer
|
|
/// </summary>
|
|
/// <param name="data">Data</param>
|
|
/// <param name="channelNumber">Number of channel (from 0 to channelsCount - 1)</param>
|
|
/// <param name="deliveryMethod">Send options (reliable, unreliable, etc.)</param>
|
|
/// <exception cref="TooBigPacketException">
|
|
/// If size exceeds maximum limit:<para/>
|
|
/// MTU - headerSize bytes for Unreliable<para/>
|
|
/// Fragment count exceeded ushort.MaxValue<para/>
|
|
/// </exception>
|
|
public void Send(byte[] data, byte channelNumber, DeliveryMethod deliveryMethod)
|
|
{
|
|
SendInternal(data, 0, data.Length, channelNumber, deliveryMethod, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send data to peer
|
|
/// </summary>
|
|
/// <param name="dataWriter">DataWriter with data</param>
|
|
/// <param name="channelNumber">Number of channel (from 0 to channelsCount - 1)</param>
|
|
/// <param name="deliveryMethod">Send options (reliable, unreliable, etc.)</param>
|
|
/// <exception cref="TooBigPacketException">
|
|
/// If size exceeds maximum limit:<para/>
|
|
/// MTU - headerSize bytes for Unreliable<para/>
|
|
/// Fragment count exceeded ushort.MaxValue<para/>
|
|
/// </exception>
|
|
public void Send(NetDataWriter dataWriter, byte channelNumber, DeliveryMethod deliveryMethod)
|
|
{
|
|
SendInternal(dataWriter.Data, 0, dataWriter.Length, channelNumber, deliveryMethod, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send data to peer
|
|
/// </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="deliveryMethod">Delivery method (reliable, unreliable, etc.)</param>
|
|
/// <exception cref="TooBigPacketException">
|
|
/// If size exceeds maximum limit:<para/>
|
|
/// MTU - headerSize bytes for Unreliable<para/>
|
|
/// Fragment count exceeded ushort.MaxValue<para/>
|
|
/// </exception>
|
|
public void Send(byte[] data, int start, int length, byte channelNumber, DeliveryMethod deliveryMethod)
|
|
{
|
|
SendInternal(data, start, length, channelNumber, deliveryMethod, null);
|
|
}
|
|
|
|
private void SendInternal(
|
|
byte[] data,
|
|
int start,
|
|
int length,
|
|
byte channelNumber,
|
|
DeliveryMethod deliveryMethod,
|
|
object userData)
|
|
{
|
|
if (_connectionState != ConnectionState.Connected || channelNumber >= _channels.Length)
|
|
return;
|
|
|
|
//Select channel
|
|
PacketProperty property;
|
|
BaseChannel channel = null;
|
|
|
|
if (deliveryMethod == DeliveryMethod.Unreliable)
|
|
{
|
|
property = PacketProperty.Unreliable;
|
|
}
|
|
else
|
|
{
|
|
property = PacketProperty.Channeled;
|
|
channel = CreateChannel((byte)(channelNumber*4 + (byte)deliveryMethod));
|
|
}
|
|
|
|
//Prepare
|
|
NetDebug.Write("[RS]Packet: " + property);
|
|
|
|
//Check fragmentation
|
|
int headerSize = NetPacket.GetHeaderSize(property);
|
|
//Save mtu for multithread
|
|
int mtu = _mtu;
|
|
if (length + headerSize > mtu)
|
|
{
|
|
//if cannot be fragmented
|
|
if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered)
|
|
throw new TooBigPacketException("Unreliable packet size exceeded maximum of " + (mtu - headerSize) + " bytes");
|
|
|
|
int packetFullSize = mtu - headerSize;
|
|
int packetDataSize = packetFullSize - NetConstants.FragmentHeaderSize;
|
|
int totalPackets = length / packetDataSize + (length % packetDataSize == 0 ? 0 : 1);
|
|
|
|
NetDebug.Write("FragmentSend:\n" +
|
|
" MTU: {0}\n" +
|
|
" headerSize: {1}\n" +
|
|
" packetFullSize: {2}\n" +
|
|
" packetDataSize: {3}\n" +
|
|
" totalPackets: {4}",
|
|
mtu, headerSize, packetFullSize, packetDataSize, totalPackets);
|
|
|
|
if (totalPackets > ushort.MaxValue)
|
|
throw new TooBigPacketException("Data was split in " + totalPackets + " fragments, which exceeds " + ushort.MaxValue);
|
|
|
|
ushort currentFramentId;
|
|
lock (_sendLock)
|
|
{
|
|
currentFramentId = _fragmentId;
|
|
_fragmentId++;
|
|
}
|
|
|
|
for(ushort partIdx = 0; partIdx < totalPackets; partIdx++)
|
|
{
|
|
int sendLength = length > packetDataSize ? packetDataSize : length;
|
|
|
|
NetPacket p = _packetPool.GetPacket(headerSize + sendLength + NetConstants.FragmentHeaderSize);
|
|
p.Property = property;
|
|
p.UserData = userData;
|
|
p.FragmentId = currentFramentId;
|
|
p.FragmentPart = partIdx;
|
|
p.FragmentsTotal = (ushort)totalPackets;
|
|
p.MarkFragmented();
|
|
|
|
Buffer.BlockCopy(data, partIdx * packetDataSize, p.RawData, NetConstants.FragmentedHeaderTotalSize, sendLength);
|
|
channel.AddToQueue(p);
|
|
|
|
length -= sendLength;
|
|
}
|
|
return;
|
|
}
|
|
|
|
//Else just send
|
|
NetPacket packet = _packetPool.GetPacket(headerSize + length);
|
|
packet.Property = property;
|
|
Buffer.BlockCopy(data, start, packet.RawData, headerSize, length);
|
|
packet.UserData = userData;
|
|
|
|
if (channel == null) //unreliable
|
|
{
|
|
lock(_unreliableChannel)
|
|
_unreliableChannel.Enqueue(packet);
|
|
}
|
|
else
|
|
{
|
|
channel.AddToQueue(packet);
|
|
}
|
|
}
|
|
|
|
public void Disconnect(byte[] data)
|
|
{
|
|
NetManager.DisconnectPeer(this, data);
|
|
}
|
|
|
|
public void Disconnect(NetDataWriter writer)
|
|
{
|
|
NetManager.DisconnectPeer(this, writer);
|
|
}
|
|
|
|
public void Disconnect(byte[] data, int start, int count)
|
|
{
|
|
NetManager.DisconnectPeer(this, data, start, count);
|
|
}
|
|
|
|
public void Disconnect()
|
|
{
|
|
NetManager.DisconnectPeer(this);
|
|
}
|
|
|
|
internal DisconnectResult ProcessDisconnect(NetPacket packet)
|
|
{
|
|
if ((_connectionState == ConnectionState.Connected || _connectionState == ConnectionState.Outgoing) &&
|
|
packet.Size >= 9 &&
|
|
BitConverter.ToInt64(packet.RawData, 1) == _connectTime &&
|
|
packet.ConnectionNumber == _connectNum)
|
|
{
|
|
return _connectionState == ConnectionState.Connected
|
|
? DisconnectResult.Disconnect
|
|
: DisconnectResult.Reject;
|
|
}
|
|
return DisconnectResult.None;
|
|
}
|
|
|
|
internal ShutdownResult Shutdown(byte[] data, int start, int length, bool force)
|
|
{
|
|
lock (_shutdownLock)
|
|
{
|
|
//trying to shutdown already disconnected
|
|
if (_connectionState == ConnectionState.Disconnected ||
|
|
_connectionState == ConnectionState.ShutdownRequested)
|
|
{
|
|
return ShutdownResult.None;
|
|
}
|
|
|
|
var result = _connectionState == ConnectionState.Connected
|
|
? ShutdownResult.WasConnected
|
|
: ShutdownResult.Success;
|
|
|
|
//don't send anything
|
|
if (force)
|
|
{
|
|
_connectionState = ConnectionState.Disconnected;
|
|
return result;
|
|
}
|
|
|
|
//reset time for reconnect protection
|
|
Interlocked.Exchange(ref _timeSinceLastPacket, 0);
|
|
|
|
//send shutdown packet
|
|
_shutdownPacket = new NetPacket(PacketProperty.Disconnect, length) {ConnectionNumber = _connectNum};
|
|
FastBitConverter.GetBytes(_shutdownPacket.RawData, 1, _connectTime);
|
|
if (_shutdownPacket.Size >= _mtu)
|
|
{
|
|
//Drop additional data
|
|
NetDebug.WriteError("[Peer] Disconnect additional data size more than MTU - 8!");
|
|
}
|
|
else if (data != null && length > 0)
|
|
{
|
|
Buffer.BlockCopy(data, start, _shutdownPacket.RawData, 9, length);
|
|
}
|
|
_connectionState = ConnectionState.ShutdownRequested;
|
|
NetDebug.Write("[Peer] Send disconnect");
|
|
NetManager.SendRaw(_shutdownPacket, EndPoint);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
private void UpdateRoundTripTime(int roundTripTime)
|
|
{
|
|
_rtt += roundTripTime;
|
|
_rttCount++;
|
|
_avgRtt = _rtt/_rttCount;
|
|
_resendDelay = 25.0 + _avgRtt * 2.1; // 25 ms + double rtt
|
|
}
|
|
|
|
internal void AddReliablePacket(DeliveryMethod method, NetPacket p)
|
|
{
|
|
if (p.IsFragmented)
|
|
{
|
|
NetDebug.Write("Fragment. Id: {0}, Part: {1}, Total: {2}", p.FragmentId, p.FragmentPart, p.FragmentsTotal);
|
|
//Get needed array from dictionary
|
|
ushort packetFragId = p.FragmentId;
|
|
IncomingFragments incomingFragments;
|
|
if (!_holdedFragments.TryGetValue(packetFragId, out incomingFragments))
|
|
{
|
|
incomingFragments = new IncomingFragments
|
|
{
|
|
Fragments = new NetPacket[p.FragmentsTotal],
|
|
ChannelId = p.ChannelId
|
|
};
|
|
_holdedFragments.Add(packetFragId, incomingFragments);
|
|
}
|
|
|
|
//Cache
|
|
var fragments = incomingFragments.Fragments;
|
|
|
|
//Error check
|
|
if (p.FragmentPart >= fragments.Length ||
|
|
fragments[p.FragmentPart] != null ||
|
|
p.ChannelId != incomingFragments.ChannelId)
|
|
{
|
|
_packetPool.Recycle(p);
|
|
NetDebug.WriteError("Invalid fragment packet");
|
|
return;
|
|
}
|
|
//Fill array
|
|
fragments[p.FragmentPart] = p;
|
|
|
|
//Increase received fragments count
|
|
incomingFragments.ReceivedCount++;
|
|
|
|
//Increase total size
|
|
incomingFragments.TotalSize += p.Size - NetConstants.FragmentedHeaderTotalSize;
|
|
|
|
//Check for finish
|
|
if (incomingFragments.ReceivedCount != fragments.Length)
|
|
return;
|
|
|
|
//just simple packet
|
|
NetPacket resultingPacket = _packetPool.GetPacket(incomingFragments.TotalSize);
|
|
|
|
int firstFragmentSize = fragments[0].Size - NetConstants.FragmentedHeaderTotalSize;
|
|
for (int i = 0; i < incomingFragments.ReceivedCount; i++)
|
|
{
|
|
var fragment = fragments[i];
|
|
//Create resulting big packet
|
|
Buffer.BlockCopy(
|
|
fragment.RawData,
|
|
NetConstants.FragmentedHeaderTotalSize,
|
|
resultingPacket.RawData,
|
|
firstFragmentSize * i,
|
|
fragment.Size - NetConstants.FragmentedHeaderTotalSize);
|
|
|
|
//Free memory
|
|
_packetPool.Recycle(fragment);
|
|
}
|
|
Array.Clear(fragments, 0, incomingFragments.ReceivedCount);
|
|
|
|
//Send to process
|
|
NetManager.CreateReceiveEvent(resultingPacket, method, 0, this);
|
|
|
|
//Clear memory
|
|
_holdedFragments.Remove(packetFragId);
|
|
}
|
|
else //Just simple packet
|
|
{
|
|
NetManager.CreateReceiveEvent(p, method, NetConstants.ChanneledHeaderSize, this);
|
|
}
|
|
}
|
|
|
|
private void ProcessMtuPacket(NetPacket packet)
|
|
{
|
|
//header + int
|
|
if (packet.Size < NetConstants.PossibleMtu[0])
|
|
return;
|
|
|
|
//first stage check (mtu check and mtu ok)
|
|
int receivedMtu = BitConverter.ToInt32(packet.RawData, 1);
|
|
int endMtuCheck = BitConverter.ToInt32(packet.RawData, packet.Size - 4);
|
|
if (receivedMtu != packet.Size || receivedMtu != endMtuCheck || receivedMtu > NetConstants.MaxPacketSize)
|
|
{
|
|
NetDebug.WriteError("[MTU] Broken packet. RMTU {0}, EMTU {1}, PSIZE {2}", receivedMtu, endMtuCheck, packet.Size);
|
|
return;
|
|
}
|
|
|
|
if (packet.Property == PacketProperty.MtuCheck)
|
|
{
|
|
_mtuCheckAttempts = 0;
|
|
NetDebug.Write("[MTU] check. send back: " + receivedMtu);
|
|
packet.Property = PacketProperty.MtuOk;
|
|
NetManager.SendRawAndRecycle(packet, EndPoint);
|
|
}
|
|
else if(receivedMtu > _mtu && !_finishMtu) //MtuOk
|
|
{
|
|
//invalid packet
|
|
if (receivedMtu != NetConstants.PossibleMtu[_mtuIdx + 1])
|
|
return;
|
|
|
|
lock (_mtuMutex)
|
|
{
|
|
_mtuIdx++;
|
|
SetMtu(_mtuIdx);
|
|
}
|
|
//if maxed - finish.
|
|
if (_mtuIdx == NetConstants.PossibleMtu.Length - 1)
|
|
_finishMtu = true;
|
|
|
|
NetDebug.Write("[MTU] ok. Increase to: " + _mtu);
|
|
}
|
|
}
|
|
|
|
private void UpdateMtuLogic(int deltaTime)
|
|
{
|
|
if (_finishMtu)
|
|
return;
|
|
|
|
_mtuCheckTimer += deltaTime;
|
|
if (_mtuCheckTimer < MtuCheckDelay)
|
|
return;
|
|
|
|
_mtuCheckTimer = 0;
|
|
_mtuCheckAttempts++;
|
|
if (_mtuCheckAttempts >= MaxMtuCheckAttempts)
|
|
{
|
|
_finishMtu = true;
|
|
return;
|
|
}
|
|
|
|
lock (_mtuMutex)
|
|
{
|
|
if (_mtuIdx >= NetConstants.PossibleMtu.Length - 1)
|
|
return;
|
|
|
|
//Send increased packet
|
|
int newMtu = NetConstants.PossibleMtu[_mtuIdx + 1];
|
|
var p = _packetPool.GetPacket(newMtu);
|
|
p.Property = PacketProperty.MtuCheck;
|
|
FastBitConverter.GetBytes(p.RawData, 1, newMtu); //place into start
|
|
FastBitConverter.GetBytes(p.RawData, p.Size - 4, newMtu);//and end of packet
|
|
|
|
//Must check result for MTU fix
|
|
if (NetManager.SendRawAndRecycle(p, EndPoint) <= 0)
|
|
_finishMtu = true;
|
|
}
|
|
}
|
|
|
|
internal ConnectRequestResult ProcessConnectRequest(NetConnectRequestPacket connRequest)
|
|
{
|
|
//current or new request
|
|
switch (_connectionState)
|
|
{
|
|
//P2P case
|
|
case ConnectionState.Outgoing:
|
|
//fast check
|
|
if (connRequest.ConnectionTime < _connectTime)
|
|
{
|
|
return ConnectRequestResult.P2PLose;
|
|
}
|
|
//slow rare case check
|
|
else if (connRequest.ConnectionTime == _connectTime)
|
|
{
|
|
var remoteBytes = EndPoint.Serialize();
|
|
var localBytes = connRequest.TargetAddress;
|
|
for (int i = remoteBytes.Size-1; i >= 0; i--)
|
|
{
|
|
byte rb = remoteBytes[i];
|
|
if (rb == localBytes[i])
|
|
continue;
|
|
if (rb < localBytes[i])
|
|
return ConnectRequestResult.P2PLose;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ConnectionState.Connected:
|
|
//Old connect request
|
|
if (connRequest.ConnectionTime == _connectTime)
|
|
{
|
|
//just reply accept
|
|
NetManager.SendRaw(_connectAcceptPacket, EndPoint);
|
|
}
|
|
//New connect request
|
|
else if (connRequest.ConnectionTime > _connectTime)
|
|
{
|
|
return ConnectRequestResult.Reconnection;
|
|
}
|
|
break;
|
|
|
|
case ConnectionState.Disconnected:
|
|
case ConnectionState.ShutdownRequested:
|
|
if (connRequest.ConnectionTime >= _connectTime)
|
|
return ConnectRequestResult.NewConnection;
|
|
break;
|
|
}
|
|
return ConnectRequestResult.None;
|
|
}
|
|
|
|
//Process incoming packet
|
|
internal void ProcessPacket(NetPacket packet)
|
|
{
|
|
//not initialized
|
|
if (_connectionState == ConnectionState.Outgoing)
|
|
{
|
|
_packetPool.Recycle(packet);
|
|
return;
|
|
}
|
|
if (packet.ConnectionNumber != _connectNum && packet.Property != PacketProperty.ShutdownOk) //without connectionNum
|
|
{
|
|
NetDebug.Write(NetLogLevel.Trace, "[RR]Old packet");
|
|
_packetPool.Recycle(packet);
|
|
return;
|
|
}
|
|
Interlocked.Exchange(ref _timeSinceLastPacket, 0);
|
|
|
|
NetDebug.Write("[RR]PacketProperty: {0}", packet.Property);
|
|
switch (packet.Property)
|
|
{
|
|
case PacketProperty.Merged:
|
|
int pos = NetConstants.HeaderSize;
|
|
while (pos < packet.Size)
|
|
{
|
|
ushort size = BitConverter.ToUInt16(packet.RawData, pos);
|
|
pos += 2;
|
|
NetPacket mergedPacket = _packetPool.GetPacket(size);
|
|
if (!mergedPacket.FromBytes(packet.RawData, pos, size))
|
|
{
|
|
_packetPool.Recycle(packet);
|
|
break;
|
|
}
|
|
pos += size;
|
|
ProcessPacket(mergedPacket);
|
|
}
|
|
break;
|
|
//If we get ping, send pong
|
|
case PacketProperty.Ping:
|
|
if (NetUtils.RelativeSequenceNumber(packet.Sequence, _pongPacket.Sequence) > 0)
|
|
{
|
|
NetDebug.Write("[PP]Ping receive, send pong");
|
|
FastBitConverter.GetBytes(_pongPacket.RawData, 3, DateTime.UtcNow.Ticks);
|
|
_pongPacket.Sequence = packet.Sequence;
|
|
NetManager.SendRaw(_pongPacket, EndPoint);
|
|
}
|
|
_packetPool.Recycle(packet);
|
|
break;
|
|
|
|
//If we get pong, calculate ping time and rtt
|
|
case PacketProperty.Pong:
|
|
if (packet.Sequence == _pingPacket.Sequence)
|
|
{
|
|
_pingTimer.Stop();
|
|
int elapsedMs = (int)_pingTimer.ElapsedMilliseconds;
|
|
_remoteDelta = BitConverter.ToInt64(packet.RawData, 3) + (elapsedMs * TimeSpan.TicksPerMillisecond ) / 2 - DateTime.UtcNow.Ticks;
|
|
UpdateRoundTripTime(elapsedMs);
|
|
NetManager.ConnectionLatencyUpdated(this, elapsedMs / 2);
|
|
NetDebug.Write("[PP]Ping: {0} - {1} - {2}", packet.Sequence, elapsedMs, _remoteDelta);
|
|
}
|
|
_packetPool.Recycle(packet);
|
|
break;
|
|
|
|
case PacketProperty.Ack:
|
|
case PacketProperty.Channeled:
|
|
if (packet.ChannelId > _channels.Length)
|
|
{
|
|
_packetPool.Recycle(packet);
|
|
break;
|
|
}
|
|
var channel = _channels[packet.ChannelId] ?? (packet.Property == PacketProperty.Ack ? null : CreateChannel(packet.ChannelId));
|
|
if (channel != null)
|
|
{
|
|
if (!channel.ProcessPacket(packet))
|
|
_packetPool.Recycle(packet);
|
|
}
|
|
break;
|
|
|
|
//Simple packet without acks
|
|
case PacketProperty.Unreliable:
|
|
NetManager.CreateReceiveEvent(packet, DeliveryMethod.Unreliable, NetConstants.HeaderSize, this);
|
|
return;
|
|
|
|
case PacketProperty.MtuCheck:
|
|
case PacketProperty.MtuOk:
|
|
ProcessMtuPacket(packet);
|
|
break;
|
|
|
|
case PacketProperty.ShutdownOk:
|
|
if(_connectionState == ConnectionState.ShutdownRequested)
|
|
_connectionState = ConnectionState.Disconnected;
|
|
_packetPool.Recycle(packet);
|
|
break;
|
|
|
|
default:
|
|
NetDebug.WriteError("Error! Unexpected packet type: " + packet.Property);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void SendMerged()
|
|
{
|
|
if (_mergeCount == 0)
|
|
return;
|
|
int bytesSent;
|
|
if (_mergeCount > 1)
|
|
{
|
|
NetDebug.Write("[P]Send merged: " + _mergePos + ", count: " + _mergeCount);
|
|
bytesSent = NetManager.SendRaw(_mergeData.RawData, 0, NetConstants.HeaderSize + _mergePos, EndPoint);
|
|
}
|
|
else
|
|
{
|
|
//Send without length information and merging
|
|
bytesSent = NetManager.SendRaw(_mergeData.RawData, NetConstants.HeaderSize + 2, _mergePos - 2, EndPoint);
|
|
}
|
|
|
|
if (NetManager.EnableStatistics)
|
|
{
|
|
Statistics.PacketsSent++;
|
|
Statistics.BytesSent += (ulong)bytesSent;
|
|
}
|
|
|
|
_mergePos = 0;
|
|
_mergeCount = 0;
|
|
}
|
|
|
|
internal void SendUserData(NetPacket packet)
|
|
{
|
|
packet.ConnectionNumber = _connectNum;
|
|
int mergedPacketSize = NetConstants.HeaderSize + packet.Size + 2;
|
|
const int sizeTreshold = 20;
|
|
if (mergedPacketSize + sizeTreshold >= _mtu)
|
|
{
|
|
NetDebug.Write(NetLogLevel.Trace, "[P]SendingPacket: " + packet.Property);
|
|
int bytesSent = NetManager.SendRaw(packet, EndPoint);
|
|
|
|
if (NetManager.EnableStatistics)
|
|
{
|
|
Statistics.PacketsSent++;
|
|
Statistics.BytesSent += (ulong)bytesSent;
|
|
}
|
|
|
|
return;
|
|
}
|
|
if (_mergePos + mergedPacketSize > _mtu)
|
|
SendMerged();
|
|
|
|
FastBitConverter.GetBytes(_mergeData.RawData, _mergePos + NetConstants.HeaderSize, (ushort)packet.Size);
|
|
Buffer.BlockCopy(packet.RawData, 0, _mergeData.RawData, _mergePos + NetConstants.HeaderSize + 2, packet.Size);
|
|
_mergePos += packet.Size + 2;
|
|
_mergeCount++;
|
|
//DebugWriteForce("Merged: " + _mergePos + "/" + (_mtu - 2) + ", count: " + _mergeCount);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Flush all queued packets
|
|
/// </summary>
|
|
public void Flush()
|
|
{
|
|
if (_connectionState != ConnectionState.Connected)
|
|
return;
|
|
lock (_flushLock)
|
|
{
|
|
BaseChannel currentChannel = _headChannel;
|
|
while (currentChannel != null)
|
|
{
|
|
currentChannel.SendNextPackets();
|
|
currentChannel = currentChannel.Next;
|
|
}
|
|
|
|
lock (_unreliableChannel)
|
|
{
|
|
while (_unreliableChannel.Count > 0)
|
|
{
|
|
NetPacket packet = _unreliableChannel.Dequeue();
|
|
SendUserData(packet);
|
|
NetManager.NetPacketPool.Recycle(packet);
|
|
}
|
|
}
|
|
|
|
SendMerged();
|
|
}
|
|
}
|
|
|
|
internal void Update(int deltaTime)
|
|
{
|
|
Interlocked.Add(ref _timeSinceLastPacket, deltaTime);
|
|
switch (_connectionState)
|
|
{
|
|
case ConnectionState.Connected:
|
|
if (_timeSinceLastPacket > NetManager.DisconnectTimeout)
|
|
{
|
|
NetDebug.Write(
|
|
"[UPDATE] Disconnect by timeout: {0} > {1}",
|
|
_timeSinceLastPacket,
|
|
NetManager.DisconnectTimeout);
|
|
NetManager.DisconnectPeerForce(this, DisconnectReason.Timeout, 0, null);
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case ConnectionState.ShutdownRequested:
|
|
if (_timeSinceLastPacket > NetManager.DisconnectTimeout)
|
|
{
|
|
_connectionState = ConnectionState.Disconnected;
|
|
}
|
|
else
|
|
{
|
|
_shutdownTimer += deltaTime;
|
|
if (_shutdownTimer >= ShutdownDelay)
|
|
{
|
|
_shutdownTimer = 0;
|
|
NetManager.SendRaw(_shutdownPacket, EndPoint);
|
|
}
|
|
}
|
|
return;
|
|
|
|
case ConnectionState.Outgoing:
|
|
_connectTimer += deltaTime;
|
|
if (_connectTimer > NetManager.ReconnectDelay)
|
|
{
|
|
_connectTimer = 0;
|
|
_connectAttempts++;
|
|
if (_connectAttempts > NetManager.MaxConnectAttempts)
|
|
{
|
|
NetManager.DisconnectPeerForce(this, DisconnectReason.ConnectionFailed, 0, null);
|
|
return;
|
|
}
|
|
|
|
//else send connect again
|
|
NetManager.SendRaw(_connectRequestPacket, EndPoint);
|
|
}
|
|
return;
|
|
|
|
case ConnectionState.Disconnected:
|
|
return;
|
|
}
|
|
|
|
//Send ping
|
|
_pingSendTimer += deltaTime;
|
|
if (_pingSendTimer >= NetManager.PingInterval)
|
|
{
|
|
NetDebug.Write("[PP] Send ping...");
|
|
//reset timer
|
|
_pingSendTimer = 0;
|
|
//send ping
|
|
_pingPacket.Sequence++;
|
|
//ping timeout
|
|
if (_pingTimer.IsRunning)
|
|
UpdateRoundTripTime((int)_pingTimer.ElapsedMilliseconds);
|
|
_pingTimer.Reset();
|
|
_pingTimer.Start();
|
|
NetManager.SendRaw(_pingPacket, EndPoint);
|
|
}
|
|
|
|
//RTT - round trip time
|
|
_rttResetTimer += deltaTime;
|
|
if (_rttResetTimer >= NetManager.PingInterval * 3)
|
|
{
|
|
_rttResetTimer = 0;
|
|
_rtt = _avgRtt;
|
|
_rttCount = 1;
|
|
}
|
|
|
|
UpdateMtuLogic(deltaTime);
|
|
|
|
//Pending send
|
|
Flush();
|
|
}
|
|
|
|
//For reliable channel
|
|
internal void RecycleAndDeliver(NetPacket packet)
|
|
{
|
|
if (packet.UserData != null)
|
|
{
|
|
if (packet.IsFragmented)
|
|
{
|
|
ushort fragCount;
|
|
_deliveredFramgnets.TryGetValue(packet.FragmentId, out fragCount);
|
|
fragCount++;
|
|
if (fragCount == packet.FragmentsTotal)
|
|
{
|
|
NetManager.MessageDelivered(this, packet.UserData);
|
|
_deliveredFramgnets.Remove(packet.FragmentId);
|
|
}
|
|
else
|
|
{
|
|
_deliveredFramgnets[packet.FragmentId] = fragCount;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NetManager.MessageDelivered(this, packet.UserData);
|
|
}
|
|
packet.UserData = null;
|
|
}
|
|
_packetPool.Recycle(packet);
|
|
}
|
|
}
|
|
}
|