diff --git a/.github/workflows/nuget-publish.yml b/.github/workflows/nuget-publish.yml
new file mode 100644
index 00000000..fd5ee070
--- /dev/null
+++ b/.github/workflows/nuget-publish.yml
@@ -0,0 +1,88 @@
+name: Publish NuGet Package
+
+on:
+ workflow_dispatch:
+
+jobs:
+ publish-release:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup .NET
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: 8.0.x
+
+ - name: Restore dependencies
+ run: dotnet restore
+
+ - name: Build LiteNetLib
+ run: dotnet build LiteNetLib/LiteNetLib.csproj --configuration Release
+
+ - name: Check version and pack if changed
+ id: check-pack
+ run: |
+ echo "Checking for version changes..."
+
+ PROJECT_PATH="LiteNetLib/LiteNetLib.csproj"
+ PACKAGE_NAME="litenetlib"
+
+ # Extract version from project file
+ LOCAL_VERSION=$(grep -o '[^<]*' "$PROJECT_PATH" | sed 's/\(.*\)<\/PackageVersion>/\1/')
+
+ if [ -z "$LOCAL_VERSION" ]; then
+ echo "No tag found in $PROJECT_PATH"
+ echo "has_packages=false" >> $GITHUB_OUTPUT
+ exit 0
+ fi
+
+ # Get the latest version from NuGet.org
+ echo "Checking NuGet.org for $PACKAGE_NAME..."
+ NUGET_VERSION=$(curl -s "https://api.nuget.org/v3-flatcontainer/$PACKAGE_NAME/index.json" | grep -o '"[^"]*"' | sed 's/"//g' | sort -V | tail -n 1)
+
+ echo "Local version: $LOCAL_VERSION"
+ echo "NuGet version: $NUGET_VERSION"
+
+ # Compare versions
+ if [ "$LOCAL_VERSION" != "$NUGET_VERSION" ]; then
+ echo "Version changed - will pack and publish"
+ mkdir -p ./nupkgs
+ dotnet pack "$PROJECT_PATH" --configuration Release --no-build --output ./nupkgs
+ echo "has_packages=true" >> $GITHUB_OUTPUT
+ else
+ echo "Version unchanged - skipping"
+ echo "has_packages=false" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Push to NuGet.org
+ if: steps.check-pack.outputs.has_packages == 'true'
+ env:
+ NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
+ run: |
+ echo "Publishing to NuGet.org..."
+ for package in ./nupkgs/*.nupkg; do
+ if [ -f "$package" ]; then
+ echo "Publishing $package..."
+ dotnet nuget push "$package" --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json --skip-duplicate || {
+ echo "Failed to publish $package"
+ exit 1
+ }
+ fi
+ done
+
+ - name: Upload package as artifact
+ if: steps.check-pack.outputs.has_packages == 'true'
+ uses: actions/upload-artifact@v4
+ with:
+ name: nuget-package
+ path: ./nupkgs/*.nupkg
+ retention-days: 7
+
+ - name: No package to publish
+ if: steps.check-pack.outputs.has_packages == 'false'
+ run: echo "Version unchanged - nothing to publish"
diff --git a/.gitignore b/.gitignore
index dbffa72f..3efd0e52 100644
--- a/.gitignore
+++ b/.gitignore
@@ -160,6 +160,8 @@ publish/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
+!LiteNetLibSampleUnity/Packages/*
+
# Windows Azure Build Output
csx/
*.build.csdef
diff --git a/LICENSE.txt b/LICENSE.txt
index ecdcd0b6..ec1bddf3 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020 Ruslan Pyrch
+Copyright (c) 2025 Ruslan Pyrch
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/LibSample/AesEncryptLayer.cs b/LibSample/AesEncryptLayer.cs
index 41d69238..1df94508 100644
--- a/LibSample/AesEncryptLayer.cs
+++ b/LibSample/AesEncryptLayer.cs
@@ -19,7 +19,7 @@ public class AesEncryptLayer : PacketLayerBase
public const int KeySizeInBytes = KeySize / 8;
public const int BlockSizeInBytes = BlockSize / 8;
- private readonly AesCryptoServiceProvider _aes;
+ private readonly Aes _aes;
private ICryptoTransform _encryptor;
private byte[] cipherBuffer = new byte[1500]; //Max possible UDP packet size
private ICryptoTransform _decryptor;
@@ -36,7 +36,7 @@ public AesEncryptLayer(byte[] key) : base(BlockSizeInBytes * 2)
if (key.Length != KeySizeInBytes) throw new NotSupportedException("EncryptLayer only supports keysize " + KeySize);
//Switch this with AesGCM for better performance, requires .NET Core 3.0 or Standard 2.1
- _aes = new AesCryptoServiceProvider();
+ _aes = Aes.Create();
_aes.KeySize = KeySize;
_aes.BlockSize = BlockSize;
_aes.Key = key;
@@ -47,22 +47,20 @@ public AesEncryptLayer(byte[] key) : base(BlockSizeInBytes * 2)
_decryptor = _aes.CreateDecryptor();
}
- public override void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int offset, ref int length)
+ public override void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int length)
{
//Can't copy directly to _aes.IV. It won't work for some reason.
- Buffer.BlockCopy(data, offset, ivBuffer, 0, ivBuffer.Length);
+ Buffer.BlockCopy(data, 0, ivBuffer, 0, ivBuffer.Length);
//_aes.IV = ivBuffer;
_decryptor = _aes.CreateDecryptor(_aes.Key, ivBuffer);
- offset += BlockSizeInBytes;
//int currentRead = ivBuffer.Length;
//int currentWrite = 0;
//TransformBlocks(_decryptor, data, length, ref currentRead, ref currentWrite);
- byte[] lastBytes = _decryptor.TransformFinalBlock(data, offset, length - offset);
+ byte[] lastBytes = _decryptor.TransformFinalBlock(data, BlockSizeInBytes, length - BlockSizeInBytes);
data = lastBytes;
- offset = 0;
length = lastBytes.Length;
}
diff --git a/LibSample/AesEncryptionTest.cs b/LibSample/AesEncryptionTest.cs
index 0876edfb..e46e249d 100644
--- a/LibSample/AesEncryptionTest.cs
+++ b/LibSample/AesEncryptionTest.cs
@@ -37,7 +37,7 @@ private void AesLayerEncryptDecrypt()
//Copy array so we dont read and write to same array
byte[] inboundData = new byte[outbound.Length];
outbound.CopyTo(inboundData, 0);
- inboundLayer.ProcessInboundPacket(ref emptyEndPoint, ref inboundData, ref start, ref length);
+ inboundLayer.ProcessInboundPacket(ref emptyEndPoint, ref inboundData, ref length);
Console.WriteLine(Encoding.ASCII.GetString(inboundData, 0, length));
byte[] expectedPlaintext = Encoding.ASCII.GetBytes(testData);
diff --git a/LibSample/BroadcastTest.cs b/LibSample/BroadcastTest.cs
index c51bdf9f..6720a3e3 100644
--- a/LibSample/BroadcastTest.cs
+++ b/LibSample/BroadcastTest.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Threading;
@@ -15,7 +16,7 @@ private class ClientListener : INetEventListener
public void OnPeerConnected(NetPeer peer)
{
- Console.WriteLine("[Client {0}] connected to: {1}:{2}", Client.LocalPort, peer.EndPoint.Address, peer.EndPoint.Port);
+ Console.WriteLine("[Client {0}] connected to: {1}:{2}", Client.LocalPort, peer.Address, peer.Port);
}
public void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo)
@@ -58,19 +59,21 @@ private class ServerListener : INetEventListener
{
public NetManager Server;
+ private readonly List _peersList = new List();
+
public void OnPeerConnected(NetPeer peer)
{
- Console.WriteLine("[Server] Peer connected: " + peer.EndPoint);
- var peers = Server.ConnectedPeerList;
- foreach (var netPeer in peers)
+ Console.WriteLine("[Server] Peer connected: " + peer);
+ Server.GetConnectedPeers(_peersList);
+ foreach (var netPeer in _peersList)
{
- Console.WriteLine("ConnectedPeersList: id={0}, ep={1}", netPeer.Id, netPeer.EndPoint);
+ Console.WriteLine("ConnectedPeersList: id={0}, ep={1}", netPeer.Id, netPeer);
}
}
public void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo)
{
- Console.WriteLine("[Server] Peer disconnected: " + peer.EndPoint + ", reason: " + disconnectInfo.Reason);
+ Console.WriteLine("[Server] Peer disconnected: " + peer + ", reason: " + disconnectInfo.Reason);
}
public void OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode)
diff --git a/LibSample/EchoMessagesTest.cs b/LibSample/EchoMessagesTest.cs
index 150a6379..7a8dc9b9 100644
--- a/LibSample/EchoMessagesTest.cs
+++ b/LibSample/EchoMessagesTest.cs
@@ -15,7 +15,7 @@ private class ClientListener : INetEventListener
{
public void OnPeerConnected(NetPeer peer)
{
- Console.WriteLine("[Client] connected to: {0}:{1}", peer.EndPoint.Address, peer.EndPoint.Port);
+ Console.WriteLine("[Client] connected to: {0}:{1}", peer.Address, peer.Port);
NetDataWriter dataWriter = new NetDataWriter();
for (int i = 0; i < 5; i++)
@@ -103,17 +103,17 @@ private class ServerListener : INetEventListener
public void OnPeerConnected(NetPeer peer)
{
- Console.WriteLine("[Server] Peer connected: " + peer.EndPoint);
+ Console.WriteLine("[Server] Peer connected: " + peer);
foreach (var netPeer in Server)
{
if(netPeer.ConnectionState == ConnectionState.Connected)
- Console.WriteLine("ConnectedPeersList: id={0}, ep={1}", netPeer.Id, netPeer.EndPoint);
+ Console.WriteLine("ConnectedPeersList: id={0}, ep={1}", netPeer.Id, netPeer);
}
}
public void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo)
{
- Console.WriteLine("[Server] Peer disconnected: " + peer.EndPoint + ", reason: " + disconnectInfo.Reason);
+ Console.WriteLine("[Server] Peer disconnected: " + peer + ", reason: " + disconnectInfo.Reason);
}
public void OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode)
diff --git a/LibSample/GithubSample.cs b/LibSample/GithubSample.cs
index 4bb8d389..0a680449 100644
--- a/LibSample/GithubSample.cs
+++ b/LibSample/GithubSample.cs
@@ -23,10 +23,10 @@ public void Server()
listener.PeerConnectedEvent += peer =>
{
- Console.WriteLine("We got connection: {0}", peer.EndPoint); // Show peer ip
- NetDataWriter writer = new NetDataWriter(); // Create writer class
- writer.Put("Hello client!"); // Put some string
- peer.Send(writer, DeliveryMethod.ReliableOrdered); // Send with reliability
+ Console.WriteLine("We got connection: {0}", peer); // Show peer ip
+ NetDataWriter writer = new NetDataWriter(); // Create writer class
+ writer.Put("Hello client!"); // Put some string
+ peer.Send(writer, DeliveryMethod.ReliableOrdered); // Send with reliability
};
while (!Console.KeyAvailable)
diff --git a/LibSample/HolePunchServerTest.cs b/LibSample/HolePunchServerTest.cs
index 2d81adee..d7564d56 100644
--- a/LibSample/HolePunchServerTest.cs
+++ b/LibSample/HolePunchServerTest.cs
@@ -91,7 +91,7 @@ public void Run()
clientListener.PeerConnectedEvent += peer =>
{
- Console.WriteLine("PeerConnected: " + peer.EndPoint);
+ Console.WriteLine("PeerConnected: " + peer);
};
clientListener.ConnectionRequestEvent += request =>
diff --git a/LibSample/LibSample.csproj b/LibSample/LibSample.csproj
index 5c6fe293..f1d24079 100644
--- a/LibSample/LibSample.csproj
+++ b/LibSample/LibSample.csproj
@@ -2,8 +2,7 @@
Exe
- net6.0;net5.0;netcoreapp3.1
- net6.0;net5.0;net471;netcoreapp3.1
+ net8.0;net9.0
diff --git a/LibSample/SerializerBenchmark.cs b/LibSample/SerializerBenchmark.cs
index 8d5c381e..31034ebb 100644
--- a/LibSample/SerializerBenchmark.cs
+++ b/LibSample/SerializerBenchmark.cs
@@ -131,7 +131,6 @@ public void Run()
//Test serializer performance
Stopwatch stopwatch = new Stopwatch();
- BinaryFormatter binaryFormatter = new BinaryFormatter();
MemoryStream memoryStream = new MemoryStream();
NetDataWriter netDataWriter = new NetDataWriter();
@@ -158,14 +157,6 @@ public void Run()
double c = Math.Sin(i);
}
- //Test binary formatter
- stopwatch.Start();
- for (int i = 0; i < LoopLength; i++)
- binaryFormatter.Serialize(memoryStream, samplePacket);
- stopwatch.Stop();
- Console.WriteLine("BinaryFormatter time: " + stopwatch.ElapsedMilliseconds + " ms");
- Console.WriteLine("BinaryFormatter size: " + memoryStream.Position / LoopLength);
-
DataWriterTest(netDataWriter, stopwatch, samplePacket);
NetSerializerTest(netSerializer, netDataWriter, stopwatch, samplePacket);
diff --git a/LibSample/SpeedBench.cs b/LibSample/SpeedBench.cs
index aa9a163f..09dcb5f4 100644
--- a/LibSample/SpeedBench.cs
+++ b/LibSample/SpeedBench.cs
@@ -69,7 +69,7 @@ void INetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, Ne
void INetEventListener.OnPeerConnected(NetPeer peer)
{
- Console.WriteLine($"Server: client connected: {peer.EndPoint}");
+ Console.WriteLine($"Server: client connected: {peer}");
}
void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo)
diff --git a/LiteNetLib.Tests/CRC32LayerTest.cs b/LiteNetLib.Tests/CRC32LayerTest.cs
index 01b4c70d..c6b716e6 100644
--- a/LiteNetLib.Tests/CRC32LayerTest.cs
+++ b/LiteNetLib.Tests/CRC32LayerTest.cs
@@ -25,11 +25,10 @@ public void ReturnsDataWithoutChecksum()
{
byte[] packet = GetTestPacketWithCrc();
- int offset = 0;
int length = packet.Length;
- _crc32Layer.ProcessInboundPacket(ref _dummyEndpoint, ref packet, ref offset, ref length);
+ _crc32Layer.ProcessInboundPacket(ref _dummyEndpoint, ref packet, ref length);
- Assert.AreEqual(packet.Length - CRC32C.ChecksumSize, length);
+ Assert.That(length, Is.EqualTo(packet.Length - CRC32C.ChecksumSize));
}
[Test]
@@ -40,11 +39,10 @@ public void ReturnsNilCountForBadChecksum()
//Fake a change to the data to cause data/crc missmatch
packet[4] = 0;
- int offset = 0;
int length = packet.Length;
- _crc32Layer.ProcessInboundPacket(ref _dummyEndpoint, ref packet, ref offset, ref length);
+ _crc32Layer.ProcessInboundPacket(ref _dummyEndpoint, ref packet, ref length);
- Assert.AreEqual(0, length);
+ Assert.That(length, Is.EqualTo(0));
}
[Test]
@@ -52,12 +50,11 @@ public void ReturnsNilCountForTooShortMessage()
{
byte[] packet = new byte[2];
- int offset = 0;
int length = packet.Length;
- _crc32Layer.ProcessInboundPacket(ref _dummyEndpoint, ref packet, ref offset, ref length);
+ _crc32Layer.ProcessInboundPacket(ref _dummyEndpoint, ref packet, ref length);
- Assert.AreEqual(0, length);
+ Assert.That(length, Is.EqualTo(0));
}
[Test]
@@ -72,7 +69,7 @@ public void CanSendAndReceiveSameMessage()
int offset = 0;
int length = message.Length;
_crc32Layer.ProcessOutBoundPacket(ref _dummyEndpoint, ref package, ref offset, ref length);
- _crc32Layer.ProcessInboundPacket(ref _dummyEndpoint, ref package, ref offset, ref length);
+ _crc32Layer.ProcessInboundPacket(ref _dummyEndpoint, ref package, ref length);
}
private static byte[] GetTestPacketWithCrc()
diff --git a/LiteNetLib.Tests/CommunicationTest.cs b/LiteNetLib.Tests/CommunicationTest.cs
index b971d438..fcc8224f 100644
--- a/LiteNetLib.Tests/CommunicationTest.cs
+++ b/LiteNetLib.Tests/CommunicationTest.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
@@ -11,15 +12,6 @@
namespace LiteNetLib.Tests
{
- class LibErrorChecker : INetLogger
- {
- public void WriteNet(NetLogLevel level, string str, params object[] args)
- {
- if(level == NetLogLevel.Error || level == NetLogLevel.Warning)
- Assert.Fail(str, args);
- }
- }
-
[TestFixture]
[Category("Communication")]
public class CommunicationTest
@@ -38,12 +30,15 @@ public void TearDown()
ManagerStack?.Dispose();
}
- private const int DefaultPort = 9050;
+ private static readonly int DefaultPort = TestPorts.ForFramework(9050);
+ private static readonly int ReconnectPort = TestPorts.ForFramework(10123);
+ private static readonly int ReuseClientPort = TestPorts.ForFramework(9049);
private const string DefaultAppKey = "test_server";
+ private static readonly byte[] DefaultAppKeyBytes = [12, 0, 116, 101, 115, 116, 95, 115, 101, 114, 118, 101, 114];
public NetManagerStack ManagerStack { get; set; }
- [Test, Timeout(TestTimeout)]
+ [Test, CancelAfter(TestTimeout)]
public void ConnectionByIpV4()
{
var server = ManagerStack.Server(1);
@@ -56,11 +51,11 @@ public void ConnectionByIpV4()
server.PollEvents();
}
- Assert.AreEqual(1, server.ConnectedPeersCount);
- Assert.AreEqual(1, client.ConnectedPeersCount);
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(1));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(1));
}
- [Test, Timeout(TestTimeout)]
+ [Test, CancelAfter(TestTimeout)]
public void P2PConnect()
{
var client1 = ManagerStack.Client(1);
@@ -76,11 +71,33 @@ public void P2PConnect()
client2.PollEvents();
}
- Assert.AreEqual(1, client1.ConnectedPeersCount);
- Assert.AreEqual(1, client2.ConnectedPeersCount);
+ Assert.That(client1.ConnectedPeersCount, Is.EqualTo(1));
+ Assert.That(client2.ConnectedPeersCount, Is.EqualTo(1));
+ }
+
+ [Test, CancelAfter(TestTimeout)]
+ public void P2PConnectWithSpan()
+ {
+ var client1 = ManagerStack.Client(1);
+ var client2 = ManagerStack.Client(2);
+
+ IPEndPoint endPoint1 = new IPEndPoint(IPAddress.Loopback, client2.LocalPort);
+ IPEndPoint endPoint2 = new IPEndPoint(IPAddress.Loopback, client1.LocalPort);
+ client1.Connect(endPoint1, DefaultAppKeyBytes.AsSpan());
+ client2.Connect(endPoint2, DefaultAppKeyBytes.AsSpan());
+
+ while (client1.ConnectedPeersCount != 1 || client2.ConnectedPeersCount != 1)
+ {
+ Thread.Sleep(15);
+ client1.PollEvents();
+ client2.PollEvents();
+ }
+
+ Assert.That(client1.ConnectedPeersCount, Is.EqualTo(1));
+ Assert.That(client2.ConnectedPeersCount, Is.EqualTo(1));
}
- [Test, Timeout(TestTimeout)]
+ [Test, CancelAfter(TestTimeout)]
public void ConnectionByIpV4Unsynced()
{
var server = ManagerStack.Server(1);
@@ -94,11 +111,11 @@ public void ConnectionByIpV4Unsynced()
Thread.Sleep(15);
}
- Assert.AreEqual(1, server.ConnectedPeersCount);
- Assert.AreEqual(1, client.ConnectedPeersCount);
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(1));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(1));
}
- [Test, Timeout(TestTimeout)]
+ [Test, CancelAfter(TestTimeout)]
public void DeliveryTest()
{
var server = ManagerStack.Server(1);
@@ -108,7 +125,7 @@ public void DeliveryTest()
const int testSize = 250 * 1024;
ManagerStack.ClientListener(1).DeliveryEvent += (peer, obj) =>
{
- Assert.AreEqual(5, (int)obj);
+ Assert.That((int)obj, Is.EqualTo(5));
msgDelivered = true;
};
ManagerStack.ClientListener(1).PeerConnectedEvent += peer =>
@@ -123,11 +140,11 @@ public void DeliveryTest()
};
ManagerStack.ServerListener(1).NetworkReceiveEvent += (peer, reader, channel, method) =>
{
- Assert.AreEqual(testSize, reader.UserDataSize);
- Assert.AreEqual(196, reader.RawData[reader.UserDataOffset]);
- Assert.AreEqual(32, reader.RawData[reader.UserDataOffset + 7000]);
- Assert.AreEqual(200, reader.RawData[reader.UserDataOffset + 12499]);
- Assert.AreEqual(254, reader.RawData[reader.UserDataOffset + testSize - 1]);
+ Assert.That(reader.UserDataSize, Is.EqualTo(testSize));
+ Assert.That(reader.RawData[reader.UserDataOffset], Is.EqualTo(196));
+ Assert.That(reader.RawData[reader.UserDataOffset + 7000], Is.EqualTo(32));
+ Assert.That(reader.RawData[reader.UserDataOffset + 12499], Is.EqualTo(200));
+ Assert.That(reader.RawData[reader.UserDataOffset + testSize - 1], Is.EqualTo(254));
msgReceived = true;
};
@@ -140,11 +157,11 @@ public void DeliveryTest()
client.PollEvents();
}
- Assert.AreEqual(1, server.ConnectedPeersCount);
- Assert.AreEqual(1, client.ConnectedPeersCount);
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(1));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(1));
}
- [Test, Timeout(TestTimeout)]
+ [Test, CancelAfter(TestTimeout)]
public void PeerNotFoundTest()
{
var server = ManagerStack.Server(1);
@@ -166,13 +183,13 @@ public void PeerNotFoundTest()
}
client.PollEvents();
- Assert.AreEqual(0, server.ConnectedPeersCount);
- Assert.AreEqual(0, client.ConnectedPeersCount);
- Assert.IsTrue(disconnectInfo.HasValue);
- Assert.AreEqual(DisconnectReason.PeerNotFound, disconnectInfo.Value.Reason);
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(0));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(0));
+ Assert.That(disconnectInfo.HasValue, Is.True);
+ Assert.That(disconnectInfo.Value.Reason, Is.EqualTo(DisconnectReason.PeerNotFound));
}
- [Test, Timeout(10000)]
+ [Test, CancelAfter(10000)]
public void ConnectionFailedTest()
{
NetManager client = ManagerStack.Client(1);
@@ -194,11 +211,11 @@ public void ConnectionFailedTest()
client.PollEvents();
}
- Assert.True(result);
- Assert.AreEqual(DisconnectReason.ConnectionFailed, disconnectInfo.Reason);
+ Assert.That(result, Is.True);
+ Assert.That(disconnectInfo.Reason, Is.EqualTo(DisconnectReason.ConnectionFailed));
}
- [Test, Timeout(10000)]
+ [Test, CancelAfter(10000)]
public void NetPeerDisconnectTimeout()
{
NetManager client = ManagerStack.Client(1);
@@ -216,18 +233,18 @@ public void NetPeerDisconnectTimeout()
client.PollEvents();
}
- Assert.AreEqual(ConnectionState.Connected, clientServerPeer.ConnectionState);
- Assert.True(server.ConnectedPeersCount == 1);
+ Assert.That(clientServerPeer.ConnectionState, Is.EqualTo(ConnectionState.Connected));
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(1));
ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) =>
{
- Assert.AreEqual(clientServerPeer, peer);
- Assert.AreEqual(DisconnectReason.Timeout, info.Reason);
+ Assert.That(peer, Is.EqualTo(clientServerPeer));
+ Assert.That(info.Reason, Is.EqualTo(DisconnectReason.Timeout));
};
server.Stop();
- Assert.True(server.ConnectedPeersCount == 0);
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(0));
while (client.ConnectedPeersCount == 1)
{
Thread.Sleep(15);
@@ -255,7 +272,7 @@ public void ReconnectTest()
};
client.Stop();
- client.Start(10123);
+ client.Start(ReconnectPort);
client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
while (connectCount < 2)
@@ -264,7 +281,7 @@ public void ReconnectTest()
{
client.Stop();
Thread.Sleep(500);
- client.Start(10123);
+ client.Start(ReconnectPort);
client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
reconnected = true;
}
@@ -272,7 +289,7 @@ public void ReconnectTest()
server.PollEvents();
Thread.Sleep(15);
}
- Assert.AreEqual(2, connectCount);
+ Assert.That(connectCount, Is.EqualTo(2));
}
[Test]
@@ -289,8 +306,8 @@ public void RejectTest()
};
ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) =>
{
- Assert.AreEqual(true, info.Reason == DisconnectReason.ConnectionRejected);
- Assert.AreEqual("reject_test", Encoding.UTF8.GetString(info.AdditionalData.GetRemainingBytes()));
+ Assert.That(info.Reason, Is.EqualTo(DisconnectReason.ConnectionRejected));
+ Assert.That(Encoding.UTF8.GetString(info.AdditionalData.GetRemainingBytes()), Is.EqualTo("reject_test"));
rejectReceived = true;
};
@@ -303,8 +320,8 @@ public void RejectTest()
Thread.Sleep(15);
}
- Assert.AreEqual(0, server.ConnectedPeersCount);
- Assert.AreEqual(0, client.ConnectedPeersCount);
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(0));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(0));
}
[Test]
@@ -321,8 +338,8 @@ public void RejectForceTest()
};
ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) =>
{
- Assert.AreEqual(true, info.Reason == DisconnectReason.ConnectionRejected);
- Assert.AreEqual("reject_test", Encoding.UTF8.GetString(info.AdditionalData.GetRemainingBytes()));
+ Assert.That(info.Reason, Is.EqualTo(DisconnectReason.ConnectionRejected));
+ Assert.That(Encoding.UTF8.GetString(info.AdditionalData.GetRemainingBytes()), Is.EqualTo("reject_test"));
rejectReceived = true;
};
@@ -335,11 +352,11 @@ public void RejectForceTest()
Thread.Sleep(15);
}
- Assert.AreEqual(0, server.ConnectedPeersCount);
- Assert.AreEqual(0, client.ConnectedPeersCount);
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(0));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(0));
}
- [Test, Timeout(10000)]
+ [Test, CancelAfter(10000)]
public void NetPeerDisconnectAll()
{
NetManager client = ManagerStack.Client(1);
@@ -357,20 +374,20 @@ public void NetPeerDisconnectAll()
client2.PollEvents();
}
- Assert.AreEqual(ConnectionState.Connected, clientServerPeer.ConnectionState);
- Assert.AreEqual(2, server.GetPeersCount(ConnectionState.Connected));
+ Assert.That(clientServerPeer.ConnectionState, Is.EqualTo(ConnectionState.Connected));
+ Assert.That(server.GetPeersCount(ConnectionState.Connected), Is.EqualTo(2));
ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) =>
{
byte[] bytes = info.AdditionalData.GetRemainingBytes();
- Assert.AreEqual(new byte[] { 1, 2, 3, 4 }, bytes);
- Assert.AreEqual(clientServerPeer, peer);
- Assert.AreEqual(DisconnectReason.RemoteConnectionClose, info.Reason);
+ Assert.That(bytes, Is.EqualTo(new byte[] { 1, 2, 3, 4 }).AsCollection);
+ Assert.That(peer, Is.EqualTo(clientServerPeer));
+ Assert.That(info.Reason, Is.EqualTo(DisconnectReason.RemoteConnectionClose));
};
server.DisconnectAll(new byte[]{1, 2, 3, 4}, 0, 4);
- Assert.AreEqual(0, server.GetPeersCount(ConnectionState.Connected));
+ Assert.That(server.GetPeersCount(ConnectionState.Connected), Is.EqualTo(0));
while (client.GetPeersCount(ConnectionState.Connected) != 0)
{
@@ -382,11 +399,11 @@ public void NetPeerDisconnectAll()
//Wait for client 'ShutdownOk' response
Thread.Sleep(100);
- Assert.AreEqual(0, server.ConnectedPeersCount);
- Assert.AreEqual(ConnectionState.Disconnected, clientServerPeer.ConnectionState);
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(0));
+ Assert.That(clientServerPeer.ConnectionState, Is.EqualTo(ConnectionState.Disconnected));
}
- [Test, Timeout(TestTimeout)]
+ [Test, CancelAfter(TestTimeout)]
public void DisconnectFromServerTest()
{
NetManager server = ManagerStack.Server(1);
@@ -412,13 +429,13 @@ public void DisconnectFromServerTest()
server.PollEvents();
}
- Assert.True(clientDisconnected);
- Assert.True(serverDisconnected);
- Assert.AreEqual(0, server.ConnectedPeersCount);
- Assert.AreEqual(0, client.ConnectedPeersCount);
+ Assert.That(clientDisconnected, Is.True);
+ Assert.That(serverDisconnected, Is.True);
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(0));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(0));
}
- [Test, Timeout(5000)]
+ [Test, CancelAfter(5000)]
public void EncryptTest()
{
EventBasedNetListener srvListener = new EventBasedNetListener();
@@ -437,20 +454,20 @@ public void EncryptTest()
srv.PollEvents();
}
Thread.Sleep(200);
- Assert.AreEqual(1, srv.ConnectedPeersCount);
- Assert.AreEqual(1, cli.ConnectedPeersCount);
+ Assert.That(srv.ConnectedPeersCount, Is.EqualTo(1));
+ Assert.That(cli.ConnectedPeersCount, Is.EqualTo(1));
cli.Stop();
srv.Stop();
}
- [Test, Timeout(5000)]
+ [Test, CancelAfter(5000)]
public void ConnectAfterDisconnectWithSamePort()
{
NetManager server = ManagerStack.Server(1);
EventBasedNetListener listener = new EventBasedNetListener();
NetManager client = new NetManager(listener, new Crc32cLayer());
- Assert.True(client.Start(9049));
+ Assert.That(client.Start(ReuseClientPort), Is.True);
client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
while (server.ConnectedPeersCount != 1)
{
@@ -464,7 +481,7 @@ public void ConnectAfterDisconnectWithSamePort()
{
connected = true;
};
- Assert.True(client.Start(9049));
+ Assert.That(client.Start(ReuseClientPort), Is.True);
client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
while (!connected)
@@ -474,13 +491,13 @@ public void ConnectAfterDisconnectWithSamePort()
client.PollEvents();
}
- Assert.True(connected);
- Assert.AreEqual(1, server.ConnectedPeersCount);
- Assert.AreEqual(1, client.ConnectedPeersCount);
+ Assert.That(connected, Is.True);
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(1));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(1));
client.Stop();
}
- [Test, Timeout(TestTimeout)]
+ [Test, CancelAfter(TestTimeout)]
public void DisconnectFromClientTest()
{
NetManager server = ManagerStack.Server(1);
@@ -490,13 +507,13 @@ public void DisconnectFromClientTest()
ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) =>
{
- Assert.AreEqual(DisconnectReason.DisconnectPeerCalled, info.Reason);
- Assert.AreEqual(0, client.ConnectedPeersCount);
+ Assert.That(info.Reason, Is.EqualTo(DisconnectReason.DisconnectPeerCalled));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(0));
clientDisconnected = true;
};
ManagerStack.ServerListener(1).PeerDisconnectedEvent += (peer, info) =>
{
- Assert.AreEqual(DisconnectReason.RemoteConnectionClose, info.Reason);
+ Assert.That(info.Reason, Is.EqualTo(DisconnectReason.RemoteConnectionClose));
serverDisconnected = true;
};
@@ -517,13 +534,13 @@ public void DisconnectFromClientTest()
server.PollEvents();
}
- Assert.True(clientDisconnected);
- Assert.True(serverDisconnected);
- Assert.AreEqual(0, server.ConnectedPeersCount);
- Assert.AreEqual(0, client.ConnectedPeersCount);
+ Assert.That(clientDisconnected, Is.True);
+ Assert.That(serverDisconnected, Is.True);
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(0));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(0));
}
- [Test, Timeout(10000)]
+ [Test, CancelAfter(10000)]
public void ChannelsTest()
{
const int channelsCount = 64;
@@ -560,7 +577,7 @@ public void ChannelsTest()
};
ManagerStack.ServerListener(1).NetworkReceiveEvent += (peer, reader, channel, method) =>
{
- Assert.AreEqual((DeliveryMethod)reader.GetByte(), method);
+ Assert.That((DeliveryMethod)reader.GetByte(), Is.EqualTo(method));
messagesReceived++;
};
client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
@@ -572,12 +589,12 @@ public void ChannelsTest()
Thread.Sleep(15);
}
- Assert.AreEqual(methods.Length*channelsCount, messagesReceived);
- Assert.AreEqual(1, server.ConnectedPeersCount);
- Assert.AreEqual(1, client.ConnectedPeersCount);
+ Assert.That(messagesReceived, Is.EqualTo(methods.Length * channelsCount));
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(1));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(1));
}
- [Test, Timeout(TestTimeout)]
+ [Test, CancelAfter(TestTimeout)]
public void ConnectionByIpV6()
{
var server = ManagerStack.Server(1);
@@ -590,11 +607,11 @@ public void ConnectionByIpV6()
server.PollEvents();
}
- Assert.AreEqual(1, server.ConnectedPeersCount);
- Assert.AreEqual(1, client.ConnectedPeersCount);
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(1));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(1));
}
- [Test, Timeout(10000)]
+ [Test, CancelAfter(10000)]
public void DiscoveryBroadcastTest()
{
var server = ManagerStack.Server(1);
@@ -623,8 +640,8 @@ public void DiscoveryBroadcastTest()
{
if (point.AddressFamily == AddressFamily.InterNetworkV6)
return;
- Assert.AreEqual(type, UnconnectedMessageType.BasicMessage);
- Assert.AreEqual("Server response", reader.GetString());
+ Assert.That(type, Is.EqualTo(UnconnectedMessageType.BasicMessage));
+ Assert.That(reader.GetString(), Is.EqualTo("Server response"));
ManagerStack.Client(cache).Connect(point, DefaultAppKey);
};
}
@@ -639,27 +656,27 @@ public void DiscoveryBroadcastTest()
Thread.Sleep(15);
}
- Assert.AreEqual(clientCount, server.ConnectedPeersCount);
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(clientCount));
ManagerStack.ClientForeach(
(i, manager, l) =>
{
- Assert.AreEqual(manager.ConnectedPeersCount, 1);
+ Assert.That(manager.ConnectedPeersCount, Is.EqualTo(1));
});
}
[Test]
public void HelperManagerStackTest()
{
- Assert.AreSame(ManagerStack.Client(1), ManagerStack.Client(1));
- Assert.AreNotSame(ManagerStack.Client(1), ManagerStack.Client(2));
- Assert.AreSame(ManagerStack.Client(2), ManagerStack.Client(2));
+ Assert.That(ManagerStack.Client(1), Is.SameAs(ManagerStack.Client(1)));
+ Assert.That(ManagerStack.Client(1), Is.Not.SameAs(ManagerStack.Client(2)));
+ Assert.That(ManagerStack.Client(2), Is.SameAs(ManagerStack.Client(2)));
- Assert.AreSame(ManagerStack.Server(1), ManagerStack.Server(1));
- Assert.AreNotSame(ManagerStack.Server(1), ManagerStack.Client(1));
- Assert.AreNotSame(ManagerStack.Server(1), ManagerStack.Client(2));
+ Assert.That(ManagerStack.Server(1), Is.SameAs(ManagerStack.Server(1)));
+ Assert.That(ManagerStack.Server(1), Is.Not.SameAs(ManagerStack.Client(1)));
+ Assert.That(ManagerStack.Server(1), Is.Not.SameAs(ManagerStack.Client(2)));
}
- [Test, Timeout(TestTimeout)]
+ [Test, CancelAfter(TestTimeout)]
public void ManualMode()
{
var serverListener = new EventBasedNetListener();
@@ -668,7 +685,7 @@ public void ManualMode()
serverListener.ConnectionRequestEvent += request => request.AcceptIfKey(DefaultAppKey);
var client = ManagerStack.Client(1);
- Assert.IsTrue(server.StartInManualMode(DefaultPort));
+ Assert.That(server.StartInManualMode(DefaultPort), Is.True);
client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
@@ -679,12 +696,12 @@ public void ManualMode()
server.ManualUpdate(15);
}
- Assert.AreEqual(1, server.ConnectedPeersCount);
- Assert.AreEqual(1, client.ConnectedPeersCount);
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(1));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(1));
server.Stop();
}
- [Test]
+ [Test, CancelAfter(TestTimeout)]
public void SendRawDataToAll()
{
var clientCount = 10;
@@ -702,9 +719,9 @@ public void SendRawDataToAll()
server.PollEvents();
}
- Assert.AreEqual(server.ConnectedPeersCount, clientCount);
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(clientCount));
Thread.Sleep(100);
- ManagerStack.ClientForeach((i, manager, l) => Assert.AreEqual(manager.ConnectedPeersCount, 1));
+ ManagerStack.ClientForeach((i, manager, l) => Assert.That(manager.ConnectedPeersCount, Is.EqualTo(1)));
var dataStack = new Stack(clientCount);
@@ -721,12 +738,12 @@ public void SendRawDataToAll()
Thread.Sleep(10);
}
- Assert.AreEqual(dataStack.Count, clientCount);
+ Assert.That(dataStack.Count, Is.EqualTo(clientCount));
- Assert.AreEqual(server.ConnectedPeersCount, clientCount);
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(clientCount));
for (ushort i = 1; i <= clientCount; i++)
{
- Assert.AreEqual(ManagerStack.Client(i).ConnectedPeersCount, 1);
+ Assert.That(ManagerStack.Client(i).ConnectedPeersCount, Is.EqualTo(1));
Assert.That(data, Is.EqualTo(dataStack.Pop()).AsCollection);
}
}
diff --git a/LiteNetLib.Tests/LibErrorChecker.cs b/LiteNetLib.Tests/LibErrorChecker.cs
new file mode 100644
index 00000000..d99538a7
--- /dev/null
+++ b/LiteNetLib.Tests/LibErrorChecker.cs
@@ -0,0 +1,13 @@
+using System;
+using NUnit.Framework;
+
+namespace LiteNetLib.Tests;
+
+class LibErrorChecker : INetLogger
+{
+ public void WriteNet(NetLogLevel level, string str, params object[] args)
+ {
+ if(level == NetLogLevel.Error || level == NetLogLevel.Warning)
+ Assert.Fail(string.Format(str, args));
+ }
+}
diff --git a/LiteNetLib.Tests/LiteCommunicationTest.cs b/LiteNetLib.Tests/LiteCommunicationTest.cs
new file mode 100644
index 00000000..608d655d
--- /dev/null
+++ b/LiteNetLib.Tests/LiteCommunicationTest.cs
@@ -0,0 +1,697 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using LiteNetLib.Layers;
+using LiteNetLib.Tests.TestUtility;
+using LiteNetLib.Utils;
+
+using NUnit.Framework;
+
+namespace LiteNetLib.Tests
+{
+ [TestFixture]
+ [Category("Communication")]
+ public class LiteCommunicationTest
+ {
+ const int TestTimeout = 4000;
+ [SetUp]
+ public void Init()
+ {
+ NetDebug.Logger = new LibErrorChecker();
+ ManagerStack = new LiteNetManagerStack(DefaultAppKey, DefaultPort);
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ ManagerStack?.Dispose();
+ }
+
+ private static readonly int DefaultPort = TestPorts.ForFramework(9050);
+ private static readonly int ReconnectPort = TestPorts.ForFramework(10123);
+ private static readonly int ReuseClientPort = TestPorts.ForFramework(9049);
+ private const string DefaultAppKey = "test_server";
+ private static readonly byte[] DefaultAppKeyBytes = [12, 0, 116, 101, 115, 116, 95, 115, 101, 114, 118, 101, 114];
+
+ public LiteNetManagerStack ManagerStack { get; set; }
+
+ [Test, CancelAfter(TestTimeout)]
+ public void ConnectionByIpV4()
+ {
+ var server = ManagerStack.Server(1);
+ var client = ManagerStack.Client(1);
+ client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
+
+ while (server.ConnectedPeersCount != 1 || client.ConnectedPeersCount != 1)
+ {
+ Thread.Sleep(15);
+ server.PollEvents();
+ }
+
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(1));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(1));
+ }
+
+ [Test, CancelAfter(TestTimeout)]
+ public void P2PConnect()
+ {
+ var client1 = ManagerStack.Client(1);
+ var client2 = ManagerStack.Client(2);
+
+ client1.Connect("127.0.0.1", client2.LocalPort, DefaultAppKey);
+ client2.Connect("127.0.0.1", client1.LocalPort, DefaultAppKey);
+
+ while (client1.ConnectedPeersCount != 1 || client2.ConnectedPeersCount != 1)
+ {
+ Thread.Sleep(15);
+ client1.PollEvents();
+ client2.PollEvents();
+ }
+
+ Assert.That(client1.ConnectedPeersCount, Is.EqualTo(1));
+ Assert.That(client2.ConnectedPeersCount, Is.EqualTo(1));
+ }
+
+ [Test, CancelAfter(TestTimeout)]
+ public void P2PConnectWithSpan()
+ {
+ var client1 = ManagerStack.Client(1);
+ var client2 = ManagerStack.Client(2);
+
+ IPEndPoint endPoint1 = new IPEndPoint(IPAddress.Loopback, client2.LocalPort);
+ IPEndPoint endPoint2 = new IPEndPoint(IPAddress.Loopback, client1.LocalPort);
+ client1.Connect(endPoint1, DefaultAppKeyBytes.AsSpan());
+ client2.Connect(endPoint2, DefaultAppKeyBytes.AsSpan());
+
+ while (client1.ConnectedPeersCount != 1 || client2.ConnectedPeersCount != 1)
+ {
+ Thread.Sleep(15);
+ client1.PollEvents();
+ client2.PollEvents();
+ }
+
+ Assert.That(client1.ConnectedPeersCount, Is.EqualTo(1));
+ Assert.That(client2.ConnectedPeersCount, Is.EqualTo(1));
+ }
+
+ [Test, CancelAfter(TestTimeout)]
+ public void ConnectionByIpV4Unsynced()
+ {
+ var server = ManagerStack.Server(1);
+ server.UnsyncedEvents = true;
+ var client = ManagerStack.Client(1);
+ client.UnsyncedEvents = true;
+ client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
+
+ while (server.ConnectedPeersCount != 1 || client.ConnectedPeersCount != 1)
+ {
+ Thread.Sleep(15);
+ }
+
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(1));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(1));
+ }
+
+ [Test, CancelAfter(TestTimeout)]
+ public void DeliveryTest()
+ {
+ var server = ManagerStack.Server(1);
+ var client = ManagerStack.Client(1);
+ bool msgDelivered = false;
+ bool msgReceived = false;
+ const int testSize = 250 * 1024;
+ ManagerStack.ClientListener(1).DeliveryEvent += (peer, obj) =>
+ {
+ Assert.That((int)obj, Is.EqualTo(5));
+ msgDelivered = true;
+ };
+ ManagerStack.ClientListener(1).PeerConnectedEvent += peer =>
+ {
+ int testData = 5;
+ byte[] arr = new byte[testSize];
+ arr[0] = 196;
+ arr[7000] = 32;
+ arr[12499] = 200;
+ arr[testSize - 1] = 254;
+ peer.SendWithDeliveryEvent(arr, DeliveryMethod.ReliableUnordered, testData);
+ };
+ ManagerStack.ServerListener(1).NetworkReceiveEvent += (peer, reader, method) =>
+ {
+ Assert.That(reader.UserDataSize, Is.EqualTo(testSize));
+ Assert.That(reader.RawData[reader.UserDataOffset], Is.EqualTo(196));
+ Assert.That(reader.RawData[reader.UserDataOffset + 7000], Is.EqualTo(32));
+ Assert.That(reader.RawData[reader.UserDataOffset + 12499], Is.EqualTo(200));
+ Assert.That(reader.RawData[reader.UserDataOffset + testSize - 1], Is.EqualTo(254));
+ msgReceived = true;
+ };
+
+ client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
+
+ while (server.ConnectedPeersCount != 1 || client.ConnectedPeersCount != 1 || !msgDelivered || !msgReceived)
+ {
+ Thread.Sleep(15);
+ server.PollEvents();
+ client.PollEvents();
+ }
+
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(1));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(1));
+ }
+
+ [Test, CancelAfter(TestTimeout)]
+ public void PeerNotFoundTest()
+ {
+ var server = ManagerStack.Server(1);
+ var client = ManagerStack.Client(1);
+ DisconnectInfo? disconnectInfo = null;
+ ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) => disconnectInfo = info;
+ client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
+
+ while (server.ConnectedPeersCount != 1 || client.ConnectedPeersCount != 1)
+ {
+ Thread.Sleep(15);
+ server.PollEvents();
+ }
+ server.Stop(false);
+ server.Start(DefaultPort);
+ while (client.ConnectedPeersCount == 1)
+ {
+ Thread.Sleep(15);
+ }
+ client.PollEvents();
+
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(0));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(0));
+ Assert.That(disconnectInfo.HasValue, Is.True);
+ Assert.That(disconnectInfo.Value.Reason, Is.EqualTo(DisconnectReason.PeerNotFound));
+ }
+
+ [Test, CancelAfter(10000)]
+ public void ConnectionFailedTest()
+ {
+ var client = ManagerStack.Client(1);
+
+ var result = false;
+ DisconnectInfo disconnectInfo = default;
+
+ ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) =>
+ {
+ result = true;
+ disconnectInfo = info;
+ };
+
+ client.Connect("127.0.0.2", DefaultPort, DefaultAppKey);
+
+ while (!result)
+ {
+ Thread.Sleep(15);
+ client.PollEvents();
+ }
+
+ Assert.That(result, Is.True);
+ Assert.That(disconnectInfo.Reason, Is.EqualTo(DisconnectReason.ConnectionFailed));
+ }
+
+ [Test, CancelAfter(10000)]
+ public void NetPeerDisconnectTimeout()
+ {
+ var client = ManagerStack.Client(1);
+ var server = ManagerStack.Server(1);
+
+ //Default 5 sec timeout for local network is too mach, set 1 for test
+ server.DisconnectTimeout = 1000;
+
+ var clientServerPeer = client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
+
+ while (clientServerPeer.ConnectionState != ConnectionState.Connected)
+ {
+ Thread.Sleep(15);
+ server.PollEvents();
+ client.PollEvents();
+ }
+
+ Assert.That(clientServerPeer.ConnectionState, Is.EqualTo(ConnectionState.Connected));
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(1));
+
+ ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) =>
+ {
+ Assert.That(peer, Is.EqualTo(clientServerPeer));
+ Assert.That(info.Reason, Is.EqualTo(DisconnectReason.Timeout));
+ };
+
+ server.Stop();
+
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(0));
+ while (client.ConnectedPeersCount == 1)
+ {
+ Thread.Sleep(15);
+ }
+ }
+
+ [Test]
+ public void ReconnectTest()
+ {
+ var server = ManagerStack.Server(1);
+ var client = ManagerStack.Client(1);
+ int connectCount = 0;
+ bool reconnected = false;
+ ManagerStack.ServerListener(1).PeerConnectedEvent += peer =>
+ {
+ if (connectCount == 0)
+ {
+ byte[] data = {1,2,3,4,5,6,7,8,9};
+ for (int i = 0; i < 1000; i++)
+ {
+ peer.Send(data, DeliveryMethod.ReliableOrdered);
+ }
+ }
+ connectCount++;
+ };
+
+ client.Stop();
+ client.Start(ReconnectPort);
+ client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
+
+ while (connectCount < 2)
+ {
+ if (connectCount == 1 && !reconnected)
+ {
+ client.Stop();
+ Thread.Sleep(500);
+ client.Start(ReconnectPort);
+ client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
+ reconnected = true;
+ }
+ client.PollEvents();
+ server.PollEvents();
+ Thread.Sleep(15);
+ }
+ Assert.That(connectCount, Is.EqualTo(2));
+ }
+
+ [Test]
+ public void RejectTest()
+ {
+ var server = ManagerStack.Server(1);
+ var client = ManagerStack.Client(1);
+ bool rejectReceived = false;
+
+ ManagerStack.ServerListener(1).ClearConnectionRequestEvent();
+ ManagerStack.ServerListener(1).ConnectionRequestEvent += request =>
+ {
+ request.Reject(Encoding.UTF8.GetBytes("reject_test"));
+ };
+ ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) =>
+ {
+ Assert.That(info.Reason, Is.EqualTo(DisconnectReason.ConnectionRejected));
+ Assert.That(Encoding.UTF8.GetString(info.AdditionalData.GetRemainingBytes()), Is.EqualTo("reject_test"));
+ rejectReceived = true;
+ };
+
+ client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
+
+ while (!rejectReceived)
+ {
+ client.PollEvents();
+ server.PollEvents();
+ Thread.Sleep(15);
+ }
+
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(0));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(0));
+ }
+
+ [Test]
+ public void RejectForceTest()
+ {
+ var server = ManagerStack.Server(1);
+ var client = ManagerStack.Client(1);
+ bool rejectReceived = false;
+
+ ManagerStack.ServerListener(1).ClearConnectionRequestEvent();
+ ManagerStack.ServerListener(1).ConnectionRequestEvent += request =>
+ {
+ request.RejectForce(Encoding.UTF8.GetBytes("reject_test"));
+ };
+ ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) =>
+ {
+ Assert.That(info.Reason, Is.EqualTo(DisconnectReason.ConnectionRejected));
+ Assert.That(Encoding.UTF8.GetString(info.AdditionalData.GetRemainingBytes()), Is.EqualTo("reject_test"));
+ rejectReceived = true;
+ };
+
+ client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
+
+ while (!rejectReceived)
+ {
+ client.PollEvents();
+ server.PollEvents();
+ Thread.Sleep(15);
+ }
+
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(0));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(0));
+ }
+
+ [Test, CancelAfter(10000)]
+ public void NetPeerDisconnectAll()
+ {
+ var client = ManagerStack.Client(1);
+ var client2 = ManagerStack.Client(2);
+ var server = ManagerStack.Server(1);
+
+ var clientServerPeer = client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
+ client2.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
+
+ while (clientServerPeer.ConnectionState != ConnectionState.Connected)
+ {
+ Thread.Sleep(15);
+ server.PollEvents();
+ client.PollEvents();
+ client2.PollEvents();
+ }
+
+ Assert.That(clientServerPeer.ConnectionState, Is.EqualTo(ConnectionState.Connected));
+ Assert.That(server.GetPeersCount(ConnectionState.Connected), Is.EqualTo(2));
+
+ ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) =>
+ {
+ byte[] bytes = info.AdditionalData.GetRemainingBytes();
+ Assert.That(bytes, Is.EqualTo(new byte[] { 1, 2, 3, 4 }).AsCollection);
+ Assert.That(peer, Is.EqualTo(clientServerPeer));
+ Assert.That(info.Reason, Is.EqualTo(DisconnectReason.RemoteConnectionClose));
+ };
+
+ server.DisconnectAll(new byte[]{1, 2, 3, 4}, 0, 4);
+
+ Assert.That(server.GetPeersCount(ConnectionState.Connected), Is.EqualTo(0));
+
+ while (client.GetPeersCount(ConnectionState.Connected) != 0)
+ {
+ Thread.Sleep(15);
+ client.PollEvents();
+ server.PollEvents();
+ }
+
+ //Wait for client 'ShutdownOk' response
+ Thread.Sleep(100);
+
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(0));
+ Assert.That(clientServerPeer.ConnectionState, Is.EqualTo(ConnectionState.Disconnected));
+ }
+
+ [Test, CancelAfter(TestTimeout)]
+ public void DisconnectFromServerTest()
+ {
+ var server = ManagerStack.Server(1);
+ var client = ManagerStack.Client(1);
+ var clientDisconnected = false;
+ var serverDisconnected = false;
+ ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) => { clientDisconnected = true; };
+ ManagerStack.ServerListener(1).PeerDisconnectedEvent += (peer, info) => { serverDisconnected = true; };
+
+ client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
+ while (server.ConnectedPeersCount != 1)
+ {
+ Thread.Sleep(15);
+ server.PollEvents();
+ }
+
+ server.DisconnectPeer(server.FirstPeer);
+
+ while (!(clientDisconnected && serverDisconnected))
+ {
+ Thread.Sleep(15);
+ client.PollEvents();
+ server.PollEvents();
+ }
+
+ Assert.That(clientDisconnected, Is.True);
+ Assert.That(serverDisconnected, Is.True);
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(0));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(0));
+ }
+
+ [Test, CancelAfter(5000)]
+ public void EncryptTest()
+ {
+ var srvListener = new EventBasedLiteNetListener();
+ var cliListener = new EventBasedLiteNetListener();
+ var srv = new LiteNetManager(srvListener, new XorEncryptLayer("secret_key"));
+ var cli = new LiteNetManager(cliListener, new XorEncryptLayer("secret_key"));
+ srv.Start(DefaultPort + 1);
+ cli.Start();
+
+ srvListener.ConnectionRequestEvent += request => { request.AcceptIfKey(DefaultAppKey); };
+ cli.Connect("127.0.0.1", DefaultPort + 1, DefaultAppKey);
+
+ while (srv.ConnectedPeersCount != 1)
+ {
+ Thread.Sleep(15);
+ srv.PollEvents();
+ }
+ Thread.Sleep(200);
+ Assert.That(srv.ConnectedPeersCount, Is.EqualTo(1));
+ Assert.That(cli.ConnectedPeersCount, Is.EqualTo(1));
+ cli.Stop();
+ srv.Stop();
+ }
+
+ [Test, CancelAfter(5000)]
+ public void ConnectAfterDisconnectWithSamePort()
+ {
+ var server = ManagerStack.Server(1);
+
+ var listener = new EventBasedLiteNetListener();
+ var client = new LiteNetManager(listener, new Crc32cLayer());
+ Assert.That(client.Start(ReuseClientPort), Is.True);
+ client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
+ while (server.ConnectedPeersCount != 1)
+ {
+ Thread.Sleep(15);
+ server.PollEvents();
+ }
+ client.Stop();
+
+ var connected = false;
+ listener.PeerConnectedEvent += (peer) =>
+ {
+ connected = true;
+ };
+ Assert.That(client.Start(ReuseClientPort), Is.True);
+ client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
+
+ while (!connected)
+ {
+ Thread.Sleep(15);
+ server.PollEvents();
+ client.PollEvents();
+ }
+
+ Assert.That(connected, Is.True);
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(1));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(1));
+ client.Stop();
+ }
+
+ [Test, CancelAfter(TestTimeout)]
+ public void DisconnectFromClientTest()
+ {
+ var server = ManagerStack.Server(1);
+ var client = ManagerStack.Client(1);
+ var clientDisconnected = false;
+ var serverDisconnected = false;
+
+ ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) =>
+ {
+ Assert.That(info.Reason, Is.EqualTo(DisconnectReason.DisconnectPeerCalled));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(0));
+ clientDisconnected = true;
+ };
+ ManagerStack.ServerListener(1).PeerDisconnectedEvent += (peer, info) =>
+ {
+ Assert.That(info.Reason, Is.EqualTo(DisconnectReason.RemoteConnectionClose));
+ serverDisconnected = true;
+ };
+
+ var serverPeer = client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
+ while (server.ConnectedPeersCount != 1)
+ {
+ Thread.Sleep(15);
+ server.PollEvents();
+ }
+
+ //User server peer from client
+ serverPeer.Disconnect();
+
+ while (!(clientDisconnected && serverDisconnected))
+ {
+ Thread.Sleep(15);
+ client.PollEvents();
+ server.PollEvents();
+ }
+
+ Assert.That(clientDisconnected, Is.True);
+ Assert.That(serverDisconnected, Is.True);
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(0));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(0));
+ }
+
+ [Test, CancelAfter(TestTimeout)]
+ public void ConnectionByIpV6()
+ {
+ var server = ManagerStack.Server(1);
+ var client = ManagerStack.Client(1);
+ client.Connect("::1", DefaultPort, DefaultAppKey);
+
+ while (server.ConnectedPeersCount != 1 || client.ConnectedPeersCount != 1)
+ {
+ Thread.Sleep(15);
+ server.PollEvents();
+ }
+
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(1));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(1));
+ }
+
+ [Test, CancelAfter(10000)]
+ public void DiscoveryBroadcastTest()
+ {
+ var server = ManagerStack.Server(1);
+ var clientCount = 10;
+
+ server.BroadcastReceiveEnabled = true;
+
+ var writer = new NetDataWriter();
+ writer.Put("Client request");
+
+ ManagerStack.ServerListener(1).NetworkReceiveUnconnectedEvent += (point, reader, type) =>
+ {
+ if (type == UnconnectedMessageType.Broadcast)
+ {
+ var serverWriter = new NetDataWriter();
+ serverWriter.Put("Server response");
+ server.SendUnconnectedMessage(serverWriter, point);
+ }
+ };
+
+ for (ushort i = 1; i <= clientCount; i++)
+ {
+ var cache = i;
+ ManagerStack.Client(i).UnconnectedMessagesEnabled = true;
+ ManagerStack.ClientListener(i).NetworkReceiveUnconnectedEvent += (point, reader, type) =>
+ {
+ if (point.AddressFamily == AddressFamily.InterNetworkV6)
+ return;
+ Assert.That(type, Is.EqualTo(UnconnectedMessageType.BasicMessage));
+ Assert.That(reader.GetString(), Is.EqualTo("Server response"));
+ ManagerStack.Client(cache).Connect(point, DefaultAppKey);
+ };
+ }
+
+ ManagerStack.ClientForeach((i, manager, l) => manager.SendBroadcast(writer, DefaultPort));
+
+ while (server.ConnectedPeersCount < clientCount)
+ {
+ server.PollEvents();
+ ManagerStack.ClientForeach((i, manager, l) => manager.PollEvents());
+
+ Thread.Sleep(15);
+ }
+
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(clientCount));
+ ManagerStack.ClientForeach(
+ (i, manager, l) =>
+ {
+ Assert.That(manager.ConnectedPeersCount, Is.EqualTo(1));
+ });
+ }
+
+ [Test]
+ public void HelperManagerStackTest()
+ {
+ Assert.That(ManagerStack.Client(1), Is.SameAs(ManagerStack.Client(1)));
+ Assert.That(ManagerStack.Client(1), Is.Not.SameAs(ManagerStack.Client(2)));
+ Assert.That(ManagerStack.Client(2), Is.SameAs(ManagerStack.Client(2)));
+
+ Assert.That(ManagerStack.Server(1), Is.SameAs(ManagerStack.Server(1)));
+ Assert.That(ManagerStack.Server(1), Is.Not.SameAs(ManagerStack.Client(1)));
+ Assert.That(ManagerStack.Server(1), Is.Not.SameAs(ManagerStack.Client(2)));
+ }
+
+ [Test, CancelAfter(TestTimeout)]
+ public void ManualMode()
+ {
+ var serverListener = new EventBasedLiteNetListener();
+ var server = new LiteNetManager(serverListener, new Crc32cLayer());
+
+ serverListener.ConnectionRequestEvent += request => request.AcceptIfKey(DefaultAppKey);
+
+ var client = ManagerStack.Client(1);
+ Assert.That(server.StartInManualMode(DefaultPort), Is.True);
+
+ client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
+
+ while (server.ConnectedPeersCount != 1 || client.ConnectedPeersCount != 1)
+ {
+ Thread.Sleep(15);
+ server.PollEvents();
+ server.ManualUpdate(15);
+ }
+
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(1));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(1));
+ server.Stop();
+ }
+
+ [Test, CancelAfter(TestTimeout)]
+ public void SendRawDataToAll()
+ {
+ var clientCount = 10;
+
+ var server = ManagerStack.Server(1);
+
+ for (ushort i = 1; i <= clientCount; i++)
+ {
+ ManagerStack.Client(i).Connect("127.0.0.1", DefaultPort, DefaultAppKey);
+ }
+
+ while (server.ConnectedPeersCount < clientCount)
+ {
+ Thread.Sleep(15);
+ server.PollEvents();
+ }
+
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(clientCount));
+ Thread.Sleep(100);
+ ManagerStack.ClientForeach((i, manager, l) => Assert.That(manager.ConnectedPeersCount, Is.EqualTo(1)));
+
+ var dataStack = new Stack(clientCount);
+
+ ManagerStack.ClientForeach(
+ (i, manager, l) => l.NetworkReceiveEvent += (peer, reader, type) => dataStack.Push(reader.GetRemainingBytes()));
+
+ var data = Encoding.Default.GetBytes("TextForTest");
+ server.SendToAll(data, DeliveryMethod.ReliableUnordered);
+
+ while (dataStack.Count < clientCount)
+ {
+ ManagerStack.ClientForeach((i, manager, l) => manager.PollEvents());
+
+ Thread.Sleep(10);
+ }
+
+ Assert.That(dataStack.Count, Is.EqualTo(clientCount));
+
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(clientCount));
+ for (ushort i = 1; i <= clientCount; i++)
+ {
+ Assert.That(ManagerStack.Client(i).ConnectedPeersCount, Is.EqualTo(1));
+ Assert.That(data, Is.EqualTo(dataStack.Pop()).AsCollection);
+ }
+ }
+ }
+}
diff --git a/LiteNetLib.Tests/LiteNetLib.Tests.csproj b/LiteNetLib.Tests/LiteNetLib.Tests.csproj
index 96504d06..b0bb097e 100644
--- a/LiteNetLib.Tests/LiteNetLib.Tests.csproj
+++ b/LiteNetLib.Tests/LiteNetLib.Tests.csproj
@@ -2,16 +2,15 @@
Library
- net6.0;net5.0;netcoreapp3.1
- net6.0;net5.0;net471;netcoreapp3.1
+ net8.0;net9.0
-
-
-
+
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -21,8 +20,4 @@
-
-
-
-
diff --git a/LiteNetLib.Tests/NetSerializerTest.cs b/LiteNetLib.Tests/NetSerializerTest.cs
index 39ed8be8..a8bd036a 100644
--- a/LiteNetLib.Tests/NetSerializerTest.cs
+++ b/LiteNetLib.Tests/NetSerializerTest.cs
@@ -1,4 +1,6 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
+using System.Linq;
using System.Runtime.Serialization;
using LiteNetLib.Utils;
@@ -16,17 +18,23 @@ public void Init()
_samplePacket = new SamplePacket
{
SomeFloat = 3.42f,
- SomeIntArray = new[] {6, 5, 4},
+ SomeIntArray = new[] { 6, 5, 4 },
SomeString = "Test String",
+ SomeGuid = Guid.NewGuid(),
SomeVector2 = new SomeVector2(4, 5),
- SomeVectors = new[] {new SomeVector2(1, 2), new SomeVector2(3, 4)},
+ SomeVectors = new[] { new SomeVector2(1, 2), new SomeVector2(3, 4) },
+ SomeFastVector3 = new SomeFastVector3(0.21f, 100.25f, -400.0f),
+ SomeFastVectors = new[] { new SomeFastVector3(1.0f, 2.0f, 3.0f), new SomeFastVector3(4.0f, 5.0f, 6.0f) },
SomeEnum = TestEnum.B,
SomeByteArray = new byte[] { 255, 1, 0 },
- TestObj = new SampleNetSerializable {Value = 5},
- TestArray = new [] { new SampleNetSerializable { Value = 6 }, new SampleNetSerializable { Value = 15 } },
- SampleClassArray = new[] { new SampleClass { Value = 6 }, new SampleClass { Value = 15 } },
- SampleClassList = new List { new SampleClass { Value = 1 }, new SampleClass { Value = 5 }},
- VectorList = new List { new SomeVector2(-1,-2), new SomeVector2(700, 800) },
+ TestObj = new SampleNetSerializable { Value = 5 },
+ TestArray = new[] { new SampleNetSerializable { Value = 6 }, new SampleNetSerializable { Value = 15 } },
+ SampleClassArray = new[]
+ { FillTestArray(new SampleClass { Value = 6, TestEnum = TestEnum.C }), FillTestArray(new SampleClass { Value = 15, TestEnum = TestEnum.B }) },
+ SampleClassList = new List
+ { FillTestArray(new SampleClass { Value = 1 }), FillTestArray(new SampleClass { Value = 5 }) },
+ VectorList = new List { new SomeVector2(-1, -2), new SomeVector2(700, 800) },
+ FastVectorList = new List { new SomeFastVector3(-100.0f, -200.0f, -300.0f), new SomeFastVector3(100.0f, 200.0f, 300.0f) },
IgnoreMe = 1337
};
@@ -34,6 +42,7 @@ public void Init()
_packetProcessor.RegisterNestedType();
_packetProcessor.RegisterNestedType(() => new SampleClass());
_packetProcessor.RegisterNestedType(SomeVector2.Serialize, SomeVector2.Deserialize);
+ _packetProcessor.RegisterNestedType(SomeFastVector3.Serialize, SomeFastVector3.Deserialize);
}
private SamplePacket _samplePacket;
@@ -65,6 +74,30 @@ public static SomeVector2 Deserialize(NetDataReader reader)
}
}
+ private readonly struct SomeFastVector3
+ {
+ public readonly float X;
+ public readonly float Y;
+ public readonly float Z;
+
+ public SomeFastVector3(float x, float y, float z)
+ {
+ X = x;
+ Y = y;
+ Z = z;
+ }
+
+ public static void Serialize(NetDataWriter writer, SomeFastVector3 vector)
+ {
+ writer.PutUnmanaged(vector);
+ }
+
+ public static SomeFastVector3 Deserialize(NetDataReader reader)
+ {
+ return reader.GetUnmanaged();
+ }
+ }
+
private struct SampleNetSerializable : INetSerializable
{
public int Value;
@@ -83,20 +116,28 @@ public void Deserialize(NetDataReader reader)
private class SampleClass : INetSerializable
{
public int Value;
+ public ChildClass[] TestArray = Array.Empty();
+ public TestEnum TestEnum;
public void Serialize(NetDataWriter writer)
{
writer.Put(Value);
+ writer.PutArray(TestArray);
+ writer.PutEnum(TestEnum);
}
public void Deserialize(NetDataReader reader)
{
Value = reader.GetInt();
+ TestArray = reader.GetArray();
+ TestEnum = reader.GetEnum();
}
public override bool Equals(object obj)
{
- return ((SampleClass)obj).Value == Value;
+ var other = (SampleClass)obj;
+ return other.Value == Value && (TestArray is null && other.TestArray is null || TestArray != null &&
+ other.TestArray != null && TestArray.SequenceEqual(other.TestArray)) && other.TestEnum == TestEnum;
}
public override int GetHashCode()
@@ -105,6 +146,31 @@ public override int GetHashCode()
}
}
+ private class ChildClass : INetSerializable
+ {
+ public int Value;
+
+ public void Serialize(NetDataWriter writer)
+ {
+ writer.Put(Value);
+ }
+
+ public void Deserialize(NetDataReader reader)
+ {
+ Value = reader.GetInt();
+ }
+
+ public override bool Equals(object obj)
+ {
+ return ((ChildClass)obj).Value == Value;
+ }
+
+ public override int GetHashCode()
+ {
+ return Value.GetHashCode();
+ }
+ }
+
private enum TestEnum
{
A = 1,
@@ -119,14 +185,18 @@ private class SamplePacket
public int[] SomeIntArray { get; set; }
public byte[] SomeByteArray { get; set; }
public string SomeString { get; set; }
+ public Guid SomeGuid { get; set; }
public SomeVector2 SomeVector2 { get; set; }
public SomeVector2[] SomeVectors { get; set; }
+ public SomeFastVector3 SomeFastVector3 { get; set; }
+ public SomeFastVector3[] SomeFastVectors { get; set; }
public TestEnum SomeEnum { get; set; }
public SampleNetSerializable TestObj { get; set; }
public SampleNetSerializable[] TestArray { get; set; }
public SampleClass[] SampleClassArray { get; set; }
public List SampleClassList { get; set; }
public List VectorList { get; set; }
+ public List FastVectorList { get; set; }
[IgnoreDataMember]
public int IgnoreMe { get; set; }
}
@@ -140,6 +210,15 @@ private static bool AreSame(string s1, string s2)
return s1 == s2;
}
+ private static SampleClass FillTestArray(SampleClass obj)
+ {
+ var random = new Random();
+ obj.TestArray = new ChildClass[random.Next(10)];
+ for (int i = 0; i < obj.TestArray.Length; i++)
+ obj.TestArray[i] = new ChildClass { Value = random.Next() };
+ return obj;
+ }
+
[Test, MaxTime(2000)]
public void CustomPackageTest()
{
@@ -157,48 +236,54 @@ public void CustomPackageTest()
_packetProcessor.ReadAllPackets(reader);
- Assert.NotNull(readPackage);
- Assert.IsTrue(AreSame(_samplePacket.EmptyString, readPackage.EmptyString));
- Assert.AreEqual(_samplePacket.SomeFloat, readPackage.SomeFloat);
- Assert.AreEqual(_samplePacket.SomeIntArray, readPackage.SomeIntArray);
- Assert.IsTrue(AreSame(_samplePacket.SomeString, readPackage.SomeString));
- Assert.AreEqual(_samplePacket.SomeVector2, readPackage.SomeVector2);
- Assert.AreEqual(_samplePacket.SomeVectors, readPackage.SomeVectors);
- Assert.AreEqual(_samplePacket.SomeEnum, readPackage.SomeEnum);
- Assert.AreEqual(_samplePacket.TestObj.Value, readPackage.TestObj.Value);
- Assert.AreEqual(_samplePacket.TestArray, readPackage.TestArray);
- Assert.AreEqual(_samplePacket.SomeByteArray, readPackage.SomeByteArray);
- Assert.AreEqual(_samplePacket.SampleClassArray, readPackage.SampleClassArray);
- Assert.AreEqual(0, readPackage.IgnoreMe); // expect 0 because it should be ignored
- CollectionAssert.AreEqual(_samplePacket.SampleClassList, readPackage.SampleClassList);
- CollectionAssert.AreEqual(_samplePacket.VectorList, readPackage.VectorList);
+ Assert.That(readPackage, Is.Not.Null);
+ Assert.That(AreSame(_samplePacket.EmptyString, readPackage.EmptyString), Is.True);
+ Assert.That(readPackage.SomeFloat, Is.EqualTo(_samplePacket.SomeFloat));
+ Assert.That(readPackage.SomeIntArray, Is.EqualTo(_samplePacket.SomeIntArray).AsCollection);
+ Assert.That(AreSame(_samplePacket.SomeString, readPackage.SomeString), Is.True);
+ Assert.That(readPackage.SomeGuid, Is.EqualTo(_samplePacket.SomeGuid));
+ Assert.That(readPackage.SomeVector2, Is.EqualTo(_samplePacket.SomeVector2));
+ Assert.That(readPackage.SomeVectors, Is.EqualTo(_samplePacket.SomeVectors).AsCollection);
+ Assert.That(readPackage.SomeFastVector3, Is.EqualTo(_samplePacket.SomeFastVector3));
+ Assert.That(readPackage.SomeFastVectors, Is.EqualTo(_samplePacket.SomeFastVectors).AsCollection);
+ Assert.That(readPackage.SomeEnum, Is.EqualTo(_samplePacket.SomeEnum));
+ Assert.That(readPackage.TestObj.Value, Is.EqualTo(_samplePacket.TestObj.Value));
+ Assert.That(readPackage.TestArray, Is.EqualTo(_samplePacket.TestArray).AsCollection);
+ Assert.That(readPackage.SomeByteArray, Is.EqualTo(_samplePacket.SomeByteArray).AsCollection);
+ Assert.That(readPackage.SampleClassArray, Is.EqualTo(_samplePacket.SampleClassArray).AsCollection);
+ Assert.That(readPackage.IgnoreMe, Is.EqualTo(0)); // expect 0 because it should be ignored
+ Assert.That(readPackage.SampleClassList, Is.EqualTo(_samplePacket.SampleClassList).AsCollection);
+ Assert.That(readPackage.VectorList, Is.EqualTo(_samplePacket.VectorList).AsCollection);
+ Assert.That(readPackage.FastVectorList, Is.EqualTo(_samplePacket.FastVectorList).AsCollection);
//remove test
_samplePacket.SampleClassList.RemoveAt(0);
_samplePacket.SampleClassArray = new []{new SampleClass {Value = 1}};
_samplePacket.VectorList.RemoveAt(0);
+ _samplePacket.FastVectorList.RemoveAt(0);
writer.Reset();
_packetProcessor.Write(writer, _samplePacket);
reader.SetSource(writer);
_packetProcessor.ReadAllPackets(reader);
- Assert.AreEqual(_samplePacket.SampleClassArray, readPackage.SampleClassArray);
- CollectionAssert.AreEqual(_samplePacket.SampleClassList, readPackage.SampleClassList);
+ Assert.That(readPackage.SampleClassArray, Is.EqualTo(_samplePacket.SampleClassArray).AsCollection);
+ Assert.That(readPackage.SampleClassList, Is.EqualTo(_samplePacket.SampleClassList).AsCollection);
//add test
_samplePacket.SampleClassList.Add(new SampleClass { Value = 152 });
_samplePacket.SampleClassList.Add(new SampleClass { Value = 154 });
_samplePacket.SampleClassArray = new[] { new SampleClass { Value = 1 }, new SampleClass { Value = 2 }, new SampleClass { Value = 3 } };
_samplePacket.VectorList.Add(new SomeVector2(500,600));
+ _samplePacket.FastVectorList.Add(new SomeFastVector3(500.0f, 600.0f, 700.0f));
writer.Reset();
_packetProcessor.Write(writer, _samplePacket);
reader.SetSource(writer);
_packetProcessor.ReadAllPackets(reader);
- Assert.AreEqual(_samplePacket.SampleClassArray, readPackage.SampleClassArray);
- CollectionAssert.AreEqual(_samplePacket.SampleClassList, readPackage.SampleClassList);
+ Assert.That(readPackage.SampleClassArray, Is.EqualTo(_samplePacket.SampleClassArray).AsCollection);
+ Assert.That(readPackage.SampleClassList, Is.EqualTo(_samplePacket.SampleClassList).AsCollection);
}
}
}
diff --git a/LiteNetLib.Tests/ReaderWriterSimpleDataTest.cs b/LiteNetLib.Tests/ReaderWriterSimpleDataTest.cs
index e54d4d33..03725915 100644
--- a/LiteNetLib.Tests/ReaderWriterSimpleDataTest.cs
+++ b/LiteNetLib.Tests/ReaderWriterSimpleDataTest.cs
@@ -1,6 +1,8 @@
using LiteNetLib.Utils;
using NUnit.Framework;
+using System;
+using System.Net;
namespace LiteNetLib.Tests
{
@@ -14,10 +16,10 @@ public void WriteReadBool()
var ndw = new NetDataWriter();
ndw.Put(true);
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readBool = ndr.GetBool();
- Assert.AreEqual(readBool, true);
+ Assert.That(readBool, Is.True);
}
[Test]
@@ -26,7 +28,7 @@ public void WriteReadBoolArray()
var ndw = new NetDataWriter();
ndw.PutArray(new[] {true, false, true, false, false});
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readBoolArray = ndr.GetBoolArray();
Assert.That(new[] {true, false, true, false, false}, Is.EqualTo(readBoolArray).AsCollection);
@@ -38,10 +40,10 @@ public void WriteReadByte()
var ndw = new NetDataWriter();
ndw.Put((byte) 8);
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readByte = ndr.GetByte();
- Assert.AreEqual(readByte, (byte) 8);
+ Assert.That(readByte, Is.EqualTo((byte) 8));
}
[Test]
@@ -50,7 +52,7 @@ public void WriteReadByteArray()
var ndw = new NetDataWriter();
ndw.Put(new byte[] {1, 2, 4, 8, 16, byte.MaxValue, byte.MinValue});
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readByteArray = new byte[7];
ndr.GetBytes(readByteArray, 7);
@@ -59,16 +61,36 @@ public void WriteReadByteArray()
Is.EqualTo(readByteArray).AsCollection);
}
+#if NET5_0_OR_GREATER
+ [Test]
+ public void WriteReadByteSpan()
+ {
+ Span tempBytes = new byte[] { 1, 2, 4, 8 };
+ var ndw = new NetDataWriter();
+ ndw.Put(tempBytes);
+ Span anotherTempBytes = new byte[] { 16, byte.MaxValue, byte.MinValue };
+ ndw.Put(anotherTempBytes);
+
+ var ndr = new NetDataReader(ndw);
+ var readByteArray = new byte[7];
+ ndr.GetBytes(readByteArray, 7);
+
+ Assert.That(
+ new byte[] { 1, 2, 4, 8, 16, byte.MaxValue, byte.MinValue },
+ Is.EqualTo(readByteArray).AsCollection);
+ }
+#endif
+
[Test]
public void WriteReadDouble()
{
var ndw = new NetDataWriter();
ndw.Put(3.1415);
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readDouble = ndr.GetDouble();
- Assert.AreEqual(readDouble, 3.1415);
+ Assert.That(readDouble, Is.EqualTo(3.1415));
}
[Test]
@@ -77,7 +99,7 @@ public void WriteReadDoubleArray()
var ndw = new NetDataWriter();
ndw.PutArray(new[] {1.1, 2.2, 3.3, 4.4, double.MaxValue, double.MinValue});
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readDoubleArray = ndr.GetDoubleArray();
Assert.That(
@@ -91,10 +113,10 @@ public void WriteReadFloat()
var ndw = new NetDataWriter();
ndw.Put(3.1415f);
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readFloat = ndr.GetFloat();
- Assert.AreEqual(readFloat, 3.1415f);
+ Assert.That(readFloat, Is.EqualTo(3.1415f));
}
[Test]
@@ -103,7 +125,7 @@ public void WriteReadFloatArray()
var ndw = new NetDataWriter();
ndw.PutArray(new[] {1.1f, 2.2f, 3.3f, 4.4f, float.MaxValue, float.MinValue});
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readFloatArray = ndr.GetFloatArray();
Assert.That(
@@ -117,10 +139,10 @@ public void WriteReadInt()
var ndw = new NetDataWriter();
ndw.Put(32);
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readInt = ndr.GetInt();
- Assert.AreEqual(readInt, 32);
+ Assert.That(readInt, Is.EqualTo(32));
}
[Test]
@@ -129,7 +151,7 @@ public void WriteReadIntArray()
var ndw = new NetDataWriter();
ndw.PutArray(new[] {1, 2, 3, 4, 5, 6, 7, int.MaxValue, int.MinValue});
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readIntArray = ndr.GetIntArray();
Assert.That(new[] {1, 2, 3, 4, 5, 6, 7, int.MaxValue, int.MinValue}, Is.EqualTo(readIntArray).AsCollection);
@@ -141,10 +163,10 @@ public void WriteReadLong()
var ndw = new NetDataWriter();
ndw.Put(64L);
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readLong = ndr.GetLong();
- Assert.AreEqual(readLong, 64L);
+ Assert.That(readLong, Is.EqualTo(64L));
}
[Test]
@@ -153,7 +175,7 @@ public void WriteReadLongArray()
var ndw = new NetDataWriter();
ndw.PutArray(new[] {1L, 2L, 3L, 4L, long.MaxValue, long.MinValue});
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readLongArray = ndr.GetLongArray();
Assert.That(new[] {1L, 2L, 3L, 4L, long.MaxValue, long.MinValue}, Is.EqualTo(readLongArray).AsCollection);
@@ -165,10 +187,10 @@ public void WriteReadNetEndPoint()
var ndw = new NetDataWriter();
ndw.Put(NetUtils.MakeEndPoint("127.0.0.1", 7777));
- var ndr = new NetDataReader(ndw.Data);
- var readNetEndPoint = ndr.GetNetEndPoint();
+ var ndr = new NetDataReader(ndw);
+ var readNetEndPoint = ndr.GetIPEndPoint();
- Assert.AreEqual(readNetEndPoint, NetUtils.MakeEndPoint("127.0.0.1", 7777));
+ Assert.That(readNetEndPoint, Is.EqualTo(NetUtils.MakeEndPoint("127.0.0.1", 7777)));
}
[Test]
@@ -177,10 +199,10 @@ public void WriteReadSByte()
var ndw = new NetDataWriter();
ndw.Put((sbyte) 8);
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readSByte = ndr.GetSByte();
- Assert.AreEqual(readSByte, (sbyte) 8);
+ Assert.That(readSByte, Is.EqualTo((sbyte) 8));
}
[Test]
@@ -189,10 +211,10 @@ public void WriteReadShort()
var ndw = new NetDataWriter();
ndw.Put((short) 16);
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readShort = ndr.GetShort();
- Assert.AreEqual(readShort, (short) 16);
+ Assert.That(readShort, Is.EqualTo((short) 16));
}
[Test]
@@ -201,7 +223,7 @@ public void WriteReadShortArray()
var ndw = new NetDataWriter();
ndw.PutArray(new short[] {1, 2, 3, 4, 5, 6, short.MaxValue, short.MinValue});
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readShortArray = ndr.GetShortArray();
Assert.That(
@@ -215,10 +237,10 @@ public void WriteReadString()
var ndw = new NetDataWriter();
ndw.Put("String", 10);
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readString = ndr.GetString(10);
- Assert.AreEqual(readString, "String");
+ Assert.That(readString, Is.EqualTo("String"));
}
[Test]
@@ -227,7 +249,7 @@ public void WriteReadStringArray()
var ndw = new NetDataWriter();
ndw.PutArray(new[] {"First", "Second", "Third", "Fourth"});
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readStringArray = ndr.GetStringArray(10);
Assert.That(new[] {"First", "Second", "Third", "Fourth"}, Is.EqualTo(readStringArray).AsCollection);
@@ -239,10 +261,10 @@ public void WriteReadUInt()
var ndw = new NetDataWriter();
ndw.Put(34U);
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readUInt = ndr.GetUInt();
- Assert.AreEqual(readUInt, 34U);
+ Assert.That(readUInt, Is.EqualTo(34U));
}
[Test]
@@ -251,7 +273,7 @@ public void WriteReadUIntArray()
var ndw = new NetDataWriter();
ndw.PutArray(new[] {1U, 2U, 3U, 4U, 5U, 6U, uint.MaxValue, uint.MinValue});
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readUIntArray = ndr.GetUIntArray();
Assert.That(
@@ -265,10 +287,10 @@ public void WriteReadULong()
var ndw = new NetDataWriter();
ndw.Put(64UL);
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readULong = ndr.GetULong();
- Assert.AreEqual(readULong, 64UL);
+ Assert.That(readULong, Is.EqualTo(64UL));
}
[Test]
@@ -277,7 +299,7 @@ public void WriteReadULongArray()
var ndw = new NetDataWriter();
ndw.PutArray(new[] {1UL, 2UL, 3UL, 4UL, 5UL, ulong.MaxValue, ulong.MinValue});
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readULongArray = ndr.GetULongArray();
Assert.That(
@@ -291,10 +313,28 @@ public void WriteReadUShort()
var ndw = new NetDataWriter();
ndw.Put((ushort) 16);
- var ndr = new NetDataReader(ndw.Data);
+ var ndr = new NetDataReader(ndw);
var readUShort = ndr.GetUShort();
- Assert.AreEqual(readUShort, (ushort) 16);
+ Assert.That(readUShort, Is.EqualTo((ushort) 16));
+ }
+
+ [Test]
+ public void WriteReadIPEndPoint()
+ {
+ var ndw = new NetDataWriter();
+ var ipep = new IPEndPoint(IPAddress.Broadcast, 12345);
+ var ipep6 = new IPEndPoint(IPAddress.IPv6Loopback, 12345);
+ ndw.Put(ipep);
+ ndw.Put(ipep6);
+
+ var ndr = new NetDataReader(ndw);
+ var readIpep = ndr.GetIPEndPoint();
+ var readIpep6 = ndr.GetIPEndPoint();
+
+ Assert.That(readIpep, Is.EqualTo(ipep));
+ Assert.That(readIpep6, Is.EqualTo(ipep6));
+ Assert.That(ndr.AvailableBytes, Is.EqualTo(0));
}
}
}
diff --git a/LiteNetLib.Tests/TestUtility/NetManagerStack.cs b/LiteNetLib.Tests/TestUtility/NetManagerStack.cs
index 8fe32528..4ade127c 100644
--- a/LiteNetLib.Tests/TestUtility/NetManagerStack.cs
+++ b/LiteNetLib.Tests/TestUtility/NetManagerStack.cs
@@ -6,36 +6,34 @@
namespace LiteNetLib.Tests.TestUtility
{
- public class NetManagerStack : IDisposable
+ public abstract class NetManagerStack : IDisposable where TManager : LiteNetManager
{
private struct NetContainer
{
- public readonly NetManager Manager;
- public readonly EventBasedNetListener Listener;
+ public readonly TManager Manager;
+ public readonly TListener Listener;
- public NetContainer(NetManager netManager, EventBasedNetListener listener)
+ public NetContainer(TManager netManager, TListener listener)
{
Manager = netManager;
Listener = listener;
}
}
- private readonly string _appKey;
+ protected readonly string AppKey;
private readonly int _serverPort;
- private readonly HashSet _clientIds = new HashSet();
- private readonly HashSet _serverIds = new HashSet();
-
- private readonly Dictionary _managers =
- new Dictionary();
+ private readonly HashSet _clientIds = new();
+ private readonly HashSet _serverIds = new();
+ private readonly Dictionary _managers = new();
public NetManagerStack(string appKey, int serverPort)
{
- _appKey = appKey;
+ AppKey = appKey;
_serverPort = serverPort;
}
- public void ClientForeach(Action action)
+ public void ClientForeach(Action action)
{
foreach (var id in _clientIds)
{
@@ -44,34 +42,34 @@ public void ClientForeach(Action acti
}
}
- public void ServerForeach(Action action)
+ public void ServerForeach(Action action)
{
- foreach (var id in _clientIds)
+ foreach (var id in _serverIds)
{
var tuple = GetNetworkManager(id, false);
action(id, tuple.Manager, tuple.Listener);
}
}
- public NetManager Client(ushort id)
+ public TManager Client(ushort id)
{
_clientIds.Add(id);
return GetNetworkManager(id, true).Manager;
}
- public EventBasedNetListener ClientListener(ushort id)
+ public TListener ClientListener(ushort id)
{
_clientIds.Add(id);
return GetNetworkManager(id, true).Listener;
}
- public NetManager Server(ushort id)
+ public TManager Server(ushort id)
{
_serverIds.Add(id);
return GetNetworkManager(id, false).Manager;
}
- public EventBasedNetListener ServerListener(ushort id)
+ public TListener ServerListener(ushort id)
{
_serverIds.Add(id);
return GetNetworkManager(id, false).Listener;
@@ -80,41 +78,31 @@ public EventBasedNetListener ServerListener(ushort id)
public void Dispose()
{
foreach (var manager in _managers.Values.Select(v => v.Manager))
- {
manager.Stop();
- }
}
+ protected abstract (TManager,TListener) CreateNetworkManager();
+
private NetContainer GetNetworkManager(ushort id, bool isClient)
{
- NetContainer container;
if (id == 0)
{
Assert.Fail("Id cannot be 0");
}
var key = isClient ? id : (uint) id << 16;
- if (!_managers.TryGetValue(key, out container))
+ if (!_managers.TryGetValue(key, out var container))
{
- var listener = new EventBasedNetListener();
- listener.ConnectionRequestEvent += request =>
- {
- request.AcceptIfKey(_appKey);
- };
- NetManager netManager = new NetManager(listener, new Crc32cLayer());
+ var (netManager, listener) = CreateNetworkManager();
if (isClient)
{
if (!netManager.Start())
- {
Assert.Fail($"Client {id} start failed");
- }
}
else
{
if (!netManager.Start(_serverPort))
- {
Assert.Fail($"Server {id} on port{_serverPort} start failed");
- }
}
container = new NetContainer(netManager, listener);
@@ -124,4 +112,32 @@ private NetContainer GetNetworkManager(ushort id, bool isClient)
return container;
}
}
+
+ public class LiteNetManagerStack : NetManagerStack
+ {
+ public LiteNetManagerStack(string appKey, int serverPort) : base(appKey, serverPort)
+ {
+ }
+
+ protected override (LiteNetManager, EventBasedLiteNetListener) CreateNetworkManager()
+ {
+ var listener = new EventBasedLiteNetListener();
+ listener.ConnectionRequestEvent += request =>request.AcceptIfKey(AppKey);
+ return (new LiteNetManager(listener, new Crc32cLayer()), listener);
+ }
+ }
+
+ public class NetManagerStack : NetManagerStack
+ {
+ public NetManagerStack(string appKey, int serverPort) : base(appKey, serverPort)
+ {
+ }
+
+ protected override (NetManager, EventBasedNetListener) CreateNetworkManager()
+ {
+ var listener = new EventBasedNetListener();
+ listener.ConnectionRequestEvent += request =>request.AcceptIfKey(AppKey);
+ return (new NetManager(listener, new Crc32cLayer()), listener);
+ }
+ }
}
diff --git a/LiteNetLib.Tests/TestUtility/TestPorts.cs b/LiteNetLib.Tests/TestUtility/TestPorts.cs
new file mode 100644
index 00000000..70b63934
--- /dev/null
+++ b/LiteNetLib.Tests/TestUtility/TestPorts.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Runtime.Versioning;
+
+namespace LiteNetLib.Tests.TestUtility
+{
+ internal static class TestPorts
+ {
+ private const int BaselineFrameworkMajor = 8;
+ private const int FrameworkPortBlockSize = 1000;
+
+ public static int ForFramework(int basePort)
+ {
+ var frameworkAttribute = (TargetFrameworkAttribute)Attribute.GetCustomAttribute(
+ typeof(TestPorts).Assembly,
+ typeof(TargetFrameworkAttribute));
+
+ if (frameworkAttribute == null)
+ return basePort;
+
+ const string versionMarker = "Version=v";
+ var versionStart = frameworkAttribute.FrameworkName.IndexOf(versionMarker, StringComparison.Ordinal);
+ if (versionStart < 0)
+ return basePort;
+
+ var versionText = frameworkAttribute.FrameworkName.Substring(versionStart + versionMarker.Length);
+ if (!Version.TryParse(versionText, out var version))
+ return basePort;
+
+ return basePort + Math.Max(0, version.Major - BaselineFrameworkMajor) * FrameworkPortBlockSize;
+ }
+ }
+}
diff --git a/LiteNetLib.Tests/packages.config b/LiteNetLib.Tests/packages.config
deleted file mode 100644
index b68e40f9..00000000
--- a/LiteNetLib.Tests/packages.config
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/LiteNetLib.Trim.Dummy/LiteNetLib.Trim.Dummy.csproj b/LiteNetLib.Trim.Dummy/LiteNetLib.Trim.Dummy.csproj
new file mode 100644
index 00000000..8fe4051b
--- /dev/null
+++ b/LiteNetLib.Trim.Dummy/LiteNetLib.Trim.Dummy.csproj
@@ -0,0 +1,17 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ enable
+ true
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/LiteNetLib.Trim.Dummy/Program.cs b/LiteNetLib.Trim.Dummy/Program.cs
new file mode 100644
index 00000000..b49dd580
--- /dev/null
+++ b/LiteNetLib.Trim.Dummy/Program.cs
@@ -0,0 +1,3 @@
+// I'm a dummy for assembly trimming since the built-in analyzer isn't perfect yet.
+// Please build me with `dotnet publish -c Release -r win-x64` (or similar) and test for warnings.
+Console.WriteLine("Hello, World!");
diff --git a/LiteNetLib.sln b/LiteNetLib.sln
index 9956f9cf..853c8778 100644
--- a/LiteNetLib.sln
+++ b/LiteNetLib.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.29519.181
+# Visual Studio Version 17
+VisualStudioVersion = 17.7.34031.279
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiteNetLib.Tests", "LiteNetLib.Tests\LiteNetLib.Tests.csproj", "{6B591E33-E71F-4A37-A08D-B56DD47EDE3F}"
EndProject
@@ -14,6 +14,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibSample", "LibSample\LibSample.csproj", "{6499F9B4-0814-42EE-90A5-119AEA07E7EE}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiteNetLib.Trim.Dummy", "LiteNetLib.Trim.Dummy\LiteNetLib.Trim.Dummy.csproj", "{5903804F-8AB4-4AF7-9152-BED0561E9C7D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -102,6 +104,30 @@ Global
{6499F9B4-0814-42EE-90A5-119AEA07E7EE}.Release|x64.Build.0 = Release|Any CPU
{6499F9B4-0814-42EE-90A5-119AEA07E7EE}.Release|x86.ActiveCfg = Release|Any CPU
{6499F9B4-0814-42EE-90A5-119AEA07E7EE}.Release|x86.Build.0 = Release|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Debug|ARM.Build.0 = Debug|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Debug|x64.Build.0 = Debug|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Debug|x86.Build.0 = Debug|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.DebugUnsafe|Any CPU.ActiveCfg = Debug|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.DebugUnsafe|Any CPU.Build.0 = Debug|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.DebugUnsafe|ARM.ActiveCfg = Debug|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.DebugUnsafe|ARM.Build.0 = Debug|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.DebugUnsafe|x64.ActiveCfg = Debug|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.DebugUnsafe|x64.Build.0 = Debug|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.DebugUnsafe|x86.ActiveCfg = Debug|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.DebugUnsafe|x86.Build.0 = Debug|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Release|ARM.ActiveCfg = Release|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Release|ARM.Build.0 = Release|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Release|x64.ActiveCfg = Release|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Release|x64.Build.0 = Release|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Release|x86.ActiveCfg = Release|Any CPU
+ {5903804F-8AB4-4AF7-9152-BED0561E9C7D}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/LiteNetLib/BaseChannel.cs b/LiteNetLib/BaseChannel.cs
index b70c4360..1dd969c5 100644
--- a/LiteNetLib/BaseChannel.cs
+++ b/LiteNetLib/BaseChannel.cs
@@ -3,19 +3,38 @@
namespace LiteNetLib
{
+ ///
+ /// Base class for reliable and sequenced communication channels.
+ /// Handles the queuing and scheduling of outgoing packets.
+ ///
internal abstract class BaseChannel
{
- protected readonly NetPeer Peer;
+ ///
+ /// The peer associated with this channel.
+ ///
+ protected readonly LiteNetPeer Peer;
+ ///
+ /// Queue containing packets waiting to be sent over the network.
+ ///
protected readonly Queue OutgoingQueue = new Queue(NetConstants.DefaultWindowSize);
private int _isAddedToPeerChannelSendQueue;
+ ///
+ /// Gets the number of packets currently residing in the outgoing queue.
+ ///
public int PacketsInQueue => OutgoingQueue.Count;
- protected BaseChannel(NetPeer peer)
- {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The peer that owns this channel.
+ protected BaseChannel(LiteNetPeer peer) =>
Peer = peer;
- }
+ ///
+ /// Adds a packet to the outgoing queue and notifies the peer to schedule a send update.
+ ///
+ /// The packet to be enqueued.
public void AddToQueue(NetPacket packet)
{
lock (OutgoingQueue)
@@ -25,14 +44,20 @@ public void AddToQueue(NetPacket packet)
AddToPeerChannelSendQueue();
}
+ ///
+ /// Thread-safely marks this channel as having pending data and adds it to the peer's update list.
+ ///
protected void AddToPeerChannelSendQueue()
{
if (Interlocked.CompareExchange(ref _isAddedToPeerChannelSendQueue, 1, 0) == 0)
- {
Peer.AddToReliableChannelSendQueue(this);
- }
}
+ ///
+ /// Attempts to send packets from the queue. If the queue becomes empty or throttled,
+ /// it resets the update flag.
+ ///
+ /// if there are still packets remaining to be sent in future updates.
public bool SendAndCheckQueue()
{
bool hasPacketsToSend = SendNextPackets();
@@ -42,7 +67,17 @@ public bool SendAndCheckQueue()
return hasPacketsToSend;
}
- protected abstract bool SendNextPackets();
+ ///
+ /// Abstract method to implement the specific logic for sending packets (e.g., windowing for reliable).
+ ///
+ /// if packets were sent and the channel should remain in the send queue.
+ public abstract bool SendNextPackets();
+
+ ///
+ /// Abstract method to handle an incoming packet received on this specific channel.
+ ///
+ /// The received packet.
+ /// if the packet was processed successfully.
public abstract bool ProcessPacket(NetPacket packet);
}
}
diff --git a/LiteNetLib/ConnectionRequest.cs b/LiteNetLib/ConnectionRequest.cs
index 4a2cdd93..080009ab 100644
--- a/LiteNetLib/ConnectionRequest.cs
+++ b/LiteNetLib/ConnectionRequest.cs
@@ -1,4 +1,5 @@
-using System.Net;
+using System;
+using System.Net;
using System.Threading;
using LiteNetLib.Utils;
@@ -12,16 +13,22 @@ internal enum ConnectionRequestResult
RejectForce
}
- public class ConnectionRequest
+ public class LiteConnectionRequest
{
- private readonly NetManager _listener;
+ private readonly LiteNetManager _listener;
private int _used;
+ ///
+ /// Data sent by the remote peer in the connection request.
+ ///
public NetDataReader Data => InternalPacket.Data;
internal ConnectionRequestResult Result { get; private set; }
internal NetConnectRequestPacket InternalPacket;
+ ///
+ /// The remote endpoint (IP and Port) of the peer requesting the connection.
+ ///
public readonly IPEndPoint RemoteEndPoint;
internal void UpdateRequest(NetConnectRequestPacket connectRequest)
@@ -37,19 +44,26 @@ internal void UpdateRequest(NetConnectRequestPacket connectRequest)
InternalPacket = connectRequest;
}
- private bool TryActivate()
- {
- return Interlocked.CompareExchange(ref _used, 1, 0) == 0;
- }
+ private bool TryActivate() =>
+ Interlocked.CompareExchange(ref _used, 1, 0) == 0;
- internal ConnectionRequest(IPEndPoint remoteEndPoint, NetConnectRequestPacket requestPacket, NetManager listener)
+ internal LiteConnectionRequest(IPEndPoint remoteEndPoint, NetConnectRequestPacket requestPacket, LiteNetManager listener)
{
InternalPacket = requestPacket;
RemoteEndPoint = remoteEndPoint;
_listener = listener;
}
- public NetPeer AcceptIfKey(string key)
+ ///
+ /// Accepts the connection if the first string in the matches the provided key.
+ ///
+ ///
+ /// This is a helper method for simple password/key validation.
+ /// If the key does not match or data is invalid, the connection is automatically rejected.
+ ///
+ /// The required string key to match.
+ /// A new if the key matches and connection is accepted; otherwise, null.
+ public LiteNetPeer AcceptIfKey(string key)
{
if (!TryActivate())
return null;
@@ -63,10 +77,10 @@ public NetPeer AcceptIfKey(string key)
NetDebug.WriteError("[AC] Invalid incoming data");
}
if (Result == ConnectionRequestResult.Accept)
- return _listener.OnConnectionSolved(this, null, 0, 0);
+ return _listener.OnConnectionSolved(this, ReadOnlySpan.Empty);
Result = ConnectionRequestResult.Reject;
- _listener.OnConnectionSolved(this, null, 0, 0);
+ _listener.OnConnectionSolved(this, ReadOnlySpan.Empty);
return null;
}
@@ -74,61 +88,127 @@ public NetPeer AcceptIfKey(string key)
/// Accept connection and get new NetPeer as result
///
/// Connected NetPeer
- public NetPeer Accept()
+ public LiteNetPeer Accept()
{
if (!TryActivate())
return null;
Result = ConnectionRequestResult.Accept;
- return _listener.OnConnectionSolved(this, null, 0, 0);
+ return _listener.OnConnectionSolved(this, ReadOnlySpan.Empty);
}
- public void Reject(byte[] rejectData, int start, int length, bool force)
+ ///
+ /// Rejects the connection request.
+ ///
+ /// Optional user data to send along with the rejection packet.
+ ///
+ /// If , immediately removes the request, if is not empty a reject packet is also sent.
+ /// If , creates a temporary peer that sends rejection packets and lingers in memory until a timeout occurs to handle late-arriving packets.
+ ///
+ public void Reject(ReadOnlySpan rejectData, bool force)
{
if (!TryActivate())
return;
Result = force ? ConnectionRequestResult.RejectForce : ConnectionRequestResult.Reject;
- _listener.OnConnectionSolved(this, rejectData, start, length);
+ _listener.OnConnectionSolved(this, rejectData);
}
- public void Reject(byte[] rejectData, int start, int length)
- {
- Reject(rejectData, start, length, false);
- }
+ ///
+ /// Rejects the connection request.
+ ///
+ /// Optional user data to send along with the rejection packet.
+ /// Offset in the array.
+ /// Length of the data to be sent from the array.
+ ///
+ /// If , immediately removes the request, if is not a reject packet is also sent.
+ /// If , creates a temporary peer that sends rejection packets and lingers in memory until a timeout occurs to handle late-arriving packets.
+ ///
+ public void Reject(byte[] rejectData, int start, int length, bool force) =>
+ Reject(new ReadOnlySpan(rejectData, start, length), force);
+ ///
+ /// Rejects the connection reliably. Creates a temporary peer to handle packet loss.
+ ///
+ /// Data to send with the rejection.
+ /// Offset in the array.
+ /// Length of the data to be sent.
+ public void Reject(byte[] rejectData, int start, int length) =>
+ Reject(new ReadOnlySpan(rejectData, start, length), false);
- public void RejectForce(byte[] rejectData, int start, int length)
- {
- Reject(rejectData, start, length, true);
- }
+ ///
+ /// Rejects the connection immediately without reliability.
+ /// Minimizes resource usage by not creating an internal peer.
+ ///
+ /// Data to send with the rejection.
+ /// Offset in the array.
+ /// Length of the data to be sent.
+ public void RejectForce(byte[] rejectData, int start, int length) =>
+ Reject(new ReadOnlySpan(rejectData, start, length), true);
- public void RejectForce()
- {
- Reject(null, 0, 0, true);
- }
+ ///
+ /// Rejects the connection immediately without sending any packet.
+ ///
+ public void RejectForce() =>
+ Reject(ReadOnlySpan.Empty, true);
- public void RejectForce(byte[] rejectData)
- {
- Reject(rejectData, 0, rejectData.Length, true);
- }
+ ///
+ /// Rejects the connection immediately without reliability.
+ ///
+ /// Data to send with the rejection.
+ public void RejectForce(byte[] rejectData) =>
+ Reject(new ReadOnlySpan(rejectData), true);
- public void RejectForce(NetDataWriter rejectData)
- {
- Reject(rejectData.Data, 0, rejectData.Length, true);
- }
+ ///
+ /// Rejects the connection immediately without reliability using data from a .
+ ///
+ /// Writer containing the data to send.
+ public void RejectForce(NetDataWriter rejectData) =>
+ Reject(rejectData.AsReadOnlySpan(), true);
- public void Reject()
- {
- Reject(null, 0, 0, false);
- }
+ ///
+ /// Rejects the connection immediately without reliability.
+ ///
+ /// Data to send with the rejection.
+ public void RejectForce(ReadOnlySpan rejectData) =>
+ Reject(rejectData, true);
- public void Reject(byte[] rejectData)
- {
- Reject(rejectData, 0, rejectData.Length, false);
- }
+ ///
+ /// Rejects the connection reliably without additional data.
+ ///
+ public void Reject() =>
+ Reject(ReadOnlySpan.Empty, false);
- public void Reject(NetDataWriter rejectData)
+ ///
+ /// Rejects the connection reliably.
+ ///
+ /// Data to send with the rejection.
+ public void Reject(byte[] rejectData) =>
+ Reject(new ReadOnlySpan(rejectData), false);
+
+ ///
+ /// Rejects the connection reliably using data from a .
+ ///
+ /// Writer containing the data to send.
+ public void Reject(NetDataWriter rejectData) =>
+ Reject(rejectData.AsReadOnlySpan(), false);
+
+ ///
+ /// Rejects the connection reliably.
+ ///
+ /// Data to send with the rejection.
+ public void Reject(ReadOnlySpan rejectData) =>
+ Reject(rejectData, false);
+ }
+
+ public class ConnectionRequest : LiteConnectionRequest
+ {
+ internal ConnectionRequest(IPEndPoint remoteEndPoint, NetConnectRequestPacket requestPacket, LiteNetManager listener) : base(remoteEndPoint, requestPacket, listener)
{
- Reject(rejectData.Data, 0, rejectData.Length, false);
}
+
+ ///
+ public new NetPeer AcceptIfKey(string key) => (NetPeer)base.AcceptIfKey(key);
+
+ ///
+ public new NetPeer Accept() => (NetPeer)base.Accept();
}
}
diff --git a/LiteNetLib/INetEventListener.cs b/LiteNetLib/INetEventListener.cs
index 13d8852e..17891c6e 100644
--- a/LiteNetLib/INetEventListener.cs
+++ b/LiteNetLib/INetEventListener.cs
@@ -18,13 +18,34 @@ public enum UnconnectedMessageType
///
public enum DisconnectReason
{
+ ///
+ /// Connection to host failed
+ ///
ConnectionFailed,
+
+ ///
+ /// Timeout
+ ///
Timeout,
+
HostUnreachable,
NetworkUnreachable,
+
+ ///
+ /// Remote host disconnected peer
+ ///
RemoteConnectionClose,
+
+ ///
+ /// Disconnect called locally
+ ///
DisconnectPeerCalled,
+
+ ///
+ /// Connection rejected by remote host
+ ///
ConnectionRejected,
+
InvalidProtocol,
UnknownHost,
Reconnect,
@@ -53,6 +74,9 @@ public struct DisconnectInfo
public NetPacketReader AdditionalData;
}
+ ///
+ /// Interface for implementing own INetEventListener. This is a bit faster than use EventBasedListener
+ ///
public interface INetEventListener
{
///
@@ -104,169 +128,397 @@ public interface INetEventListener
///
/// Request information (EndPoint, internal id, additional data)
void OnConnectionRequest(ConnectionRequest request);
- }
- public interface IDeliveryEventListener
- {
///
/// On reliable message delivered
///
///
///
- void OnMessageDelivered(NetPeer peer, object userData);
- }
+ void OnMessageDelivered(NetPeer peer, object userData) { }
- public interface INtpEventListener
- {
///
/// Ntp response
///
///
- void OnNtpResponse(NtpPacket packet);
+ void OnNtpResponse(NtpPacket packet) { }
+
+ ///
+ /// Called when peer address changed (when AllowPeerAddressChange is enabled)
+ ///
+ /// Peer that changed address (with new address)
+ /// previous IP
+ void OnPeerAddressChanged(NetPeer peer, IPEndPoint previousAddress) { }
}
- public interface IPeerAddressChangedListener
+ ///
+ /// Interface for implementing own ILiteNetEventListener. This is a bit faster than use EventBasedListener
+ ///
+ public interface ILiteNetEventListener
{
+ ///
+ /// New remote peer connected to host, or client connected to remote host
+ ///
+ /// Connected peer object
+ void OnPeerConnected(LiteNetPeer peer);
+
+ ///
+ /// Peer disconnected
+ ///
+ /// disconnected peer
+ /// additional info about reason, errorCode or data received with disconnect message
+ void OnPeerDisconnected(LiteNetPeer peer, DisconnectInfo disconnectInfo);
+
+ ///
+ /// Network error (on send or receive)
+ ///
+ /// From endPoint (can be null)
+ /// Socket error
+ void OnNetworkError(IPEndPoint endPoint, SocketError socketError) { }
+
+ ///
+ /// Received some data
+ ///
+ /// From peer
+ /// DataReader containing all received data
+ /// Type of received packet
+ void OnNetworkReceive(LiteNetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod);
+
+ ///
+ /// Received unconnected message
+ ///
+ /// From address (IP and Port)
+ /// Message data
+ /// Message type (simple, discovery request or response)
+ void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType) { }
+
+ ///
+ /// Latency information updated
+ ///
+ /// Peer with updated latency
+ /// latency value in milliseconds
+ void OnNetworkLatencyUpdate(LiteNetPeer peer, int latency) { }
+
+ ///
+ /// On peer connection requested
+ ///
+ /// Request information (EndPoint, internal id, additional data)
+ void OnConnectionRequest(LiteConnectionRequest request);
+
///
/// Called when peer address changed (when AllowPeerAddressChange is enabled)
///
/// Peer that changed address (with new address)
/// previous IP
- void OnPeerAddressChanged(NetPeer peer, IPEndPoint previousAddress);
+ void OnPeerAddressChanged(LiteNetPeer peer, IPEndPoint previousAddress) { }
+
+ ///
+ /// On reliable message delivered
+ ///
+ ///
+ ///
+ void OnMessageDelivered(LiteNetPeer peer, object userData) { }
}
- public class EventBasedNetListener : INetEventListener, IDeliveryEventListener, INtpEventListener, IPeerAddressChangedListener
+ ///
+ /// Simple event based listener for simple setups and benchmarks
+ ///
+ public class EventBasedNetListener : INetEventListener
{
+ ///
+ /// Delegate for the event that occurs when a new peer has successfully connected.
+ ///
+ /// The connected peer.
public delegate void OnPeerConnected(NetPeer peer);
+ ///
+ /// Delegate for the event that occurs when a peer disconnects or the connection is lost.
+ ///
+ /// The disconnected peer.
+ /// Information regarding the reason and data associated with the disconnection.
public delegate void OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo);
+ ///
+ /// Delegate for the event that occurs when a network error is detected in the underlying socket.
+ ///
+ /// The endpoint associated with the error.
+ /// The specific socket error code.
public delegate void OnNetworkError(IPEndPoint endPoint, SocketError socketError);
+ ///
+ /// Delegate for the event that occurs when data is received from a connected peer.
+ ///
+ /// The peer that sent the data.
+ /// The reader containing the received payload.
+ /// The channel on which the data was received.
+ /// The delivery method used for this packet.
public delegate void OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channel, DeliveryMethod deliveryMethod);
+ ///
+ /// Delegate for the event that occurs when a message is received from an unconnected endpoint.
+ ///
+ /// The endpoint that sent the message.
+ /// The reader containing the received payload.
+ /// The type of unconnected message (e.g., Discovery or UnconnectedData).
public delegate void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType);
+ ///
+ /// Delegate for the event that occurs when the round-trip time (RTT) to a peer is updated.
+ ///
+ /// The peer whose latency was updated.
+ /// The new latency value in milliseconds.
public delegate void OnNetworkLatencyUpdate(NetPeer peer, int latency);
+ ///
+ /// Delegate for the event that occurs when a new connection request is received.
+ ///
+ /// The connection request object used to accept or reject the connection.
public delegate void OnConnectionRequest(ConnectionRequest request);
+ ///
+ /// Delegate for the event that occurs when a reliable packet is successfully delivered or acknowledged.
+ ///
+ /// The peer that received the packet.
+ /// The custom user data that was attached to the sent packet.
public delegate void OnDeliveryEvent(NetPeer peer, object userData);
+ ///
+ /// Delegate for the event that occurs when an NTP response is received from a time server.
+ ///
+ /// The NTP packet containing time information.
public delegate void OnNtpResponseEvent(NtpPacket packet);
+ ///
+ /// Delegate for the event that occurs when a peer's remote address changes (roaming).
+ ///
+ /// The peer whose address changed.
+ /// The previous IP endpoint of the peer.
public delegate void OnPeerAddressChangedEvent(NetPeer peer, IPEndPoint previousAddress);
+ ///
+ /// Occurs when a new peer has successfully connected.
+ ///
public event OnPeerConnected PeerConnectedEvent;
+ ///
+ /// Occurs when a peer disconnects or the connection is lost.
+ ///
public event OnPeerDisconnected PeerDisconnectedEvent;
+ ///
+ /// Occurs when a network error is detected in the underlying socket.
+ ///
public event OnNetworkError NetworkErrorEvent;
+ ///
+ /// Occurs when data is received from a connected peer.
+ ///
public event OnNetworkReceive NetworkReceiveEvent;
+ ///
+ /// Occurs when a message is received from an unconnected endpoint.
+ ///
public event OnNetworkReceiveUnconnected NetworkReceiveUnconnectedEvent;
+ ///
+ /// Occurs when the round-trip time (RTT) to a peer is updated.
+ ///
public event OnNetworkLatencyUpdate NetworkLatencyUpdateEvent;
+ ///
+ /// Occurs when a new connection request is received.
+ ///
public event OnConnectionRequest ConnectionRequestEvent;
+ ///
+ /// Occurs when a reliable packet is successfully delivered or acknowledged.
+ ///
public event OnDeliveryEvent DeliveryEvent;
+ ///
+ /// Occurs when an NTP response is received.
+ ///
public event OnNtpResponseEvent NtpResponseEvent;
+ ///
+ /// Occurs when a peer's remote address changes.
+ ///
+ public event OnPeerAddressChangedEvent PeerAddressChangedEvent;
+
+ /// Clears all subscribers from .
+ public void ClearPeerConnectedEvent() => PeerConnectedEvent = null;
+ /// Clears all subscribers from .
+ public void ClearPeerDisconnectedEvent() => PeerDisconnectedEvent = null;
+ /// Clears all subscribers from .
+ public void ClearNetworkErrorEvent() => NetworkErrorEvent = null;
+ /// Clears all subscribers from .
+ public void ClearNetworkReceiveEvent() => NetworkReceiveEvent = null;
+ /// Clears all subscribers from .
+ public void ClearNetworkReceiveUnconnectedEvent() => NetworkReceiveUnconnectedEvent = null;
+ /// Clears all subscribers from .
+ public void ClearNetworkLatencyUpdateEvent() => NetworkLatencyUpdateEvent = null;
+ /// Clears all subscribers from .
+ public void ClearConnectionRequestEvent() => ConnectionRequestEvent = null;
+ /// Clears all subscribers from .
+ public void ClearDeliveryEvent() => DeliveryEvent = null;
+ /// Clears all subscribers from .
+ public void ClearNtpResponseEvent() => NtpResponseEvent = null;
+ /// Clears all subscribers from .
+ public void ClearPeerAddressChangedEvent() => PeerAddressChangedEvent = null;
+
+ void INetEventListener.OnPeerConnected(NetPeer peer) =>
+ PeerConnectedEvent?.Invoke(peer);
+
+ void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo) =>
+ PeerDisconnectedEvent?.Invoke(peer, disconnectInfo);
+
+ void INetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode) =>
+ NetworkErrorEvent?.Invoke(endPoint, socketErrorCode);
+
+ void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod) =>
+ NetworkReceiveEvent?.Invoke(peer, reader, channelNumber, deliveryMethod);
+
+ void INetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType) =>
+ NetworkReceiveUnconnectedEvent?.Invoke(remoteEndPoint, reader, messageType);
+
+ void INetEventListener.OnNetworkLatencyUpdate(NetPeer peer, int latency) =>
+ NetworkLatencyUpdateEvent?.Invoke(peer, latency);
+
+ void INetEventListener.OnConnectionRequest(ConnectionRequest request) =>
+ ConnectionRequestEvent?.Invoke(request);
+
+ void INetEventListener.OnMessageDelivered(NetPeer peer, object userData) =>
+ DeliveryEvent?.Invoke(peer, userData);
+
+ void INetEventListener.OnNtpResponse(NtpPacket packet) =>
+ NtpResponseEvent?.Invoke(packet);
+
+ void INetEventListener.OnPeerAddressChanged(NetPeer peer, IPEndPoint previousAddress) =>
+ PeerAddressChangedEvent?.Invoke(peer, previousAddress);
+ }
+
+ ///
+ /// Simple event based listener for simple setups and benchmarks
+ ///
+ public class EventBasedLiteNetListener : ILiteNetEventListener
+ {
+ ///
+ /// Delegate for the event that occurs when a new peer has successfully connected.
+ ///
+ /// The connected peer.
+ public delegate void OnPeerConnected(LiteNetPeer peer);
+ ///
+ /// Delegate for the event that occurs when a peer disconnects or the connection is lost.
+ ///
+ /// The disconnected peer.
+ /// Information regarding the reason and data associated with the disconnection.
+ public delegate void OnPeerDisconnected(LiteNetPeer peer, DisconnectInfo disconnectInfo);
+ ///
+ /// Delegate for the event that occurs when a network error is detected in the underlying socket.
+ ///
+ /// The endpoint associated with the error.
+ /// The specific socket error code.
+ public delegate void OnNetworkError(IPEndPoint endPoint, SocketError socketError);
+ ///
+ /// Delegate for the event that occurs when data is received from a connected peer.
+ ///
+ /// The peer that sent the data.
+ /// The reader containing the received payload.
+ /// The delivery method used for this packet.
+ public delegate void OnNetworkReceive(LiteNetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod);
+ ///
+ /// Delegate for the event that occurs when a message is received from an unconnected endpoint.
+ ///
+ /// The endpoint that sent the message.
+ /// The reader containing the received payload.
+ /// The type of unconnected message (e.g., Discovery or UnconnectedData).
+ public delegate void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType);
+ ///
+ /// Delegate for the event that occurs when the round-trip time (RTT) to a peer is updated.
+ ///
+ /// The peer whose latency was updated.
+ /// The new latency value in milliseconds.
+ public delegate void OnNetworkLatencyUpdate(LiteNetPeer peer, int latency);
+ ///
+ /// Delegate for the event that occurs when a new connection request is received.
+ ///
+ /// The connection request object used to accept or reject the connection.
+ public delegate void OnConnectionRequest(LiteConnectionRequest request);
+ ///
+ /// Delegate for the event that occurs when a reliable packet is successfully delivered or acknowledged.
+ ///
+ /// The peer that received the packet.
+ /// The custom user data that was attached to the sent packet.
+ public delegate void OnDeliveryEvent(LiteNetPeer peer, object userData);
+ ///
+ /// Delegate for the event that occurs when a peer's remote address changes (roaming).
+ ///
+ /// The peer whose address changed.
+ /// The previous IP endpoint of the peer.
+ public delegate void OnPeerAddressChangedEvent(LiteNetPeer peer, IPEndPoint previousAddress);
+
+ ///
+ /// Occurs when a new peer has successfully connected.
+ ///
+ public event OnPeerConnected PeerConnectedEvent;
+ ///
+ /// Occurs when a peer disconnects or the connection is lost.
+ ///
+ public event OnPeerDisconnected PeerDisconnectedEvent;
+ ///
+ /// Occurs when a network error is detected in the underlying socket.
+ ///
+ public event OnNetworkError NetworkErrorEvent;
+ ///
+ /// Occurs when data is received from a connected peer.
+ ///
+ public event OnNetworkReceive NetworkReceiveEvent;
+ ///
+ /// Occurs when a message is received from an unconnected endpoint.
+ ///
+ public event OnNetworkReceiveUnconnected NetworkReceiveUnconnectedEvent;
+ ///
+ /// Occurs when the round-trip time (RTT) to a peer is updated.
+ ///
+ public event OnNetworkLatencyUpdate NetworkLatencyUpdateEvent;
+ ///
+ /// Occurs when a new connection request is received.
+ ///
+ public event OnConnectionRequest ConnectionRequestEvent;
+ ///
+ /// Occurs when a reliable packet is successfully delivered or acknowledged.
+ ///
+ public event OnDeliveryEvent DeliveryEvent;
+ ///
+ /// Occurs when a peer's remote address changes.
+ ///
public event OnPeerAddressChangedEvent PeerAddressChangedEvent;
- public void ClearPeerConnectedEvent()
- {
- PeerConnectedEvent = null;
- }
-
- public void ClearPeerDisconnectedEvent()
- {
- PeerDisconnectedEvent = null;
- }
-
- public void ClearNetworkErrorEvent()
- {
- NetworkErrorEvent = null;
- }
-
- public void ClearNetworkReceiveEvent()
- {
- NetworkReceiveEvent = null;
- }
-
- public void ClearNetworkReceiveUnconnectedEvent()
- {
- NetworkReceiveUnconnectedEvent = null;
- }
-
- public void ClearNetworkLatencyUpdateEvent()
- {
- NetworkLatencyUpdateEvent = null;
- }
-
- public void ClearConnectionRequestEvent()
- {
- ConnectionRequestEvent = null;
- }
-
- public void ClearDeliveryEvent()
- {
- DeliveryEvent = null;
- }
-
- public void ClearNtpResponseEvent()
- {
- NtpResponseEvent = null;
- }
-
- public void ClearPeerAddressChangedEvent()
- {
- PeerAddressChangedEvent = null;
- }
-
- void INetEventListener.OnPeerConnected(NetPeer peer)
- {
- if (PeerConnectedEvent != null)
- PeerConnectedEvent(peer);
- }
-
- void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo)
- {
- if (PeerDisconnectedEvent != null)
- PeerDisconnectedEvent(peer, disconnectInfo);
- }
-
- void INetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode)
- {
- if (NetworkErrorEvent != null)
- NetworkErrorEvent(endPoint, socketErrorCode);
- }
-
- void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod)
- {
- if (NetworkReceiveEvent != null)
- NetworkReceiveEvent(peer, reader, channelNumber, deliveryMethod);
- }
-
- void INetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType)
- {
- if (NetworkReceiveUnconnectedEvent != null)
- NetworkReceiveUnconnectedEvent(remoteEndPoint, reader, messageType);
- }
-
- void INetEventListener.OnNetworkLatencyUpdate(NetPeer peer, int latency)
- {
- if (NetworkLatencyUpdateEvent != null)
- NetworkLatencyUpdateEvent(peer, latency);
- }
-
- void INetEventListener.OnConnectionRequest(ConnectionRequest request)
- {
- if (ConnectionRequestEvent != null)
- ConnectionRequestEvent(request);
- }
-
- void IDeliveryEventListener.OnMessageDelivered(NetPeer peer, object userData)
- {
- if (DeliveryEvent != null)
- DeliveryEvent(peer, userData);
- }
-
- void INtpEventListener.OnNtpResponse(NtpPacket packet)
- {
- if (NtpResponseEvent != null)
- NtpResponseEvent(packet);
- }
-
- void IPeerAddressChangedListener.OnPeerAddressChanged(NetPeer peer, IPEndPoint previousAddress)
- {
- if (PeerAddressChangedEvent != null)
- PeerAddressChangedEvent(peer, previousAddress);
- }
+ /// Clears all subscribers from .
+ public void ClearPeerConnectedEvent() => PeerConnectedEvent = null;
+ /// Clears all subscribers from .
+ public void ClearPeerDisconnectedEvent() => PeerDisconnectedEvent = null;
+ /// Clears all subscribers from .
+ public void ClearNetworkErrorEvent() => NetworkErrorEvent = null;
+ /// Clears all subscribers from .
+ public void ClearNetworkReceiveEvent() => NetworkReceiveEvent = null;
+ /// Clears all subscribers from .
+ public void ClearNetworkReceiveUnconnectedEvent() => NetworkReceiveUnconnectedEvent = null;
+ /// Clears all subscribers from .
+ public void ClearNetworkLatencyUpdateEvent() => NetworkLatencyUpdateEvent = null;
+ /// Clears all subscribers from .
+ public void ClearConnectionRequestEvent() => ConnectionRequestEvent = null;
+ /// Clears all subscribers from .
+ public void ClearDeliveryEvent() => DeliveryEvent = null;
+ /// Clears all subscribers from .
+ public void ClearPeerAddressChangedEvent() => PeerAddressChangedEvent = null;
+
+ void ILiteNetEventListener.OnPeerConnected(LiteNetPeer peer) =>
+ PeerConnectedEvent?.Invoke(peer);
+
+ void ILiteNetEventListener.OnPeerDisconnected(LiteNetPeer peer, DisconnectInfo disconnectInfo) =>
+ PeerDisconnectedEvent?.Invoke(peer, disconnectInfo);
+
+ void ILiteNetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode) =>
+ NetworkErrorEvent?.Invoke(endPoint, socketErrorCode);
+
+ void ILiteNetEventListener.OnNetworkReceive(LiteNetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod) =>
+ NetworkReceiveEvent?.Invoke(peer, reader, deliveryMethod);
+
+ void ILiteNetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType) =>
+ NetworkReceiveUnconnectedEvent?.Invoke(remoteEndPoint, reader, messageType);
+
+ void ILiteNetEventListener.OnNetworkLatencyUpdate(LiteNetPeer peer, int latency) =>
+ NetworkLatencyUpdateEvent?.Invoke(peer, latency);
+
+ void ILiteNetEventListener.OnConnectionRequest(LiteConnectionRequest request) =>
+ ConnectionRequestEvent?.Invoke(request);
+
+ void ILiteNetEventListener.OnMessageDelivered(LiteNetPeer peer, object userData) =>
+ DeliveryEvent?.Invoke(peer, userData);
+
+ void ILiteNetEventListener.OnPeerAddressChanged(LiteNetPeer peer, IPEndPoint previousAddress) =>
+ PeerAddressChangedEvent?.Invoke(peer, previousAddress);
}
}
diff --git a/LiteNetLib/InternalPackets.cs b/LiteNetLib/InternalPackets.cs
index 2eb09fe5..6cd9a8db 100644
--- a/LiteNetLib/InternalPackets.cs
+++ b/LiteNetLib/InternalPackets.cs
@@ -1,10 +1,10 @@
-using System;
+using System;
using System.Net;
using LiteNetLib.Utils;
namespace LiteNetLib
{
- internal sealed class NetConnectRequestPacket
+ public sealed class NetConnectRequestPacket
{
public const int HeaderSize = 18;
public readonly long ConnectionTime;
@@ -22,12 +22,10 @@ private NetConnectRequestPacket(long connectionTime, byte connectionNumber, int
PeerId = localId;
}
- public static int GetProtocolId(NetPacket packet)
- {
- return BitConverter.ToInt32(packet.RawData, 1);
- }
+ internal static int GetProtocolId(NetPacket packet) =>
+ BitConverter.ToInt32(packet.RawData, 1);
- public static NetConnectRequestPacket FromData(NetPacket packet)
+ internal static NetConnectRequestPacket FromData(NetPacket packet)
{
if (packet.ConnectionNumber >= NetConstants.MaxConnectionNumber)
return null;
@@ -53,7 +51,7 @@ public static NetConnectRequestPacket FromData(NetPacket packet)
return new NetConnectRequestPacket(connectionTime, packet.ConnectionNumber, peerId, addressBytes, reader);
}
- public static NetPacket Make(NetDataWriter connectData, SocketAddress addressBytes, long connectTime, int localId)
+ internal static NetPacket Make(ReadOnlySpan connectData, SocketAddress addressBytes, long connectTime, int localId)
{
//Make initial packet
var packet = new NetPacket(PacketProperty.ConnectRequest, connectData.Length+addressBytes.Size);
@@ -62,10 +60,10 @@ public static NetPacket Make(NetDataWriter connectData, SocketAddress addressByt
FastBitConverter.GetBytes(packet.RawData, 1, NetConstants.ProtocolId);
FastBitConverter.GetBytes(packet.RawData, 5, connectTime);
FastBitConverter.GetBytes(packet.RawData, 13, localId);
- packet.RawData[HeaderSize-1] = (byte)addressBytes.Size;
+ packet.RawData[HeaderSize - 1] = (byte)addressBytes.Size;
for (int i = 0; i < addressBytes.Size; i++)
packet.RawData[HeaderSize + i] = addressBytes[i];
- Buffer.BlockCopy(connectData.Data, 0, packet.RawData, HeaderSize + addressBytes.Size, connectData.Length);
+ connectData.CopyTo(packet.RawData.AsSpan(HeaderSize + addressBytes.Size));
return packet;
}
}
@@ -119,8 +117,8 @@ public static NetPacket Make(long connectTime, byte connectNum, int localPeerId)
FastBitConverter.GetBytes(packet.RawData, 11, localPeerId);
return packet;
}
-
- public static NetPacket MakeNetworkChanged(NetPeer peer)
+
+ public static NetPacket MakeNetworkChanged(LiteNetPeer peer)
{
var packet = new NetPacket(PacketProperty.PeerNotFound, Size-1);
FastBitConverter.GetBytes(packet.RawData, 1, peer.ConnectTime);
@@ -130,4 +128,4 @@ public static NetPacket MakeNetworkChanged(NetPeer peer)
return packet;
}
}
-}
\ No newline at end of file
+}
diff --git a/LiteNetLib/Layers/Crc32cLayer.cs b/LiteNetLib/Layers/Crc32cLayer.cs
index 3ee97d6c..dc07fe19 100644
--- a/LiteNetLib/Layers/Crc32cLayer.cs
+++ b/LiteNetLib/Layers/Crc32cLayer.cs
@@ -11,7 +11,7 @@ public Crc32cLayer() : base(CRC32C.ChecksumSize)
}
- public override void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int offset, ref int length)
+ public override void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int length)
{
if (length < NetConstants.HeaderSize + CRC32C.ChecksumSize)
{
@@ -22,7 +22,7 @@ public override void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] da
}
int checksumPoint = length - CRC32C.ChecksumSize;
- if (CRC32C.Compute(data, offset, checksumPoint) != BitConverter.ToUInt32(data, checksumPoint))
+ if (CRC32C.Compute(data, 0, checksumPoint) != BitConverter.ToUInt32(data, checksumPoint))
{
NetDebug.Write("[NM] DataReceived checksum: bad!");
//Set length to 0 to have netManager drop the packet.
diff --git a/LiteNetLib/Layers/PacketLayerBase.cs b/LiteNetLib/Layers/PacketLayerBase.cs
index b3d9b3a7..f2e4c881 100644
--- a/LiteNetLib/Layers/PacketLayerBase.cs
+++ b/LiteNetLib/Layers/PacketLayerBase.cs
@@ -11,7 +11,7 @@ protected PacketLayerBase(int extraPacketSizeForLayer)
ExtraPacketSizeForLayer = extraPacketSizeForLayer;
}
- public abstract void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int offset, ref int length);
+ public abstract void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int length);
public abstract void ProcessOutBoundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int offset, ref int length);
}
}
diff --git a/LiteNetLib/Layers/XorEncryptLayer.cs b/LiteNetLib/Layers/XorEncryptLayer.cs
index 9b671969..a3a130bf 100644
--- a/LiteNetLib/Layers/XorEncryptLayer.cs
+++ b/LiteNetLib/Layers/XorEncryptLayer.cs
@@ -35,14 +35,13 @@ public void SetKey(byte[] key)
Buffer.BlockCopy(key, 0, _byteKey, 0, key.Length);
}
- public override void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int offset, ref int length)
+ public override void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int length)
{
if (_byteKey == null)
return;
- var cur = offset;
- for (var i = 0; i < length; i++, cur++)
+ for (int i = 0; i < length; i++)
{
- data[cur] = (byte)(data[cur] ^ _byteKey[i % _byteKey.Length]);
+ data[i] = (byte)(data[i] ^ _byteKey[i % _byteKey.Length]);
}
}
@@ -50,8 +49,8 @@ public override void ProcessOutBoundPacket(ref IPEndPoint endPoint, ref byte[] d
{
if (_byteKey == null)
return;
- var cur = offset;
- for (var i = 0; i < length; i++, cur++)
+ int cur = offset;
+ for (int i = 0; i < length; i++, cur++)
{
data[cur] = (byte)(data[cur] ^ _byteKey[i % _byteKey.Length]);
}
diff --git a/LiteNetLib/LiteNetLib.csproj b/LiteNetLib/LiteNetLib.csproj
index 4a58e9ea..1b1249b1 100644
--- a/LiteNetLib/LiteNetLib.csproj
+++ b/LiteNetLib/LiteNetLib.csproj
@@ -3,16 +3,18 @@
LiteNetLib
LiteNetLib
- net6.0;net5.0;netcoreapp3.1;netstandard2.0;netstandard2.1
- net471;net6.0;net5.0;netstandard2.0;netstandard2.1;netcoreapp3.1
+ net8.0;netstandard2.1
true
Library
- 7.3
+ 8
true
1701;1702;1705;1591
- 1.1.0
+ 2.1.4
Lite reliable UDP library for Mono and .NET
+ true
+ true
+ 2.1.4
@@ -23,31 +25,27 @@
TRACE
+
+ $(DefineConstants);SIMULATE_NETWORK
+
+
true
$(DefineConstants);LITENETLIB_UNSAFE
udp reliable-udp network
- https://github.com/RevenantX/LiteNetLib/releases/tag/v1.1.0
+ https://github.com/RevenantX/LiteNetLib/releases/tag/2.1.4
git
https://github.com/RevenantX/LiteNetLib
https://github.com/RevenantX/LiteNetLib
MIT
True
- 1.1.0
Ruslan Pyrch
- Copyright 2023 Ruslan Pyrch
+ Copyright 2026 Ruslan Pyrch
Lite reliable UDP library for .NET, Mono, and .NET Core
LNL.png
README.md
-
-
-
-
-
-
-
True
diff --git a/LiteNetLib/LiteNetManager.HashSet.cs b/LiteNetLib/LiteNetManager.HashSet.cs
new file mode 100644
index 00000000..11fdd114
--- /dev/null
+++ b/LiteNetLib/LiteNetManager.HashSet.cs
@@ -0,0 +1,323 @@
+using System;
+using System.Net;
+using System.Threading;
+
+namespace LiteNetLib
+{
+ //minimal hashset class from dotnet with some optimizations
+ public partial class LiteNetManager
+ {
+ private const int MaxPrimeArrayLength = 0x7FFFFFC3;
+ private const int HashPrime = 101;
+ private const int Lower31BitMask = 0x7FFFFFFF;
+ private static readonly int[] Primes =
+ {
+ 3, 7, 11, 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919,
+ 1103, 1327, 1597, 1931, 2333, 2801, 3371, 4049, 4861, 5839, 7013, 8419, 10103, 12143, 14591,
+ 17519, 21023, 25229, 30293, 36353, 43627, 52361, 62851, 75431, 90523, 108631, 130363, 156437,
+ 187751, 225307, 270371, 324449, 389357, 467237, 560689, 672827, 807403, 968897, 1162687, 1395263,
+ 1674319, 2009191, 2411033, 2893249, 3471899, 4166287, 4999559, 5999471, 7199369
+ };
+
+ private static int HashSetGetPrime(int min)
+ {
+ foreach (int prime in Primes)
+ {
+ if (prime >= min)
+ return prime;
+ }
+
+ // Outside of our predefined table. Compute the hard way.
+ for (int i = (min | 1); i < int.MaxValue; i += 2)
+ {
+ if (IsPrime(i) && ((i - 1) % HashPrime != 0))
+ return i;
+ }
+ return min;
+
+ bool IsPrime(int candidate)
+ {
+ if ((candidate & 1) != 0)
+ {
+ int limit = (int)Math.Sqrt(candidate);
+ for (int divisor = 3; divisor <= limit; divisor += 2)
+ {
+ if (candidate % divisor == 0)
+ return false;
+ }
+ return true;
+ }
+ return candidate == 2;
+ }
+ }
+
+ private struct Slot
+ {
+ internal int HashCode;
+ internal int Next;
+ internal LiteNetPeer Value;
+ }
+
+ private int[] _buckets;
+ private Slot[] _slots;
+ private int _count;
+ private int _lastIndex;
+ private int _freeList = -1;
+ private LiteNetPeer[] _peersArray = new LiteNetPeer[32];
+
+ protected readonly ReaderWriterLockSlim _peersLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
+ protected volatile LiteNetPeer _headPeer;
+
+ private void ClearPeerSet()
+ {
+ _peersLock.EnterWriteLock();
+ _headPeer = null;
+ if (_lastIndex > 0)
+ {
+ Array.Clear(_slots, 0, _lastIndex);
+ Array.Clear(_buckets, 0, _buckets.Length);
+ _lastIndex = 0;
+ _count = 0;
+ _freeList = -1;
+ }
+ _peersArray = new LiteNetPeer[32];
+ _peersLock.ExitWriteLock();
+ }
+
+ protected bool ContainsPeer(LiteNetPeer item)
+ {
+ if (item == null)
+ {
+ NetDebug.WriteError($"Contains peer null: {item}");
+ return false;
+ }
+ if (_buckets != null)
+ {
+ int hashCode = item.GetHashCode() & Lower31BitMask;
+ for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].Next)
+ {
+ if (_slots[i].HashCode == hashCode && _slots[i].Value.Equals(item))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ ///
+ /// Gets peer by peer id
+ ///
+ /// id of peer
+ /// Peer if peer with id exist, otherwise null
+ public LiteNetPeer GetPeerById(int id)
+ {
+ return id >= 0 && id < _peersArray.Length ? _peersArray[id] : null;
+ }
+
+ ///
+ /// Gets peer by peer id
+ ///
+ /// id of peer
+ /// resulting peer
+ /// if peer with id exist; otherwise
+ public bool TryGetPeerById(int id, out LiteNetPeer peer)
+ {
+ peer = GetPeerById(id);
+ return peer != null;
+ }
+
+ private void AddPeer(LiteNetPeer peer)
+ {
+ if (peer == null)
+ {
+ NetDebug.WriteError($"Add peer null: {peer}");
+ return;
+ }
+ _peersLock.EnterWriteLock();
+ if (_headPeer != null)
+ {
+ peer.NextPeer = _headPeer;
+ _headPeer.PrevPeer = peer;
+ }
+ _headPeer = peer;
+ AddPeerToSet(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(LiteNetPeer peer, bool enableWriteLock)
+ {
+ if(enableWriteLock)
+ _peersLock.EnterWriteLock();
+ if (!RemovePeerFromSet(peer))
+ {
+ if(enableWriteLock)
+ _peersLock.ExitWriteLock();
+ 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;
+ _peerIds.Enqueue(peer.Id);
+
+ if(enableWriteLock)
+ _peersLock.ExitWriteLock();
+ }
+
+ protected bool RemovePeerFromSet(LiteNetPeer peer)
+ {
+ if (_buckets == null || peer == null)
+ return false;
+ int hashCode = peer.GetHashCode() & Lower31BitMask;
+ int bucket = hashCode % _buckets.Length;
+ int last = -1;
+ for (int i = _buckets[bucket] - 1; i >= 0; last = i, i = _slots[i].Next)
+ {
+ if (_slots[i].HashCode == hashCode && _slots[i].Value.Equals(peer))
+ {
+ if (last < 0)
+ _buckets[bucket] = _slots[i].Next + 1;
+ else
+ _slots[last].Next = _slots[i].Next;
+ _slots[i].HashCode = -1;
+ _slots[i].Value = null;
+ _slots[i].Next = _freeList;
+
+ _count--;
+ if (_count == 0)
+ {
+ _lastIndex = 0;
+ _freeList = -1;
+ }
+ else
+ {
+ _freeList = i;
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private bool TryGetPeer(IPEndPoint endPoint, out LiteNetPeer actualValue)
+ {
+ if (_buckets != null)
+ {
+#if NET8_0_OR_GREATER
+ //can be NetPeer or IPEndPoint
+ int hashCode = (UseNativeSockets ? endPoint.GetHashCode() : endPoint.Serialize().GetHashCode()) & Lower31BitMask;
+#else
+ int hashCode = endPoint.GetHashCode() & Lower31BitMask;
+#endif
+ _peersLock.EnterReadLock();
+ for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].Next)
+ {
+ if (_slots[i].HashCode == hashCode && _slots[i].Value.Equals(endPoint))
+ {
+ actualValue = _slots[i].Value;
+ _peersLock.ExitReadLock();
+ return true;
+ }
+ }
+ _peersLock.ExitReadLock();
+ }
+ actualValue = null;
+ return false;
+ }
+
+ //only used for NET8
+ private bool TryGetPeer(SocketAddress saddr, out LiteNetPeer actualValue)
+ {
+ if (_buckets != null)
+ {
+ int hashCode = saddr.GetHashCode() & Lower31BitMask;
+ _peersLock.EnterReadLock();
+ for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].Next)
+ {
+ if (_slots[i].HashCode == hashCode && _slots[i].Value.Serialize().Equals(saddr))
+ {
+ actualValue = _slots[i].Value;
+ _peersLock.ExitReadLock();
+ return true;
+ }
+ }
+ _peersLock.ExitReadLock();
+ }
+ actualValue = null;
+ return false;
+ }
+
+ protected bool AddPeerToSet(LiteNetPeer value)
+ {
+ if (_buckets == null)
+ {
+ int size = HashSetGetPrime(0);
+ _buckets = new int[size];
+ _slots = new Slot[size];
+ }
+
+ int hashCode = value.GetHashCode() & Lower31BitMask;
+ int bucket = hashCode % _buckets.Length;
+ for (int i = _buckets[hashCode % _buckets.Length] - 1; i >= 0; i = _slots[i].Next)
+ {
+ if (_slots[i].HashCode == hashCode && _slots[i].Value.Equals(value))
+ return false;
+ }
+
+ int index;
+ if (_freeList >= 0)
+ {
+ index = _freeList;
+ _freeList = _slots[index].Next;
+ }
+ else
+ {
+ if (_lastIndex == _slots.Length)
+ {
+ //increase capacity
+ int newSize = 2 * _count;
+ newSize = (uint)newSize > MaxPrimeArrayLength && MaxPrimeArrayLength > _count
+ ? MaxPrimeArrayLength
+ : HashSetGetPrime(newSize);
+
+ // Able to increase capacity; copy elements to larger array and rehash
+ Slot[] newSlots = new Slot[newSize];
+ Array.Copy(_slots, 0, newSlots, 0, _lastIndex);
+ _buckets = new int[newSize];
+ for (int i = 0; i < _lastIndex; i++)
+ {
+ int b = newSlots[i].HashCode % newSize;
+ newSlots[i].Next = _buckets[b] - 1;
+ _buckets[b] = i + 1;
+ }
+ _slots = newSlots;
+ // this will change during resize
+ bucket = hashCode % _buckets.Length;
+ }
+ index = _lastIndex;
+ _lastIndex++;
+ }
+ _slots[index].HashCode = hashCode;
+ _slots[index].Value = value;
+ _slots[index].Next = _buckets[bucket] - 1;
+ _buckets[bucket] = index + 1;
+ _count++;
+
+ return true;
+ }
+ }
+
+}
diff --git a/LiteNetLib/NetManager.PacketPool.cs b/LiteNetLib/LiteNetManager.PacketPool.cs
similarity index 98%
rename from LiteNetLib/NetManager.PacketPool.cs
rename to LiteNetLib/LiteNetManager.PacketPool.cs
index 08312209..fd8520f6 100644
--- a/LiteNetLib/NetManager.PacketPool.cs
+++ b/LiteNetLib/LiteNetManager.PacketPool.cs
@@ -2,7 +2,7 @@
namespace LiteNetLib
{
- public partial class NetManager
+ public partial class LiteNetManager
{
private NetPacket _poolHead;
private int _poolCount;
diff --git a/LiteNetLib/NetManager.Socket.cs b/LiteNetLib/LiteNetManager.Socket.cs
similarity index 72%
rename from LiteNetLib/NetManager.Socket.cs
rename to LiteNetLib/LiteNetManager.Socket.cs
index aabeaa36..97d6870b 100644
--- a/LiteNetLib/NetManager.Socket.cs
+++ b/LiteNetLib/LiteNetManager.Socket.cs
@@ -1,4 +1,7 @@
-using System.Runtime.InteropServices;
+#if UNITY_2018_3_OR_NEWER
+#define UNITY_SOCKET_FIX
+#endif
+using System.Runtime.InteropServices;
using System;
using System.Collections.Generic;
using System.Net;
@@ -8,38 +11,35 @@
namespace LiteNetLib
{
- public partial class NetManager
+ public partial class LiteNetManager
{
- private const int ReceivePollingTime = 500000; //0.5 second
-
- private Socket _udpSocketv4;
+ protected Socket _udpSocketv4;
private Socket _udpSocketv6;
private Thread _receiveThread;
private IPEndPoint _bufferEndPointv4;
private IPEndPoint _bufferEndPointv6;
-#if UNITY_2018_3_OR_NEWER
+#if UNITY_SOCKET_FIX
private PausedSocketFix _pausedSocketFix;
+ private bool _useSocketFix;
#endif
-#if !LITENETLIB_UNSAFE
- [ThreadStatic] private static byte[] _sendToBuffer;
+#if NET8_0_OR_GREATER
+ private readonly SocketAddress _sockAddrCacheV4 = new SocketAddress(AddressFamily.InterNetwork);
+ private readonly SocketAddress _sockAddrCacheV6 = new SocketAddress(AddressFamily.InterNetworkV6);
#endif
- [ThreadStatic] private static byte[] _endPointBuffer;
-
- private readonly Dictionary _nativeAddrMap = new Dictionary();
private const int SioUdpConnreset = -1744830452; //SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12
private static readonly IPAddress MulticastAddressV6 = IPAddress.Parse("ff02::1");
public static readonly bool IPv6Support;
- ///
- /// Maximum packets count that will be processed in Manual PollEvents
- ///
- public int MaxPacketsReceivePerUpdate = 0;
-
// special case in iOS (and possibly android that should be resolved in unity)
internal bool NotConnected;
+ ///
+ /// Poll timeout in microseconds. Increasing can slightly increase performance in cost of slow NetManager.Stop(Socket.Close)
+ ///
+ public int ReceivePollingTime = 50000; //0.05 second
+
public short Ttl
{
get
@@ -58,7 +58,7 @@ internal set
}
}
- static NetManager()
+ static LiteNetManager()
{
#if DISABLE_IPV6
IPv6Support = false;
@@ -70,23 +70,6 @@ static NetManager()
#endif
}
- private void RegisterEndPoint(IPEndPoint ep)
- {
- if (UseNativeSockets && ep is NativeEndPoint nep)
- {
- _nativeAddrMap.Add(new NativeAddr(nep.NativeAddress, nep.NativeAddress.Length), nep);
- }
- }
-
- private void UnregisterEndPoint(IPEndPoint ep)
- {
- if (UseNativeSockets && ep is NativeEndPoint nep)
- {
- var nativeAddr = new NativeAddr(nep.NativeAddress, nep.NativeAddress.Length);
- _nativeAddrMap.Remove(nativeAddr);
- }
- }
-
private bool ProcessError(SocketException ex)
{
switch (ex.SocketErrorCode)
@@ -102,6 +85,7 @@ private bool ProcessError(SocketException ex)
case SocketError.MessageSize:
case SocketError.TimedOut:
case SocketError.NetworkReset:
+ case SocketError.WouldBlock:
//NetDebug.Write($"[R]Ignored error: {(int)ex.SocketErrorCode} - {ex}");
break;
default:
@@ -117,12 +101,12 @@ private void ManualReceive(Socket socket, EndPoint bufferEndPoint)
//Reading data
try
{
- int packetsReceived = 0;
- while (socket.Available > 0)
+ int count = MaxPacketPerManualReceive;
+ while (socket.Available > 0 && (count > 0 || MaxPacketPerManualReceive == 0))
{
- ReceiveFrom(socket, ref bufferEndPoint);
- packetsReceived++;
- if (packetsReceived == MaxPacketsReceivePerUpdate)
+ if(ReceiveFrom(socket, ref bufferEndPoint) > 0)
+ count--;
+ else
break;
}
}
@@ -137,75 +121,54 @@ private void ManualReceive(Socket socket, EndPoint bufferEndPoint)
catch (Exception e)
{
//protects socket receive thread
- NetDebug.WriteError("[NM] SocketReceiveThread error: " + e );
+ NetDebug.WriteError("[NM] SocketReceiveThread error: " + e);
}
}
- private bool NativeReceiveFrom(ref NetPacket packet, IntPtr s, byte[] addrBuffer, int addrSize)
- {
- //Reading data
- packet.Size = NativeSocket.RecvFrom(s, packet.RawData, NetConstants.MaxPacketSize, addrBuffer, ref addrSize);
- if (packet.Size == 0)
- return false; //socket closed
- if (packet.Size == -1)
- {
- var errorCode = NativeSocket.GetSocketError();
- //Linux timeout EAGAIN
- return errorCode == SocketError.WouldBlock || errorCode == SocketError.TimedOut || ProcessError(new SocketException((int)errorCode)) == false;
- }
-
- var nativeAddr = new NativeAddr(addrBuffer, addrSize);
- if (!_nativeAddrMap.TryGetValue(nativeAddr, out var endPoint))
- endPoint = new NativeEndPoint(addrBuffer);
-
- //All ok!
- //NetDebug.WriteForce($"[R]Received data from {endPoint}, result: {packet.Size}");
- OnMessageReceived(packet, endPoint);
- packet = PoolGetPacket(NetConstants.MaxPacketSize);
- return true;
- }
-
private void NativeReceiveLogic()
{
IntPtr socketHandle4 = _udpSocketv4.Handle;
IntPtr socketHandle6 = _udpSocketv6?.Handle ?? IntPtr.Zero;
byte[] addrBuffer4 = new byte[NativeSocket.IPv4AddrSize];
byte[] addrBuffer6 = new byte[NativeSocket.IPv6AddrSize];
- int addrSize4 = addrBuffer4.Length;
- int addrSize6 = addrBuffer6.Length;
+ var tempEndPoint = new IPEndPoint(IPAddress.Any, 0);
var selectReadList = new List(2);
var socketv4 = _udpSocketv4;
var socketV6 = _udpSocketv6;
var packet = PoolGetPacket(NetConstants.MaxPacketSize);
- while (IsRunning)
+ while (_isRunning)
{
- if (socketV6 == null)
- {
- if (NativeReceiveFrom(ref packet, socketHandle4, addrBuffer4, addrSize4) == false)
- return;
- continue;
- }
- bool messageReceived = false;
- if (socketv4.Available != 0)
- {
- if (NativeReceiveFrom(ref packet, socketHandle4, addrBuffer4, addrSize4) == false)
- return;
- messageReceived = true;
- }
- if (socketV6.Available != 0)
- {
- if (NativeReceiveFrom(ref packet, socketHandle6, addrBuffer6, addrSize6) == false)
- return;
- messageReceived = true;
- }
- if (messageReceived)
- continue;
- selectReadList.Clear();
- selectReadList.Add(socketv4);
- selectReadList.Add(socketV6);
try
{
+ if (socketV6 == null)
+ {
+ if (NativeReceiveFrom(socketHandle4, addrBuffer4) == false)
+ return;
+ continue;
+ }
+ bool messageReceived = false;
+ if (socketv4.Available != 0 || selectReadList.Contains(socketv4))
+ {
+ if (NativeReceiveFrom(socketHandle4, addrBuffer4) == false)
+ return;
+ messageReceived = true;
+ }
+ if (socketV6.Available != 0 || selectReadList.Contains(socketV6))
+ {
+ if (NativeReceiveFrom(socketHandle6, addrBuffer6) == false)
+ return;
+ messageReceived = true;
+ }
+
+ selectReadList.Clear();
+
+ if (messageReceived)
+ continue;
+
+ selectReadList.Add(socketv4);
+ selectReadList.Add(socketV6);
+
Socket.Select(selectReadList, null, null, ReceivePollingTime);
}
catch (SocketException ex)
@@ -226,16 +189,72 @@ private void NativeReceiveLogic()
catch (Exception e)
{
//protects socket receive thread
- NetDebug.WriteError("[NM] SocketReceiveThread error: " + e );
+ NetDebug.WriteError("[NM] SocketReceiveThread error: " + e);
}
}
+
+ bool NativeReceiveFrom(IntPtr s, byte[] address)
+ {
+ int addrSize = address.Length;
+ packet.Size = NativeSocket.RecvFrom(s, packet.RawData, NetConstants.MaxPacketSize, address, ref addrSize);
+ if (packet.Size == 0)
+ return true; //socket closed or empty packet
+
+ if (packet.Size == -1)
+ {
+ //Linux timeout EAGAIN
+ return ProcessError(new SocketException((int)NativeSocket.GetSocketError())) == false;
+ }
+
+ //NetDebug.WriteForce($"[R]Received data from {endPoint}, result: {packet.Size}");
+ //refresh temp Addr/Port
+ short family = (short)((address[1] << 8) | address[0]);
+ tempEndPoint.Port = (ushort)((address[2] << 8) | address[3]);
+ if ((NativeSocket.UnixMode && family == NativeSocket.AF_INET6) || (!NativeSocket.UnixMode && (AddressFamily)family == AddressFamily.InterNetworkV6))
+ {
+ uint scope = unchecked((uint)(
+ (address[27] << 24) +
+ (address[26] << 16) +
+ (address[25] << 8) +
+ (address[24])));
+ tempEndPoint.Address = new IPAddress(new ReadOnlySpan(address, 8, 16), scope);
+ }
+ else //IPv4
+ {
+ long ipv4Addr = unchecked((uint)((address[4] & 0x000000FF) |
+ (address[5] << 8 & 0x0000FF00) |
+ (address[6] << 16 & 0x00FF0000) |
+ (address[7] << 24)));
+ tempEndPoint.Address = new IPAddress(ipv4Addr);
+ }
+
+ if (TryGetPeer(tempEndPoint, out var peer))
+ {
+ //use cached native ep
+ OnMessageReceived(packet, peer);
+ }
+ else
+ {
+ OnMessageReceived(packet, tempEndPoint);
+ tempEndPoint = new IPEndPoint(IPAddress.Any, 0);
+ }
+ packet = PoolGetPacket(NetConstants.MaxPacketSize);
+ return true;
+ }
}
- private void ReceiveFrom(Socket s, ref EndPoint bufferEndPoint)
+ private int ReceiveFrom(Socket s, ref EndPoint bufferEndPoint)
{
var packet = PoolGetPacket(NetConstants.MaxPacketSize);
+#if NET8_0_OR_GREATER
+ var sockAddr = s.AddressFamily == AddressFamily.InterNetwork ? _sockAddrCacheV4 : _sockAddrCacheV6;
+ packet.Size = s.ReceiveFrom(new Span(packet.RawData, 0, NetConstants.MaxPacketSize), SocketFlags.None, sockAddr);
+ OnMessageReceived(packet, TryGetPeer(sockAddr, out var peer) ? peer : (IPEndPoint)bufferEndPoint.Create(sockAddr));
+#else
packet.Size = s.ReceiveFrom(packet.RawData, 0, NetConstants.MaxPacketSize, SocketFlags.None, ref bufferEndPoint);
OnMessageReceived(packet, (IPEndPoint)bufferEndPoint);
+#endif
+ return packet.Size;
}
private void ReceiveLogic()
@@ -246,7 +265,7 @@ private void ReceiveLogic()
var socketv4 = _udpSocketv4;
var socketV6 = _udpSocketv6;
- while (IsRunning)
+ while (_isRunning)
{
//Reading data
try
@@ -260,20 +279,22 @@ private void ReceiveLogic()
else
{
bool messageReceived = false;
- if (socketv4.Available != 0)
+ if (socketv4.Available != 0 || selectReadList.Contains(socketv4))
{
ReceiveFrom(socketv4, ref bufferEndPoint4);
messageReceived = true;
}
- if (socketV6.Available != 0)
+ if (socketV6.Available != 0 || selectReadList.Contains(socketV6))
{
ReceiveFrom(socketV6, ref bufferEndPoint6);
messageReceived = true;
}
+
+ selectReadList.Clear();
+
if (messageReceived)
continue;
- selectReadList.Clear();
selectReadList.Add(socketv4);
selectReadList.Add(socketV6);
Socket.Select(selectReadList, null, null, ReceivePollingTime);
@@ -298,18 +319,22 @@ private void ReceiveLogic()
catch (Exception e)
{
//protects socket receive thread
- NetDebug.WriteError("[NM] SocketReceiveThread error: " + e );
+ NetDebug.WriteError("[NM] SocketReceiveThread error: " + e);
}
}
}
- ///
+ ///
/// Start logic thread and listening on selected port
///
/// bind to specific ipv4 address
/// bind to specific ipv6 address
/// port to listen
- /// mode of library
+ ///
+ /// When , disables internal background threads.
+ /// You must manually call and .
+ /// Can be used when e.g. 10,000 instances are running on the same machine, reducing the amount of threads used.
+ ///
public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port, bool manualMode)
{
if (IsRunning && NotConnected == false)
@@ -322,14 +347,14 @@ public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port, bool m
if (!BindSocket(_udpSocketv4, new IPEndPoint(addressIPv4, port)))
return false;
- LocalPort = ((IPEndPoint) _udpSocketv4.LocalEndPoint).Port;
+ LocalPort = ((IPEndPoint)_udpSocketv4.LocalEndPoint).Port;
-#if UNITY_2018_3_OR_NEWER
- if (_pausedSocketFix == null)
+#if UNITY_SOCKET_FIX
+ if (_useSocketFix && _pausedSocketFix == null)
_pausedSocketFix = new PausedSocketFix(this, addressIPv4, addressIPv6, port, manualMode);
#endif
- IsRunning = true;
+ _isRunning = true;
if (_manualMode)
{
_bufferEndPointv4 = new IPEndPoint(IPAddress.Any, 0);
@@ -343,9 +368,7 @@ public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port, bool m
if (BindSocket(_udpSocketv6, new IPEndPoint(addressIPv6, LocalPort)))
{
if (_manualMode)
- {
_bufferEndPointv6 = new IPEndPoint(IPAddress.IPv6Any, 0);
- }
}
else
{
@@ -387,7 +410,7 @@ private bool BindSocket(Socket socket, IPEndPoint ep)
{
try
{
- socket.IOControl(SioUdpConnreset, new byte[] {0}, null);
+ socket.IOControl(SioUdpConnreset, new byte[] { 0 }, null);
}
catch
{
@@ -399,6 +422,7 @@ private bool BindSocket(Socket socket, IPEndPoint ep)
{
socket.ExclusiveAddressUse = !ReuseAddress;
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, ReuseAddress);
+ socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontRoute, DontRoute);
}
catch
{
@@ -434,7 +458,7 @@ private bool BindSocket(Socket socket, IPEndPoint ep)
{
try
{
-#if !UNITY_2018_3_OR_NEWER
+#if !UNITY_SOCKET_FIX
socket.SetSocketOption(
SocketOptionLevel.IPv6,
SocketOptionName.AddMembership,
@@ -487,14 +511,12 @@ internal int SendRawAndRecycle(NetPacket packet, IPEndPoint remoteEndPoint)
return result;
}
- internal int SendRaw(NetPacket packet, IPEndPoint remoteEndPoint)
- {
- return SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint);
- }
+ internal int SendRaw(NetPacket packet, IPEndPoint remoteEndPoint) =>
+ SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint);
internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEndPoint)
{
- if (!IsRunning)
+ if (!_isRunning)
return 0;
NetPacket expandedPacket = null;
@@ -507,6 +529,34 @@ internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEnd
message = expandedPacket.RawData;
}
+#if DEBUG || SIMULATE_NETWORK
+ if (HandleSimulateOutboundPacketLoss())
+ {
+ if (expandedPacket != null)
+ PoolRecycle(expandedPacket);
+ return 0; // Simulate successful send to avoid triggering error handling
+ }
+
+ if (HandleSimulateOutboundLatency(message, start, length, remoteEndPoint))
+ {
+ if (expandedPacket != null)
+ PoolRecycle(expandedPacket);
+ return length; // Simulate successful send
+ }
+#endif
+
+ int result = SendRawCore(message, start, length, remoteEndPoint);
+ if (expandedPacket != null)
+ PoolRecycle(expandedPacket);
+ return result;
+ }
+
+ // Core socket sending logic without simulation - used by both SendRaw and delayed packet processing
+ internal int SendRawCore(byte[] message, int start, int length, IPEndPoint remoteEndPoint)
+ {
+ if (!_isRunning)
+ return 0;
+
var socket = _udpSocketv4;
if (remoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6 && IPv6Support)
{
@@ -518,75 +568,23 @@ internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEnd
int result;
try
{
- if (UseNativeSockets)
+ if (UseNativeSockets && remoteEndPoint is LiteNetPeer peer)
{
- byte[] socketAddress;
-
- if (remoteEndPoint is NativeEndPoint nep)
- {
- socketAddress = nep.NativeAddress;
- }
- else //Convert endpoint to raw
- {
- if (_endPointBuffer == null)
- _endPointBuffer = new byte[NativeSocket.IPv6AddrSize];
- socketAddress = _endPointBuffer;
-
- bool ipv4 = remoteEndPoint.AddressFamily == AddressFamily.InterNetwork;
- short addressFamily = NativeSocket.GetNativeAddressFamily(remoteEndPoint);
-
- socketAddress[0] = (byte) (addressFamily);
- socketAddress[1] = (byte) (addressFamily >> 8);
- socketAddress[2] = (byte) (remoteEndPoint.Port >> 8);
- socketAddress[3] = (byte) (remoteEndPoint.Port);
-
- if (ipv4)
- {
-#pragma warning disable 618
- long addr = remoteEndPoint.Address.Address;
-#pragma warning restore 618
- socketAddress[4] = (byte) (addr);
- socketAddress[5] = (byte) (addr >> 8);
- socketAddress[6] = (byte) (addr >> 16);
- socketAddress[7] = (byte) (addr >> 24);
- }
- else
- {
-#if NETCOREAPP || NETSTANDARD2_1 || NETSTANDARD2_1_OR_GREATER
- remoteEndPoint.Address.TryWriteBytes(new Span(socketAddress, 8, 16), out _);
-#else
- byte[] addrBytes = remoteEndPoint.Address.GetAddressBytes();
- Buffer.BlockCopy(addrBytes, 0, socketAddress, 8, 16);
-#endif
- }
- }
-
-#if LITENETLIB_UNSAFE
unsafe
{
fixed (byte* dataWithOffset = &message[start])
- {
- result =
- NativeSocket.SendTo(socket.Handle, dataWithOffset, length, socketAddress, socketAddress.Length);
- }
- }
-#else
- if (start > 0)
- {
- if (_sendToBuffer == null)
- _sendToBuffer = new byte[NetConstants.MaxPacketSize];
- Buffer.BlockCopy(message, start, _sendToBuffer, 0, length);
- message = _sendToBuffer;
+ result = NativeSocket.SendTo(socket.Handle, dataWithOffset, length, peer.NativeAddress, peer.NativeAddress.Length);
}
-
- result = NativeSocket.SendTo(socket.Handle, message, length, socketAddress, socketAddress.Length);
-#endif
if (result == -1)
throw NativeSocket.GetSocketException();
}
else
{
+#if NET8_0_OR_GREATER
+ result = socket.SendTo(new ReadOnlySpan(message, start, length), SocketFlags.None, remoteEndPoint.Serialize());
+#else
result = socket.SendTo(message, start, length, SocketFlags.None, remoteEndPoint);
+#endif
}
//NetDebug.WriteForce("[S]Send packet to {0}, result: {1}", remoteEndPoint, result);
}
@@ -603,10 +601,10 @@ internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEnd
case SocketError.HostUnreachable:
case SocketError.NetworkUnreachable:
- if (DisconnectOnUnreachable && TryGetPeer(remoteEndPoint, out var fromPeer))
+ if (DisconnectOnUnreachable && remoteEndPoint is LiteNetPeer peer)
{
DisconnectPeerForce(
- fromPeer,
+ peer,
ex.SocketErrorCode == SocketError.HostUnreachable
? DisconnectReason.HostUnreachable
: DisconnectReason.NetworkUnreachable,
@@ -631,13 +629,6 @@ internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEnd
NetDebug.WriteError($"[S] {ex}");
return 0;
}
- finally
- {
- if (expandedPacket != null)
- {
- PoolRecycle(expandedPacket);
- }
- }
if (result <= 0)
return 0;
@@ -651,15 +642,11 @@ internal int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEnd
return result;
}
- public bool SendBroadcast(NetDataWriter writer, int port)
- {
- return SendBroadcast(writer.Data, 0, writer.Length, port);
- }
+ public bool SendBroadcast(NetDataWriter writer, int port) =>
+ 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 port) =>
+ SendBroadcast(data, 0, data.Length, port);
public bool SendBroadcast(byte[] data, int start, int length, int port)
{
@@ -704,6 +691,13 @@ public bool SendBroadcast(byte[] data, int start, int length, int port)
new IPEndPoint(MulticastAddressV6, port)) > 0;
}
}
+ catch (SocketException ex)
+ {
+ if (ex.SocketErrorCode == SocketError.HostUnreachable)
+ return broadcastSuccess;
+ NetDebug.WriteError($"[S][MCAST] {ex}");
+ return broadcastSuccess;
+ }
catch (Exception ex)
{
NetDebug.WriteError($"[S][MCAST] {ex}");
@@ -719,14 +713,14 @@ public bool SendBroadcast(byte[] data, int start, int length, int port)
private void CloseSocket()
{
- IsRunning = false;
+ _isRunning = false;
+ if (_receiveThread != null && _receiveThread != Thread.CurrentThread)
+ _receiveThread.Join();
+ _receiveThread = null;
_udpSocketv4?.Close();
_udpSocketv6?.Close();
_udpSocketv4 = null;
_udpSocketv6 = null;
- if (_receiveThread != null && _receiveThread != Thread.CurrentThread)
- _receiveThread.Join();
- _receiveThread = null;
}
}
}
diff --git a/LiteNetLib/LiteNetManager.cs b/LiteNetLib/LiteNetManager.cs
new file mode 100644
index 00000000..3d173ce7
--- /dev/null
+++ b/LiteNetLib/LiteNetManager.cs
@@ -0,0 +1,1672 @@
+#if UNITY_2018_3_OR_NEWER
+#define UNITY_SOCKET_FIX
+#endif
+using System;
+using System.Collections;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using LiteNetLib.Layers;
+using LiteNetLib.Utils;
+
+namespace LiteNetLib
+{
+ ///
+ /// Main class for all network operations. Can be used as client and/or server.
+ ///
+ public partial class LiteNetManager : IEnumerable
+ {
+ public struct NetPeerEnumerator : IEnumerator where T : LiteNetPeer
+ {
+ private readonly T _initialPeer;
+ private T _p;
+
+ public NetPeerEnumerator(T p)
+ {
+ _initialPeer = p;
+ _p = null;
+ }
+
+ public void Dispose() { }
+
+ public bool MoveNext()
+ {
+ _p = _p == null ? _initialPeer : (T)_p.NextPeer;
+ return _p != null;
+ }
+
+ public void Reset() =>
+ throw new NotSupportedException();
+
+ public T Current => _p;
+ object IEnumerator.Current => _p;
+ }
+
+#if DEBUG || SIMULATE_NETWORK
+ private struct IncomingData
+ {
+ public NetPacket Data;
+ public IPEndPoint EndPoint;
+ public DateTime TimeWhenGet;
+ }
+ private readonly List _pingSimulationList = new List();
+
+ private struct OutboundDelayedPacket
+ {
+ public byte[] Data;
+ public int Start;
+ public int Length;
+ public IPEndPoint EndPoint;
+ public DateTime TimeWhenSend;
+ }
+ private readonly List _outboundSimulationList = new List();
+#endif
+
+ private readonly Random _randomGenerator = new Random();
+ private const int MinLatencyThreshold = 5;
+
+ private Thread _logicThread;
+ private bool _manualMode;
+ private readonly AutoResetEvent _updateTriggerEvent = new AutoResetEvent(true);
+
+ private NetEvent _pendingEventHead;
+ private NetEvent _pendingEventTail;
+
+ private NetEvent _netEventPoolHead;
+ private readonly ILiteNetEventListener _netEventListener;
+
+ private readonly Dictionary _requestsDict = new Dictionary();
+
+ private long _connectedPeersCount;
+ private readonly PacketLayerBase _extraPacketLayer;
+ private int _lastPeerId;
+ private ConcurrentQueue _peerIds = new ConcurrentQueue();
+
+ private readonly object _eventLock = new object();
+ private volatile bool _isRunning;
+
+ ///
+ /// Used with and to tag packets that
+ /// need to be dropped. Only relevant when DEBUG is defined.
+ ///
+ private bool _dropPacket;
+
+ //config section
+ ///
+ /// Enable messages receiving without connection. (with SendUnconnectedMessage method)
+ ///
+ public bool UnconnectedMessagesEnabled = false;
+
+ ///
+ /// Enable nat punch messages
+ ///
+ public bool NatPunchEnabled = false;
+
+ ///
+ /// Library logic update and send period in milliseconds
+ /// Lowest values in Windows doesn't change much because of Thread.Sleep precision
+ /// To more frequent sends (or sends tied to your game logic) use
+ ///
+ public int UpdateTime = 15;
+
+ ///
+ /// Interval for latency detection and checking connection (in milliseconds)
+ ///
+ public int PingInterval = 1000;
+
+ ///
+ /// If NetManager doesn't receive any packet from remote peer during this time (in milliseconds) then connection will be closed
+ /// (including library internal keepalive packets)
+ ///
+ public int DisconnectTimeout = 5000;
+
+ ///
+ /// Simulate packet loss by dropping random amount of packets. (Works only in DEBUG builds or when SIMULATE_NETWORK is defined)
+ ///
+ public bool SimulatePacketLoss = false;
+
+ ///
+ /// Simulate latency by holding packets for random time. (Works only in DEBUG builds or when SIMULATE_NETWORK is defined)
+ ///
+ public bool SimulateLatency = false;
+
+ ///
+ /// Chance of packet loss when simulation enabled. value in percents (1 - 100).
+ ///
+ public int SimulationPacketLossChance = 10;
+
+ ///
+ /// Minimum simulated round-trip latency (in milliseconds). Actual latency applied per direction is half of this value.
+ ///
+ public int SimulationMinLatency = 30;
+
+ ///
+ /// Maximum simulated round-trip latency (in milliseconds). Actual latency applied per direction is half of this value.
+ ///
+ public int SimulationMaxLatency = 100;
+
+ ///
+ /// Events automatically will be called without PollEvents method from another thread
+ ///
+ public bool UnsyncedEvents = false;
+
+ ///
+ /// If true - receive event will be called from "receive" thread immediately otherwise on PollEvents call
+ ///
+ public bool UnsyncedReceiveEvent = false;
+
+ ///
+ /// If true - delivery event will be called from "receive" thread immediately otherwise on PollEvents call
+ ///
+ public bool UnsyncedDeliveryEvent = false;
+
+ ///
+ /// Allows receive broadcast packets
+ ///
+ public bool BroadcastReceiveEnabled = false;
+
+ ///
+ /// Delay between initial connection attempts (in milliseconds)
+ ///
+ public int ReconnectDelay = 500;
+
+ ///
+ /// Maximum connection attempts before client stops and call disconnect event.
+ ///
+ public int MaxConnectAttempts = 10;
+
+ ///
+ /// Enables socket option "ReuseAddress" for specific purposes
+ ///
+ public bool ReuseAddress = false;
+
+ ///
+ /// UDP Only Socket Option
+ /// Normally IP sockets send packets of data through routers and gateways until they reach the final destination.
+ /// If the DontRoute flag is set to True, then data will be delivered on the local subnet only.
+ ///
+ public bool DontRoute = false;
+
+ ///
+ /// Statistics of all connections
+ ///
+ public readonly NetStatistics Statistics = new NetStatistics();
+
+ ///
+ /// Toggles the collection of network statistics for the instance and all known peers
+ ///
+ public bool EnableStatistics = false;
+
+ ///
+ /// Max fragmented packets size for reliable channels - that equals to data of size fragments count * (MTU-reliable header size)
+ ///
+ public ushort MaxFragmentsCount = ushort.MaxValue;
+
+ ///
+ /// NatPunchModule for NAT hole punching operations
+ ///
+ public NatPunchModule NatPunchModule => _natPunchModule.Value;
+
+ private readonly Lazy _natPunchModule;
+
+ ///
+ /// Returns true if socket listening and update thread is running
+ ///
+ public bool IsRunning => _isRunning;
+
+ ///
+ /// Local EndPoint (host and port)
+ ///
+ public int LocalPort { get; private set; }
+
+ ///
+ /// Automatically recycle NetPacketReader after OnReceive event
+ ///
+ public bool AutoRecycle;
+
+ ///
+ /// IPv6 support
+ ///
+ public bool IPv6Enabled = true;
+
+ ///
+ /// Override MTU for all new peers registered in this NetManager, will ignores MTU Discovery!
+ ///
+ public int MtuOverride = 0;
+
+ ///
+ /// Automatically discovery mtu starting from. Use at own risk because some routers can break MTU detection
+ /// and connection in result
+ ///
+ public bool MtuDiscovery = false;
+
+ ///
+ /// First peer. Useful for Client mode
+ ///
+ public LiteNetPeer FirstPeer => _headPeer;
+
+ ///
+ /// Experimental feature mostly for servers. Only for Windows/Linux
+ /// use direct socket calls for send/receive to drastically increase speed and reduce GC pressure
+ ///
+ public bool UseNativeSockets = false;
+
+ ///
+ /// Disconnect peers if HostUnreachable or NetworkUnreachable spawned (old behaviour 0.9.x was true)
+ ///
+ public bool DisconnectOnUnreachable = false;
+
+ ///
+ /// Allows peer change it's ip (lte to wifi, wifi to lte, etc). Use only on server
+ ///
+ public bool AllowPeerAddressChange = false;
+
+ ///
+ /// Returns connected peers count
+ ///
+ public int ConnectedPeersCount => (int)Interlocked.Read(ref _connectedPeersCount);
+
+ ///
+ /// Maximum packets that can be received per one ManualReceive (PollEvents when started using StartInManualMode)
+ /// 0 - infinite - but this can cause some big delays there is too many incoming packets
+ ///
+ public int MaxPacketPerManualReceive = 256;
+
+ ///
+ /// Gets the additional size in bytes required by the active .
+ ///
+ ///
+ /// This value is used by to calculate the available MTU for user data.
+ /// If a packet layer is active (e.g., for encryption or CRC), this returns the overhead added to every packet.
+ /// Returns 0 if no packet layer is assigned.
+ ///
+ public int ExtraPacketSizeForLayer => _extraPacketLayer?.ExtraPacketSizeForLayer ?? 0;
+
+ ///
+ /// NetManager constructor
+ ///
+ /// Network events listener (also can implement IDeliveryEventListener)
+ /// Extra processing of packages, like CRC checksum or encryption. All connected NetManagers must have same layer.
+#if UNITY_SOCKET_FIX
+ public LiteNetManager(ILiteNetEventListener listener, PacketLayerBase extraPacketLayer = null, bool useSocketFix = true)
+ {
+ _useSocketFix = useSocketFix;
+#else
+ public LiteNetManager(ILiteNetEventListener listener, PacketLayerBase extraPacketLayer = null)
+ {
+#endif
+ _netEventListener = listener;
+ _natPunchModule = new Lazy(() => new NatPunchModule(this));
+ _extraPacketLayer = extraPacketLayer;
+ }
+
+ internal void ConnectionLatencyUpdated(LiteNetPeer fromPeer, int latency) =>
+ CreateEvent(NetEvent.EType.ConnectionLatencyUpdated, fromPeer, latency: latency);
+
+ internal void MessageDelivered(LiteNetPeer fromPeer, object userData) =>
+ CreateEvent(NetEvent.EType.MessageDelivered, fromPeer, userData: userData);
+
+ internal void DisconnectPeerForce(LiteNetPeer peer,
+ DisconnectReason reason,
+ SocketError socketErrorCode,
+ NetPacket eventData) =>
+ DisconnectPeer(peer, reason, socketErrorCode, true, ReadOnlySpan.Empty, eventData);
+
+ ///
+ /// Disconnects a peer and handles internal state cleanup.
+ ///
+ /// The peer to disconnect.
+ /// The reason for disconnection provided to the event listener.
+ /// The error code from the underlying socket, if any.
+ ///
+ /// If , immediately sets state to without sending a notification.
+ /// If , sends unreliable disconnect packets until and sets state to .
+ /// Peer will linger until to ignore late-arriving packets from the old session.
+ ///
+ /// Optional custom data to include in the disconnect packet.
+ /// Internal packet data associated with the disconnect event.
+ private void DisconnectPeer(
+ LiteNetPeer peer,
+ DisconnectReason reason,
+ SocketError socketErrorCode,
+ bool force,
+ ReadOnlySpan data,
+ NetPacket eventData)
+ {
+ var shutdownResult = peer.Shutdown(data, 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,
+ LiteNetPeer peer = null,
+ IPEndPoint remoteEndPoint = null,
+ SocketError errorCode = 0,
+ int latency = 0,
+ DisconnectReason disconnectReason = DisconnectReason.ConnectionFailed,
+ LiteConnectionRequest connectionRequest = null,
+ DeliveryMethod deliveryMethod = DeliveryMethod.Unreliable,
+ byte channelNumber = 0,
+ NetPacket readerSource = null,
+ object userData = null)
+ {
+ NetEvent evt;
+ bool unsyncEvent = UnsyncedEvents;
+
+ if (type == NetEvent.EType.Connect)
+ Interlocked.Increment(ref _connectedPeersCount);
+ else if (type == NetEvent.EType.MessageDelivered)
+ unsyncEvent = UnsyncedDeliveryEvent;
+
+ lock (_eventLock)
+ {
+ evt = _netEventPoolHead;
+ if (evt == null)
+ evt = new NetEvent(this);
+ else
+ _netEventPoolHead = evt.Next;
+ }
+
+ evt.Next = null;
+ evt.Type = type;
+ evt.DataReader.SetSource(readerSource, readerSource?.HeaderSize ?? 0);
+ evt.Peer = peer;
+ evt.RemoteEndPoint = remoteEndPoint;
+ evt.Latency = latency;
+ evt.ErrorCode = errorCode;
+ evt.DisconnectReason = disconnectReason;
+ evt.ConnectionRequest = connectionRequest;
+ evt.DeliveryMethod = deliveryMethod;
+ evt.ChannelNumber = channelNumber;
+ evt.UserData = userData;
+
+ if (unsyncEvent || _manualMode)
+ {
+ ProcessEvent(evt);
+ }
+ else
+ {
+ lock (_eventLock)
+ {
+ if (_pendingEventTail == null)
+ _pendingEventHead = evt;
+ else
+ _pendingEventTail.Next = evt;
+ _pendingEventTail = evt;
+ }
+ }
+ }
+
+ protected virtual 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:
+ _netEventListener.OnMessageDelivered(evt.Peer, evt.UserData);
+ break;
+ case NetEvent.EType.PeerAddressChanged:
+ _peersLock.EnterUpgradeableReadLock();
+ IPEndPoint previousAddress = null;
+ if (ContainsPeer(evt.Peer))
+ {
+ _peersLock.EnterWriteLock();
+ RemovePeerFromSet(evt.Peer);
+ previousAddress = new IPEndPoint(evt.Peer.Address, evt.Peer.Port);
+ evt.Peer.FinishEndPointChange(evt.RemoteEndPoint);
+ AddPeerToSet(evt.Peer);
+ _peersLock.ExitWriteLock();
+ }
+ _peersLock.ExitUpgradeableReadLock();
+ if (previousAddress != null)
+ _netEventListener.OnPeerAddressChanged(evt.Peer, previousAddress);
+ break;
+ }
+ //Recycle if not message
+ if (emptyData)
+ RecycleEvent(evt);
+ else if (AutoRecycle)
+ evt.DataReader.RecycleInternal();
+ }
+
+ internal void RecycleEvent(NetEvent evt)
+ {
+ evt.Peer = null;
+ evt.ErrorCode = 0;
+ evt.RemoteEndPoint = null;
+ evt.ConnectionRequest = null;
+ lock (_eventLock)
+ {
+ evt.Next = _netEventPoolHead;
+ _netEventPoolHead = evt;
+ }
+ }
+
+ protected virtual void ProcessNtpRequests(float elapsedMilliseconds)
+ {
+ //not used in lite version
+ }
+
+ //Update function
+ private void UpdateLogic()
+ {
+ var peersToRemove = new List();
+ var stopwatch = new Stopwatch();
+ stopwatch.Start();
+
+ while (_isRunning)
+ {
+ try
+ {
+ ProcessDelayedPackets();
+ float elapsed = (float)(stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0);
+ elapsed = elapsed <= 0.0f ? 0.001f : elapsed;
+ stopwatch.Restart();
+
+ for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
+ {
+ if (netPeer.ConnectionState == ConnectionState.Disconnected &&
+ netPeer.TimeSinceLastPacket > DisconnectTimeout)
+ {
+ peersToRemove.Add(netPeer);
+ }
+ else
+ {
+ netPeer.Update(elapsed);
+ }
+ }
+
+ if (peersToRemove.Count > 0)
+ {
+ _peersLock.EnterWriteLock();
+ for (int i = 0; i < peersToRemove.Count; i++)
+ RemovePeer(peersToRemove[i], false);
+ _peersLock.ExitWriteLock();
+ peersToRemove.Clear();
+ }
+
+ ProcessNtpRequests(elapsed);
+
+ int sleepTime = UpdateTime - (int)stopwatch.ElapsedMilliseconds;
+ if (sleepTime > 0)
+ _updateTriggerEvent.WaitOne(sleepTime);
+ }
+ catch (ThreadAbortException)
+ {
+ return;
+ }
+ catch (Exception e)
+ {
+ NetDebug.WriteError("[NM] LogicThread error: " + e);
+ }
+ }
+ stopwatch.Stop();
+ }
+
+ [Conditional("DEBUG"), Conditional("SIMULATE_NETWORK")]
+ private void ProcessDelayedPackets()
+ {
+ if (!SimulateLatency)
+ return;
+
+#if DEBUG || SIMULATE_NETWORK
+ var time = DateTime.UtcNow;
+ lock (_pingSimulationList)
+ {
+ for (int i = 0; i < _pingSimulationList.Count; i++)
+ {
+ var incomingData = _pingSimulationList[i];
+ if (incomingData.TimeWhenGet <= time)
+ {
+ HandleMessageReceived(incomingData.Data, incomingData.EndPoint);
+ _pingSimulationList.RemoveAt(i);
+ i--;
+ }
+ }
+ }
+
+ lock (_outboundSimulationList)
+ {
+ for (int i = 0; i < _outboundSimulationList.Count; i++)
+ {
+ var outboundData = _outboundSimulationList[i];
+ if (outboundData.TimeWhenSend <= time)
+ {
+ // Send the delayed packet directly to socket layer bypassing simulation
+ SendRawCore(outboundData.Data, outboundData.Start, outboundData.Length, outboundData.EndPoint);
+ _outboundSimulationList.RemoveAt(i);
+ i--;
+ }
+ }
+ }
+#endif
+ }
+
+
+ ///
+ /// Updates internal peer states, handles timeouts, processes NTP requests and sends buffered packets.
+ ///
+ /// Time passed since the last update frame.
+ ///
+ /// Must be called continuously from the main loop if was set to .
+ ///
+ public void ManualUpdate(float elapsedMilliseconds)
+ {
+ if (!_manualMode)
+ return;
+
+ for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
+ {
+ if (netPeer.ConnectionState == ConnectionState.Disconnected && netPeer.TimeSinceLastPacket > DisconnectTimeout)
+ {
+ RemovePeer(netPeer, false);
+ }
+ else
+ {
+ netPeer.Update(elapsedMilliseconds);
+ }
+ }
+ ProcessNtpRequests(elapsedMilliseconds);
+ }
+
+ //connect to
+ protected virtual LiteNetPeer CreateOutgoingPeer(IPEndPoint remoteEndPoint, int id, byte connectNum, ReadOnlySpan connectData) =>
+ new LiteNetPeer(this, remoteEndPoint, id, connectNum, connectData);
+
+ //accept
+ protected virtual LiteNetPeer CreateIncomingPeer(LiteConnectionRequest request, int id) =>
+ new LiteNetPeer(this, request, id);
+
+ //reject
+ protected virtual LiteNetPeer CreateRejectPeer(IPEndPoint remoteEndPoint, int id) =>
+ new LiteNetPeer(this, remoteEndPoint, id);
+
+ protected virtual LiteConnectionRequest CreateConnectionRequest(IPEndPoint remoteEndPoint, NetConnectRequestPacket requestPacket) =>
+ new LiteConnectionRequest(remoteEndPoint, requestPacket, this);
+
+ internal LiteNetPeer OnConnectionSolved(LiteConnectionRequest request, ReadOnlySpan rejectData)
+ {
+ LiteNetPeer netPeer = null;
+
+ if (request.Result == ConnectionRequestResult.RejectForce)
+ {
+ NetDebug.Write(NetLogLevel.Trace, "[NM] Peer connect reject force.");
+ if (!rejectData.IsEmpty)
+ {
+ var shutdownPacket = PoolGetWithProperty(PacketProperty.Disconnect, rejectData.Length);
+ shutdownPacket.ConnectionNumber = request.InternalPacket.ConnectionNumber;
+ FastBitConverter.GetBytes(shutdownPacket.RawData, 1, request.InternalPacket.ConnectionTime);
+ if (shutdownPacket.Size >= NetConstants.PossibleMtu[0])
+ NetDebug.WriteError("[Peer] Disconnect additional data size more than MTU!");
+ else
+ rejectData.CopyTo(shutdownPacket.RawData.AsSpan(9));
+ SendRawAndRecycle(shutdownPacket, request.RemoteEndPoint);
+ }
+ lock (_requestsDict)
+ _requestsDict.Remove(request.RemoteEndPoint);
+ }
+ else lock (_requestsDict)
+ {
+ if (TryGetPeer(request.RemoteEndPoint, out netPeer))
+ {
+ //already have peer
+ }
+ else if (request.Result == ConnectionRequestResult.Reject)
+ {
+ netPeer = CreateRejectPeer(request.RemoteEndPoint, GetNextPeerId());
+ netPeer.Reject(request.InternalPacket, rejectData);
+ AddPeer(netPeer);
+ NetDebug.Write(NetLogLevel.Trace, "[NM] Peer connect reject.");
+ }
+ else //Accept
+ {
+ netPeer = CreateIncomingPeer(request, GetNextPeerId());
+ AddPeer(netPeer);
+ CreateEvent(NetEvent.EType.Connect, netPeer);
+ NetDebug.Write(NetLogLevel.Trace, $"[NM] Received peer connection Id: {netPeer.ConnectTime}, EP: {netPeer}");
+ }
+ _requestsDict.Remove(request.RemoteEndPoint);
+ }
+
+ return netPeer;
+ }
+
+ private int GetNextPeerId() =>
+ _peerIds.TryDequeue(out int id) ? id : _lastPeerId++;
+
+ private void ProcessConnectRequest(
+ IPEndPoint remoteEndPoint,
+ LiteNetPeer netPeer,
+ NetConnectRequestPacket connRequest)
+ {
+ //if we have peer
+ if (netPeer != null)
+ {
+ var processResult = netPeer.ProcessConnectRequest(connRequest);
+ NetDebug.Write($"ConnectRequest LastId: {netPeer.ConnectTime}, NewId: {connRequest.ConnectionTime}, EP: {remoteEndPoint}, Result: {processResult}");
+
+ switch (processResult)
+ {
+ case ConnectRequestResult.Reconnection:
+ DisconnectPeerForce(netPeer, DisconnectReason.Reconnect, 0, null);
+ RemovePeer(netPeer, true);
+ //go to new connection
+ break;
+ case ConnectRequestResult.NewConnection:
+ RemovePeer(netPeer, true);
+ //go to new connection
+ break;
+ case ConnectRequestResult.P2PLose:
+ DisconnectPeerForce(netPeer, DisconnectReason.PeerToPeerConnection, 0, null);
+ RemovePeer(netPeer, true);
+ //go to new connection
+ break;
+ default:
+ //no operations needed
+ return;
+ }
+ //ConnectRequestResult.NewConnection
+ //Set next connection number
+ if (processResult != ConnectRequestResult.P2PLose)
+ connRequest.ConnectionNumber = (byte)((netPeer.ConnectionNum + 1) % NetConstants.MaxConnectionNumber);
+ //To reconnect peer
+ }
+ else
+ {
+ NetDebug.Write($"ConnectRequest Id: {connRequest.ConnectionTime}, EP: {remoteEndPoint}");
+ }
+
+ LiteConnectionRequest req;
+ lock (_requestsDict)
+ {
+ if (_requestsDict.TryGetValue(remoteEndPoint, out req))
+ {
+ req.UpdateRequest(connRequest);
+ return;
+ }
+ req = CreateConnectionRequest(remoteEndPoint, connRequest);
+ _requestsDict.Add(remoteEndPoint, req);
+ }
+ NetDebug.Write($"[NM] Creating request event: {connRequest.ConnectionTime}");
+ CreateEvent(NetEvent.EType.ConnectionRequest, connectionRequest: req);
+ }
+
+ private void OnMessageReceived(NetPacket packet, IPEndPoint remoteEndPoint)
+ {
+ if (packet.Size == 0)
+ {
+ PoolRecycle(packet);
+ return;
+ }
+
+ _dropPacket = false;
+ HandleSimulateLatency(packet, remoteEndPoint);
+ HandleSimulatePacketLoss();
+ if (_dropPacket)
+ {
+ return;
+ }
+
+ // ProcessEvents
+ HandleMessageReceived(packet, remoteEndPoint);
+ }
+
+ [Conditional("DEBUG"), Conditional("SIMULATE_NETWORK")]
+ private void HandleSimulateLatency(NetPacket packet, IPEndPoint remoteEndPoint)
+ {
+ if (!SimulateLatency)
+ {
+ return;
+ }
+#if DEBUG || SIMULATE_NETWORK
+ int roundTripLatency = _randomGenerator.Next(SimulationMinLatency, SimulationMaxLatency);
+ int inboundLatency = roundTripLatency / 2;
+ if (inboundLatency > MinLatencyThreshold)
+ {
+ lock (_pingSimulationList)
+ {
+ _pingSimulationList.Add(new IncomingData
+ {
+ Data = packet,
+ EndPoint = remoteEndPoint,
+ TimeWhenGet = DateTime.UtcNow.AddMilliseconds(inboundLatency)
+ });
+ }
+ // hold packet
+ _dropPacket = true;
+ }
+#endif
+ }
+
+ [Conditional("DEBUG"), Conditional("SIMULATE_NETWORK")]
+ private void HandleSimulatePacketLoss()
+ {
+ if (SimulatePacketLoss && _randomGenerator.NextDouble() * 100 < SimulationPacketLossChance)
+ {
+ _dropPacket = true;
+ }
+ }
+
+#if DEBUG || SIMULATE_NETWORK
+ private bool HandleSimulateOutboundLatency(byte[] data, int start, int length, IPEndPoint remoteEndPoint)
+ {
+ if (!SimulateLatency)
+ {
+ return false;
+ }
+
+ int roundTripLatency = _randomGenerator.Next(SimulationMinLatency, SimulationMaxLatency);
+ int outboundLatency = roundTripLatency / 2;
+ if (outboundLatency > MinLatencyThreshold)
+ {
+ // Create a copy of the data to avoid issues with recycled packets
+ byte[] dataCopy = new byte[length];
+ Array.Copy(data, start, dataCopy, 0, length);
+
+ lock (_outboundSimulationList)
+ {
+ _outboundSimulationList.Add(new OutboundDelayedPacket
+ {
+ Data = dataCopy,
+ Start = 0,
+ Length = length,
+ EndPoint = remoteEndPoint,
+ TimeWhenSend = DateTime.UtcNow.AddMilliseconds(outboundLatency)
+ });
+ }
+
+ return true;
+ }
+ return false;
+ }
+#endif
+
+#if DEBUG || SIMULATE_NETWORK
+ private bool HandleSimulateOutboundPacketLoss() => //Should Drop?
+ SimulatePacketLoss && _randomGenerator.NextDouble() * 100 < SimulationPacketLossChance;
+#endif
+
+ internal virtual bool CustomMessageHandle(NetPacket packet, IPEndPoint remoteEndPoint) =>
+ false;
+
+ private void HandleMessageReceived(NetPacket packet, IPEndPoint remoteEndPoint)
+ {
+ var originalPacketSize = packet.Size;
+ if (EnableStatistics)
+ {
+ Statistics.IncrementPacketsReceived();
+ Statistics.AddBytesReceived(originalPacketSize);
+ }
+
+ if (CustomMessageHandle(packet, remoteEndPoint))
+ return;
+
+ if (_extraPacketLayer != null)
+ {
+ _extraPacketLayer.ProcessInboundPacket(ref remoteEndPoint, ref packet.RawData, ref packet.Size);
+ if (packet.Size == 0)
+ return;
+ }
+
+ if (!packet.Verify())
+ {
+ NetDebug.WriteError("[NM] DataReceived: bad!");
+ PoolRecycle(packet);
+ return;
+ }
+
+ switch (packet.Property)
+ {
+ //special case connect request
+ case PacketProperty.ConnectRequest:
+ if (NetConnectRequestPacket.GetProtocolId(packet) != NetConstants.ProtocolId)
+ {
+ SendRawAndRecycle(PoolGetWithProperty(PacketProperty.InvalidProtocol), remoteEndPoint);
+ return;
+ }
+ break;
+ //unconnected messages
+ case PacketProperty.Broadcast:
+ if (!BroadcastReceiveEnabled)
+ return;
+ CreateEvent(NetEvent.EType.Broadcast, remoteEndPoint: remoteEndPoint, readerSource: packet);
+ return;
+ case PacketProperty.UnconnectedMessage:
+ if (!UnconnectedMessagesEnabled)
+ return;
+ CreateEvent(NetEvent.EType.ReceiveUnconnected, remoteEndPoint: remoteEndPoint, readerSource: packet);
+ return;
+ case PacketProperty.NatMessage:
+ if (NatPunchEnabled)
+ NatPunchModule.ProcessMessage(remoteEndPoint, packet);
+ return;
+ }
+
+ //Check normal packets
+ bool peerFound = remoteEndPoint is LiteNetPeer netPeer || TryGetPeer(remoteEndPoint, out netPeer);
+
+ if (peerFound && EnableStatistics)
+ {
+ netPeer.Statistics.IncrementPacketsReceived();
+ netPeer.Statistics.AddBytesReceived(originalPacketSize);
+ }
+
+ switch (packet.Property)
+ {
+ case PacketProperty.ConnectRequest:
+ var connRequest = NetConnectRequestPacket.FromData(packet);
+ if (connRequest != null)
+ ProcessConnectRequest(remoteEndPoint, netPeer, connRequest);
+ break;
+ case PacketProperty.PeerNotFound:
+ if (peerFound) //local
+ {
+ if (netPeer.ConnectionState != ConnectionState.Connected)
+ return;
+ if (packet.Size == 1)
+ {
+ //first reply
+ //send NetworkChanged packet
+ netPeer.ResetMtu();
+ SendRaw(NetConnectAcceptPacket.MakeNetworkChanged(netPeer), remoteEndPoint);
+ NetDebug.Write($"PeerNotFound sending connection info: {remoteEndPoint}");
+ }
+ else if (packet.Size == 2 && packet.RawData[1] == 1)
+ {
+ //second reply
+ DisconnectPeerForce(netPeer, DisconnectReason.PeerNotFound, 0, null);
+ }
+ }
+ else if (packet.Size > 1) //remote
+ {
+ //check if this is old peer
+ bool isOldPeer = false;
+
+ if (AllowPeerAddressChange)
+ {
+ NetDebug.Write($"[NM] Looks like address change: {packet.Size}");
+ var remoteData = NetConnectAcceptPacket.FromData(packet);
+ if (remoteData != null &&
+ remoteData.PeerNetworkChanged &&
+ remoteData.PeerId < _peersArray.Length)
+ {
+ _peersLock.EnterUpgradeableReadLock();
+ var peer = _peersArray[remoteData.PeerId];
+ _peersLock.ExitUpgradeableReadLock();
+ if (peer != null &&
+ peer.ConnectTime == remoteData.ConnectionTime &&
+ peer.ConnectionNum == remoteData.ConnectionNumber)
+ {
+ if (peer.ConnectionState == ConnectionState.Connected)
+ {
+ peer.InitiateEndPointChange();
+ CreateEvent(NetEvent.EType.PeerAddressChanged, peer, remoteEndPoint);
+ NetDebug.Write("[NM] PeerNotFound change address of remote peer");
+ }
+ isOldPeer = true;
+ }
+ }
+ }
+
+ PoolRecycle(packet);
+
+ //else peer really not found
+ if (!isOldPeer)
+ {
+ var secondResponse = PoolGetWithProperty(PacketProperty.PeerNotFound, 1);
+ secondResponse.RawData[1] = 1;
+ SendRawAndRecycle(secondResponse, remoteEndPoint);
+ }
+ }
+ break;
+ case PacketProperty.InvalidProtocol:
+ if (peerFound && netPeer.ConnectionState == ConnectionState.Outgoing)
+ DisconnectPeerForce(netPeer, DisconnectReason.InvalidProtocol, 0, null);
+ break;
+ case PacketProperty.Disconnect:
+ if (peerFound)
+ {
+ var disconnectResult = netPeer.ProcessDisconnect(packet);
+ if (disconnectResult == DisconnectResult.None)
+ {
+ PoolRecycle(packet);
+ return;
+ }
+ DisconnectPeerForce(
+ netPeer,
+ disconnectResult == DisconnectResult.Disconnect
+ ? DisconnectReason.RemoteConnectionClose
+ : DisconnectReason.ConnectionRejected,
+ 0, packet);
+ }
+ else
+ {
+ PoolRecycle(packet);
+ }
+ //Send shutdown
+ SendRawAndRecycle(PoolGetWithProperty(PacketProperty.ShutdownOk), remoteEndPoint);
+ break;
+ case PacketProperty.ConnectAccept:
+ if (!peerFound)
+ return;
+ var connAccept = NetConnectAcceptPacket.FromData(packet);
+ if (connAccept != null && netPeer.ProcessConnectAccept(connAccept))
+ CreateEvent(NetEvent.EType.Connect, netPeer);
+ break;
+ default:
+ if (peerFound)
+ netPeer.ProcessPacket(packet);
+ else
+ SendRawAndRecycle(PoolGetWithProperty(PacketProperty.PeerNotFound), remoteEndPoint);
+ break;
+ }
+ }
+
+ internal void CreateReceiveEvent(NetPacket packet, DeliveryMethod method, byte channelNumber, int headerSize, LiteNetPeer fromPeer)
+ {
+ NetEvent evt;
+
+ if (UnsyncedEvents || UnsyncedReceiveEvent || _manualMode)
+ {
+ lock (_eventLock)
+ {
+ evt = _netEventPoolHead;
+ if (evt == null)
+ evt = new NetEvent(this);
+ else
+ _netEventPoolHead = evt.Next;
+ }
+ evt.Next = null;
+ evt.Type = NetEvent.EType.Receive;
+ evt.DataReader.SetSource(packet, headerSize);
+ evt.Peer = fromPeer;
+ evt.DeliveryMethod = method;
+ evt.ChannelNumber = channelNumber;
+ ProcessEvent(evt);
+ }
+ else
+ {
+ lock (_eventLock)
+ {
+ evt = _netEventPoolHead;
+ if (evt == null)
+ evt = new NetEvent(this);
+ else
+ _netEventPoolHead = evt.Next;
+
+ evt.Next = null;
+ evt.Type = NetEvent.EType.Receive;
+ evt.DataReader.SetSource(packet, headerSize);
+ evt.Peer = fromPeer;
+ evt.DeliveryMethod = method;
+ evt.ChannelNumber = channelNumber;
+
+ if (_pendingEventTail == null)
+ _pendingEventHead = evt;
+ else
+ _pendingEventTail.Next = evt;
+ _pendingEventTail = evt;
+ }
+ }
+ }
+
+ ///
+ /// Send data to all connected peers (channel - 0)
+ ///
+ /// DataWriter with data
+ /// Send options (reliable, unreliable, etc.)
+ public void SendToAll(NetDataWriter writer, DeliveryMethod options) =>
+ SendToAll(writer.Data, 0, writer.Length, options);
+
+ ///
+ /// Send data to all connected peers (channel - 0)
+ ///
+ /// Data
+ /// Send options (reliable, unreliable, etc.)
+ public void SendToAll(byte[] data, DeliveryMethod options) =>
+ SendToAll(data, 0, data.Length, options);
+
+ ///
+ /// Send data to all connected peers (channel - 0)
+ ///
+ /// Data
+ /// Start of data
+ /// Length of data
+ /// Send options (reliable, unreliable, etc.)
+ public void SendToAll(byte[] data, int start, int length, DeliveryMethod options)
+ {
+ try
+ {
+ _peersLock.EnterReadLock();
+ for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
+ netPeer.Send(data, start, length, options);
+ }
+ finally
+ {
+ _peersLock.ExitReadLock();
+ }
+ }
+
+ ///
+ /// Send data to all connected peers (channel - 0)
+ ///
+ /// DataWriter with data
+ /// Send options (reliable, unreliable, etc.)
+ /// Excluded peer
+ public void SendToAll(NetDataWriter writer, DeliveryMethod options, LiteNetPeer excludePeer) =>
+ SendToAll(writer.Data, 0, writer.Length, options, excludePeer);
+
+ ///
+ /// Send data to all connected peers (channel - 0)
+ ///
+ /// Data
+ /// Send options (reliable, unreliable, etc.)
+ /// Excluded peer
+ public void SendToAll(byte[] data, DeliveryMethod options, LiteNetPeer 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, DeliveryMethod options, LiteNetPeer excludePeer)
+ {
+ try
+ {
+ _peersLock.EnterReadLock();
+ for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
+ {
+ if (netPeer != excludePeer)
+ netPeer.Send(data, start, length, options);
+ }
+ }
+ finally
+ {
+ _peersLock.ExitReadLock();
+ }
+ }
+
+ ///
+ /// Send data to all connected peers (channel - 0)
+ ///
+ /// Data
+ /// Send options (reliable, unreliable, etc.)
+ public void SendToAll(ReadOnlySpan data, DeliveryMethod options) =>
+ SendToAll(data, options, null);
+
+ ///
+ /// Send data to all connected peers (channel - 0)
+ ///
+ /// Data
+ /// Send options (reliable, unreliable, etc.)
+ /// Excluded peer
+ public void SendToAll(ReadOnlySpan data, DeliveryMethod options, LiteNetPeer excludePeer)
+ {
+ try
+ {
+ _peersLock.EnterReadLock();
+ for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
+ {
+ if (netPeer != excludePeer)
+ netPeer.Send(data, options);
+ }
+ }
+ finally
+ {
+ _peersLock.ExitReadLock();
+ }
+ }
+
+ ///
+ /// Send message without connection
+ ///
+ /// Raw data
+ /// Packet destination
+ /// Operation result
+ public bool SendUnconnectedMessage(ReadOnlySpan message, IPEndPoint remoteEndPoint)
+ {
+ int headerSize = NetPacket.GetHeaderSize(PacketProperty.UnconnectedMessage);
+ var packet = PoolGetPacket(message.Length + headerSize);
+ packet.Property = PacketProperty.UnconnectedMessage;
+ message.CopyTo(new Span(packet.RawData, headerSize, message.Length));
+ return SendRawAndRecycle(packet, remoteEndPoint) > 0;
+ }
+
+ ///
+ /// Start logic thread and listening on available port
+ ///
+ public bool Start() =>
+ Start(0);
+
+ ///
+ /// Start logic thread and listening on selected port
+ ///
+ /// bind to specific ipv4 address
+ /// bind to specific ipv6 address
+ /// port to listen
+ public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port) =>
+ Start(addressIPv4, addressIPv6, port, false);
+
+ ///
+ /// Start logic thread and listening on selected port
+ ///
+ /// bind to specific ipv4 address
+ /// bind to specific ipv6 address
+ /// port to listen
+ public bool Start(string addressIPv4, string addressIPv6, int port)
+ {
+ IPAddress ipv4 = NetUtils.ResolveAddress(addressIPv4);
+ IPAddress ipv6 = NetUtils.ResolveAddress(addressIPv6);
+ return Start(ipv4, ipv6, port);
+ }
+
+ ///
+ /// Start logic thread and listening on selected port
+ ///
+ /// port to listen
+ public bool Start(int port) =>
+ Start(IPAddress.Any, IPAddress.IPv6Any, port);
+
+ ///
+ /// Start in manual mode and listening on selected port
+ /// In this mode you should use ManualReceive (without PollEvents) for receive packets
+ /// and ManualUpdate(...) for update and send packets
+ /// This mode useful mostly for single-threaded servers
+ ///
+ /// bind to specific ipv4 address
+ /// bind to specific ipv6 address
+ /// port to listen
+ public bool StartInManualMode(IPAddress addressIPv4, IPAddress addressIPv6, int port) =>
+ Start(addressIPv4, addressIPv6, port, true);
+
+ ///
+ /// Start in manual mode and listening on selected port
+ /// In this mode you should use ManualReceive (without PollEvents) for receive packets
+ /// and ManualUpdate(...) for update and send packets
+ /// This mode useful mostly for single-threaded servers
+ ///
+ /// bind to specific ipv4 address
+ /// bind to specific ipv6 address
+ /// port to listen
+ public bool StartInManualMode(string addressIPv4, string addressIPv6, int port)
+ {
+ IPAddress ipv4 = NetUtils.ResolveAddress(addressIPv4);
+ IPAddress ipv6 = NetUtils.ResolveAddress(addressIPv6);
+ return StartInManualMode(ipv4, ipv6, port);
+ }
+
+ ///
+ /// Start in manual mode and listening on selected port
+ /// In this mode you should use ManualReceive (without PollEvents) for receive packets
+ /// and ManualUpdate(...) for update and send packets
+ /// This mode useful mostly for single-threaded servers
+ ///
+ /// port to listen
+ public bool StartInManualMode(int port) =>
+ StartInManualMode(IPAddress.Any, IPAddress.IPv6Any, port);
+
+ ///
+ /// Send message without connection
+ ///
+ /// Raw data
+ /// Packet destination
+ /// Operation result
+ public bool SendUnconnectedMessage(byte[] message, IPEndPoint remoteEndPoint) =>
+ SendUnconnectedMessage(message, 0, message.Length, remoteEndPoint);
+
+ ///
+ /// Send message without connection. WARNING This method allocates a new IPEndPoint object and
+ /// synchronously makes a DNS request. If you're calling this method every frame it will be
+ /// much faster to just cache the IPEndPoint.
+ ///
+ /// Data serializer
+ /// Packet destination IP or hostname
+ /// Packet destination port
+ /// Operation result
+ public bool SendUnconnectedMessage(NetDataWriter writer, string address, int port) =>
+ SendUnconnectedMessage(writer.Data, 0, writer.Length, NetUtils.MakeEndPoint(address, port));
+
+ ///
+ /// Send message without connection
+ ///
+ /// Data serializer
+ /// Packet destination
+ /// Operation result
+ public bool SendUnconnectedMessage(NetDataWriter writer, IPEndPoint remoteEndPoint) =>
+ SendUnconnectedMessage(writer.Data, 0, writer.Length, remoteEndPoint);
+
+ ///
+ /// Send message without connection
+ ///
+ /// Raw data
+ /// data start
+ /// data length
+ /// Packet destination
+ /// Operation result
+ public bool SendUnconnectedMessage(byte[] message, int start, int length, IPEndPoint remoteEndPoint)
+ {
+ //No need for CRC here, SendRaw does that
+ NetPacket packet = PoolGetWithData(PacketProperty.UnconnectedMessage, message, start, length);
+ return SendRawAndRecycle(packet, remoteEndPoint) > 0;
+ }
+
+ ///
+ /// Triggers update and send logic immediately (works asynchronously)
+ ///
+ public void TriggerUpdate() =>
+ _updateTriggerEvent.Set();
+
+ ///
+ /// Synchronizes arrived events from the background thread to your main-thread/
+ ///
+ ///
+ /// Must be called continuously from the main loop if was set to to receive data from the UDP sockets.
+ ///
+ public void PollEvents()
+ {
+ if (_manualMode)
+ {
+ if (_udpSocketv4 != null)
+ ManualReceive(_udpSocketv4, _bufferEndPointv4);
+ if (_udpSocketv6 != null && _udpSocketv6 != _udpSocketv4)
+ ManualReceive(_udpSocketv6, _bufferEndPointv6);
+ ProcessDelayedPackets();
+ return;
+ }
+ if (UnsyncedEvents)
+ return;
+ NetEvent pendingEvent;
+ lock (_eventLock)
+ {
+ pendingEvent = _pendingEventHead;
+ _pendingEventHead = null;
+ _pendingEventTail = null;
+ }
+
+ while (pendingEvent != null)
+ {
+ var next = pendingEvent.Next;
+ ProcessEvent(pendingEvent);
+ pendingEvent = next;
+ }
+ }
+
+ ///
+ /// Connect to remote host
+ ///
+ /// Server IP or hostname
+ /// Server Port
+ /// Connection key
+ /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting
+ /// Manager is not running. Call
+ public LiteNetPeer Connect(string address, int port, string key) =>
+ Connect(address, port, NetDataWriter.FromString(key));
+
+ ///
+ /// Connect to remote host
+ ///
+ /// Server IP or hostname
+ /// Server Port
+ /// Additional data for remote peer
+ /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting
+ /// Manager is not running. Call
+ public LiteNetPeer Connect(string address, int port, NetDataWriter connectionData)
+ {
+ IPEndPoint ep;
+ try
+ {
+ ep = NetUtils.MakeEndPoint(address, port);
+ }
+ catch
+ {
+ CreateEvent(NetEvent.EType.Disconnect, disconnectReason: DisconnectReason.UnknownHost);
+ return null;
+ }
+ return Connect(ep, connectionData);
+ }
+
+ ///
+ /// Connect to remote host
+ ///
+ /// Server end point (ip and port)
+ /// Connection key
+ /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting
+ /// Manager is not running. Call
+ public LiteNetPeer Connect(IPEndPoint target, string key) =>
+ Connect(target, NetDataWriter.FromString(key));
+
+ ///
+ /// Connect to remote host
+ ///
+ /// Server end point (ip and port)
+ /// Additional data for remote peer
+ /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting
+ /// Manager is not running. Call
+ public LiteNetPeer Connect(IPEndPoint target, NetDataWriter connectionData)
+ {
+ if (!_isRunning)
+ throw new InvalidOperationException("Client is not running");
+
+ lock (_requestsDict)
+ {
+ if (_requestsDict.ContainsKey(target))
+ return null;
+
+ byte connectionNumber = 0;
+ if (TryGetPeer(target, out var peer))
+ {
+ switch (peer.ConnectionState)
+ {
+ //just return already connected peer
+ case ConnectionState.Connected:
+ case ConnectionState.Outgoing:
+ return peer;
+ }
+ //else reconnect
+ connectionNumber = (byte)((peer.ConnectionNum + 1) % NetConstants.MaxConnectionNumber);
+ RemovePeer(peer, true);
+ }
+
+ //Create reliable connection
+ //And send connection request
+ peer = CreateOutgoingPeer(target, GetNextPeerId(), connectionNumber, connectionData.AsReadOnlySpan());
+ AddPeer(peer);
+ return peer;
+ }
+ }
+
+ ///
+ /// Connect to remote host
+ ///
+ /// Server end point (ip and port)
+ /// Additional data for remote peer
+ /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting
+ /// Manager is not running. Call
+ public LiteNetPeer Connect(IPEndPoint target, ReadOnlySpan connectionData)
+ {
+ if (!_isRunning)
+ throw new InvalidOperationException("Client is not running");
+
+ lock (_requestsDict)
+ {
+ if (_requestsDict.ContainsKey(target))
+ return null;
+
+ byte connectionNumber = 0;
+ if (TryGetPeer(target, out var peer))
+ {
+ switch (peer.ConnectionState)
+ {
+ //just return already connected peer
+ case ConnectionState.Connected:
+ case ConnectionState.Outgoing:
+ return peer;
+ }
+ //else reconnect
+ connectionNumber = (byte)((peer.ConnectionNum + 1) % NetConstants.MaxConnectionNumber);
+ RemovePeer(peer, true);
+ }
+
+ //Create reliable connection
+ //And send connection request
+ peer = CreateOutgoingPeer(target, GetNextPeerId(), connectionNumber, connectionData);
+ AddPeer(peer);
+ return peer;
+ }
+ }
+
+ ///
+ /// Force closes connection and stop all threads.
+ ///
+ public void Stop() =>
+ Stop(true);
+
+ ///
+ /// Force closes connection and stop all threads.
+ ///
+ /// Send disconnect messages
+ public void Stop(bool sendDisconnectMessages)
+ {
+ if (!_isRunning)
+ return;
+
+ NetDebug.Write("[NM] Stop");
+
+ //Little hack so shutdown message will not be queued by Outbound latency simulation
+ //In real world packet will be delayed by routers not by application(that closes) logic
+ var simLat = SimulateLatency;
+ SimulateLatency = false;
+
+ //Send last disconnect
+ for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
+ netPeer.Shutdown(ReadOnlySpan.Empty, !sendDisconnectMessages);
+
+ //Stop
+ CloseSocket();
+
+ //Return setting
+ SimulateLatency = simLat;
+
+#if UNITY_SOCKET_FIX
+ if (_useSocketFix)
+ {
+ _pausedSocketFix.Deinitialize();
+ _pausedSocketFix = null;
+ }
+#endif
+
+ _updateTriggerEvent.Set();
+ if (!_manualMode)
+ {
+ _logicThread.Join();
+ _logicThread = null;
+ }
+
+ //clear peers
+ ClearPeerSet();
+ _peerIds = new ConcurrentQueue();
+ _lastPeerId = 0;
+
+#if DEBUG || SIMULATE_NETWORK
+ lock (_pingSimulationList)
+ _pingSimulationList.Clear();
+ lock (_outboundSimulationList)
+ _outboundSimulationList.Clear();
+#endif
+
+ _connectedPeersCount = 0;
+ _pendingEventHead = null;
+ _pendingEventTail = null;
+ }
+
+ ///
+ /// Return peers count with connection state
+ ///
+ /// peer connection state (you can use as bit flags)
+ /// peers count
+ public int GetPeersCount(ConnectionState peerState)
+ {
+ int count = 0;
+ _peersLock.EnterReadLock();
+ for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
+ {
+ if ((netPeer.ConnectionState & peerState) != 0)
+ count++;
+ }
+ _peersLock.ExitReadLock();
+ return count;
+ }
+
+ ///
+ /// Get copy of peers (without allocations)
+ ///
+ /// List that will contain result
+ /// State of peers
+ public void GetPeers(List peers, ConnectionState peerState)
+ {
+ peers.Clear();
+ _peersLock.EnterReadLock();
+ for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
+ {
+ if ((netPeer.ConnectionState & peerState) != 0)
+ peers.Add(netPeer);
+ }
+ _peersLock.ExitReadLock();
+ }
+
+ ///
+ /// Get copy of connected peers (without allocations)
+ ///
+ /// List that will contain result
+ public void GetConnectedPeers(List peers) =>
+ GetPeers(peers, ConnectionState.Connected);
+
+ ///
+ /// Disconnect all peers without any additional data
+ ///
+ public void DisconnectAll() =>
+ DisconnectAll(ReadOnlySpan.Empty);
+
+ ///
+ /// Disconnect all peers with shutdown message
+ ///
+ /// Data to send (must be less or equal MTU)
+ /// Data start
+ /// Data count
+ public void DisconnectAll(byte[] data, int start, int count) =>
+ DisconnectAll(new ReadOnlySpan(data, start, count));
+
+ ///
+ /// Disconnect all peers with shutdown message
+ ///
+ /// Data to send (must be less or equal MTU)
+ public void DisconnectAll(ReadOnlySpan data)
+ {
+ //Send disconnect packets
+ _peersLock.EnterReadLock();
+ for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
+ {
+ DisconnectPeer(
+ netPeer,
+ DisconnectReason.DisconnectPeerCalled,
+ 0,
+ false,
+ data,
+ null);
+ }
+ _peersLock.ExitReadLock();
+ }
+
+ ///
+ /// Immediately disconnect peer from server without additional data
+ ///
+ /// peer to disconnect
+ public void DisconnectPeerForce(LiteNetPeer peer) =>
+ DisconnectPeerForce(peer, DisconnectReason.DisconnectPeerCalled, 0, null);
+
+ ///
+ /// Disconnect peer from server
+ ///
+ /// peer to disconnect
+ public void DisconnectPeer(LiteNetPeer peer) =>
+ DisconnectPeer(peer, ReadOnlySpan.Empty);
+
+ ///
+ /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8)
+ ///
+ /// peer to disconnect
+ /// additional data
+ public void DisconnectPeer(LiteNetPeer peer, byte[] data) =>
+ DisconnectPeer(peer, new ReadOnlySpan(data));
+
+ ///
+ /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8)
+ ///
+ /// peer to disconnect
+ /// additional data
+ public void DisconnectPeer(LiteNetPeer peer, NetDataWriter writer) =>
+ DisconnectPeer(peer, writer.AsReadOnlySpan());
+
+ ///
+ /// 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(LiteNetPeer peer, byte[] data, int start, int count) =>
+ DisconnectPeer(peer, new ReadOnlySpan(data, start, count));
+
+ ///
+ /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8)
+ ///
+ /// peer to disconnect
+ /// additional data
+ public void DisconnectPeer(LiteNetPeer peer, ReadOnlySpan data)
+ {
+ DisconnectPeer(
+ peer,
+ DisconnectReason.DisconnectPeerCalled,
+ 0,
+ false,
+ data,
+ null);
+ }
+
+ public NetPeerEnumerator GetEnumerator() =>
+ new NetPeerEnumerator(_headPeer);
+
+ IEnumerator IEnumerable.GetEnumerator() =>
+ new NetPeerEnumerator(_headPeer);
+
+ IEnumerator IEnumerable.GetEnumerator() =>
+ new NetPeerEnumerator(_headPeer);
+ }
+}
diff --git a/LiteNetLib/LiteNetPeer.cs b/LiteNetLib/LiteNetPeer.cs
new file mode 100644
index 00000000..f36796c3
--- /dev/null
+++ b/LiteNetLib/LiteNetPeer.cs
@@ -0,0 +1,1357 @@
+#if DEBUG
+#define STATS_ENABLED
+#endif
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Net;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using LiteNetLib.Utils;
+
+namespace LiteNetLib
+{
+ ///
+ /// Peer connection state
+ ///
+ [Flags]
+ public enum ConnectionState : byte
+ {
+ Outgoing = 1 << 1,
+ Connected = 1 << 2,
+ ShutdownRequested = 1 << 3,
+ Disconnected = 1 << 4,
+ EndPointChange = 1 << 5,
+ Any = Outgoing | Connected | ShutdownRequested | EndPointChange
+ }
+
+ 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
+ }
+
+ ///
+ /// Network peer. Main purpose is sending messages to specific peer.
+ ///
+ public class LiteNetPeer : IPEndPoint
+ {
+ //Ping and RTT
+ private int _rtt;
+ private int _avgRtt;
+ private int _rttCount;
+ private float _resendDelay = 27.0f;
+ private float _pingSendTimer;
+ private float _rttResetTimer;
+ private readonly Stopwatch _pingTimer = new Stopwatch();
+ private float _timeSinceLastPacket;
+ private long _remoteDelta;
+
+ //Common
+ private readonly object _shutdownLock = new object();
+
+ internal volatile LiteNetPeer NextPeer;
+ internal LiteNetPeer PrevPeer;
+
+ internal byte ConnectionNum
+ {
+ get => _connectNum;
+ private set
+ {
+ _connectNum = value;
+ _mergeData.ConnectionNumber = value;
+ _pingPacket.ConnectionNumber = value;
+ _pongPacket.ConnectionNumber = value;
+ }
+ }
+
+ //Channels
+ private NetPacket[] _unreliableSecondQueue;
+ private NetPacket[] _unreliableChannel;
+ private int _unreliablePendingCount;
+ private readonly object _unreliableChannelLock = new object();
+
+ //MTU
+ private int _mtu;
+ private int _mtuIdx;
+ private bool _finishMtu;
+ private float _mtuCheckTimer;
+ private int _mtuCheckAttempts;
+ private const int MtuCheckDelay = 1000;
+ private const int MaxMtuCheckAttempts = 4;
+ private readonly object _mtuMutex = new object();
+
+ //Fragment
+ private sealed class IncomingFragments
+ {
+ public readonly NetPacket[] Fragments;
+ public readonly ushort TotalFragments;
+ public readonly byte ChannelId;
+
+ public int ReceivedCount;
+ public int TotalSize;
+
+ public IncomingFragments(ushort totalFragments, byte channelId)
+ {
+ TotalFragments = totalFragments;
+ ChannelId = channelId;
+ Fragments = new NetPacket[totalFragments];
+ }
+
+ public void RecycleAll(LiteNetManager netManager)
+ {
+ for (int i = 0; i < Fragments.Length; i++)
+ {
+ NetPacket packet = Fragments[i];
+ if (packet != null)
+ {
+ netManager.PoolRecycle(packet);
+ Fragments[i] = null;
+ }
+ }
+ }
+ }
+ private int _fragmentId;
+ private readonly Dictionary _holdedFragments;
+ private readonly Dictionary _deliveredFragments;
+
+ //Merging
+ private readonly NetPacket _mergeData;
+ private int _mergePos;
+ private int _mergeCount;
+
+ //Connection
+ private int _connectAttempts;
+ private float _connectTimer;
+ private long _connectTime;
+ private byte _connectNum;
+ private ConnectionState _connectionState;
+ private NetPacket _shutdownPacket;
+ private const int ShutdownDelay = 300;
+ private float _shutdownTimer;
+ private readonly NetPacket _pingPacket;
+ private readonly NetPacket _pongPacket;
+ private readonly NetPacket _connectRequestPacket;
+ private readonly NetPacket _connectAcceptPacket;
+
+ ///
+ /// Peer parent NetManager
+ ///
+ public readonly LiteNetManager NetManager;
+
+ ///
+ /// Current connection state
+ ///
+ public ConnectionState ConnectionState => _connectionState;
+
+ ///
+ /// Connection time for internal purposes
+ ///
+ internal long ConnectTime => _connectTime;
+
+ ///
+ /// Peer id can be used as key in your dictionary of peers
+ ///
+ public readonly int Id;
+
+ ///
+ /// Id assigned from server
+ ///
+ public int RemoteId { get; private set; }
+
+ ///
+ /// Current one-way ping (RTT/2) in milliseconds
+ ///
+ public int Ping => _avgRtt/2;
+
+ ///
+ /// Round trip time in milliseconds
+ ///
+ public int RoundTripTime => _avgRtt;
+
+ ///
+ /// Current MTU - Maximum Transfer Unit ( maximum udp packet size without fragmentation )
+ ///
+ public int Mtu => _mtu;
+
+ ///
+ /// Delta with remote time in ticks (not accurate)
+ /// positive - remote time > our time
+ ///
+ public long RemoteTimeDelta => _remoteDelta;
+
+ ///
+ /// Remote UTC time (not accurate)
+ ///
+ public DateTime RemoteUtcTime => new DateTime(DateTime.UtcNow.Ticks + _remoteDelta);
+
+ ///
+ /// Time since last packet received (including internal library packets) in milliseconds
+ ///
+ public float TimeSinceLastPacket => _timeSinceLastPacket;
+
+ ///
+ /// Fixed part of the resend delay
+ ///
+ public float ResendFixedDelay = 25.0f;
+
+ ///
+ /// Multiplication factor of Rtt in the resend delay calculation
+ ///
+ public float ResendRttMultiplier = 2.1f;
+
+ internal float ResendDelay => _resendDelay;
+
+ ///
+ /// Application defined object containing data about the connection
+ ///
+ public object Tag;
+
+ ///
+ /// Statistics of peer connection
+ ///
+ public readonly NetStatistics Statistics;
+
+ private SocketAddress _cachedSocketAddr;
+ private int _cachedHashCode;
+ private ReliableChannel _reliableChannel;
+ private ReliableChannel _reliableUnorderedChannel;
+ private SequencedChannel _sequencedChannel;
+
+ internal byte[] NativeAddress;
+
+ protected virtual int ChannelsCount => 1;
+
+ ///
+ /// IPEndPoint serialize
+ ///
+ /// SocketAddress
+ public override SocketAddress Serialize() =>
+ _cachedSocketAddr;
+
+ public override int GetHashCode() =>
+ //uses SocketAddress hash in NET8 and IPEndPoint hash for NativeSockets and previous NET versions
+ _cachedHashCode;
+
+ //incoming connection constructor
+ internal LiteNetPeer(LiteNetManager netManager, IPEndPoint remoteEndPoint, int id) : base(remoteEndPoint.Address, remoteEndPoint.Port)
+ {
+ Id = id;
+ Statistics = new NetStatistics();
+ NetManager = netManager;
+
+ _cachedSocketAddr = base.Serialize();
+ if (NetManager.UseNativeSockets)
+ {
+ NativeAddress = new byte[_cachedSocketAddr.Size];
+ for (int i = 0; i < _cachedSocketAddr.Size; i++)
+ NativeAddress[i] = _cachedSocketAddr[i];
+ }
+#if NET8_0_OR_GREATER
+ _cachedHashCode = NetManager.UseNativeSockets ? base.GetHashCode() : _cachedSocketAddr.GetHashCode();
+#else
+ _cachedHashCode = base.GetHashCode();
+#endif
+
+ ResetMtu();
+
+ _connectionState = ConnectionState.Connected;
+ _mergeData = new NetPacket(PacketProperty.Merged, NetConstants.MaxPacketSize);
+ _pongPacket = new NetPacket(PacketProperty.Pong, 0);
+ _pingPacket = new NetPacket(PacketProperty.Ping, 0) {Sequence = 1};
+
+ _unreliableSecondQueue = new NetPacket[8];
+ _unreliableChannel = new NetPacket[8];
+ _holdedFragments = new Dictionary();
+ _deliveredFragments = new Dictionary();
+ }
+
+ internal void InitiateEndPointChange()
+ {
+ ResetMtu();
+ _connectionState = ConnectionState.EndPointChange;
+ }
+
+ internal void FinishEndPointChange(IPEndPoint newEndPoint)
+ {
+ if (_connectionState != ConnectionState.EndPointChange)
+ return;
+ _connectionState = ConnectionState.Connected;
+
+ Address = newEndPoint.Address;
+ Port = newEndPoint.Port;
+
+ _cachedSocketAddr = base.Serialize();
+ if (NetManager.UseNativeSockets)
+ {
+ NativeAddress = new byte[_cachedSocketAddr.Size];
+ for (int i = 0; i < _cachedSocketAddr.Size; i++)
+ NativeAddress[i] = _cachedSocketAddr[i];
+ }
+#if NET8_0_OR_GREATER
+ _cachedHashCode = NetManager.UseNativeSockets ? base.GetHashCode() : _cachedSocketAddr.GetHashCode();
+#else
+ _cachedHashCode = base.GetHashCode();
+#endif
+ }
+
+ internal void ResetMtu()
+ {
+ //finish if discovery disabled
+ _finishMtu = !NetManager.MtuDiscovery;
+ if (NetManager.MtuOverride > 0)
+ OverrideMtu(NetManager.MtuOverride);
+ else
+ SetMtu(0);
+ }
+
+ private void SetMtu(int mtuIdx)
+ {
+ _mtuIdx = mtuIdx;
+ _mtu = NetConstants.PossibleMtu[mtuIdx] - NetManager.ExtraPacketSizeForLayer;
+ }
+
+ private void OverrideMtu(int mtuValue)
+ {
+ _mtu = mtuValue;
+ _finishMtu = true;
+ }
+
+ ///
+ /// Returns packets count in queue for reliable channel 0
+ ///
+ /// type of channel ReliableOrdered or ReliableUnordered
+ /// packets count in channel queue
+ public int GetPacketsCountInReliableQueue(bool ordered) =>
+ (ordered ? _reliableChannel : _reliableUnorderedChannel)?.PacketsInQueue ?? 0;
+
+ ///
+ /// Create temporary packet (maximum size MTU - headerSize) to send later without additional copies
+ ///
+ /// Delivery method (reliable, unreliable, etc.)
+ /// PooledPacket that you can use to write data starting from UserDataOffset
+ public PooledPacket CreatePacketFromPool(DeliveryMethod deliveryMethod)
+ {
+ //multithreaded variable
+ int mtu = _mtu;
+ var packet = NetManager.PoolGetPacket(mtu);
+ if (deliveryMethod == DeliveryMethod.Unreliable)
+ {
+ packet.Property = PacketProperty.Unreliable;
+ return new PooledPacket(packet, mtu, 0);
+ }
+ else
+ {
+ packet.Property = PacketProperty.Channeled;
+ return new PooledPacket(packet, mtu, (byte)deliveryMethod);
+ }
+ }
+
+ ///
+ /// Sends pooled packet without data copy
+ ///
+ /// packet to send
+ /// size of user data you want to send
+ public void SendPooledPacket(PooledPacket packet, int userDataSize)
+ {
+ if (_connectionState != ConnectionState.Connected)
+ return;
+ packet._packet.Size = packet.UserDataOffset + userDataSize;
+ if (packet._packet.Property == PacketProperty.Channeled)
+ {
+ CreateChannel(packet._channelNumber).AddToQueue(packet._packet);
+ }
+ else
+ {
+ lock (_unreliableChannelLock)
+ {
+ if (_unreliablePendingCount == _unreliableChannel.Length)
+ Array.Resize(ref _unreliableChannel, _unreliablePendingCount*2);
+ _unreliableChannel[_unreliablePendingCount++] = packet._packet;
+ }
+ }
+ }
+
+ internal virtual BaseChannel CreateChannel(byte channelNumber)
+ {
+ switch ((DeliveryMethod)channelNumber)
+ {
+ case DeliveryMethod.ReliableOrdered:
+ case DeliveryMethod.ReliableSequenced:
+ return _reliableChannel ??= new ReliableChannel(this, true, (int)DeliveryMethod.ReliableOrdered);
+
+ case DeliveryMethod.ReliableUnordered:
+ return _reliableUnorderedChannel ??= new ReliableChannel(this, false, channelNumber);
+
+ case DeliveryMethod.Sequenced:
+ return _sequencedChannel ??= new SequencedChannel(this, true, channelNumber);
+
+ default:
+ throw new Exception("Invalid channel type");
+ }
+ }
+
+ //"Connect to" constructor
+ internal LiteNetPeer(LiteNetManager netManager, IPEndPoint remoteEndPoint, int id, byte connectNum, ReadOnlySpan connectData)
+ : this(netManager, remoteEndPoint, id)
+ {
+ _connectTime = DateTime.UtcNow.Ticks;
+ _connectionState = ConnectionState.Outgoing;
+ ConnectionNum = connectNum;
+
+ //Make initial packet
+ _connectRequestPacket = NetConnectRequestPacket.Make(connectData, remoteEndPoint.Serialize(), _connectTime, id);
+ _connectRequestPacket.ConnectionNumber = connectNum;
+
+ //Send request
+ NetManager.SendRaw(_connectRequestPacket, this);
+
+ NetDebug.Write(NetLogLevel.Trace, $"[CC] ConnectId: {_connectTime}, ConnectNum: {connectNum}");
+ }
+
+ //"Accept" incoming constructor
+ internal LiteNetPeer(LiteNetManager netManager, LiteConnectionRequest request, int id)
+ : this(netManager, request.RemoteEndPoint, id)
+ {
+ _connectTime = request.InternalPacket.ConnectionTime;
+ ConnectionNum = request.InternalPacket.ConnectionNumber;
+ RemoteId = request.InternalPacket.PeerId;
+
+ //Make initial packet
+ _connectAcceptPacket = NetConnectAcceptPacket.Make(_connectTime, ConnectionNum, id);
+
+ //Make Connected
+ _connectionState = ConnectionState.Connected;
+
+ //Send
+ NetManager.SendRaw(_connectAcceptPacket, this);
+
+ NetDebug.Write(NetLogLevel.Trace, $"[CC] ConnectId: {_connectTime}");
+ }
+
+ //Reject
+ internal void Reject(NetConnectRequestPacket requestData, ReadOnlySpan data)
+ {
+ _connectTime = requestData.ConnectionTime;
+ _connectNum = requestData.ConnectionNumber;
+ Shutdown(data, false);
+ }
+
+ internal bool ProcessConnectAccept(NetConnectAcceptPacket packet)
+ {
+ if (_connectionState != ConnectionState.Outgoing)
+ return false;
+
+ //check connection id
+ if (packet.ConnectionTime != _connectTime)
+ {
+ NetDebug.Write(NetLogLevel.Trace, $"[NC] Invalid connectId: {packet.ConnectionTime} != our({_connectTime})");
+ return false;
+ }
+ //check connect num
+ ConnectionNum = packet.ConnectionNumber;
+ RemoteId = packet.PeerId;
+
+ NetDebug.Write(NetLogLevel.Trace, "[NC] Received connection accept");
+ Interlocked.Exchange(ref _timeSinceLastPacket, 0);
+ _connectionState = ConnectionState.Connected;
+ return true;
+ }
+
+ ///
+ /// Gets maximum size of packet that will be not fragmented.
+ ///
+ /// Type of packet that you want send
+ /// size in bytes
+ public int GetMaxSinglePacketSize(DeliveryMethod options) =>
+ _mtu - NetPacket.GetHeaderSize(options == DeliveryMethod.Unreliable ? PacketProperty.Unreliable : PacketProperty.Channeled);
+
+ ///
+ /// Send data to peer with delivery event called
+ ///
+ /// Data
+ /// Delivery method (reliable, unreliable, etc.)
+ /// User data that will be received in DeliveryEvent
+ ///
+ /// If you trying to send unreliable packet type
+ ///
+ public void SendWithDeliveryEvent(byte[] data, DeliveryMethod deliveryMethod, object userData)
+ {
+ if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered)
+ throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets");
+ SendInternal(new ReadOnlySpan(data, 0, data.Length), 0, deliveryMethod, userData);
+ }
+
+ ///
+ /// Send data to peer with delivery event called
+ ///
+ /// Data
+ /// Start of data
+ /// Length of data
+ /// Delivery method (reliable, unreliable, etc.)
+ /// User data that will be received in DeliveryEvent
+ ///
+ /// If you trying to send unreliable packet type
+ ///
+ public void SendWithDeliveryEvent(byte[] data, int start, int length, DeliveryMethod deliveryMethod, object userData)
+ {
+ if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered)
+ throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets");
+ SendInternal(new ReadOnlySpan(data, start, length), 0, deliveryMethod, userData);
+ }
+
+ ///
+ /// Send data to peer with delivery event called
+ ///
+ /// Data
+ /// Delivery method (reliable, unreliable, etc.)
+ /// User data that will be received in DeliveryEvent
+ ///
+ /// If you trying to send unreliable packet type
+ ///
+ public void SendWithDeliveryEvent(NetDataWriter dataWriter, 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.AsReadOnlySpan(), 0, deliveryMethod, userData);
+ }
+
+ ///
+ /// Send data to peer (channel - 0)
+ ///
+ /// Data
+ /// Send options (reliable, unreliable, etc.)
+ ///
+ /// If size exceeds maximum limit:
+ /// MTU - headerSize bytes for Unreliable
+ /// Fragment count exceeded ushort.MaxValue
+ ///
+ public void Send(byte[] data, DeliveryMethod deliveryMethod) =>
+ SendInternal(new ReadOnlySpan(data, 0, data.Length), 0, deliveryMethod, null);
+
+ ///
+ /// Send data to peer (channel - 0)
+ ///
+ /// DataWriter with data
+ /// Send options (reliable, unreliable, etc.)
+ ///
+ /// If size exceeds maximum limit:
+ /// MTU - headerSize bytes for Unreliable
+ /// Fragment count exceeded ushort.MaxValue
+ ///
+ public void Send(NetDataWriter dataWriter, DeliveryMethod deliveryMethod) =>
+ SendInternal(dataWriter.AsReadOnlySpan(), 0, deliveryMethod, null);
+
+ ///
+ /// Send data to peer (channel - 0)
+ ///
+ /// Data
+ /// Start of data
+ /// Length of data
+ /// Send options (reliable, unreliable, etc.)
+ ///
+ /// If size exceeds maximum limit:
+ /// MTU - headerSize bytes for Unreliable
+ /// Fragment count exceeded ushort.MaxValue
+ ///
+ public void Send(byte[] data, int start, int length, DeliveryMethod options) =>
+ SendInternal(new ReadOnlySpan(data, start, length), 0, options, null);
+
+ ///
+ /// Send data to peer with delivery event called
+ ///
+ /// Data
+ /// Delivery method (reliable, unreliable, etc.)
+ /// User data that will be received in DeliveryEvent
+ ///
+ /// If you trying to send unreliable packet type
+ ///
+ public void SendWithDeliveryEvent(ReadOnlySpan data, 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, deliveryMethod, userData);
+ }
+
+ ///
+ /// Send data to peer (channel - 0)
+ ///
+ /// Data
+ /// Send options (reliable, unreliable, etc.)
+ ///
+ /// If size exceeds maximum limit:
+ /// MTU - headerSize bytes for Unreliable
+ /// Fragment count exceeded ushort.MaxValue
+ ///
+ public void Send(ReadOnlySpan data, DeliveryMethod deliveryMethod) =>
+ SendInternal(data, 0, deliveryMethod, null);
+
+ protected void SendInternal(
+ ReadOnlySpan data,
+ byte channelNumber,
+ DeliveryMethod deliveryMethod,
+ object userData)
+ {
+ if (_connectionState != ConnectionState.Connected || channelNumber >= ChannelsCount)
+ return;
+
+ //Select channel
+ PacketProperty property;
+ BaseChannel channel = null;
+
+ if (deliveryMethod == DeliveryMethod.Unreliable)
+ {
+ property = PacketProperty.Unreliable;
+ }
+ else
+ {
+ property = PacketProperty.Channeled;
+ channel = CreateChannel((byte)(channelNumber * NetConstants.ChannelTypeCount + (byte)deliveryMethod));
+ }
+
+ //Prepare
+ NetDebug.Write("[RS]Packet: " + property);
+
+ //Check fragmentation
+ int headerSize = NetPacket.GetHeaderSize(property);
+ //Save mtu for multithread
+ int mtu = _mtu;
+ int length = data.Length;
+ if (length + headerSize > mtu)
+ {
+ //if cannot be fragmented
+ if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered)
+ throw new TooBigPacketException("Unreliable or ReliableSequenced packet size exceeded maximum of " + (mtu - headerSize) + " bytes, Check allowed size by GetMaxSinglePacketSize()");
+
+ int packetFullSize = mtu - headerSize;
+ int packetDataSize = packetFullSize - NetConstants.FragmentHeaderSize;
+ int totalPackets = length / packetDataSize + (length % packetDataSize == 0 ? 0 : 1);
+
+ if (totalPackets > NetManager.MaxFragmentsCount)
+ throw new TooBigPacketException("Data was split in " + totalPackets + " fragments, which exceeds " + NetManager.MaxFragmentsCount);
+
+ ushort currentFragmentId = (ushort)Interlocked.Increment(ref _fragmentId);
+
+ for (ushort partIdx = 0; partIdx < totalPackets; partIdx++)
+ {
+ int sendLength = length > packetDataSize ? packetDataSize : length;
+
+ NetPacket p = NetManager.PoolGetPacket(headerSize + sendLength + NetConstants.FragmentHeaderSize);
+ p.Property = property;
+ p.UserData = userData;
+ p.FragmentId = currentFragmentId;
+ p.FragmentPart = partIdx;
+ p.FragmentsTotal = (ushort)totalPackets;
+ p.MarkFragmented();
+
+ data.Slice(partIdx * packetDataSize, sendLength).CopyTo(new Span(p.RawData, NetConstants.FragmentedHeaderTotalSize, sendLength));
+ channel.AddToQueue(p);
+
+ length -= sendLength;
+ }
+ return;
+ }
+
+ //Else just send
+ NetPacket packet = NetManager.PoolGetPacket(headerSize + length);
+ packet.Property = property;
+ data.CopyTo(new Span(packet.RawData, headerSize, length));
+ packet.UserData = userData;
+
+ if (channel == null) //unreliable
+ {
+ lock (_unreliableChannelLock)
+ {
+ if (_unreliablePendingCount == _unreliableChannel.Length)
+ Array.Resize(ref _unreliableChannel, _unreliablePendingCount*2);
+ _unreliableChannel[_unreliablePendingCount++] = 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(ReadOnlySpan data) =>
+ NetManager.DisconnectPeer(this, data);
+
+ 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 virtual void AddToReliableChannelSendQueue(BaseChannel channel)
+ {
+
+ }
+
+ ///
+ /// Internally handles the shutdown process for this peer.
+ ///
+ /// Optional data to include in the unreliable disconnect packet.
+ ///
+ /// If , immediately sets state to without sending a notification.
+ /// If , sends unreliable disconnect packets until a timeout occurs and sets state to
+ /// Queued reliable packets are bypassed and dropped immediately.
+ ///
+ /// A indicating the state change transition.
+ internal ShutdownResult Shutdown(ReadOnlySpan data, 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, data.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.IsEmpty)
+ {
+ data.CopyTo(_shutdownPacket.RawData.AsSpan(9));
+ }
+ _connectionState = ConnectionState.ShutdownRequested;
+ NetDebug.Write("[Peer] Send disconnect");
+ NetManager.SendRaw(_shutdownPacket, this);
+ return result;
+ }
+ }
+
+ private void UpdateRoundTripTime(int roundTripTime)
+ {
+ _rtt += roundTripTime;
+ _rttCount++;
+ _avgRtt = _rtt/_rttCount;
+ _resendDelay = ResendFixedDelay + _avgRtt * ResendRttMultiplier;
+ }
+
+ internal void AddReliablePacket(DeliveryMethod method, NetPacket p)
+ {
+ if (p.IsFragmented)
+ {
+ if (p.FragmentsTotal == 0 || p.FragmentsTotal > NetManager.MaxFragmentsCount)
+ {
+ NetManager.PoolRecycle(p);
+ NetDebug.WriteError($"Invalid FragmentsTotal: {p.FragmentsTotal}");
+ return;
+ }
+
+ if (p.FragmentPart >= p.FragmentsTotal)
+ {
+ NetManager.PoolRecycle(p);
+ NetDebug.WriteError($"FragmentPart {p.FragmentPart} >= FragmentsTotal {p.FragmentsTotal}");
+ return;
+ }
+
+ NetDebug.Write($"Fragment. Id: {p.FragmentId}, Part: {p.FragmentPart}, Total: {p.FragmentsTotal}");
+
+ //Get needed array from dictionary
+ ushort packetFragId = p.FragmentId;
+ byte packetChannelId = p.ChannelId;
+ if (!_holdedFragments.TryGetValue(packetFragId, out var incomingFragments))
+ {
+ //Holded fragments limit reached
+ if (_holdedFragments.Count >= NetConstants.MaxFragmentsInWindow * ChannelsCount * NetConstants.FragmentedChannelsCount)
+ {
+ NetManager.PoolRecycle(p);
+ //NetDebug.WriteError($"Holded fragments limit reached ({_holdedFragments.Count}/{(NetConstants.DefaultWindowSize / 2) * ChannelsCount * NetConstants.FragmentedChannelsCount}). Dropping fragment id: {packetFragId}");
+ return;
+ }
+
+ incomingFragments = new IncomingFragments(p.FragmentsTotal, p.ChannelId);
+ _holdedFragments.Add(packetFragId, incomingFragments);
+ }
+ else if (p.FragmentsTotal != incomingFragments.TotalFragments || p.ChannelId != incomingFragments.ChannelId)
+ {
+ NetManager.PoolRecycle(p);
+ NetDebug.WriteError("Fragment metadata mismatch");
+ return;
+ }
+
+ //Cache
+ var fragments = incomingFragments.Fragments;
+
+ //Error check
+ if (fragments[p.FragmentPart] != null)
+ {
+ NetManager.PoolRecycle(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 != incomingFragments.TotalFragments)
+ return;
+
+ //Just simple packet
+ NetPacket resultingPacket = NetManager.PoolGetPacket(incomingFragments.TotalSize);
+
+ void AbortReassembly(string error)
+ {
+ _holdedFragments.Remove(packetFragId);
+ incomingFragments.RecycleAll(NetManager);
+ NetManager.PoolRecycle(resultingPacket);
+ NetDebug.WriteError(error);
+ }
+
+ int pos = 0;
+ for (ushort i = 0; i < incomingFragments.TotalFragments; i++)
+ {
+ NetPacket fragment = fragments[i];
+ if (fragment == null)
+ {
+ AbortReassembly($"Fragment {i} missing during reassembly");
+ return;
+ }
+
+ if (fragment.Size > fragment.RawData.Length)
+ {
+ AbortReassembly($"Fragment error size: {fragment.Size} > fragment.RawData.Length: {fragment.RawData.Length}");
+ return;
+ }
+
+ int writtenSize = fragment.Size - NetConstants.FragmentedHeaderTotalSize;
+ if (pos + writtenSize > resultingPacket.RawData.Length)
+ {
+ AbortReassembly($"Fragment error pos: {pos + writtenSize} >= resultPacketSize: {resultingPacket.RawData.Length}");
+ return;
+ }
+
+ //Create resulting big packet
+ Buffer.BlockCopy(
+ fragment.RawData,
+ NetConstants.FragmentedHeaderTotalSize,
+ resultingPacket.RawData,
+ pos,
+ writtenSize);
+
+ pos += writtenSize;
+
+ //Free memory
+ NetManager.PoolRecycle(fragment);
+ fragments[i] = null;
+ }
+
+ //Clear memory
+ _holdedFragments.Remove(packetFragId);
+
+ //Send to process
+ NetManager.CreateReceiveEvent(resultingPacket, method, (byte)(packetChannelId / NetConstants.ChannelTypeCount), 0, this);
+ }
+ else //Just simple packet
+ {
+ NetManager.CreateReceiveEvent(p, method, (byte)(p.ChannelId / NetConstants.ChannelTypeCount), 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 {receivedMtu}, EMTU {endMtuCheck}, PSIZE {packet.Size}");
+ return;
+ }
+
+ if (packet.Property == PacketProperty.MtuCheck)
+ {
+ _mtuCheckAttempts = 0;
+ NetDebug.Write("[MTU] check. send back: " + receivedMtu);
+ packet.Property = PacketProperty.MtuOk;
+ NetManager.SendRawAndRecycle(packet, this);
+ }
+ else if(receivedMtu > _mtu && !_finishMtu) //MtuOk
+ {
+ //invalid packet
+ if (receivedMtu != NetConstants.PossibleMtu[_mtuIdx + 1] - NetManager.ExtraPacketSizeForLayer)
+ return;
+
+ lock (_mtuMutex)
+ {
+ SetMtu(_mtuIdx+1);
+ }
+ //if maxed - finish.
+ if (_mtuIdx == NetConstants.PossibleMtu.Length - 1)
+ _finishMtu = true;
+ NetManager.PoolRecycle(packet);
+ NetDebug.Write("[MTU] ok. Increase to: " + _mtu);
+ }
+ }
+
+ private void UpdateMtuLogic(float 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] - NetManager.ExtraPacketSizeForLayer;
+ var p = NetManager.PoolGetPacket(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, this) <= 0)
+ _finishMtu = true;
+ }
+ }
+
+ ///
+ /// Evaluates incoming connection requests against the current peer state to supply Reconnect Protection.
+ ///
+ /// The incoming connection request packet.
+ /// A directing how the manager should handle the request.
+ ///
+ /// If the state is , the peer lingers to ignore older connection requests
+ /// (where the packet timestamp is smaller than internal ), ensuring older connections are ignored.
+ ///
+ 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
+ if (connRequest.ConnectionTime == _connectTime)
+ {
+ var localBytes = connRequest.TargetAddress;
+ for (int i = _cachedSocketAddr.Size-1; i >= 0; i--)
+ {
+ byte rb = _cachedSocketAddr[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, this);
+ }
+ //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;
+ }
+
+ internal virtual void ProcessChanneled(NetPacket packet)
+ {
+ if (packet.ChannelId >= NetConstants.ChannelTypeCount ||
+ packet.ChannelId == (int)DeliveryMethod.ReliableSequenced)
+ {
+ NetManager.PoolRecycle(packet);
+ return;
+ }
+
+ if (!CreateChannel(packet.ChannelId).ProcessPacket(packet))
+ NetManager.PoolRecycle(packet);
+ }
+
+ //Process incoming packet
+ internal void ProcessPacket(NetPacket packet)
+ {
+ //not initialized
+ if (_connectionState == ConnectionState.Outgoing || _connectionState == ConnectionState.Disconnected)
+ {
+ NetManager.PoolRecycle(packet);
+ return;
+ }
+ if (packet.Property == PacketProperty.ShutdownOk)
+ {
+ if (_connectionState == ConnectionState.ShutdownRequested)
+ _connectionState = ConnectionState.Disconnected;
+ NetManager.PoolRecycle(packet);
+ return;
+ }
+ if (packet.ConnectionNumber != _connectNum)
+ {
+ NetDebug.Write(NetLogLevel.Trace, "[RR]Old packet");
+ NetManager.PoolRecycle(packet);
+ return;
+ }
+ Interlocked.Exchange(ref _timeSinceLastPacket, 0);
+
+ NetDebug.Write($"[RR]PacketProperty: {packet.Property}");
+ switch (packet.Property)
+ {
+ case PacketProperty.Merged:
+ int pos = NetConstants.HeaderSize;
+ while (pos < packet.Size)
+ {
+ ushort size = BitConverter.ToUInt16(packet.RawData, pos);
+ if (size == 0)
+ break;
+
+ pos += 2;
+ if (packet.Size - pos < size)
+ break;
+
+ NetPacket mergedPacket = NetManager.PoolGetPacket(size);
+ Buffer.BlockCopy(packet.RawData, pos, mergedPacket.RawData, 0, size);
+ mergedPacket.Size = size;
+
+ if (!mergedPacket.Verify())
+ break;
+
+ pos += size;
+ ProcessPacket(mergedPacket);
+ }
+ NetManager.PoolRecycle(packet);
+ 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, this);
+ }
+ NetManager.PoolRecycle(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: {packet.Sequence} - {elapsedMs} - {_remoteDelta}");
+ }
+ NetManager.PoolRecycle(packet);
+ break;
+
+ case PacketProperty.Ack:
+ case PacketProperty.Channeled:
+ case PacketProperty.ReliableMerged:
+ ProcessChanneled(packet);
+ break;
+
+ //Simple packet without acks
+ case PacketProperty.Unreliable:
+ NetManager.CreateReceiveEvent(packet, DeliveryMethod.Unreliable, 0, NetConstants.HeaderSize, this);
+ return;
+
+ case PacketProperty.MtuCheck:
+ case PacketProperty.MtuOk:
+ ProcessMtuPacket(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, this);
+ }
+ else
+ {
+ //Send without length information and merging
+ bytesSent = NetManager.SendRaw(_mergeData.RawData, NetConstants.HeaderSize + 2, _mergePos - 2, this);
+ }
+
+ if (NetManager.EnableStatistics)
+ {
+ Statistics.IncrementPacketsSent();
+ Statistics.AddBytesSent(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, this);
+
+ if (NetManager.EnableStatistics)
+ {
+ Statistics.IncrementPacketsSent();
+ Statistics.AddBytesSent(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);
+ }
+
+ protected virtual void UpdateChannels()
+ {
+ _reliableChannel?.SendNextPackets();
+ _reliableUnorderedChannel?.SendNextPackets();
+ _sequencedChannel?.SendNextPackets();
+ }
+
+ internal void Update(float deltaTime)
+ {
+ Interlocked.Exchange(ref _timeSinceLastPacket, _timeSinceLastPacket + deltaTime);
+ switch (_connectionState)
+ {
+ case ConnectionState.Connected:
+ if (_timeSinceLastPacket > NetManager.DisconnectTimeout)
+ {
+ NetDebug.Write($"[UPDATE] Disconnect by timeout: {_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, this);
+ }
+ }
+ 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, this);
+ }
+ 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.Restart();
+ NetManager.SendRaw(_pingPacket, this);
+ }
+
+ //RTT - round trip time
+ _rttResetTimer += deltaTime;
+ if (_rttResetTimer >= NetManager.PingInterval * 3)
+ {
+ _rttResetTimer = 0;
+ _rtt = _avgRtt;
+ _rttCount = 1;
+ }
+
+ UpdateMtuLogic(deltaTime);
+
+ UpdateChannels();
+
+ if (_unreliablePendingCount > 0)
+ {
+ int unreliableCount;
+ lock (_unreliableChannelLock)
+ {
+ (_unreliableChannel, _unreliableSecondQueue) = (_unreliableSecondQueue, _unreliableChannel);
+ unreliableCount = _unreliablePendingCount;
+ _unreliablePendingCount = 0;
+ }
+ for (int i = 0; i < unreliableCount; i++)
+ {
+ var packet = _unreliableSecondQueue[i];
+ SendUserData(packet);
+ NetManager.PoolRecycle(packet);
+ }
+ }
+
+ SendMerged();
+ }
+
+ //For reliable channel
+ internal void RecycleAndDeliver(NetPacket packet)
+ {
+ if (packet.UserData is MergedPacketUserData mergedUserData)
+ {
+ for (int i = 0; i < mergedUserData.Items.Length; i++)
+ NetManager.MessageDelivered(this, mergedUserData.Items[i]);
+ packet.UserData = null;
+ }
+ else if (packet.UserData != null)
+ {
+ if (packet.IsFragmented)
+ {
+ _deliveredFragments.TryGetValue(packet.FragmentId, out ushort fragCount);
+ fragCount++;
+ if (fragCount == packet.FragmentsTotal)
+ {
+ NetManager.MessageDelivered(this, packet.UserData);
+ _deliveredFragments.Remove(packet.FragmentId);
+ }
+ else
+ {
+ _deliveredFragments[packet.FragmentId] = fragCount;
+ }
+ }
+ else
+ {
+ NetManager.MessageDelivered(this, packet.UserData);
+ }
+ packet.UserData = null;
+ }
+ NetManager.PoolRecycle(packet);
+ }
+ }
+}
diff --git a/LiteNetLib/NatPunchModule.cs b/LiteNetLib/NatPunchModule.cs
index 7d79ef8a..3259384b 100644
--- a/LiteNetLib/NatPunchModule.cs
+++ b/LiteNetLib/NatPunchModule.cs
@@ -1,27 +1,71 @@
using System.Collections.Concurrent;
+using System.Diagnostics.CodeAnalysis;
using System.Net;
+using System.Net.Sockets;
using LiteNetLib.Utils;
namespace LiteNetLib
{
+ ///
+ /// Specifies the type of network address discovered during NAT punchthrough.
+ ///
public enum NatAddressType
{
+ ///
+ /// Address within the local area network (LAN).
+ ///
Internal,
+ ///
+ /// Publicly accessible address on the wide area network (WAN).
+ ///
External
}
+ ///
+ /// Interface for handling events related to NAT punchthrough and introduction.
+ ///
public interface INatPunchListener
{
+ ///
+ /// Called when a NAT introduction request is received from the mediator server.
+ ///
+ /// The local endpoint of the client requesting connection.
+ /// The remote endpoint of the client requesting connection.
+ /// Custom data token associated with the request.
void OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token);
+
+ ///
+ /// Called when NAT punchthrough is successful and a direct connection can be established.
+ ///
+ /// The resolved endpoint of the remote peer.
+ /// The type of address (Internal or External) that succeeded.
+ /// Custom data token associated with the request.
void OnNatIntroductionSuccess(IPEndPoint targetEndPoint, NatAddressType type, string token);
}
+ ///
+ /// An implementation of that maps callbacks to events.
+ ///
public class EventBasedNatPunchListener : INatPunchListener
{
+ ///
+ /// Delegate for NAT introduction request events.
+ ///
public delegate void OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token);
+
+ ///
+ /// Delegate for NAT introduction success events.
+ ///
public delegate void OnNatIntroductionSuccess(IPEndPoint targetEndPoint, NatAddressType type, string token);
+ ///
+ /// Event triggered when a NAT introduction request is received.
+ ///
public event OnNatIntroductionRequest NatIntroductionRequest;
+
+ ///
+ /// Event triggered when NAT punchthrough is successfully completed.
+ ///
public event OnNatIntroductionSuccess NatIntroductionSuccess;
void INatPunchListener.OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token)
@@ -75,13 +119,16 @@ class NatPunchPacket
public bool IsExternal { [Preserve] get; [Preserve] set; }
}
- private readonly NetManager _socket;
+ private readonly LiteNetManager _socket;
private readonly ConcurrentQueue _requestEvents = new ConcurrentQueue();
private readonly ConcurrentQueue _successEvents = new ConcurrentQueue();
private readonly NetDataReader _cacheReader = new NetDataReader();
private readonly NetDataWriter _cacheWriter = new NetDataWriter();
private readonly NetPacketProcessor _netPacketProcessor = new NetPacketProcessor(MaxTokenLength);
private INatPunchListener _natPunchListener;
+ ///
+ /// Maximum allowed length for the NAT introduction token string.
+ ///
public const int MaxTokenLength = 256;
///
@@ -89,7 +136,7 @@ class NatPunchPacket
///
public bool UnsyncedEvents = false;
- internal NatPunchModule(NetManager socket)
+ internal NatPunchModule(LiteNetManager socket)
{
_socket = socket;
_netPacketProcessor.SubscribeReusable(OnNatIntroductionResponse);
@@ -106,12 +153,20 @@ internal void ProcessMessage(IPEndPoint senderEndPoint, NetPacket packet)
}
}
+ ///
+ /// Initializes the NAT punch module with a listener to handle punchthrough events.
+ ///
+ /// The listener implementation that will receive NAT events.
public void Init(INatPunchListener listener)
{
_natPunchListener = listener;
}
- private void Send(T packet, IPEndPoint target) where T : class, new()
+ private void Send<
+#if NET5_0_OR_GREATER
+ [DynamicallyAccessedMembers(Trimming.SerializerMemberTypes)]
+#endif
+ T>(T packet, IPEndPoint target) where T : class, new()
{
_cacheWriter.Reset();
_cacheWriter.Put((byte)PacketProperty.NatMessage);
@@ -119,6 +174,17 @@ public void Init(INatPunchListener listener)
_socket.SendRaw(_cacheWriter.Data, 0, _cacheWriter.Length, target);
}
+ ///
+ /// Sends NAT introduction packets to both the host and the client to facilitate punchthrough.
+ ///
+ ///
+ /// This is typically called by a mediator (e.g. a master server).
+ ///
+ /// Internal (LAN) endpoint of the host.
+ /// External (WAN) endpoint of the host.
+ /// Internal (LAN) endpoint of the client.
+ /// External (WAN) endpoint of the client.
+ /// Custom token or data to include in the introduction.
public void NatIntroduce(
IPEndPoint hostInternal,
IPEndPoint hostExternal,
@@ -142,6 +208,12 @@ public void NatIntroduce(
Send(req, hostExternal);
}
+ ///
+ /// Triggers queued NAT punchthrough events (Success or Request) on the provided .
+ ///
+ ///
+ /// This should be called from the main thread if is .
+ ///
public void PollEvents()
{
if (UnsyncedEvents)
@@ -164,16 +236,27 @@ public void PollEvents()
}
}
+ ///
+ /// Sends a request to the Master Server to introduce this peer to another peer.
+ ///
+ /// The hostname or IP of the Master Server.
+ /// The port of the Master Server.
+ /// Custom token to identify the connection or room.
public void SendNatIntroduceRequest(string host, int port, string additionalInfo)
{
SendNatIntroduceRequest(NetUtils.MakeEndPoint(host, port), additionalInfo);
}
+ ///
+ /// Sends a request to the Master Server to introduce this peer to another peer.
+ ///
+ /// The endpoint of the Master Server.
+ /// Custom token to identify the connection or room.
public void SendNatIntroduceRequest(IPEndPoint masterServerEndPoint, string additionalInfo)
{
//prepare outgoing data
string networkIp = NetUtils.GetLocalIp(LocalAddrType.IPv4);
- if (string.IsNullOrEmpty(networkIp))
+ if (string.IsNullOrEmpty(networkIp) || masterServerEndPoint.AddressFamily == AddressFamily.InterNetworkV6)
{
networkIp = NetUtils.GetLocalIp(LocalAddrType.IPv6);
}
diff --git a/LiteNetLib/NativeSocket.cs b/LiteNetLib/NativeSocket.cs
index fc846c8b..84ecedc4 100644
--- a/LiteNetLib/NativeSocket.cs
+++ b/LiteNetLib/NativeSocket.cs
@@ -7,111 +7,9 @@
namespace LiteNetLib
{
- internal readonly struct NativeAddr : IEquatable
- {
- //common parts
- private readonly long _part1; //family, port, etc
- private readonly long _part2;
- //ipv6 parts
- private readonly long _part3;
- private readonly int _part4;
-
- private readonly int _hash;
-
- public NativeAddr(byte[] address, int len)
- {
- _part1 = BitConverter.ToInt64(address, 0);
- _part2 = BitConverter.ToInt64(address, 8);
- if (len > 16)
- {
- _part3 = BitConverter.ToInt64(address, 16);
- _part4 = BitConverter.ToInt32(address, 24);
- }
- else
- {
- _part3 = 0;
- _part4 = 0;
- }
- _hash = (int)(_part1 >> 32) ^ (int)_part1 ^
- (int)(_part2 >> 32) ^ (int)_part2 ^
- (int)(_part3 >> 32) ^ (int)_part3 ^
- _part4;
- }
-
- public override int GetHashCode()
- {
- return _hash;
- }
-
- public bool Equals(NativeAddr other)
- {
- return _part1 == other._part1 &&
- _part2 == other._part2 &&
- _part3 == other._part3 &&
- _part4 == other._part4;
- }
-
- public override bool Equals(object obj)
- {
- return obj is NativeAddr other && Equals(other);
- }
-
- public static bool operator ==(NativeAddr left, NativeAddr right)
- {
- return left.Equals(right);
- }
-
- public static bool operator !=(NativeAddr left, NativeAddr right)
- {
- return !left.Equals(right);
- }
- }
-
- internal class NativeEndPoint : IPEndPoint
- {
- public readonly byte[] NativeAddress;
-
- public NativeEndPoint(byte[] address) : base(IPAddress.Any, 0)
- {
- NativeAddress = new byte[address.Length];
- Buffer.BlockCopy(address, 0, NativeAddress, 0, address.Length);
-
- short family = (short)((address[1] << 8) | address[0]);
- Port =(ushort)((address[2] << 8) | address[3]);
-
- if ((NativeSocket.UnixMode && family == NativeSocket.AF_INET6) || (!NativeSocket.UnixMode && (AddressFamily)family == AddressFamily.InterNetworkV6))
- {
- uint scope = unchecked((uint)(
- (address[27] << 24) +
- (address[26] << 16) +
- (address[25] << 8) +
- (address[24])));
-#if NETCOREAPP || NETSTANDARD2_1 || NETSTANDARD2_1_OR_GREATER
- Address = new IPAddress(new ReadOnlySpan(address, 8, 16), scope);
-#else
- byte[] addrBuffer = new byte[16];
- Buffer.BlockCopy(address, 8, addrBuffer, 0, 16);
- Address = new IPAddress(addrBuffer, scope);
-#endif
- }
- else //IPv4
- {
- long ipv4Addr = unchecked((uint)((address[4] & 0x000000FF) |
- (address[5] << 8 & 0x0000FF00) |
- (address[6] << 16 & 0x00FF0000) |
- (address[7] << 24)));
- Address = new IPAddress(ipv4Addr);
- }
- }
- }
-
internal static class NativeSocket
{
- static
-#if LITENETLIB_UNSAFE
- unsafe
-#endif
- class WinSock
+ static unsafe class WinSock
{
private const string LibName = "ws2_32.dll";
@@ -127,22 +25,14 @@ public static extern int recvfrom(
[DllImport(LibName, SetLastError = true)]
internal static extern int sendto(
IntPtr socketHandle,
-#if LITENETLIB_UNSAFE
byte* pinnedBuffer,
-#else
- [In] byte[] pinnedBuffer,
-#endif
[In] int len,
[In] SocketFlags socketFlags,
[In] byte[] socketAddress,
[In] int socketAddressSize);
}
- static
-#if LITENETLIB_UNSAFE
- unsafe
-#endif
- class UnixSock
+ static unsafe class UnixSock
{
private const string LibName = "libc";
@@ -158,23 +48,37 @@ public static extern int recvfrom(
[DllImport(LibName, SetLastError = true)]
internal static extern int sendto(
IntPtr socketHandle,
-#if LITENETLIB_UNSAFE
byte* pinnedBuffer,
-#else
- [In] byte[] pinnedBuffer,
-#endif
[In] int len,
[In] SocketFlags socketFlags,
[In] byte[] socketAddress,
[In] int socketAddressSize);
}
+ ///
+ /// Indicates whether the native socket optimizations are supported on the current platform.
+ ///
public static readonly bool IsSupported = false;
+ ///
+ /// Indicates whether the current environment requires Unix-style native socket calls.
+ ///
public static readonly bool UnixMode = false;
+ ///
+ /// The size of the native sockaddr_in structure for IPv4.
+ ///
public const int IPv4AddrSize = 16;
+ ///
+ /// The size of the native sockaddr_in6 structure for IPv6.
+ ///
public const int IPv6AddrSize = 28;
+ ///
+ /// Native Address Family constant for IPv4 (AF_INET).
+ ///
public const int AF_INET = 2;
+ ///
+ /// Native Address Family constant for IPv6 (AF_INET6).
+ ///
public const int AF_INET6 = 10;
private static readonly Dictionary NativeErrorToSocketError = new Dictionary
@@ -236,40 +140,50 @@ static NativeSocket()
}
}
+ ///
+ /// Receives a datagram from the specified socket handle using native OS calls.
+ ///
+ /// The OS handle for the socket.
+ /// A pinned byte array to receive the data.
+ /// The number of s to receive.
+ /// A pinned byte array to store the source address (sockaddr).
+ /// The size of the structure.
+ /// The number of s received, or a negative value on error.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int RecvFrom(
IntPtr socketHandle,
byte[] pinnedBuffer,
int len,
byte[] socketAddress,
- ref int socketAddressSize)
- {
- return UnixMode
+ ref int socketAddressSize) =>
+ UnixMode
? UnixSock.recvfrom(socketHandle, pinnedBuffer, len, 0, socketAddress, ref socketAddressSize)
: WinSock.recvfrom(socketHandle, pinnedBuffer, len, 0, socketAddress, ref socketAddressSize);
- }
+ ///
+ /// Sends a datagram to the specified socket handle using native OS calls.
+ ///
+ /// The OS handle for the socket.
+ /// A pointer to the pinned memory containing data to send.
+ /// The number of s to send.
+ /// A pinned byte array containing the destination address (sockaddr).
+ /// The size of the structure.
+ /// The number of s sent, or a negative value on error.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public
-#if LITENETLIB_UNSAFE
- unsafe
-#endif
- static int SendTo(
+ public static unsafe int SendTo(
IntPtr socketHandle,
-#if LITENETLIB_UNSAFE
byte* pinnedBuffer,
-#else
- byte[] pinnedBuffer,
-#endif
int len,
byte[] socketAddress,
- int socketAddressSize)
- {
- return UnixMode
+ int socketAddressSize) =>
+ UnixMode
? UnixSock.sendto(socketHandle, pinnedBuffer, len, 0, socketAddress, socketAddressSize)
: WinSock.sendto(socketHandle, pinnedBuffer, len, 0, socketAddress, socketAddressSize);
- }
+ ///
+ /// Retrieves the last OS-specific socket error and translates it to .
+ ///
+ /// The translated .
public static SocketError GetSocketError()
{
int error = Marshal.GetLastWin32Error();
@@ -280,6 +194,10 @@ public static SocketError GetSocketError()
return (SocketError)error;
}
+ ///
+ /// Retrieves the last OS-specific socket error and encapsulates it in a .
+ ///
+ /// A representing the last native error.
public static SocketException GetSocketException()
{
int error = Marshal.GetLastWin32Error();
@@ -290,12 +208,15 @@ public static SocketException GetSocketException()
return new SocketException(error);
}
+ ///
+ /// Converts the of an endpoint to the corresponding native constant.
+ ///
+ /// The endpoint to evaluate.
+ /// The native address family identifier.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static short GetNativeAddressFamily(IPEndPoint remoteEndPoint)
- {
- return UnixMode
+ public static short GetNativeAddressFamily(IPEndPoint remoteEndPoint) =>
+ UnixMode
? (short)(remoteEndPoint.AddressFamily == AddressFamily.InterNetwork ? AF_INET : AF_INET6)
: (short)remoteEndPoint.AddressFamily;
- }
}
}
diff --git a/LiteNetLib/NetConstants.cs b/LiteNetLib/NetConstants.cs
index ca7dfbcd..947cc865 100644
--- a/LiteNetLib/NetConstants.cs
+++ b/LiteNetLib/NetConstants.cs
@@ -37,26 +37,58 @@ public enum DeliveryMethod : byte
///
public static class NetConstants
{
- //can be tuned
+ ///
+ /// Default window size for reliable channels (number of packets).
+ ///
public const int DefaultWindowSize = 64;
- public const int SocketBufferSize = 1024 * 1024; //1mb
+ ///
+ /// Size of the underlying UDP socket receive and send buffers in bytes.
+ /// Default is 1MB.
+ ///
+ public const int SocketBufferSize = 1024 * 1024;
+ ///
+ /// Time To Live (TTL) for the UDP packets.
+ ///
public const int SocketTTL = 255;
+ ///
+ /// Size of the base packet header (PacketProperty) in s.
+ ///
public const int HeaderSize = 1;
+ ///
+ /// Size of the header for sequenced or reliable messages in s.
+ /// Includes , Sequence, and ChannelId.
+ ///
public const int ChanneledHeaderSize = 4;
+ ///
+ /// Additional header size required for fragmented packets in s.
+ /// Includes FragmentId, FragmentPart, and FragmentsTotal.
+ ///
public const int FragmentHeaderSize = 6;
+ ///
+ /// Total header size for a fragmented channeled packet in s.
+ /// Combines and .
+ ///
public const int FragmentedHeaderTotalSize = ChanneledHeaderSize + FragmentHeaderSize;
+ ///
+ /// Maximum possible sequence number before wrapping back to zero.
+ ///
public const ushort MaxSequence = 32768;
+ ///
+ /// Half of the , used for sequence comparison and wrap-around logic.
+ ///
public const ushort HalfMaxSequence = MaxSequence / 2;
//protocol
internal const int ProtocolId = 13;
internal const int MaxUdpHeaderSize = 68;
internal const int ChannelTypeCount = 4;
+ internal const int FragmentedChannelsCount = 2;
+ internal const int MaxFragmentsInWindow = DefaultWindowSize / 2;
internal static readonly int[] PossibleMtu =
{
- 576 - MaxUdpHeaderSize, //minimal (RFC 1191)
+ //576 - MaxUdpHeaderSize minimal (RFC 1191)
1024, //most games standard
1232 - MaxUdpHeaderSize,
1460 - MaxUdpHeaderSize, //google cloud
@@ -65,11 +97,28 @@ public static class NetConstants
1500 - MaxUdpHeaderSize //Ethernet II (RFC 1191)
};
- //Max possible single packet size
+ ///
+ /// The starting Maximum Transmission Unit (MTU) used for new connections before path MTU discovery.
+ ///
+ public static readonly int InitialMtu = PossibleMtu[0];
+ ///
+ /// Maximum possible packet size allowed by the library based on the largest supported MTU.
+ ///
public static readonly int MaxPacketSize = PossibleMtu[PossibleMtu.Length - 1];
+ ///
+ /// Maximum payload size for a single unreliable packet in s.
+ /// Calculated as - .
+ ///
public static readonly int MaxUnreliableDataSize = MaxPacketSize - HeaderSize;
- //peer specific
+ ///
+ /// Maximum possible value for .
+ ///
+ ///
+ /// This value is used to distinguish between different connection instances from the same .
+ /// It allows the receiver to identify and discard packets belonging to previous connection attempts that may arrive
+ /// late due to network jitter, even if they originate from the same address and port.
+ ///
public const byte MaxConnectionNumber = 4;
}
}
diff --git a/LiteNetLib/NetEvent.cs b/LiteNetLib/NetEvent.cs
new file mode 100644
index 00000000..7b701e69
--- /dev/null
+++ b/LiteNetLib/NetEvent.cs
@@ -0,0 +1,107 @@
+using System.Net;
+using System.Net.Sockets;
+
+namespace LiteNetLib
+{
+ ///
+ /// Internally used event type
+ ///
+ public sealed class NetEvent
+ {
+ ///
+ /// Reference to the next event in the pool or event queue.
+ ///
+ public NetEvent Next;
+
+ ///
+ /// Specifies the category of the network event.
+ ///
+ public enum EType
+ {
+ /// New peer connected.
+ Connect,
+ /// Peer disconnected.
+ Disconnect,
+ /// Data received from a connected peer.
+ Receive,
+ /// Unconnected message received.
+ ReceiveUnconnected,
+ /// Socket or internal protocol error occurred.
+ Error,
+ /// Round-trip time (RTT) for a peer has been updated.
+ ConnectionLatencyUpdated,
+ /// Broadcast message received.
+ Broadcast,
+ /// Incoming connection request from a new peer.
+ ConnectionRequest,
+ /// Reliable message was successfully delivered to the remote peer.
+ MessageDelivered,
+ /// The IP address or port of an existing peer has changed (e.g., roaming).
+ PeerAddressChanged
+ }
+
+ ///
+ /// The type of network event that occurred.
+ ///
+ public EType Type;
+
+ ///
+ /// The peer associated with this event. for unconnected events.
+ ///
+ public LiteNetPeer Peer;
+
+ ///
+ /// The remote endpoint (IP and Port) from which the event originated.
+ ///
+ public IPEndPoint RemoteEndPoint;
+
+ ///
+ /// Optional user data associated with a connection request or disconnect.
+ ///
+ public object UserData;
+
+ ///
+ /// The updated latency value in milliseconds. Only valid when is .
+ ///
+ public int Latency;
+
+ ///
+ /// The specific socket error. Only valid when is .
+ ///
+ public SocketError ErrorCode;
+
+ ///
+ /// The reason for a peer's disconnection. Only valid when is .
+ ///
+ public DisconnectReason DisconnectReason;
+
+ ///
+ /// Information about an incoming connection. Only valid when is .
+ ///
+ public LiteConnectionRequest ConnectionRequest;
+
+ ///
+ /// The delivery method used for the received packet. Only valid when is .
+ ///
+ public DeliveryMethod DeliveryMethod;
+
+ ///
+ /// The channel on which the packet was received.
+ ///
+ public byte ChannelNumber;
+
+ ///
+ /// A reader for accessing the payload of received data, broadcast, or unconnected messages.
+ ///
+ public readonly NetPacketReader DataReader;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The that owns the packet pool and buffers for this event.
+ public NetEvent(LiteNetManager manager)
+ {
+ DataReader = new NetPacketReader(manager, this);
+ }
+ }
+}
diff --git a/LiteNetLib/NetManager.cs b/LiteNetLib/NetManager.cs
index acd54f63..fc02a659 100644
--- a/LiteNetLib/NetManager.cs
+++ b/LiteNetLib/NetManager.cs
@@ -1,331 +1,20 @@
using System;
-using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Net;
-using System.Net.Sockets;
-using System.Threading;
using LiteNetLib.Layers;
using LiteNetLib.Utils;
namespace LiteNetLib
{
- public sealed class NetPacketReader : NetDataReader
- {
- private NetPacket _packet;
- private readonly NetManager _manager;
- private readonly NetEvent _evt;
-
- internal NetPacketReader(NetManager manager, NetEvent evt)
- {
- _manager = manager;
- _evt = evt;
- }
-
- internal void SetSource(NetPacket packet, int headerSize)
- {
- if (packet == null)
- return;
- _packet = packet;
- SetSource(packet.RawData, headerSize, packet.Size);
- }
-
- internal void RecycleInternal()
- {
- Clear();
- if (_packet != null)
- _manager.PoolRecycle(_packet);
- _packet = null;
- _manager.RecycleEvent(_evt);
- }
-
- public void Recycle()
- {
- if (_manager.AutoRecycle)
- return;
- RecycleInternal();
- }
- }
-
- internal sealed class NetEvent
- {
- public NetEvent Next;
-
- public enum EType
- {
- Connect,
- Disconnect,
- Receive,
- ReceiveUnconnected,
- Error,
- ConnectionLatencyUpdated,
- Broadcast,
- ConnectionRequest,
- MessageDelivered,
- PeerAddressChanged
- }
- public EType Type;
-
- public NetPeer Peer;
- public IPEndPoint RemoteEndPoint;
- public object UserData;
- public int Latency;
- public SocketError ErrorCode;
- public DisconnectReason DisconnectReason;
- public ConnectionRequest ConnectionRequest;
- public DeliveryMethod DeliveryMethod;
- public byte ChannelNumber;
- public readonly NetPacketReader DataReader;
-
- public NetEvent(NetManager manager)
- {
- DataReader = new NetPacketReader(manager, this);
- }
- }
-
///
- /// Main class for all network operations. Can be used as client and/or server.
+ /// More feature rich network manager with adjustable channels count
///
- public partial class NetManager : IEnumerable
+ public class NetManager : LiteNetManager, IEnumerable
{
- private class IPEndPointComparer : IEqualityComparer
- {
- public bool Equals(IPEndPoint x, IPEndPoint y)
- {
- return x.Address.Equals(y.Address) && x.Port == y.Port;
- }
-
- public int GetHashCode(IPEndPoint obj)
- {
- return obj.GetHashCode();
- }
- }
-
- public struct NetPeerEnumerator : IEnumerator
- {
- private readonly NetPeer _initialPeer;
- private NetPeer _p;
-
- public NetPeerEnumerator(NetPeer p)
- {
- _initialPeer = p;
- _p = null;
- }
-
- public void Dispose()
- {
-
- }
-
- public bool MoveNext()
- {
- _p = _p == null ? _initialPeer : _p.NextPeer;
- return _p != null;
- }
-
- public void Reset()
- {
- throw new NotSupportedException();
- }
-
- public NetPeer Current => _p;
- object IEnumerator.Current => _p;
- }
-
-#if DEBUG
- private struct IncomingData
- {
- public NetPacket Data;
- public IPEndPoint EndPoint;
- public DateTime TimeWhenGet;
- }
- private readonly List _pingSimulationList = new List();
- private readonly Random _randomGenerator = new Random();
- private const int MinLatencyThreshold = 5;
-#endif
-
- private Thread _logicThread;
- private bool _manualMode;
- private readonly AutoResetEvent _updateTriggerEvent = new AutoResetEvent(true);
-
- private NetEvent _pendingEventHead;
- private NetEvent _pendingEventTail;
-
- private NetEvent _netEventPoolHead;
private readonly INetEventListener _netEventListener;
- private readonly IDeliveryEventListener _deliveryEventListener;
- private readonly INtpEventListener _ntpEventListener;
- private readonly IPeerAddressChangedListener _peerAddressChangedListener;
-
- private readonly Dictionary _peersDict = new Dictionary(new IPEndPointComparer());
- private readonly Dictionary _requestsDict = new Dictionary(new IPEndPointComparer());
- private readonly Dictionary _ntpRequests = new Dictionary(new IPEndPointComparer());
- private readonly ReaderWriterLockSlim _peersLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
- private volatile NetPeer _headPeer;
- private int _connectedPeersCount;
- private readonly List _connectedPeerListCache = new List();
- private NetPeer[] _peersArray = new NetPeer[32];
- private readonly PacketLayerBase _extraPacketLayer;
- private int _lastPeerId;
- private ConcurrentQueue _peerIds = new ConcurrentQueue();
private byte _channelsCount = 1;
- private readonly object _eventLock = new object();
-
- //config section
- ///
- /// Enable messages receiving without connection. (with SendUnconnectedMessage method)
- ///
- public bool UnconnectedMessagesEnabled = false;
-
- ///
- /// Enable nat punch messages
- ///
- public bool NatPunchEnabled = false;
-
- ///
- /// Library logic update and send period in milliseconds
- /// Lowest values in Windows doesn't change much because of Thread.Sleep precision
- /// To more frequent sends (or sends tied to your game logic) use
- ///
- public int UpdateTime = 15;
-
- ///
- /// Interval for latency detection and checking connection (in milliseconds)
- ///
- public int PingInterval = 1000;
-
- ///
- /// If NetManager doesn't receive any packet from remote peer during this time (in milliseconds) then connection will be closed
- /// (including library internal keepalive packets)
- ///
- public int DisconnectTimeout = 5000;
-
- ///
- /// Simulate packet loss by dropping random amount of packets. (Works only in DEBUG mode)
- ///
- public bool SimulatePacketLoss = false;
-
- ///
- /// Simulate latency by holding packets for random time. (Works only in DEBUG mode)
- ///
- public bool SimulateLatency = false;
-
- ///
- /// Chance of packet loss when simulation enabled. value in percents (1 - 100).
- ///
- public int SimulationPacketLossChance = 10;
-
- ///
- /// Minimum simulated latency (in milliseconds)
- ///
- public int SimulationMinLatency = 30;
-
- ///
- /// Maximum simulated latency (in milliseconds)
- ///
- public int SimulationMaxLatency = 100;
-
- ///
- /// Events automatically will be called without PollEvents method from another thread
- ///
- public bool UnsyncedEvents = false;
-
- ///
- /// If true - receive event will be called from "receive" thread immediately otherwise on PollEvents call
- ///
- public bool UnsyncedReceiveEvent = false;
-
- ///
- /// If true - delivery event will be called from "receive" thread immediately otherwise on PollEvents call
- ///
- public bool UnsyncedDeliveryEvent = false;
-
- ///
- /// Allows receive broadcast packets
- ///
- public bool BroadcastReceiveEnabled = false;
-
- ///
- /// Delay between initial connection attempts (in milliseconds)
- ///
- public int ReconnectDelay = 500;
-
- ///
- /// Maximum connection attempts before client stops and call disconnect event.
- ///
- public int MaxConnectAttempts = 10;
-
- ///
- /// Enables socket option "ReuseAddress" for specific purposes
- ///
- public bool ReuseAddress = false;
-
- ///
- /// Statistics of all connections
- ///
- public readonly NetStatistics Statistics = new NetStatistics();
-
- ///
- /// Toggles the collection of network statistics for the instance and all known peers
- ///
- public bool EnableStatistics = false;
-
- ///
- /// NatPunchModule for NAT hole punching operations
- ///
- public readonly NatPunchModule NatPunchModule;
-
- ///
- /// Returns true if socket listening and update thread is running
- ///
- public bool IsRunning { get; private set; }
-
- ///
- /// Local EndPoint (host and port)
- ///
- public int LocalPort { get; private set; }
-
- ///
- /// Automatically recycle NetPacketReader after OnReceive event
- ///
- public bool AutoRecycle;
-
- ///
- /// IPv6 support
- ///
- public bool IPv6Enabled = true;
-
- ///
- /// Override MTU for all new peers registered in this NetManager, will ignores MTU Discovery!
- ///
- public int MtuOverride = 0;
-
- ///
- /// Sets initial MTU to lowest possible value according to RFC1191 (576 bytes)
- ///
- public bool UseSafeMtu = false;
-
- ///
- /// First peer. Useful for Client mode
- ///
- public NetPeer FirstPeer => _headPeer;
-
- ///
- /// Experimental feature mostly for servers. Only for Windows/Linux
- /// use direct socket calls for send/receive to drastically increase speed and reduce GC pressure
- ///
- public bool UseNativeSockets = false;
-
- ///
- /// Disconnect peers if HostUnreachable or NetworkUnreachable spawned (old behaviour 0.9.x was true)
- ///
- public bool DisconnectOnUnreachable = false;
-
- ///
- /// Allows peer change it's ip (lte to wifi, wifi to lte, etc). Use only on server
- ///
- public bool AllowPeerAddressChange = false;
+ private readonly ConcurrentDictionary _ntpRequests = new ConcurrentDictionary();
///
/// QoS channel count per message type (value must be between 1 and 64 channels)
@@ -342,232 +31,124 @@ public byte ChannelsCount
}
///
- /// Returns connected peers list (with internal cached list)
+ /// First peer. Useful for Client mode
///
- public List ConnectedPeerList
- {
- get
- {
- GetPeersNonAlloc(_connectedPeerListCache, ConnectionState.Connected);
- return _connectedPeerListCache;
- }
- }
+ public new NetPeer FirstPeer => (NetPeer)_headPeer;
///
- /// Gets peer by peer id
+ /// Get copy of peers (without allocations)
///
- /// id of peer
- /// Peer if peer with id exist, otherwise null
- public NetPeer GetPeerById(int id)
+ /// List that will contain result
+ /// State of peers
+ public void GetPeers(List peers, ConnectionState peerState)
{
- if (id >= 0 && id < _peersArray.Length)
+ peers.Clear();
+ _peersLock.EnterReadLock();
+ for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
{
- return _peersArray[id];
+ if ((netPeer.ConnectionState & peerState) != 0)
+ peers.Add((NetPeer)netPeer);
}
-
- return null;
+ _peersLock.ExitReadLock();
}
///
- /// Gets peer by peer id
+ /// Get copy of connected peers (without allocations)
///
- /// id of peer
- /// resulting peer
- /// True if peer with id exist, otherwise false
- public bool TryGetPeerById(int id, out NetPeer peer)
- {
- peer = GetPeerById(id);
+ /// List that will contain result
+ public void GetConnectedPeers(List peers) =>
+ GetPeers(peers, ConnectionState.Connected);
- return peer != null;
- }
+ public NetManager(INetEventListener listener, PacketLayerBase extraPacketLayer = null) : base(null, extraPacketLayer) =>
+ _netEventListener = listener;
///
- /// Returns connected peers count
+ /// Create the requests for NTP server
///
- public int ConnectedPeersCount => Interlocked.CompareExchange(ref _connectedPeersCount,0,0);
-
- public int ExtraPacketSizeForLayer => _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;
- RegisterEndPoint(peer.EndPoint);
- _peersLock.ExitWriteLock();
- }
-
- private void RemovePeer(NetPeer peer)
- {
- _peersLock.EnterWriteLock();
- RemovePeerInternal(peer);
- _peersLock.ExitWriteLock();
- }
+ /// NTP Server address.
+ public void CreateNtpRequest(IPEndPoint endPoint) =>
+ _ntpRequests.TryAdd(endPoint, new NtpRequest(endPoint));
- private void RemovePeerInternal(NetPeer peer)
+ ///
+ /// Create the requests for NTP server
+ ///
+ /// NTP Server address.
+ /// port
+ public void CreateNtpRequest(string ntpServerAddress, int port)
{
- 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;
- _peerIds.Enqueue(peer.Id);
- UnregisterEndPoint(peer.EndPoint);
+ var endPoint = NetUtils.MakeEndPoint(ntpServerAddress, port);
+ _ntpRequests.TryAdd(endPoint, new NtpRequest(endPoint));
}
///
- /// NetManager constructor
+ /// Create the requests for NTP server (default port)
///
- /// Network events listener (also can implement IDeliveryEventListener)
- /// Extra processing of packages, like CRC checksum or encryption. All connected NetManagers must have same layer.
- public NetManager(INetEventListener listener, PacketLayerBase extraPacketLayer = null)
+ /// NTP Server address.
+ public void CreateNtpRequest(string ntpServerAddress)
{
- _netEventListener = listener;
- _deliveryEventListener = listener as IDeliveryEventListener;
- _ntpEventListener = listener as INtpEventListener;
- _peerAddressChangedListener = listener as IPeerAddressChangedListener;
- NatPunchModule = new NatPunchModule(this);
- _extraPacketLayer = extraPacketLayer;
+ var endPoint = NetUtils.MakeEndPoint(ntpServerAddress, NtpRequest.DefaultPort);
+ _ntpRequests.TryAdd(endPoint, new NtpRequest(endPoint));
}
- internal void ConnectionLatencyUpdated(NetPeer fromPeer, int latency)
+ internal override bool CustomMessageHandle(NetPacket packet, IPEndPoint remoteEndPoint)
{
- CreateEvent(NetEvent.EType.ConnectionLatencyUpdated, fromPeer, latency: latency);
- }
+ if (_ntpRequests.Count > 0 && _ntpRequests.TryGetValue(remoteEndPoint, out _))
+ {
+ if (packet.Size < 48)
+ {
+ NetDebug.Write(NetLogLevel.Trace, $"NTP response too short: {packet.Size}");
+ return true;
+ }
- internal void MessageDelivered(NetPeer fromPeer, object userData)
- {
- if(_deliveryEventListener != null)
- CreateEvent(NetEvent.EType.MessageDelivered, fromPeer, userData: userData);
- }
+ byte[] copiedData = new byte[packet.Size];
+ Buffer.BlockCopy(packet.RawData, 0, copiedData, 0, packet.Size);
+ NtpPacket ntpPacket = NtpPacket.FromServerResponse(copiedData, DateTime.UtcNow);
+ try
+ {
+ ntpPacket.ValidateReply();
+ }
+ catch (InvalidOperationException ex)
+ {
+ NetDebug.Write(NetLogLevel.Trace, $"NTP response error: {ex.Message}");
+ ntpPacket = null;
+ }
- internal void DisconnectPeerForce(NetPeer peer,
- DisconnectReason reason,
- SocketError socketErrorCode,
- NetPacket eventData)
- {
- DisconnectPeer(peer, reason, socketErrorCode, true, null, 0, 0, eventData);
- }
+ if (ntpPacket != null)
+ {
+ _ntpRequests.TryRemove(remoteEndPoint, out _);
+ _netEventListener.OnNtpResponse(ntpPacket);
+ }
+ return true;
+ }
- 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);
+ return false;
}
- private void CreateEvent(
- NetEvent.EType type,
- NetPeer peer = null,
- IPEndPoint remoteEndPoint = null,
- SocketError errorCode = 0,
- int latency = 0,
- DisconnectReason disconnectReason = DisconnectReason.ConnectionFailed,
- ConnectionRequest connectionRequest = null,
- DeliveryMethod deliveryMethod = DeliveryMethod.Unreliable,
- byte channelNumber = 0,
- NetPacket readerSource = null,
- object userData = null)
- {
- NetEvent evt;
- bool unsyncEvent = UnsyncedEvents;
-
- if (type == NetEvent.EType.Connect)
- Interlocked.Increment(ref _connectedPeersCount);
- else if (type == NetEvent.EType.MessageDelivered)
- unsyncEvent = UnsyncedDeliveryEvent;
+ //connect to
+ protected override LiteNetPeer CreateOutgoingPeer(IPEndPoint remoteEndPoint, int id, byte connectNum, ReadOnlySpan connectData) =>
+ new NetPeer(this, remoteEndPoint, id, connectNum, connectData);
- lock(_eventLock)
- {
- evt = _netEventPoolHead;
- if (evt == null)
- evt = new NetEvent(this);
- else
- _netEventPoolHead = evt.Next;
- }
+ //accept
+ protected override LiteNetPeer CreateIncomingPeer(LiteConnectionRequest request, int id) =>
+ new NetPeer(this, request, id);
- evt.Next = null;
- evt.Type = type;
- evt.DataReader.SetSource(readerSource, readerSource?.GetHeaderSize() ?? 0);
- evt.Peer = peer;
- evt.RemoteEndPoint = remoteEndPoint;
- evt.Latency = latency;
- evt.ErrorCode = errorCode;
- evt.DisconnectReason = disconnectReason;
- evt.ConnectionRequest = connectionRequest;
- evt.DeliveryMethod = deliveryMethod;
- evt.ChannelNumber = channelNumber;
- evt.UserData = userData;
+ //reject
+ protected override LiteNetPeer CreateRejectPeer(IPEndPoint remoteEndPoint, int id) =>
+ new NetPeer(this, remoteEndPoint, id);
- if (unsyncEvent || _manualMode)
- {
- ProcessEvent(evt);
- }
- else
- {
- lock (_eventLock)
- {
- if (_pendingEventTail == null)
- _pendingEventHead = evt;
- else
- _pendingEventTail.Next = evt;
- _pendingEventTail = evt;
- }
- }
- }
+ //connection request when you use Accept for getting NetPeer instead of LiteNetPeer
+ protected override LiteConnectionRequest CreateConnectionRequest(IPEndPoint remoteEndPoint, NetConnectRequestPacket requestPacket) =>
+ new ConnectionRequest(remoteEndPoint, requestPacket, this);
- private void ProcessEvent(NetEvent evt)
+ protected override void ProcessEvent(NetEvent evt)
{
NetDebug.Write("[NM] Processing event: " + evt.Type);
bool emptyData = evt.DataReader.IsNull;
+ var netPeer = evt.Peer as NetPeer;
switch (evt.Type)
{
case NetEvent.EType.Connect:
- _netEventListener.OnPeerConnected(evt.Peer);
+ _netEventListener.OnPeerConnected(netPeer);
break;
case NetEvent.EType.Disconnect:
var info = new DisconnectInfo
@@ -576,10 +157,10 @@ private void ProcessEvent(NetEvent evt)
AdditionalData = evt.DataReader,
SocketErrorCode = evt.ErrorCode
};
- _netEventListener.OnPeerDisconnected(evt.Peer, info);
+ _netEventListener.OnPeerDisconnected(netPeer, info);
break;
case NetEvent.EType.Receive:
- _netEventListener.OnNetworkReceive(evt.Peer, evt.DataReader, evt.ChannelNumber, evt.DeliveryMethod);
+ _netEventListener.OnNetworkReceive(netPeer, evt.DataReader, evt.ChannelNumber, evt.DeliveryMethod);
break;
case NetEvent.EType.ReceiveUnconnected:
_netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.BasicMessage);
@@ -591,29 +172,29 @@ private void ProcessEvent(NetEvent evt)
_netEventListener.OnNetworkError(evt.RemoteEndPoint, evt.ErrorCode);
break;
case NetEvent.EType.ConnectionLatencyUpdated:
- _netEventListener.OnNetworkLatencyUpdate(evt.Peer, evt.Latency);
+ _netEventListener.OnNetworkLatencyUpdate(netPeer, evt.Latency);
break;
case NetEvent.EType.ConnectionRequest:
- _netEventListener.OnConnectionRequest(evt.ConnectionRequest);
+ _netEventListener.OnConnectionRequest((ConnectionRequest)evt.ConnectionRequest);
break;
case NetEvent.EType.MessageDelivered:
- _deliveryEventListener.OnMessageDelivered(evt.Peer, evt.UserData);
+ _netEventListener.OnMessageDelivered(netPeer, evt.UserData);
break;
case NetEvent.EType.PeerAddressChanged:
_peersLock.EnterUpgradeableReadLock();
IPEndPoint previousAddress = null;
- if (_peersDict.ContainsKey(evt.Peer.EndPoint))
+ if (ContainsPeer(evt.Peer))
{
_peersLock.EnterWriteLock();
- _peersDict.Remove(evt.Peer.EndPoint);
- previousAddress = evt.Peer.EndPoint;
+ RemovePeerFromSet(evt.Peer);
+ previousAddress = new IPEndPoint(evt.Peer.Address, evt.Peer.Port);
evt.Peer.FinishEndPointChange(evt.RemoteEndPoint);
- _peersDict.Add(evt.Peer.EndPoint, evt.Peer);
+ AddPeerToSet(evt.Peer);
_peersLock.ExitWriteLock();
}
_peersLock.ExitUpgradeableReadLock();
- if(previousAddress != null && _peerAddressChangedListener != null)
- _peerAddressChangedListener.OnPeerAddressChanged(evt.Peer, previousAddress);
+ if (previousAddress != null)
+ _netEventListener.OnPeerAddressChanged(netPeer, previousAddress);
break;
}
//Recycle if not message
@@ -623,574 +204,47 @@ private void ProcessEvent(NetEvent evt)
evt.DataReader.RecycleInternal();
}
- internal void RecycleEvent(NetEvent evt)
- {
- evt.Peer = null;
- evt.ErrorCode = 0;
- evt.RemoteEndPoint = null;
- evt.ConnectionRequest = null;
- lock(_eventLock)
- {
- evt.Next = _netEventPoolHead;
- _netEventPoolHead = evt;
- }
- }
-
- //Update function
- private void UpdateLogic()
+ protected override void ProcessNtpRequests(float elapsedMilliseconds)
{
- var peersToRemove = new List();
- var stopwatch = new Stopwatch();
- stopwatch.Start();
+ if (_ntpRequests.IsEmpty)
+ return;
- while (IsRunning)
+ List requestsToRemove = null;
+ foreach (var ntpRequest in _ntpRequests)
{
- try
- {
- ProcessDelayedPackets();
- int elapsed = (int)stopwatch.ElapsedMilliseconds;
- elapsed = elapsed <= 0 ? 1 : elapsed;
- stopwatch.Restart();
-
- for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
- {
- if (netPeer.ConnectionState == ConnectionState.Disconnected &&
- netPeer.TimeSinceLastPacket > DisconnectTimeout)
- {
- peersToRemove.Add(netPeer);
- }
- else
- {
- netPeer.Update(elapsed);
- }
- }
-
- if (peersToRemove.Count > 0)
- {
- _peersLock.EnterWriteLock();
- for (int i = 0; i < peersToRemove.Count; i++)
- RemovePeerInternal(peersToRemove[i]);
- _peersLock.ExitWriteLock();
- peersToRemove.Clear();
- }
-
- ProcessNtpRequests(elapsed);
-
- int sleepTime = UpdateTime - (int)stopwatch.ElapsedMilliseconds;
- if (sleepTime > 0)
- _updateTriggerEvent.WaitOne(sleepTime);
- }
- catch (ThreadAbortException)
- {
- return;
- }
- catch (Exception e)
+ ntpRequest.Value.Send(_udpSocketv4, elapsedMilliseconds);
+ if (ntpRequest.Value.NeedToKill)
{
- NetDebug.WriteError("[NM] LogicThread error: " + e);
- }
- }
- stopwatch.Stop();
- }
-
- [Conditional("DEBUG")]
- private void ProcessDelayedPackets()
- {
-#if DEBUG
- if (!SimulateLatency)
- return;
-
- var time = DateTime.UtcNow;
- lock (_pingSimulationList)
- {
- for (int i = 0; i < _pingSimulationList.Count; i++)
- {
- var incomingData = _pingSimulationList[i];
- if (incomingData.TimeWhenGet <= time)
- {
- DebugMessageReceived(incomingData.Data, incomingData.EndPoint);
- _pingSimulationList.RemoveAt(i);
- i--;
- }
- }
- }
-#endif
- }
-
- private void ProcessNtpRequests(int elapsedMilliseconds)
- {
- List requestsToRemove = null;
- foreach (var ntpRequest in _ntpRequests)
- {
- ntpRequest.Value.Send(_udpSocketv4, elapsedMilliseconds);
- if(ntpRequest.Value.NeedToKill)
- {
- if (requestsToRemove == null)
- requestsToRemove = new List();
- requestsToRemove.Add(ntpRequest.Key);
+ if (requestsToRemove == null)
+ requestsToRemove = new List();
+ requestsToRemove.Add(ntpRequest.Key);
}
}
if (requestsToRemove != null)
{
foreach (var ipEndPoint in requestsToRemove)
- {
- _ntpRequests.Remove(ipEndPoint);
- }
- }
- }
-
- ///
- /// Update and send logic. Use this only when NetManager started in manual mode
- ///
- /// elapsed milliseconds since last update call
- public void ManualUpdate(int elapsedMilliseconds)
- {
- if (!_manualMode)
- return;
-
- for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
- {
- if (netPeer.ConnectionState == ConnectionState.Disconnected && netPeer.TimeSinceLastPacket > DisconnectTimeout)
- {
- RemovePeerInternal(netPeer);
- }
- else
- {
- netPeer.Update(elapsedMilliseconds);
- }
- }
- ProcessNtpRequests(elapsedMilliseconds);
- }
-
- internal NetPeer OnConnectionSolved(ConnectionRequest request, byte[] rejectData, int start, int length)
- {
- NetPeer netPeer = null;
-
- if (request.Result == ConnectionRequestResult.RejectForce)
- {
- NetDebug.Write(NetLogLevel.Trace, "[NM] Peer connect reject force.");
- if (rejectData != null && length > 0)
- {
- var shutdownPacket = PoolGetWithProperty(PacketProperty.Disconnect, length);
- shutdownPacket.ConnectionNumber = request.InternalPacket.ConnectionNumber;
- FastBitConverter.GetBytes(shutdownPacket.RawData, 1, request.InternalPacket.ConnectionTime);
- if (shutdownPacket.Size >= NetConstants.PossibleMtu[0])
- NetDebug.WriteError("[Peer] Disconnect additional data size more than MTU!");
- else
- Buffer.BlockCopy(rejectData, start, shutdownPacket.RawData, 9, length);
- SendRawAndRecycle(shutdownPacket, request.RemoteEndPoint);
- }
- }
- 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.InternalPacket, rejectData, start, length);
- AddPeer(netPeer);
- _peersLock.ExitUpgradeableReadLock();
- NetDebug.Write(NetLogLevel.Trace, "[NM] Peer connect reject.");
- }
- else //Accept
- {
- netPeer = new NetPeer(this, request, GetNextPeerId());
- AddPeer(netPeer);
- _peersLock.ExitUpgradeableReadLock();
- CreateEvent(NetEvent.EType.Connect, netPeer);
- NetDebug.Write(NetLogLevel.Trace, $"[NM] Received peer connection Id: {netPeer.ConnectTime}, EP: {netPeer.EndPoint}");
- }
- }
-
- lock(_requestsDict)
- _requestsDict.Remove(request.RemoteEndPoint);
-
- return netPeer;
- }
-
- private int GetNextPeerId()
- {
- return _peerIds.TryDequeue(out int id) ? id : _lastPeerId++;
- }
-
- private void ProcessConnectRequest(
- IPEndPoint remoteEndPoint,
- NetPeer netPeer,
- NetConnectRequestPacket connRequest)
- {
- //if we have peer
- if (netPeer != null)
- {
- var processResult = netPeer.ProcessConnectRequest(connRequest);
- NetDebug.Write($"ConnectRequest LastId: {netPeer.ConnectTime}, NewId: {connRequest.ConnectionTime}, EP: {remoteEndPoint}, Result: {processResult}");
-
- switch (processResult)
- {
- case ConnectRequestResult.Reconnection:
- DisconnectPeerForce(netPeer, DisconnectReason.Reconnect, 0, null);
- RemovePeer(netPeer);
- //go to new connection
- break;
- case ConnectRequestResult.NewConnection:
- RemovePeer(netPeer);
- //go to new connection
- break;
- case ConnectRequestResult.P2PLose:
- DisconnectPeerForce(netPeer, DisconnectReason.PeerToPeerConnection, 0, null);
- RemovePeer(netPeer);
- //go to new connection
- break;
- default:
- //no operations needed
- return;
- }
- //ConnectRequestResult.NewConnection
- //Set next connection number
- if(processResult != ConnectRequestResult.P2PLose)
- connRequest.ConnectionNumber = (byte)((netPeer.ConnectionNum + 1) % NetConstants.MaxConnectionNumber);
- //To reconnect peer
- }
- else
- {
- NetDebug.Write($"ConnectRequest Id: {connRequest.ConnectionTime}, EP: {remoteEndPoint}");
- }
-
- ConnectionRequest req;
- lock (_requestsDict)
- {
- if (_requestsDict.TryGetValue(remoteEndPoint, out req))
- {
- req.UpdateRequest(connRequest);
- return;
- }
- req = new ConnectionRequest(remoteEndPoint, connRequest, this);
- _requestsDict.Add(remoteEndPoint, req);
- }
- NetDebug.Write($"[NM] Creating request event: {connRequest.ConnectionTime}");
- CreateEvent(NetEvent.EType.ConnectionRequest, connectionRequest: req);
- }
-
- private void OnMessageReceived(NetPacket packet, IPEndPoint remoteEndPoint)
- {
-#if DEBUG
- if (SimulatePacketLoss && _randomGenerator.NextDouble() * 100 < SimulationPacketLossChance)
- {
- //drop packet
- return;
- }
- if (SimulateLatency)
- {
- int latency = _randomGenerator.Next(SimulationMinLatency, SimulationMaxLatency);
- if (latency > MinLatencyThreshold)
- {
- lock (_pingSimulationList)
- {
- _pingSimulationList.Add(new IncomingData
- {
- Data = packet,
- EndPoint = remoteEndPoint,
- TimeWhenGet = DateTime.UtcNow.AddMilliseconds(latency)
- });
- }
- //hold packet
- return;
- }
- }
-
- //ProcessEvents
- DebugMessageReceived(packet, remoteEndPoint);
- }
-
- private void DebugMessageReceived(NetPacket packet, IPEndPoint remoteEndPoint)
- {
-#endif
- var originalPacketSize = packet.Size;
- if (EnableStatistics)
- {
- Statistics.IncrementPacketsReceived();
- Statistics.AddBytesReceived(originalPacketSize);
- }
-
- if (_ntpRequests.Count > 0)
- {
- if (_ntpRequests.TryGetValue(remoteEndPoint, out var request))
- {
- if (packet.Size < 48)
- {
- NetDebug.Write(NetLogLevel.Trace, $"NTP response too short: {packet.Size}");
- return;
- }
-
- byte[] copiedData = new byte[packet.Size];
- Buffer.BlockCopy(packet.RawData, 0, copiedData, 0, packet.Size);
- NtpPacket ntpPacket = NtpPacket.FromServerResponse(copiedData, DateTime.UtcNow);
- try
- {
- ntpPacket.ValidateReply();
- }
- catch (InvalidOperationException ex)
- {
- NetDebug.Write(NetLogLevel.Trace, $"NTP response error: {ex.Message}");
- ntpPacket = null;
- }
-
- if (ntpPacket != null)
- {
- _ntpRequests.Remove(remoteEndPoint);
- _ntpEventListener?.OnNtpResponse(ntpPacket);
- }
- return;
- }
- }
-
- if (_extraPacketLayer != null)
- {
- int start = 0;
- _extraPacketLayer.ProcessInboundPacket(ref remoteEndPoint, ref packet.RawData, ref start, ref packet.Size);
- if (packet.Size == 0)
- return;
- }
-
- if (!packet.Verify())
- {
- NetDebug.WriteError("[NM] DataReceived: bad!");
- PoolRecycle(packet);
- return;
- }
-
- switch (packet.Property)
- {
- //special case connect request
- case PacketProperty.ConnectRequest:
- if (NetConnectRequestPacket.GetProtocolId(packet) != NetConstants.ProtocolId)
- {
- SendRawAndRecycle(PoolGetWithProperty(PacketProperty.InvalidProtocol), remoteEndPoint);
- return;
- }
- break;
- //unconnected messages
- case PacketProperty.Broadcast:
- if (!BroadcastReceiveEnabled)
- return;
- CreateEvent(NetEvent.EType.Broadcast, remoteEndPoint: remoteEndPoint, readerSource: packet);
- return;
- case PacketProperty.UnconnectedMessage:
- if (!UnconnectedMessagesEnabled)
- return;
- CreateEvent(NetEvent.EType.ReceiveUnconnected, remoteEndPoint: remoteEndPoint, readerSource: packet);
- return;
- case PacketProperty.NatMessage:
- if (NatPunchEnabled)
- NatPunchModule.ProcessMessage(remoteEndPoint, packet);
- return;
- }
-
- //Check normal packets
- _peersLock.EnterReadLock();
- bool peerFound = _peersDict.TryGetValue(remoteEndPoint, out var netPeer);
- _peersLock.ExitReadLock();
-
- if (peerFound && EnableStatistics)
- {
- netPeer.Statistics.IncrementPacketsReceived();
- netPeer.Statistics.AddBytesReceived(originalPacketSize);
- }
-
- switch (packet.Property)
- {
- case PacketProperty.ConnectRequest:
- var connRequest = NetConnectRequestPacket.FromData(packet);
- if (connRequest != null)
- ProcessConnectRequest(remoteEndPoint, netPeer, connRequest);
- break;
- case PacketProperty.PeerNotFound:
- if (peerFound) //local
- {
- if (netPeer.ConnectionState != ConnectionState.Connected)
- return;
- if (packet.Size == 1)
- {
- //first reply
- //send NetworkChanged packet
- netPeer.ResetMtu();
- SendRaw(NetConnectAcceptPacket.MakeNetworkChanged(netPeer), remoteEndPoint);
- NetDebug.Write($"PeerNotFound sending connection info: {remoteEndPoint}");
- }
- else if (packet.Size == 2 && packet.RawData[1] == 1)
- {
- //second reply
- DisconnectPeerForce(netPeer, DisconnectReason.PeerNotFound, 0, null);
- }
- }
- else if (packet.Size > 1) //remote
- {
- //check if this is old peer
- bool isOldPeer = false;
-
- if (AllowPeerAddressChange)
- {
- NetDebug.Write($"[NM] Looks like address change: {packet.Size}");
- var remoteData = NetConnectAcceptPacket.FromData(packet);
- if (remoteData != null &&
- remoteData.PeerNetworkChanged &&
- remoteData.PeerId < _peersArray.Length)
- {
- _peersLock.EnterUpgradeableReadLock();
- var peer = _peersArray[remoteData.PeerId];
- if (peer != null &&
- peer.ConnectTime == remoteData.ConnectionTime &&
- peer.ConnectionNum == remoteData.ConnectionNumber)
- {
- if (peer.ConnectionState == ConnectionState.Connected)
- {
- peer.InitiateEndPointChange();
- CreateEvent(NetEvent.EType.PeerAddressChanged, peer, remoteEndPoint);
- NetDebug.Write("[NM] PeerNotFound change address of remote peer");
- }
- isOldPeer = true;
- }
- _peersLock.ExitUpgradeableReadLock();
- }
- }
-
- PoolRecycle(packet);
-
- //else peer really not found
- if (!isOldPeer)
- {
- var secondResponse = PoolGetWithProperty(PacketProperty.PeerNotFound, 1);
- secondResponse.RawData[1] = 1;
- SendRawAndRecycle(secondResponse, remoteEndPoint);
- }
- }
- break;
- case PacketProperty.InvalidProtocol:
- if (peerFound && netPeer.ConnectionState == ConnectionState.Outgoing)
- DisconnectPeerForce(netPeer, DisconnectReason.InvalidProtocol, 0, null);
- break;
- case PacketProperty.Disconnect:
- if (peerFound)
- {
- var disconnectResult = netPeer.ProcessDisconnect(packet);
- if (disconnectResult == DisconnectResult.None)
- {
- PoolRecycle(packet);
- return;
- }
- DisconnectPeerForce(
- netPeer,
- disconnectResult == DisconnectResult.Disconnect
- ? DisconnectReason.RemoteConnectionClose
- : DisconnectReason.ConnectionRejected,
- 0, packet);
- }
- else
- {
- PoolRecycle(packet);
- }
- //Send shutdown
- SendRawAndRecycle(PoolGetWithProperty(PacketProperty.ShutdownOk), remoteEndPoint);
- break;
- case PacketProperty.ConnectAccept:
- if (!peerFound)
- return;
- var connAccept = NetConnectAcceptPacket.FromData(packet);
- if (connAccept != null && netPeer.ProcessConnectAccept(connAccept))
- CreateEvent(NetEvent.EType.Connect, netPeer);
- break;
- default:
- if(peerFound)
- netPeer.ProcessPacket(packet);
- else
- SendRawAndRecycle(PoolGetWithProperty(PacketProperty.PeerNotFound), remoteEndPoint);
- break;
- }
- }
-
- internal void CreateReceiveEvent(NetPacket packet, DeliveryMethod method, byte channelNumber, int headerSize, NetPeer fromPeer)
- {
- NetEvent evt;
-
- if (UnsyncedEvents || UnsyncedReceiveEvent || _manualMode)
- {
- lock (_eventLock)
- {
- evt = _netEventPoolHead;
- if (evt == null)
- evt = new NetEvent(this);
- else
- _netEventPoolHead = evt.Next;
- }
- evt.Next = null;
- evt.Type = NetEvent.EType.Receive;
- evt.DataReader.SetSource(packet, headerSize);
- evt.Peer = fromPeer;
- evt.DeliveryMethod = method;
- evt.ChannelNumber = channelNumber;
- ProcessEvent(evt);
- }
- else
- {
- lock (_eventLock)
- {
- evt = _netEventPoolHead;
- if (evt == null)
- evt = new NetEvent(this);
- else
- _netEventPoolHead = evt.Next;
-
- evt.Next = null;
- evt.Type = NetEvent.EType.Receive;
- evt.DataReader.SetSource(packet, headerSize);
- evt.Peer = fromPeer;
- evt.DeliveryMethod = method;
- evt.ChannelNumber = channelNumber;
-
- if (_pendingEventTail == null)
- _pendingEventHead = evt;
- else
- _pendingEventTail.Next = evt;
- _pendingEventTail = evt;
- }
+ _ntpRequests.TryRemove(ipEndPoint, out _);
}
}
///
- /// Send data to all connected peers (channel - 0)
+ /// Send data to all connected peers
///
/// DataWriter with data
+ /// Number of channel (from 0 to channelsCount - 1)
/// Send options (reliable, unreliable, etc.)
- public void SendToAll(NetDataWriter writer, DeliveryMethod options)
- {
- SendToAll(writer.Data, 0, writer.Length, options);
- }
-
- ///
- /// Send data to all connected peers (channel - 0)
- ///
- /// Data
- /// Send options (reliable, unreliable, etc.)
- public void SendToAll(byte[] data, DeliveryMethod options)
- {
- SendToAll(data, 0, data.Length, options);
- }
+ public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options) =>
+ SendToAll(writer.Data, 0, writer.Length, channelNumber, options);
///
- /// Send data to all connected peers (channel - 0)
+ /// Send data to all connected peers
///
/// Data
- /// Start of data
- /// Length of data
+ /// Number of channel (from 0 to channelsCount - 1)
/// Send options (reliable, unreliable, etc.)
- public void SendToAll(byte[] data, int start, int length, DeliveryMethod options)
- {
- SendToAll(data, start, length, 0, options);
- }
+ public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options) =>
+ SendToAll(data, 0, data.Length, channelNumber, options);
///
/// Send data to all connected peers
@@ -1198,10 +252,9 @@ public void SendToAll(byte[] data, int start, int length, DeliveryMethod options
/// DataWriter with data
/// Number of channel (from 0 to channelsCount - 1)
/// Send options (reliable, unreliable, etc.)
- public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options)
- {
- SendToAll(writer.Data, 0, writer.Length, channelNumber, options);
- }
+ /// Excluded peer
+ public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options, LiteNetPeer excludePeer) =>
+ SendToAll(writer.Data, 0, writer.Length, channelNumber, options, excludePeer);
///
/// Send data to all connected peers
@@ -1209,10 +262,9 @@ public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod o
/// Data
/// Number of channel (from 0 to channelsCount - 1)
/// Send options (reliable, unreliable, etc.)
- public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options)
- {
- SendToAll(data, 0, data.Length, channelNumber, options);
- }
+ /// Excluded peer
+ public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options, LiteNetPeer excludePeer) =>
+ SendToAll(data, 0, data.Length, channelNumber, options, excludePeer);
///
/// Send data to all connected peers
@@ -1222,13 +274,17 @@ public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options)
/// Length of data
/// Number of channel (from 0 to channelsCount - 1)
/// Send options (reliable, unreliable, etc.)
- public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options)
+ /// Excluded peer
+ public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options, LiteNetPeer excludePeer)
{
try
{
_peersLock.EnterReadLock();
for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
- netPeer.Send(data, start, length, channelNumber, options);
+ {
+ if (netPeer != excludePeer)
+ ((NetPeer)netPeer).Send(data, start, length, channelNumber, options);
+ }
}
finally
{
@@ -1237,50 +293,25 @@ public void SendToAll(byte[] data, int start, int length, byte channelNumber, De
}
///
- /// Send data to all connected peers (channel - 0)
- ///
- /// DataWriter with data
- /// Send options (reliable, unreliable, etc.)
- /// Excluded peer
- public void SendToAll(NetDataWriter writer, DeliveryMethod options, NetPeer excludePeer)
- {
- SendToAll(writer.Data, 0, writer.Length, 0, options, excludePeer);
- }
-
- ///
- /// Send data to all connected peers (channel - 0)
- ///
- /// Data
- /// Send options (reliable, unreliable, etc.)
- /// Excluded peer
- public void SendToAll(byte[] data, DeliveryMethod options, NetPeer excludePeer)
- {
- SendToAll(data, 0, data.Length, 0, options, excludePeer);
- }
-
- ///
- /// Send data to all connected peers (channel - 0)
+ /// 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, DeliveryMethod options, NetPeer excludePeer)
- {
- SendToAll(data, start, length, 0, options, excludePeer);
- }
-
- ///
- /// Send data to all connected peers
- ///
- /// DataWriter with data
/// Number of channel (from 0 to channelsCount - 1)
/// Send options (reliable, unreliable, etc.)
- /// Excluded peer
- public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options, NetPeer excludePeer)
+ public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options)
{
- SendToAll(writer.Data, 0, writer.Length, channelNumber, options, excludePeer);
+ try
+ {
+ _peersLock.EnterReadLock();
+ for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
+ ((NetPeer)netPeer).Send(data, start, length, channelNumber, options);
+ }
+ finally
+ {
+ _peersLock.ExitReadLock();
+ }
}
///
@@ -1289,23 +320,17 @@ public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod o
/// Data
/// Number of channel (from 0 to channelsCount - 1)
/// Send options (reliable, unreliable, etc.)
- /// Excluded peer
- public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options, NetPeer excludePeer)
- {
- SendToAll(data, 0, data.Length, channelNumber, options, excludePeer);
- }
-
+ public void SendToAll(ReadOnlySpan data, byte channelNumber, DeliveryMethod options) =>
+ SendToAll(data, channelNumber, options, null);
///
/// Send data to all connected peers
///
/// Data
- /// Start of data
- /// Length of data
/// Number of channel (from 0 to channelsCount - 1)
/// Send options (reliable, unreliable, etc.)
/// Excluded peer
- public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options, NetPeer excludePeer)
+ public void SendToAll(ReadOnlySpan data, byte channelNumber, DeliveryMethod options, LiteNetPeer excludePeer)
{
try
{
@@ -1313,7 +338,7 @@ public void SendToAll(byte[] data, int start, int length, byte channelNumber, De
for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
{
if (netPeer != excludePeer)
- netPeer.Send(data, start, length, channelNumber, options);
+ ((NetPeer)netPeer).Send(data, channelNumber, options);
}
}
finally
@@ -1322,183 +347,6 @@ public void SendToAll(byte[] data, int start, int length, byte channelNumber, De
}
}
- ///
- /// Start logic thread and listening on available port
- ///
- public bool Start()
- {
- return Start(0);
- }
-
- ///
- /// Start logic thread and listening on selected port
- ///
- /// bind to specific ipv4 address
- /// bind to specific ipv6 address
- /// port to listen
- public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port)
- {
- return Start(addressIPv4, addressIPv6, port, false);
- }
-
- ///
- /// Start logic thread and listening on selected port
- ///
- /// bind to specific ipv4 address
- /// bind to specific ipv6 address
- /// port to listen
- public bool Start(string addressIPv4, string addressIPv6, int port)
- {
- IPAddress ipv4 = NetUtils.ResolveAddress(addressIPv4);
- IPAddress ipv6 = NetUtils.ResolveAddress(addressIPv6);
- return Start(ipv4, ipv6, port);
- }
-
- ///
- /// Start logic thread and listening on selected port
- ///
- /// port to listen
- public bool Start(int port)
- {
- return Start(IPAddress.Any, IPAddress.IPv6Any, port);
- }
-
- ///
- /// Start in manual mode and listening on selected port
- /// In this mode you should use ManualReceive (without PollEvents) for receive packets
- /// and ManualUpdate(...) for update and send packets
- /// This mode useful mostly for single-threaded servers
- ///
- /// bind to specific ipv4 address
- /// bind to specific ipv6 address
- /// port to listen
- public bool StartInManualMode(IPAddress addressIPv4, IPAddress addressIPv6, int port)
- {
- return Start(addressIPv4, addressIPv6, port, true);
- }
-
- ///
- /// Start in manual mode and listening on selected port
- /// In this mode you should use ManualReceive (without PollEvents) for receive packets
- /// and ManualUpdate(...) for update and send packets
- /// This mode useful mostly for single-threaded servers
- ///
- /// bind to specific ipv4 address
- /// bind to specific ipv6 address
- /// port to listen
- public bool StartInManualMode(string addressIPv4, string addressIPv6, int port)
- {
- IPAddress ipv4 = NetUtils.ResolveAddress(addressIPv4);
- IPAddress ipv6 = NetUtils.ResolveAddress(addressIPv6);
- return StartInManualMode(ipv4, ipv6, port);
- }
-
- ///
- /// Start in manual mode and listening on selected port
- /// In this mode you should use ManualReceive (without PollEvents) for receive packets
- /// and ManualUpdate(...) for update and send packets
- /// This mode useful mostly for single-threaded servers
- ///
- /// port to listen
- public bool StartInManualMode(int port)
- {
- return StartInManualMode(IPAddress.Any, IPAddress.IPv6Any, port);
- }
-
- ///
- /// Send message without connection
- ///
- /// Raw data
- /// Packet destination
- /// Operation result
- public bool SendUnconnectedMessage(byte[] message, IPEndPoint remoteEndPoint)
- {
- return SendUnconnectedMessage(message, 0, message.Length, remoteEndPoint);
- }
-
- ///
- /// Send message without connection. WARNING This method allocates a new IPEndPoint object and
- /// synchronously makes a DNS request. If you're calling this method every frame it will be
- /// much faster to just cache the IPEndPoint.
- ///
- /// Data serializer
- /// Packet destination IP or hostname
- /// Packet destination port
- /// Operation result
- public bool SendUnconnectedMessage(NetDataWriter writer, string address, int port)
- {
- IPEndPoint remoteEndPoint = NetUtils.MakeEndPoint(address, port);
-
- return SendUnconnectedMessage(writer.Data, 0, writer.Length, remoteEndPoint);
- }
-
- ///
- /// Send message without connection
- ///
- /// Data serializer
- /// Packet destination
- /// Operation result
- public bool SendUnconnectedMessage(NetDataWriter writer, IPEndPoint remoteEndPoint)
- {
- return SendUnconnectedMessage(writer.Data, 0, writer.Length, remoteEndPoint);
- }
-
- ///
- /// Send message without connection
- ///
- /// Raw data
- /// data start
- /// data length
- /// Packet destination
- /// Operation result
- public bool SendUnconnectedMessage(byte[] message, int start, int length, IPEndPoint remoteEndPoint)
- {
- //No need for CRC here, SendRaw does that
- NetPacket packet = PoolGetWithData(PacketProperty.UnconnectedMessage, message, start, length);
- return SendRawAndRecycle(packet, remoteEndPoint) > 0;
- }
-
- ///
- /// Triggers update and send logic immediately (works asynchronously)
- ///
- public void TriggerUpdate()
- {
- _updateTriggerEvent.Set();
- }
-
- ///
- /// Receive all pending events. Call this in game update code
- /// In Manual mode it will call also socket Receive (which can be slow)
- ///
- public void PollEvents()
- {
- if (_manualMode)
- {
- if (_udpSocketv4 != null)
- ManualReceive(_udpSocketv4, _bufferEndPointv4);
- if (_udpSocketv6 != null && _udpSocketv6 != _udpSocketv4)
- ManualReceive(_udpSocketv6, _bufferEndPointv6);
- ProcessDelayedPackets();
- return;
- }
- if (UnsyncedEvents)
- return;
- NetEvent pendingEvent;
- lock (_eventLock)
- {
- pendingEvent = _pendingEventHead;
- _pendingEventHead = null;
- _pendingEventTail = null;
- }
-
- while (pendingEvent != null)
- {
- var next = pendingEvent.Next;
- ProcessEvent(pendingEvent);
- pendingEvent = next;
- }
- }
-
///
/// Connect to remote host
///
@@ -1506,11 +354,9 @@ public void PollEvents()
/// Server Port
/// Connection key
/// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting
- /// Manager is not running. Call
- public NetPeer Connect(string address, int port, string key)
- {
- return Connect(address, port, NetDataWriter.FromString(key));
- }
+ /// Manager is not running. Call
+ public new NetPeer Connect(string address, int port, string key) =>
+ Connect(address, port, NetDataWriter.FromString(key));
///
/// Connect to remote host
@@ -1519,21 +365,9 @@ public NetPeer Connect(string address, int port, string key)
/// Server Port
/// Additional data for remote peer
/// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting
- /// Manager is not running. Call
- public NetPeer Connect(string address, int port, NetDataWriter connectionData)
- {
- IPEndPoint ep;
- try
- {
- ep = NetUtils.MakeEndPoint(address, port);
- }
- catch
- {
- CreateEvent(NetEvent.EType.Disconnect, disconnectReason: DisconnectReason.UnknownHost);
- return null;
- }
- return Connect(ep, connectionData);
- }
+ /// Manager is not running. Call
+ public new NetPeer Connect(string address, int port, NetDataWriter connectionData) =>
+ (NetPeer)base.Connect(address, port, connectionData);
///
/// Connect to remote host
@@ -1541,11 +375,9 @@ public NetPeer Connect(string address, int port, NetDataWriter connectionData)
/// Server end point (ip and port)
/// Connection key
/// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting
- /// Manager is not running. Call
- public NetPeer Connect(IPEndPoint target, string key)
- {
- return Connect(target, NetDataWriter.FromString(key));
- }
+ /// Manager is not running. Call
+ public new NetPeer Connect(IPEndPoint target, string key) =>
+ (NetPeer)base.Connect(target, key);
///
/// Connect to remote host
@@ -1553,266 +385,24 @@ public NetPeer Connect(IPEndPoint target, string key)
/// Server end point (ip and port)
/// Additional data for remote peer
/// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting
- /// Manager is not running. Call
- public NetPeer Connect(IPEndPoint target, NetDataWriter connectionData)
- {
- if (!IsRunning)
- throw new InvalidOperationException("Client is not running");
-
- lock(_requestsDict)
- {
- if (_requestsDict.ContainsKey(target))
- return null;
- }
-
- byte connectionNumber = 0;
- _peersLock.EnterUpgradeableReadLock();
- if (_peersDict.TryGetValue(target, out var 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;
- }
+ /// Manager is not running. Call
+ public new NetPeer Connect(IPEndPoint target, NetDataWriter connectionData) =>
+ (NetPeer)base.Connect(target, connectionData);
///
- /// Force closes connection and stop all threads.
- ///
- public void Stop()
- {
- Stop(true);
- }
-
- ///
- /// Force closes connection and stop all threads.
- ///
- /// Send disconnect messages
- public void Stop(bool sendDisconnectMessages)
- {
- if (!IsRunning)
- return;
- NetDebug.Write("[NM] Stop");
-
-#if UNITY_2018_3_OR_NEWER
- _pausedSocketFix.Deinitialize();
- _pausedSocketFix = null;
-#endif
-
- //Send last disconnect
- for(var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
- netPeer.Shutdown(null, 0, 0, !sendDisconnectMessages);
-
- //Stop
- CloseSocket();
- _updateTriggerEvent.Set();
- if (!_manualMode)
- {
- _logicThread.Join();
- _logicThread = null;
- }
-
- //clear peers
- _peersLock.EnterWriteLock();
- _headPeer = null;
- _peersDict.Clear();
- _peersArray = new NetPeer[32];
- _peersLock.ExitWriteLock();
- _peerIds = new ConcurrentQueue();
- _lastPeerId = 0;
-#if DEBUG
- lock (_pingSimulationList)
- _pingSimulationList.Clear();
-#endif
- _connectedPeersCount = 0;
- _pendingEventHead = null;
- _pendingEventTail = null;
- }
-
- ///
- /// Return peers count with connection state
- ///
- /// peer connection state (you can use as bit flags)
- /// peers count
- public int GetPeersCount(ConnectionState peerState)
- {
- int count = 0;
- _peersLock.EnterReadLock();
- for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
- {
- if ((netPeer.ConnectionState & peerState) != 0)
- count++;
- }
- _peersLock.ExitReadLock();
- return count;
- }
-
- ///
- /// Get copy of peers (without allocations)
- ///
- /// List that will contain result
- /// State of peers
- public void GetPeersNonAlloc(List peers, ConnectionState peerState)
- {
- peers.Clear();
- _peersLock.EnterReadLock();
- for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
- {
- if ((netPeer.ConnectionState & peerState) != 0)
- peers.Add(netPeer);
- }
- _peersLock.ExitReadLock();
- }
-
- ///
- /// Disconnect all peers without any additional data
- ///
- public void DisconnectAll()
- {
- DisconnectAll(null, 0, 0);
- }
-
- ///
- /// Disconnect all peers with shutdown message
- ///
- /// Data to send (must be less or equal MTU)
- /// Data start
- /// Data count
- public void DisconnectAll(byte[] data, int start, int count)
- {
- //Send disconnect packets
- _peersLock.EnterReadLock();
- for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
- {
- DisconnectPeer(
- netPeer,
- DisconnectReason.DisconnectPeerCalled,
- 0,
- false,
- data,
- start,
- count,
- null);
- }
- _peersLock.ExitReadLock();
- }
-
- ///
- /// Immediately disconnect peer from server without additional data
- ///
- /// peer to disconnect
- public void DisconnectPeerForce(NetPeer peer)
- {
- DisconnectPeerForce(peer, DisconnectReason.DisconnectPeerCalled, 0, null);
- }
-
- ///
- /// Disconnect peer from server
- ///
- /// peer to disconnect
- public void DisconnectPeer(NetPeer peer)
- {
- DisconnectPeer(peer, null, 0, 0);
- }
-
- ///
- /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8)
- ///
- /// peer to disconnect
- /// additional data
- public void DisconnectPeer(NetPeer peer, byte[] data)
- {
- DisconnectPeer(peer, data, 0, data.Length);
- }
-
- ///
- /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8)
- ///
- /// peer to disconnect
- /// additional data
- public void DisconnectPeer(NetPeer peer, NetDataWriter writer)
- {
- DisconnectPeer(peer, writer.Data, 0, writer.Length);
- }
-
- ///
- /// Disconnect peer from server and send additional data (Size must be less or equal MTU - 8)
- ///
- /// peer to disconnect
- /// additional data
- /// data start
- /// data length
- public void DisconnectPeer(NetPeer peer, byte[] data, int start, int count)
- {
- DisconnectPeer(
- peer,
- DisconnectReason.DisconnectPeerCalled,
- 0,
- false,
- data,
- start,
- count,
- null);
- }
-
- ///
- /// Create the requests for NTP server
- ///
- /// NTP Server address.
- public void CreateNtpRequest(IPEndPoint endPoint)
- {
- _ntpRequests.Add(endPoint, new NtpRequest(endPoint));
- }
-
- ///
- /// Create the requests for NTP server
- ///
- /// NTP Server address.
- /// port
- public void CreateNtpRequest(string ntpServerAddress, int port)
- {
- IPEndPoint endPoint = NetUtils.MakeEndPoint(ntpServerAddress, port);
- _ntpRequests.Add(endPoint, new NtpRequest(endPoint));
- }
-
- ///
- /// Create the requests for NTP server (default port)
+ /// Connect to remote host
///
- /// NTP Server address.
- public void CreateNtpRequest(string ntpServerAddress)
- {
- IPEndPoint endPoint = NetUtils.MakeEndPoint(ntpServerAddress, NtpRequest.DefaultPort);
- _ntpRequests.Add(endPoint, new NtpRequest(endPoint));
- }
-
- public NetPeerEnumerator GetEnumerator()
- {
- return new NetPeerEnumerator(_headPeer);
- }
+ /// Server end point (ip and port)
+ /// Additional data for remote peer
+ /// New NetPeer if new connection, Old NetPeer if already connected, null peer if there is ConnectionRequest awaiting
+ /// Manager is not running. Call
+ public new NetPeer Connect(IPEndPoint target, ReadOnlySpan connectionData) =>
+ (NetPeer)base.Connect(target, connectionData);
- IEnumerator IEnumerable.GetEnumerator()
- {
- return new NetPeerEnumerator(_headPeer);
- }
+ public new NetPeerEnumerator GetEnumerator() =>
+ new NetPeerEnumerator((NetPeer)_headPeer);
- IEnumerator IEnumerable.GetEnumerator()
- {
- return new NetPeerEnumerator(_headPeer);
- }
+ IEnumerator IEnumerable.GetEnumerator() =>
+ new NetPeerEnumerator((NetPeer)_headPeer);
}
}
diff --git a/LiteNetLib/NetPacket.cs b/LiteNetLib/NetPacket.cs
index 4b403084..d0ecdd6a 100644
--- a/LiteNetLib/NetPacket.cs
+++ b/LiteNetLib/NetPacket.cs
@@ -7,6 +7,7 @@ internal enum PacketProperty : byte
{
Unreliable,
Channeled,
+ ReliableMerged,
Ack,
Ping,
Pong,
@@ -22,12 +23,13 @@ internal enum PacketProperty : byte
PeerNotFound,
InvalidProtocol,
NatMessage,
- Empty
+ Empty,
+ Total
}
internal sealed class NetPacket
{
- private static readonly int PropertiesCount = Enum.GetValues(typeof(PacketProperty)).Length;
+ private static readonly int PropertiesCount = (int)PacketProperty.Total;
private static readonly int[] HeaderSizes;
static NetPacket()
@@ -39,6 +41,7 @@ static NetPacket()
{
case PacketProperty.Channeled:
case PacketProperty.Ack:
+ case PacketProperty.ReliableMerged:
HeaderSizes[i] = NetConstants.ChanneledHeaderSize;
break;
case PacketProperty.Ping:
@@ -63,90 +66,148 @@ static NetPacket()
}
}
- //Header
+ ///
+ /// Gets or sets the packet property (type).
+ /// Stored in the first 5 bits of the first byte (0x1F mask).
+ ///
public PacketProperty Property
{
get => (PacketProperty)(RawData[0] & 0x1F);
set => RawData[0] = (byte)((RawData[0] & 0xE0) | (byte)value);
}
+ ///
+ /// Gets or sets the connection number used to distinguish between multiple connection instances from the same endpoint.
+ /// Stored in bits 6 and 7 of the first byte (0x60 mask).
+ ///
+ ///
+ /// Used to discard packets from previous connections made in the same frame/time.
+ ///
public byte ConnectionNumber
{
get => (byte)((RawData[0] & 0x60) >> 5);
- set => RawData[0] = (byte) ((RawData[0] & 0x9F) | (value << 5));
+ set => RawData[0] = (byte)((RawData[0] & 0x9F) | (value << 5));
}
+ ///
+ /// Gets or sets the sequence number of the packet.
+ /// Located at offset 1 in .
+ ///
public ushort Sequence
{
get => BitConverter.ToUInt16(RawData, 1);
set => FastBitConverter.GetBytes(RawData, 1, value);
}
+ ///
+ /// Returns if the fragmentation bit (the highest bit of the first byte) is set.
+ ///
public bool IsFragmented => (RawData[0] & 0x80) != 0;
- public void MarkFragmented()
- {
- RawData[0] |= 0x80; //set first bit
- }
+ ///
+ /// Sets the fragmentation bit (0x80) in the packet header.
+ ///
+ public void MarkFragmented() => RawData[0] |= 0x80;
+ ///
+ /// Gets or sets the channel identifier.
+ /// Located at offset 3 in .
+ ///
public byte ChannelId
{
get => RawData[3];
set => RawData[3] = value;
}
+ ///
+ /// Gets or sets the unique identifier for a fragmented message.
+ /// Located at offset 4 in .
+ ///
public ushort FragmentId
{
get => BitConverter.ToUInt16(RawData, 4);
set => FastBitConverter.GetBytes(RawData, 4, value);
}
+ ///
+ /// Gets or sets the index of the current fragment part.
+ /// Located at offset 6 in .
+ ///
public ushort FragmentPart
{
get => BitConverter.ToUInt16(RawData, 6);
set => FastBitConverter.GetBytes(RawData, 6, value);
}
+ ///
+ /// Gets or sets the total number of fragments in the message.
+ /// Located at offset 8 in .
+ ///
public ushort FragmentsTotal
{
get => BitConverter.ToUInt16(RawData, 8);
set => FastBitConverter.GetBytes(RawData, 8, value);
}
- //Data
+ ///
+ /// The raw array containing the packet header and payload.
+ ///
public byte[] RawData;
+
+ ///
+ /// The actual size of the data in , including headers.
+ ///
public int Size;
- //Delivery
+ ///
+ /// Custom user data associated with this packet. Used for delivery notifications.
+ ///
public object UserData;
- //Pool node
+ ///
+ /// Reference to the next packet in the NetPacketPool.
+ ///
public NetPacket Next;
+ ///
+ /// Initializes a new instance of the class with a specific buffer size.
+ ///
+ /// Total size of the packet including headers.
public NetPacket(int size)
{
RawData = new byte[size];
Size = size;
}
- public NetPacket(PacketProperty property, int size)
+ ///
+ /// Initializes a new instance of the class, calculating the required size based on the property.
+ ///
+ /// The type of packet to create.
+ /// Size of the user data payload.
+ public NetPacket(PacketProperty property, int payloadSize)
{
- size += GetHeaderSize(property);
- RawData = new byte[size];
+ int totalSize = payloadSize + GetHeaderSize(property);
+ RawData = new byte[totalSize];
Property = property;
- Size = size;
- }
-
- public static int GetHeaderSize(PacketProperty property)
- {
- return HeaderSizes[(int)property];
- }
-
- public int GetHeaderSize()
- {
- return HeaderSizes[RawData[0] & 0x1F];
+ Size = totalSize;
}
+ ///
+ /// Gets the fixed header size for a specific .
+ ///
+ /// The packet type.
+ /// Header size in bytes.
+ public static int GetHeaderSize(PacketProperty property) => HeaderSizes[(int)property];
+
+ ///
+ /// Gets the header size of the current packet based on its property bits.
+ ///
+ public int HeaderSize => HeaderSizes[RawData[0] & 0x1F];
+
+ ///
+ /// Performs a basic check on the packet header and size.
+ ///
+ /// if the packet property is valid and the size is sufficient for the headers.
public bool Verify()
{
byte property = (byte)(RawData[0] & 0x1F);
diff --git a/LiteNetLib/NetPacketReader.cs b/LiteNetLib/NetPacketReader.cs
new file mode 100644
index 00000000..2098dde2
--- /dev/null
+++ b/LiteNetLib/NetPacketReader.cs
@@ -0,0 +1,44 @@
+using LiteNetLib.Utils;
+
+namespace LiteNetLib
+{
+ public sealed class NetPacketReader : NetDataReader
+ {
+ private NetPacket _packet;
+ private readonly LiteNetManager _manager;
+ private readonly NetEvent _evt;
+
+ internal NetPacketReader(LiteNetManager manager, NetEvent evt)
+ {
+ _manager = manager;
+ _evt = evt;
+ }
+
+ internal void SetSource(NetPacket packet, int headerSize)
+ {
+ if (packet == null)
+ return;
+ _packet = packet;
+ _data = packet.RawData;
+ _position = headerSize;
+ _offset = headerSize;
+ _dataSize = packet.Size;
+ }
+
+ internal void RecycleInternal()
+ {
+ Clear();
+ if (_packet != null)
+ _manager.PoolRecycle(_packet);
+ _packet = null;
+ _manager.RecycleEvent(_evt);
+ }
+
+ public void Recycle()
+ {
+ if (_manager.AutoRecycle)
+ return;
+ RecycleInternal();
+ }
+ }
+}
diff --git a/LiteNetLib/NetPeer.cs b/LiteNetLib/NetPeer.cs
index 66906789..3be8844e 100644
--- a/LiteNetLib/NetPeer.cs
+++ b/LiteNetLib/NetPeer.cs
@@ -1,10 +1,5 @@
-#if DEBUG
-#define STATS_ENABLED
-#endif
-using System;
+using System;
using System.Collections.Concurrent;
-using System.Collections.Generic;
-using System.Diagnostics;
using System.Net;
using System.Threading;
using LiteNetLib.Utils;
@@ -12,414 +7,43 @@
namespace LiteNetLib
{
///
- /// Peer connection state
+ /// Improved LiteNetPeer with full multi-channel support
///
- [Flags]
- public enum ConnectionState : byte
+ public class NetPeer : LiteNetPeer
{
- Outgoing = 1 << 1,
- Connected = 1 << 2,
- ShutdownRequested = 1 << 3,
- Disconnected = 1 << 4,
- EndPointChange = 1 << 5,
- Any = Outgoing | Connected | ShutdownRequested | EndPointChange
- }
-
- 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
- }
-
- ///
- /// Network peer. Main purpose is sending messages to specific peer.
- ///
- 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 object _shutdownLock = new object();
-
- internal volatile NetPeer NextPeer;
- internal NetPeer PrevPeer;
-
- internal byte ConnectionNum
- {
- get => _connectNum;
- private set
- {
- _connectNum = value;
- _mergeData.ConnectionNumber = value;
- _pingPacket.ConnectionNumber = value;
- _pongPacket.ConnectionNumber = value;
- }
- }
-
- //Channels
- private readonly Queue _unreliableChannel;
- private readonly ConcurrentQueue _channelSendQueue;
+ private readonly ConcurrentQueue _channelSendQueue = new ConcurrentQueue();
private readonly BaseChannel[] _channels;
- //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 int _fragmentId;
- private readonly Dictionary _holdedFragments;
- private readonly Dictionary _deliveredFragments;
-
- //Merging
- private readonly NetPacket _mergeData;
- private int _mergePos;
- private int _mergeCount;
-
- //Connection
- private IPEndPoint _remoteEndPoint;
- 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;
-
- ///
- /// Peer ip address and port
- ///
- public IPEndPoint EndPoint => _remoteEndPoint;
-
- ///
- /// Peer parent NetManager
- ///
- public readonly NetManager NetManager;
-
- ///
- /// Current connection state
- ///
- public ConnectionState ConnectionState => _connectionState;
-
- ///
- /// Connection time for internal purposes
- ///
- internal long ConnectTime => _connectTime;
-
- ///
- /// Peer id can be used as key in your dictionary of peers
- ///
- public readonly int Id;
-
- ///
- /// Id assigned from server
- ///
- public int RemoteId { get; private set; }
-
- ///
- /// Current one-way ping (RTT/2) in milliseconds
- ///
- public int Ping => _avgRtt/2;
-
- ///
- /// Round trip time in milliseconds
- ///
- public int RoundTripTime => _avgRtt;
-
- ///
- /// Current MTU - Maximum Transfer Unit ( maximum udp packet size without fragmentation )
- ///
- public int Mtu => _mtu;
-
- ///
- /// Delta with remote time in ticks (not accurate)
- /// positive - remote time > our time
- ///
- public long RemoteTimeDelta => _remoteDelta;
-
- ///
- /// Remote UTC time (not accurate)
- ///
- public DateTime RemoteUtcTime => new DateTime(DateTime.UtcNow.Ticks + _remoteDelta);
-
- ///
- /// Time since last packet received (including internal library packets)
- ///
- public int TimeSinceLastPacket => _timeSinceLastPacket;
-
- internal double ResendDelay => _resendDelay;
+ protected override int ChannelsCount => ((NetManager)NetManager).ChannelsCount;
- ///
- /// Application defined object containing data about the connection
- ///
- public object Tag;
-
- ///
- /// Statistics of peer connection
- ///
- public readonly NetStatistics Statistics;
-
- //incoming connection constructor
- internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id)
- {
- Id = id;
- Statistics = new NetStatistics();
- NetManager = netManager;
- ResetMtu();
- _remoteEndPoint = 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();
- _holdedFragments = new Dictionary();
- _deliveredFragments = new Dictionary();
-
- _channels = new BaseChannel[netManager.ChannelsCount * NetConstants.ChannelTypeCount];
- _channelSendQueue = new ConcurrentQueue();
- }
-
- internal void InitiateEndPointChange()
- {
- ResetMtu();
- _connectionState = ConnectionState.EndPointChange;
- }
-
- internal void FinishEndPointChange(IPEndPoint newEndPoint)
- {
- if (_connectionState != ConnectionState.EndPointChange)
- return;
- _connectionState = ConnectionState.Connected;
- _remoteEndPoint = newEndPoint;
- }
-
- internal void ResetMtu()
+ internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id) : base(netManager, remoteEndPoint, id)
{
- _finishMtu = false;
- if (NetManager.MtuOverride > 0)
- OverrideMtu(NetManager.MtuOverride);
- else if (NetManager.UseSafeMtu)
- SetMtu(0);
- else
- SetMtu(1);
- }
- private void SetMtu(int mtuIdx)
- {
- _mtuIdx = mtuIdx;
- _mtu = NetConstants.PossibleMtu[mtuIdx] - NetManager.ExtraPacketSizeForLayer;
}
- private void OverrideMtu(int mtuValue)
+ internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id, byte connectNum, ReadOnlySpan connectData) : base(netManager, remoteEndPoint, id, connectNum, connectData)
{
- _mtu = mtuValue;
- _finishMtu = true;
+ _channels = new BaseChannel[netManager.ChannelsCount * NetConstants.ChannelTypeCount];
}
- ///
- /// Returns packets count in queue for reliable channel
- ///
- /// number of channel 0-63
- /// type of channel ReliableOrdered or ReliableUnordered
- /// packets count in channel queue
- public int GetPacketsCountInReliableQueue(byte channelNumber, bool ordered)
+ internal NetPeer(NetManager netManager, LiteConnectionRequest request, int id) : base(netManager, request, id)
{
- int idx = channelNumber * NetConstants.ChannelTypeCount +
- (byte) (ordered ? DeliveryMethod.ReliableOrdered : DeliveryMethod.ReliableUnordered);
- var channel = _channels[idx];
- return channel != null ? ((ReliableChannel)channel).PacketsInQueue : 0;
+ _channels = new BaseChannel[netManager.ChannelsCount * NetConstants.ChannelTypeCount];
}
///
- /// Create temporary packet (maximum size MTU - headerSize) to send later without additional copies
+ /// Send data to peer
///
- /// Delivery method (reliable, unreliable, etc.)
+ /// DataWriter with data
/// Number of channel (from 0 to channelsCount - 1)
- /// PooledPacket that you can use to write data starting from UserDataOffset
- public PooledPacket CreatePacketFromPool(DeliveryMethod deliveryMethod, byte channelNumber)
- {
- //multithreaded variable
- int mtu = _mtu;
- var packet = NetManager.PoolGetPacket(mtu);
- if (deliveryMethod == DeliveryMethod.Unreliable)
- {
- packet.Property = PacketProperty.Unreliable;
- return new PooledPacket(packet, mtu, 0);
- }
- else
- {
- packet.Property = PacketProperty.Channeled;
- return new PooledPacket(packet, mtu, (byte)(channelNumber * NetConstants.ChannelTypeCount + (byte)deliveryMethod));
- }
- }
-
- ///
- /// Sends pooled packet without data copy
- ///
- /// packet to send
- /// size of user data you want to send
- public void SendPooledPacket(PooledPacket packet, int userDataSize)
- {
- if (_connectionState != ConnectionState.Connected)
- return;
- packet._packet.Size = packet.UserDataOffset + userDataSize;
- if (packet._packet.Property == PacketProperty.Channeled)
- {
- CreateChannel(packet._channelNumber).AddToQueue(packet._packet);
- }
- else
- {
- lock(_unreliableChannel)
- _unreliableChannel.Enqueue(packet._packet);
- }
- }
-
- private BaseChannel CreateChannel(byte idx)
- {
- BaseChannel newChannel = _channels[idx];
- if (newChannel != null)
- return newChannel;
- switch ((DeliveryMethod)(idx % NetConstants.ChannelTypeCount))
- {
- 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;
- }
- BaseChannel prevChannel = Interlocked.CompareExchange(ref _channels[idx], newChannel, null);
- if (prevChannel != null)
- return prevChannel;
-
- 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, id);
- _connectRequestPacket.ConnectionNumber = connectNum;
-
- //Send request
- NetManager.SendRaw(_connectRequestPacket, _remoteEndPoint);
-
- NetDebug.Write(NetLogLevel.Trace, $"[CC] ConnectId: {_connectTime}, ConnectNum: {connectNum}");
- }
-
- //"Accept" incoming constructor
- internal NetPeer(NetManager netManager, ConnectionRequest request, int id)
- : this(netManager, request.RemoteEndPoint, id)
- {
- _connectTime = request.InternalPacket.ConnectionTime;
- ConnectionNum = request.InternalPacket.ConnectionNumber;
- RemoteId = request.InternalPacket.PeerId;
-
- //Make initial packet
- _connectAcceptPacket = NetConnectAcceptPacket.Make(_connectTime, ConnectionNum, id);
-
- //Make Connected
- _connectionState = ConnectionState.Connected;
-
- //Send
- NetManager.SendRaw(_connectAcceptPacket, _remoteEndPoint);
-
- NetDebug.Write(NetLogLevel.Trace, $"[CC] ConnectId: {_connectTime}");
- }
-
- //Reject
- internal void Reject(NetConnectRequestPacket requestData, byte[] data, int start, int length)
- {
- _connectTime = requestData.ConnectionTime;
- _connectNum = requestData.ConnectionNumber;
- Shutdown(data, start, length, false);
- }
-
- internal bool ProcessConnectAccept(NetConnectAcceptPacket packet)
- {
- if (_connectionState != ConnectionState.Outgoing)
- return false;
-
- //check connection id
- if (packet.ConnectionTime != _connectTime)
- {
- NetDebug.Write(NetLogLevel.Trace, $"[NC] Invalid connectId: {packet.ConnectionTime} != our({_connectTime})");
- return false;
- }
- //check connect num
- ConnectionNum = packet.ConnectionNumber;
- RemoteId = packet.PeerId;
-
- NetDebug.Write(NetLogLevel.Trace, "[NC] Received connection accept");
- Interlocked.Exchange(ref _timeSinceLastPacket, 0);
- _connectionState = ConnectionState.Connected;
- return true;
- }
-
- ///
- /// Gets maximum size of packet that will be not fragmented.
- ///
- /// Type of packet that you want send
- /// size in bytes
- public int GetMaxSinglePacketSize(DeliveryMethod options)
- {
- return _mtu - NetPacket.GetHeaderSize(options == DeliveryMethod.Unreliable ? PacketProperty.Unreliable : PacketProperty.Channeled);
- }
+ /// Send options (reliable, unreliable, etc.)
+ ///
+ /// If size exceeds maximum limit:
+ /// MTU - headerSize bytes for Unreliable
+ /// Fragment count exceeded ushort.MaxValue
+ ///
+ public void Send(NetDataWriter dataWriter, byte channelNumber, DeliveryMethod deliveryMethod) =>
+ SendInternal(dataWriter.AsReadOnlySpan(), channelNumber, deliveryMethod, null);
///
/// Send data to peer with delivery event called
@@ -435,7 +59,7 @@ public void SendWithDeliveryEvent(byte[] data, byte channelNumber, DeliveryMetho
{
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);
+ SendInternal(new ReadOnlySpan(data, 0, data.Length), channelNumber, deliveryMethod, userData);
}
///
@@ -454,7 +78,7 @@ public void SendWithDeliveryEvent(byte[] data, int start, int length, byte chann
{
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);
+ SendInternal(new ReadOnlySpan(data, start, length), channelNumber, deliveryMethod, userData);
}
///
@@ -471,54 +95,47 @@ public void SendWithDeliveryEvent(NetDataWriter dataWriter, byte channelNumber,
{
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);
+ SendInternal(dataWriter.AsReadOnlySpan(), channelNumber, deliveryMethod, userData);
}
///
- /// Send data to peer (channel - 0)
+ /// Send data to peer with delivery event called
///
/// Data
- /// Send options (reliable, unreliable, etc.)
- ///
- /// If size exceeds maximum limit:
- /// MTU - headerSize bytes for Unreliable
- /// Fragment count exceeded ushort.MaxValue
- ///
- public void Send(byte[] data, DeliveryMethod deliveryMethod)
- {
- SendInternal(data, 0, data.Length, 0, deliveryMethod, null);
- }
-
- ///
- /// Send data to peer (channel - 0)
- ///
- /// DataWriter with data
- /// Send options (reliable, unreliable, etc.)
- ///
- /// If size exceeds maximum limit:
- /// MTU - headerSize bytes for Unreliable
- /// Fragment count exceeded ushort.MaxValue
+ /// Number of channel (from 0 to channelsCount - 1)
+ /// Delivery method (reliable, unreliable, etc.)
+ /// User data that will be received in DeliveryEvent
+ ///
+ /// If you trying to send unreliable packet type
///
- public void Send(NetDataWriter dataWriter, DeliveryMethod deliveryMethod)
+ public void SendWithDeliveryEvent(ReadOnlySpan data, byte channelNumber, DeliveryMethod deliveryMethod, object userData)
{
- SendInternal(dataWriter.Data, 0, dataWriter.Length, 0, deliveryMethod, null);
+ if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered)
+ throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets");
+ SendInternal(data, channelNumber, deliveryMethod, userData);
}
///
- /// Send data to peer (channel - 0)
+ /// Create temporary packet (maximum size MTU - headerSize) to send later without additional copies
///
- /// Data
- /// Start of data
- /// Length of data
- /// Send options (reliable, unreliable, etc.)
- ///
- /// If size exceeds maximum limit:
- /// MTU - headerSize bytes for Unreliable
- /// Fragment count exceeded ushort.MaxValue
- ///
- public void Send(byte[] data, int start, int length, DeliveryMethod options)
+ /// Delivery method (reliable, unreliable, etc.)
+ /// Number of channel (from 0 to channelsCount - 1)
+ /// PooledPacket that you can use to write data starting from UserDataOffset
+ public PooledPacket CreatePacketFromPool(DeliveryMethod deliveryMethod, byte channelNumber)
{
- SendInternal(data, start, length, 0, options, null);
+ //multithreaded variable
+ int mtu = Mtu;
+ var packet = NetManager.PoolGetPacket(mtu);
+ if (deliveryMethod == DeliveryMethod.Unreliable)
+ {
+ packet.Property = PacketProperty.Unreliable;
+ return new PooledPacket(packet, mtu, 0);
+ }
+ else
+ {
+ packet.Property = PacketProperty.Channeled;
+ return new PooledPacket(packet, mtu, (byte)(channelNumber * NetConstants.ChannelTypeCount + (byte)deliveryMethod));
+ }
}
///
@@ -532,15 +149,13 @@ public void Send(byte[] data, int start, int length, DeliveryMethod options)
/// MTU - headerSize bytes for Unreliable
/// Fragment count exceeded ushort.MaxValue
///
- public void Send(byte[] data, byte channelNumber, DeliveryMethod deliveryMethod)
- {
- SendInternal(data, 0, data.Length, channelNumber, deliveryMethod, null);
- }
+ public void Send(byte[] data, byte channelNumber, DeliveryMethod deliveryMethod) =>
+ SendInternal(new ReadOnlySpan(data, 0, data.Length), channelNumber, deliveryMethod, null);
///
/// Send data to peer
///
- /// DataWriter with data
+ /// Data
/// Number of channel (from 0 to channelsCount - 1)
/// Send options (reliable, unreliable, etc.)
///
@@ -548,10 +163,8 @@ public void Send(byte[] data, byte channelNumber, DeliveryMethod deliveryMethod)
/// MTU - headerSize bytes for Unreliable
/// Fragment count exceeded ushort.MaxValue
///
- public void Send(NetDataWriter dataWriter, byte channelNumber, DeliveryMethod deliveryMethod)
- {
- SendInternal(dataWriter.Data, 0, dataWriter.Length, channelNumber, deliveryMethod, null);
- }
+ public void Send(ReadOnlySpan data, byte channelNumber, DeliveryMethod deliveryMethod) =>
+ SendInternal(data, channelNumber, deliveryMethod, null);
///
/// Send data to peer
@@ -566,833 +179,89 @@ public void Send(NetDataWriter dataWriter, byte channelNumber, DeliveryMethod de
/// MTU - headerSize bytes for Unreliable
/// Fragment count exceeded ushort.MaxValue
///
- public void Send(byte[] data, int start, int length, byte channelNumber, DeliveryMethod deliveryMethod)
- {
- SendInternal(data, start, length, channelNumber, deliveryMethod, null);
- }
+ public void Send(byte[] data, int start, int length, byte channelNumber, DeliveryMethod deliveryMethod) =>
+ SendInternal(new ReadOnlySpan(data, start, length), channelNumber, deliveryMethod, null);
- private void SendInternal(
- byte[] data,
- int start,
- int length,
- byte channelNumber,
- DeliveryMethod deliveryMethod,
- object userData)
+ ///
+ /// Returns packets count in queue for reliable channel
+ ///
+ /// number of channel 0-63
+ /// type of channel ReliableOrdered or ReliableUnordered
+ /// packets count in channel queue
+ public int GetPacketsCountInReliableQueue(byte channelNumber, bool ordered)
{
- if (_connectionState != ConnectionState.Connected || channelNumber >= _channels.Length)
- return;
+ int idx = channelNumber * NetConstants.ChannelTypeCount +
+ (byte) (ordered ? DeliveryMethod.ReliableOrdered : DeliveryMethod.ReliableUnordered);
+ return ((ReliableChannel)_channels[idx])?.PacketsInQueue ?? 0;
+ }
- //Select channel
- PacketProperty property;
- BaseChannel channel = null;
-
- if (deliveryMethod == DeliveryMethod.Unreliable)
- {
- property = PacketProperty.Unreliable;
- }
- else
- {
- property = PacketProperty.Channeled;
- channel = CreateChannel((byte)(channelNumber * NetConstants.ChannelTypeCount + (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 or ReliableSequenced packet size exceeded maximum of " + (mtu - headerSize) + " bytes, Check allowed size by GetMaxSinglePacketSize()");
-
- int packetFullSize = mtu - headerSize;
- int packetDataSize = packetFullSize - NetConstants.FragmentHeaderSize;
- int totalPackets = length / packetDataSize + (length % packetDataSize == 0 ? 0 : 1);
-
- NetDebug.Write($@"FragmentSend:
- MTU: {mtu}
- headerSize: {headerSize}
- packetFullSize: {packetFullSize}
- packetDataSize: {packetDataSize}
- totalPackets: {totalPackets}");
-
- if (totalPackets > ushort.MaxValue)
- throw new TooBigPacketException("Data was split in " + totalPackets + " fragments, which exceeds " + ushort.MaxValue);
-
- ushort currentFragmentId = (ushort)Interlocked.Increment(ref _fragmentId);
-
- for(ushort partIdx = 0; partIdx < totalPackets; partIdx++)
- {
- int sendLength = length > packetDataSize ? packetDataSize : length;
-
- NetPacket p = NetManager.PoolGetPacket(headerSize + sendLength + NetConstants.FragmentHeaderSize);
- p.Property = property;
- p.UserData = userData;
- p.FragmentId = currentFragmentId;
- p.FragmentPart = partIdx;
- p.FragmentsTotal = (ushort)totalPackets;
- p.MarkFragmented();
-
- Buffer.BlockCopy(data, start + partIdx * packetDataSize, p.RawData, NetConstants.FragmentedHeaderTotalSize, sendLength);
- channel.AddToQueue(p);
-
- length -= sendLength;
- }
- return;
- }
-
- //Else just send
- NetPacket packet = NetManager.PoolGetPacket(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);
- }
- }
-
-#if LITENETLIB_SPANS || NETCOREAPP2_1_OR_GREATER || NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1 || NETCOREAPP3_1 || NET5_0 || NETSTANDARD2_1
///
- /// Send data to peer with delivery event called
+ /// Returns packets count in queue for reliable channel 0
///
- /// Data
- /// Number of channel (from 0 to channelsCount - 1)
- /// Delivery method (reliable, unreliable, etc.)
- /// User data that will be received in DeliveryEvent
- ///
- /// If you trying to send unreliable packet type
- ///
- public void SendWithDeliveryEvent(ReadOnlySpan 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, channelNumber, deliveryMethod, userData);
- }
-
- ///
- /// Send data to peer (channel - 0)
- ///
- /// Data
- /// Send options (reliable, unreliable, etc.)
- ///
- /// If size exceeds maximum limit:
- /// MTU - headerSize bytes for Unreliable
- /// Fragment count exceeded ushort.MaxValue
- ///
- public void Send(ReadOnlySpan data, DeliveryMethod deliveryMethod)
- {
- SendInternal(data, 0, deliveryMethod, null);
- }
-
- ///
- /// Send data to peer
- ///
- /// Data
- /// Number of channel (from 0 to channelsCount - 1)
- /// Send options (reliable, unreliable, etc.)
- ///
- /// If size exceeds maximum limit:
- /// MTU - headerSize bytes for Unreliable
- /// Fragment count exceeded ushort.MaxValue
- ///
- public void Send(ReadOnlySpan data, byte channelNumber, DeliveryMethod deliveryMethod)
- {
- SendInternal(data, channelNumber, deliveryMethod, null);
- }
+ /// type of channel ReliableOrdered or ReliableUnordered
+ /// packets count in channel queue
+ public new int GetPacketsCountInReliableQueue(bool ordered) =>
+ GetPacketsCountInReliableQueue(0, ordered);
- private void SendInternal(
- ReadOnlySpan data,
- byte channelNumber,
- DeliveryMethod deliveryMethod,
- object userData)
+ protected override void UpdateChannels()
{
- 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 * NetConstants.ChannelTypeCount + (byte)deliveryMethod));
- }
-
- //Prepare
- NetDebug.Write("[RS]Packet: " + property);
-
- //Check fragmentation
- int headerSize = NetPacket.GetHeaderSize(property);
- //Save mtu for multithread
- int mtu = _mtu;
- int length = data.Length;
- if (length + headerSize > mtu)
- {
- //if cannot be fragmented
- if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered)
- throw new TooBigPacketException("Unreliable or ReliableSequenced packet size exceeded maximum of " + (mtu - headerSize) + " bytes, Check allowed size by GetMaxSinglePacketSize()");
-
- int packetFullSize = mtu - headerSize;
- int packetDataSize = packetFullSize - NetConstants.FragmentHeaderSize;
- int totalPackets = length / packetDataSize + (length % packetDataSize == 0 ? 0 : 1);
-
- if (totalPackets > ushort.MaxValue)
- throw new TooBigPacketException("Data was split in " + totalPackets + " fragments, which exceeds " + ushort.MaxValue);
-
- ushort currentFragmentId = (ushort)Interlocked.Increment(ref _fragmentId);
-
- for (ushort partIdx = 0; partIdx < totalPackets; partIdx++)
- {
- int sendLength = length > packetDataSize ? packetDataSize : length;
-
- NetPacket p = NetManager.PoolGetPacket(headerSize + sendLength + NetConstants.FragmentHeaderSize);
- p.Property = property;
- p.UserData = userData;
- p.FragmentId = currentFragmentId;
- p.FragmentPart = partIdx;
- p.FragmentsTotal = (ushort)totalPackets;
- p.MarkFragmented();
-
- data.Slice(partIdx * packetDataSize, sendLength).CopyTo(new Span(p.RawData, NetConstants.FragmentedHeaderTotalSize, sendLength));
- channel.AddToQueue(p);
-
- length -= sendLength;
- }
+ //Pending send
+ if (_channelSendQueue.IsEmpty)
return;
- }
-
- //Else just send
- NetPacket packet = NetManager.PoolGetPacket(headerSize + length);
- packet.Property = property;
- data.CopyTo(new Span(packet.RawData, headerSize, length));
- packet.UserData = userData;
-
- if (channel == null) //unreliable
- {
- lock(_unreliableChannel)
- _unreliableChannel.Enqueue(packet);
- }
- else
- {
- channel.AddToQueue(packet);
- }
- }
-#endif
-
- 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 void AddToReliableChannelSendQueue(BaseChannel channel)
- {
- _channelSendQueue.Enqueue(channel);
- }
-
- 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, _remoteEndPoint);
- 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)
+ int count = _channelSendQueue.Count;
+ while (count-- > 0)
{
- NetDebug.Write($"Fragment. Id: {p.FragmentId}, Part: {p.FragmentPart}, Total: {p.FragmentsTotal}");
- //Get needed array from dictionary
- ushort packetFragId = p.FragmentId;
- byte packetChannelId = p.ChannelId;
- if (!_holdedFragments.TryGetValue(packetFragId, out var 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)
- {
- NetManager.PoolRecycle(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 = NetManager.PoolGetPacket(incomingFragments.TotalSize);
-
- int pos = 0;
- for (int i = 0; i < incomingFragments.ReceivedCount; i++)
+ if (!_channelSendQueue.TryDequeue(out var channel))
+ break;
+ if (channel.SendAndCheckQueue())
{
- var fragment = fragments[i];
- int writtenSize = fragment.Size - NetConstants.FragmentedHeaderTotalSize;
-
- if (pos+writtenSize > resultingPacket.RawData.Length)
- {
- _holdedFragments.Remove(packetFragId);
- NetDebug.WriteError($"Fragment error pos: {pos + writtenSize} >= resultPacketSize: {resultingPacket.RawData.Length} , totalSize: {incomingFragments.TotalSize}");
- return;
- }
- if (fragment.Size > fragment.RawData.Length)
- {
- _holdedFragments.Remove(packetFragId);
- NetDebug.WriteError($"Fragment error size: {fragment.Size} > fragment.RawData.Length: {fragment.RawData.Length}");
- return;
- }
-
- //Create resulting big packet
- Buffer.BlockCopy(
- fragment.RawData,
- NetConstants.FragmentedHeaderTotalSize,
- resultingPacket.RawData,
- pos,
- writtenSize);
- pos += writtenSize;
-
- //Free memory
- NetManager.PoolRecycle(fragment);
- fragments[i] = null;
+ // still has something to send, re-add it to the send queue
+ _channelSendQueue.Enqueue(channel);
}
-
- //Clear memory
- _holdedFragments.Remove(packetFragId);
-
- //Send to process
- NetManager.CreateReceiveEvent(resultingPacket, method, (byte)(packetChannelId / NetConstants.ChannelTypeCount), 0, this);
- }
- else //Just simple packet
- {
- NetManager.CreateReceiveEvent(p, method, (byte)(p.ChannelId / NetConstants.ChannelTypeCount), NetConstants.ChanneledHeaderSize, this);
}
}
- private void ProcessMtuPacket(NetPacket packet)
+ internal override void ProcessChanneled(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)
+ if (packet.ChannelId >= _channels.Length)
{
- NetDebug.WriteError($"[MTU] Broken packet. RMTU {receivedMtu}, EMTU {endMtuCheck}, PSIZE {packet.Size}");
- return;
- }
-
- if (packet.Property == PacketProperty.MtuCheck)
- {
- _mtuCheckAttempts = 0;
- NetDebug.Write("[MTU] check. send back: " + receivedMtu);
- packet.Property = PacketProperty.MtuOk;
- NetManager.SendRawAndRecycle(packet, _remoteEndPoint);
- }
- else if(receivedMtu > _mtu && !_finishMtu) //MtuOk
- {
- //invalid packet
- if (receivedMtu != NetConstants.PossibleMtu[_mtuIdx + 1] - NetManager.ExtraPacketSizeForLayer)
- return;
-
- lock (_mtuMutex)
- {
- SetMtu(_mtuIdx+1);
- }
- //if maxed - finish.
- if (_mtuIdx == NetConstants.PossibleMtu.Length - 1)
- _finishMtu = true;
NetManager.PoolRecycle(packet);
- 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] - NetManager.ExtraPacketSizeForLayer;
- var p = NetManager.PoolGetPacket(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, _remoteEndPoint) <= 0)
- _finishMtu = true;
}
+ var channel = _channels[packet.ChannelId] ?? (packet.Property == PacketProperty.Ack ? null : CreateChannel(packet.ChannelId));
+ if (channel != null && !channel.ProcessPacket(packet))
+ NetManager.PoolRecycle(packet);
}
- 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
- if (connRequest.ConnectionTime == _connectTime)
- {
- var remoteBytes = _remoteEndPoint.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, _remoteEndPoint);
- }
- //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;
- }
+ internal override void AddToReliableChannelSendQueue(BaseChannel channel) =>
+ _channelSendQueue.Enqueue(channel);
- //Process incoming packet
- internal void ProcessPacket(NetPacket packet)
+ internal override BaseChannel CreateChannel(byte idx)
{
- //not initialized
- if (_connectionState == ConnectionState.Outgoing || _connectionState == ConnectionState.Disconnected)
- {
- NetManager.PoolRecycle(packet);
- return;
- }
- if (packet.Property == PacketProperty.ShutdownOk)
- {
- if (_connectionState == ConnectionState.ShutdownRequested)
- _connectionState = ConnectionState.Disconnected;
- NetManager.PoolRecycle(packet);
- return;
- }
- if (packet.ConnectionNumber != _connectNum)
- {
- NetDebug.Write(NetLogLevel.Trace, "[RR]Old packet");
- NetManager.PoolRecycle(packet);
- return;
- }
- Interlocked.Exchange(ref _timeSinceLastPacket, 0);
-
- NetDebug.Write($"[RR]PacketProperty: {packet.Property}");
- switch (packet.Property)
+ var newChannel = _channels[idx];
+ if (newChannel != null)
+ return newChannel;
+ switch ((DeliveryMethod)(idx % NetConstants.ChannelTypeCount))
{
- case PacketProperty.Merged:
- int pos = NetConstants.HeaderSize;
- while (pos < packet.Size)
- {
- ushort size = BitConverter.ToUInt16(packet.RawData, pos);
- pos += 2;
- if (packet.RawData.Length - pos < size)
- break;
-
- NetPacket mergedPacket = NetManager.PoolGetPacket(size);
- Buffer.BlockCopy(packet.RawData, pos, mergedPacket.RawData, 0, size);
- mergedPacket.Size = size;
-
- if (!mergedPacket.Verify())
- break;
-
- pos += size;
- ProcessPacket(mergedPacket);
- }
- NetManager.PoolRecycle(packet);
- 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, _remoteEndPoint);
- }
- NetManager.PoolRecycle(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: {packet.Sequence} - {elapsedMs} - {_remoteDelta}");
- }
- NetManager.PoolRecycle(packet);
- break;
-
- case PacketProperty.Ack:
- case PacketProperty.Channeled:
- if (packet.ChannelId > _channels.Length)
- {
- NetManager.PoolRecycle(packet);
- break;
- }
- var channel = _channels[packet.ChannelId] ?? (packet.Property == PacketProperty.Ack ? null : CreateChannel(packet.ChannelId));
- if (channel != null)
- {
- if (!channel.ProcessPacket(packet))
- NetManager.PoolRecycle(packet);
- }
- break;
-
- //Simple packet without acks
- case PacketProperty.Unreliable:
- NetManager.CreateReceiveEvent(packet, DeliveryMethod.Unreliable, 0, NetConstants.HeaderSize, this);
- return;
-
- case PacketProperty.MtuCheck:
- case PacketProperty.MtuOk:
- ProcessMtuPacket(packet);
+ case DeliveryMethod.ReliableUnordered:
+ newChannel = new ReliableChannel(this, false, idx);
break;
-
- default:
- NetDebug.WriteError("Error! Unexpected packet type: " + packet.Property);
+ case DeliveryMethod.Sequenced:
+ newChannel = new SequencedChannel(this, false, idx);
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, _remoteEndPoint);
- }
- else
- {
- //Send without length information and merging
- bytesSent = NetManager.SendRaw(_mergeData.RawData, NetConstants.HeaderSize + 2, _mergePos - 2, _remoteEndPoint);
- }
-
- if (NetManager.EnableStatistics)
- {
- Statistics.IncrementPacketsSent();
- Statistics.AddBytesSent(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, _remoteEndPoint);
-
- if (NetManager.EnableStatistics)
- {
- Statistics.IncrementPacketsSent();
- Statistics.AddBytesSent(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);
- }
-
- 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: {_timeSinceLastPacket} > {NetManager.DisconnectTimeout}");
- NetManager.DisconnectPeerForce(this, DisconnectReason.Timeout, 0, null);
- return;
- }
+ case DeliveryMethod.ReliableOrdered:
+ newChannel = new ReliableChannel(this, true, idx);
break;
-
- case ConnectionState.ShutdownRequested:
- if (_timeSinceLastPacket > NetManager.DisconnectTimeout)
- {
- _connectionState = ConnectionState.Disconnected;
- }
- else
- {
- _shutdownTimer += deltaTime;
- if (_shutdownTimer >= ShutdownDelay)
- {
- _shutdownTimer = 0;
- NetManager.SendRaw(_shutdownPacket, _remoteEndPoint);
- }
- }
- 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, _remoteEndPoint);
- }
- 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.Restart();
- NetManager.SendRaw(_pingPacket, _remoteEndPoint);
- }
-
- //RTT - round trip time
- _rttResetTimer += deltaTime;
- if (_rttResetTimer >= NetManager.PingInterval * 3)
- {
- _rttResetTimer = 0;
- _rtt = _avgRtt;
- _rttCount = 1;
- }
-
- UpdateMtuLogic(deltaTime);
-
- //Pending send
- int count = _channelSendQueue.Count;
- while (count-- > 0)
- {
- if (!_channelSendQueue.TryDequeue(out var channel))
+ case DeliveryMethod.ReliableSequenced:
+ newChannel = new SequencedChannel(this, true, idx);
break;
- if (channel.SendAndCheckQueue())
- {
- // still has something to send, re-add it to the send queue
- _channelSendQueue.Enqueue(channel);
- }
- }
-
- lock (_unreliableChannel)
- {
- int unreliableCount = _unreliableChannel.Count;
- for (int i = 0; i < unreliableCount; i++)
- {
- var packet = _unreliableChannel.Dequeue();
- SendUserData(packet);
- NetManager.PoolRecycle(packet);
- }
}
+ var prevChannel = Interlocked.CompareExchange(ref _channels[idx], newChannel, null);
+ if (prevChannel != null)
+ return prevChannel;
- SendMerged();
- }
-
- //For reliable channel
- internal void RecycleAndDeliver(NetPacket packet)
- {
- if (packet.UserData != null)
- {
- if (packet.IsFragmented)
- {
- _deliveredFragments.TryGetValue(packet.FragmentId, out ushort fragCount);
- fragCount++;
- if (fragCount == packet.FragmentsTotal)
- {
- NetManager.MessageDelivered(this, packet.UserData);
- _deliveredFragments.Remove(packet.FragmentId);
- }
- else
- {
- _deliveredFragments[packet.FragmentId] = fragCount;
- }
- }
- else
- {
- NetManager.MessageDelivered(this, packet.UserData);
- }
- packet.UserData = null;
- }
- NetManager.PoolRecycle(packet);
+ return newChannel;
}
}
}
diff --git a/LiteNetLib/NetStatistics.cs b/LiteNetLib/NetStatistics.cs
index 032e2754..e2d0304c 100644
--- a/LiteNetLib/NetStatistics.cs
+++ b/LiteNetLib/NetStatistics.cs
@@ -2,6 +2,9 @@
namespace LiteNetLib
{
+ ///
+ /// Thread-safe counter for network statistics including sent/received packets, bytes, and packet loss.
+ ///
public sealed class NetStatistics
{
private long _packetsSent;
@@ -10,12 +13,35 @@ public sealed class NetStatistics
private long _bytesReceived;
private long _packetLoss;
+ ///
+ /// Total number of packets sent.
+ ///
public long PacketsSent => Interlocked.Read(ref _packetsSent);
+
+ ///
+ /// Total number of packets received.
+ ///
public long PacketsReceived => Interlocked.Read(ref _packetsReceived);
+
+ ///
+ /// Total number of bytes sent.
+ ///
public long BytesSent => Interlocked.Read(ref _bytesSent);
+
+ ///
+ /// Total number of bytes received.
+ ///
public long BytesReceived => Interlocked.Read(ref _bytesReceived);
+
+ ///
+ /// Total number of packets lost during transmission.
+ ///
public long PacketLoss => Interlocked.Read(ref _packetLoss);
+ ///
+ /// Percentage of sent packets that were lost.
+ /// Calculated as (PacketLoss * 100) / PacketsSent.
+ ///
public long PacketLossPercent
{
get
@@ -26,6 +52,9 @@ public long PacketLossPercent
}
}
+ ///
+ /// Resets all statistical counters to zero.
+ ///
public void Reset()
{
Interlocked.Exchange(ref _packetsSent, 0);
@@ -35,36 +64,49 @@ public void Reset()
Interlocked.Exchange(ref _packetLoss, 0);
}
- public void IncrementPacketsSent()
- {
+ ///
+ /// Increments the count of sent packets by one.
+ ///
+ public void IncrementPacketsSent() =>
Interlocked.Increment(ref _packetsSent);
- }
- public void IncrementPacketsReceived()
- {
+ ///
+ /// Increments the count of received packets by one.
+ ///
+ public void IncrementPacketsReceived() =>
Interlocked.Increment(ref _packetsReceived);
- }
- public void AddBytesSent(long bytesSent)
- {
+ ///
+ /// Adds a specific amount to the total bytes sent.
+ ///
+ /// Number of bytes to add.
+ public void AddBytesSent(long bytesSent) =>
Interlocked.Add(ref _bytesSent, bytesSent);
- }
- public void AddBytesReceived(long bytesReceived)
- {
+ ///
+ /// Adds a specific amount to the total bytes received.
+ ///
+ /// Number of bytes to add.
+ public void AddBytesReceived(long bytesReceived) =>
Interlocked.Add(ref _bytesReceived, bytesReceived);
- }
- public void IncrementPacketLoss()
- {
+ ///
+ /// Increments the count of lost packets by one.
+ ///
+ public void IncrementPacketLoss() =>
Interlocked.Increment(ref _packetLoss);
- }
- public void AddPacketLoss(long packetLoss)
- {
+ ///
+ /// Adds a specific amount to the total packet loss count.
+ ///
+ /// Number of lost packets to add.
+ public void AddPacketLoss(long packetLoss) =>
Interlocked.Add(ref _packetLoss, packetLoss);
- }
+ ///
+ /// Returns a string representation of the current network statistics.
+ ///
+ /// A formatted string containing bytes received/sent, packets received/sent, and loss information.
public override string ToString()
{
return
diff --git a/LiteNetLib/NetUtils.cs b/LiteNetLib/NetUtils.cs
index f7b2bd82..593c2f2a 100644
--- a/LiteNetLib/NetUtils.cs
+++ b/LiteNetLib/NetUtils.cs
@@ -25,11 +25,25 @@ public static class NetUtils
{
private static readonly NetworkSorter NetworkSorter = new NetworkSorter();
- public static IPEndPoint MakeEndPoint(string hostStr, int port)
- {
- return new IPEndPoint(ResolveAddress(hostStr), port);
- }
+ ///
+ /// Creates an from a host string and a port.
+ ///
+ /// The host name or IP address string to resolve.
+ /// The port number for the endpoint.
+ /// A new instance.
+ public static IPEndPoint MakeEndPoint(string hostStr, int port) =>
+ new IPEndPoint(ResolveAddress(hostStr), port);
+ ///
+ /// Resolves a host string into an .
+ ///
+ ///
+ /// This method handles "localhost" specifically, attempts to parse the string as a direct IP,
+ /// and falls back to DNS resolution. It prioritizes IPv6 if is enabled.
+ ///
+ /// The host name or IP address string (e.g., "127.0.0.1", "localhost", or "google.com").
+ /// The resolved .
+ /// Thrown when the address cannot be resolved or is invalid.
public static IPAddress ResolveAddress(string hostStr)
{
if(hostStr == "localhost")
@@ -37,7 +51,7 @@ public static IPAddress ResolveAddress(string hostStr)
if (!IPAddress.TryParse(hostStr, out var ipAddress))
{
- if (NetManager.IPv6Support)
+ if (LiteNetManager.IPv6Support)
ipAddress = ResolveAddress(hostStr, AddressFamily.InterNetworkV6);
if (ipAddress == null)
ipAddress = ResolveAddress(hostStr, AddressFamily.InterNetwork);
@@ -48,6 +62,12 @@ public static IPAddress ResolveAddress(string hostStr)
return ipAddress;
}
+ ///
+ /// Resolves a host string using DNS for a specific .
+ ///
+ /// The host name to resolve via DNS.
+ /// The preferred address family (e.g., or ).
+ /// The first matching the family, or if no match is found.
public static IPAddress ResolveAddress(string hostStr, AddressFamily addressFamily)
{
IPAddress[] addresses = Dns.GetHostEntry(hostStr).AddressList;
@@ -158,7 +178,7 @@ public static string GetLocalIp(LocalAddrType addrType)
// ===========================================
internal static void PrintInterfaceInfos()
{
- NetDebug.WriteForce(NetLogLevel.Info, $"IPv6Support: { NetManager.IPv6Support}");
+ NetDebug.WriteForce(NetLogLevel.Info, $"IPv6Support: { LiteNetManager.IPv6Support}");
try
{
foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces())
@@ -181,10 +201,8 @@ internal static void PrintInterfaceInfos()
}
}
- internal static int RelativeSequenceNumber(int number, int expected)
- {
- return (number - expected + NetConstants.MaxSequence + NetConstants.HalfMaxSequence) % NetConstants.MaxSequence - NetConstants.HalfMaxSequence;
- }
+ internal static int RelativeSequenceNumber(int number, int expected) =>
+ (number - expected + NetConstants.MaxSequence + NetConstants.HalfMaxSequence) % NetConstants.MaxSequence - NetConstants.HalfMaxSequence;
internal static T[] AllocatePinnedUninitializedArray(int count) where T : unmanaged
{
diff --git a/LiteNetLib/PausedSocketFix.cs b/LiteNetLib/PausedSocketFix.cs
index dc92122a..a8947348 100644
--- a/LiteNetLib/PausedSocketFix.cs
+++ b/LiteNetLib/PausedSocketFix.cs
@@ -1,32 +1,42 @@
#if UNITY_2018_3_OR_NEWER
using System.Net;
+using UnityEngine;
namespace LiteNetLib
{
public class PausedSocketFix
{
- private readonly NetManager _netManager;
+ private readonly LiteNetManager _netManager;
private readonly IPAddress _ipv4;
private readonly IPAddress _ipv6;
private readonly int _port;
private readonly bool _manualMode;
private bool _initialized;
- public PausedSocketFix(NetManager netManager, IPAddress ipv4, IPAddress ipv6, int port, bool manualMode)
+ public PausedSocketFix(LiteNetManager netManager, IPAddress ipv4, IPAddress ipv6, int port, bool manualMode)
{
_netManager = netManager;
_ipv4 = ipv4;
_ipv6 = ipv6;
_port = port;
_manualMode = manualMode;
- UnityEngine.Application.focusChanged += Application_focusChanged;
+ Application.focusChanged += Application_focusChanged;
+ Application.quitting += Deinitialize;
_initialized = true;
}
public void Deinitialize()
{
if (_initialized)
- UnityEngine.Application.focusChanged -= Application_focusChanged;
+ {
+ Application.focusChanged -= Application_focusChanged;
+ Application.quitting -= Deinitialize;
+ }
+
+ if (_netManager.IsRunning)
+ {
+ _netManager.Stop();
+ }
_initialized = false;
}
diff --git a/LiteNetLib/PooledPacket.cs b/LiteNetLib/PooledPacket.cs
index 26ef7bd8..927b0335 100644
--- a/LiteNetLib/PooledPacket.cs
+++ b/LiteNetLib/PooledPacket.cs
@@ -23,7 +23,7 @@ public readonly ref struct PooledPacket
internal PooledPacket(NetPacket packet, int maxDataSize, byte channelNumber)
{
_packet = packet;
- UserDataOffset = _packet.GetHeaderSize();
+ UserDataOffset = _packet.HeaderSize;
_packet.Size = UserDataOffset;
MaxUserDataSize = maxDataSize - UserDataOffset;
_channelNumber = channelNumber;
diff --git a/LiteNetLib/ReliableChannel.cs b/LiteNetLib/ReliableChannel.cs
index 4a10d170..8aa49851 100644
--- a/LiteNetLib/ReliableChannel.cs
+++ b/LiteNetLib/ReliableChannel.cs
@@ -1,19 +1,33 @@
using System;
+using System.Collections.Generic;
+using LiteNetLib.Utils;
namespace LiteNetLib
{
+ internal sealed class MergedPacketUserData
+ {
+ public readonly object[] Items;
+
+ public MergedPacketUserData(object[] items)
+ {
+ Items = items;
+ }
+ }
+
internal sealed class ReliableChannel : BaseChannel
{
+ [ThreadStatic]
+ private static List