diff --git a/.editorconfig b/.editorconfig
index 65651a6c..7fb304be 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,4 +1,12 @@
root = true
+
[*]
indent_style = space
-indent_size = 4
\ No newline at end of file
+indent_size = 4
+
+[*.cs]
+charset = utf-8-bom
+trim_trailing_whitespace = true
+insert_final_newline = true
+# IDE0032: Use auto property
+dotnet_style_prefer_auto_properties = true:none
diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 00000000..4973482b
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,12 @@
+# These are supported funding model platforms
+
+github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+patreon: # Replace with a single Patreon username
+open_collective: # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+custom: ['https://www.paypal.com/donate/?business=G9PDX5KLGKBA2&no_recurring=0¤cy_code=USD']
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/.travis.yml b/.travis.yml
deleted file mode 100644
index 99393544..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-language: csharp
-solution: LiteNetLib.sln
-install:
- - nuget restore LiteNetLib.sln
-script:
- - xbuild /p:Configuration=Release /target:"LiteNetLib;LibSample" LiteNetLib.sln
-notifications:
- email: false
\ No newline at end of file
diff --git a/LICENSE.txt b/LICENSE.txt
index a2f4003a..ec1bddf3 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2019 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/LNL.png b/LNL.png
new file mode 100644
index 00000000..33aedfff
Binary files /dev/null and b/LNL.png differ
diff --git a/LNL.svg b/LNL.svg
new file mode 100644
index 00000000..3a8ef232
--- /dev/null
+++ b/LNL.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/LibSample/AesEncryptLayer.cs b/LibSample/AesEncryptLayer.cs
new file mode 100644
index 00000000..1df94508
--- /dev/null
+++ b/LibSample/AesEncryptLayer.cs
@@ -0,0 +1,110 @@
+using LiteNetLib.Layers;
+using System;
+using System.Net;
+using System.Security.Cryptography;
+
+namespace LibSample
+{
+ ///
+ /// Uses AES encryption in CBC mode. Make sure you handle your key properly.
+ /// GCHandle.Alloc(key, GCHandleType.Pinned) to avoid your key being moved around different memory segments.
+ /// ZeroMemory(gch.AddrOfPinnedObject(), key.Length); to erase it when you are done.
+ /// Speed varies greatly depending on hardware encryption support.
+ /// Why encrypt: http://ithare.com/udp-for-games-security-encryption-and-ddos-protection/
+ ///
+ public class AesEncryptLayer : PacketLayerBase
+ {
+ public const int KeySize = 256;
+ public const int BlockSize = 128;
+ public const int KeySizeInBytes = KeySize / 8;
+ public const int BlockSizeInBytes = BlockSize / 8;
+
+ private readonly Aes _aes;
+ private ICryptoTransform _encryptor;
+ private byte[] cipherBuffer = new byte[1500]; //Max possible UDP packet size
+ private ICryptoTransform _decryptor;
+ private byte[] ivBuffer = new byte[BlockSizeInBytes];
+
+ ///
+ /// Should be safe against eavesdropping, but is vulnerable to tampering
+ /// Needs a HMAC on top of the encrypted content to be fully safe
+ ///
+ ///
+ ///
+ 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 = Aes.Create();
+ _aes.KeySize = KeySize;
+ _aes.BlockSize = BlockSize;
+ _aes.Key = key;
+ _aes.Mode = CipherMode.CBC;
+ _aes.Padding = PaddingMode.PKCS7;
+
+ _encryptor = _aes.CreateEncryptor();
+ _decryptor = _aes.CreateDecryptor();
+ }
+
+ 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, 0, ivBuffer, 0, ivBuffer.Length);
+ //_aes.IV = ivBuffer;
+ _decryptor = _aes.CreateDecryptor(_aes.Key, ivBuffer);
+
+ //int currentRead = ivBuffer.Length;
+ //int currentWrite = 0;
+
+ //TransformBlocks(_decryptor, data, length, ref currentRead, ref currentWrite);
+ byte[] lastBytes = _decryptor.TransformFinalBlock(data, BlockSizeInBytes, length - BlockSizeInBytes);
+
+ data = lastBytes;
+ length = lastBytes.Length;
+ }
+
+ public override void ProcessOutBoundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int offset, ref int length)
+ {
+ //Some Unity platforms may need these (and will be slower + generate garbage)
+ if (!_encryptor.CanReuseTransform)
+ {
+ _aes.GenerateIV();
+ _encryptor = _aes.CreateEncryptor();
+ }
+
+ //Copy IV in plaintext to output, this is standard practice
+ _aes.IV.CopyTo(cipherBuffer, 0);
+
+ int currentRead = offset;
+ int currentWrite = _aes.IV.Length;
+ byte[] lastBytes = _encryptor.TransformFinalBlock(data, currentRead, length - offset);
+ lastBytes.CopyTo(cipherBuffer, currentWrite);
+ //TransformBlocks(_encryptor, data, length, ref currentRead, ref currentWrite);
+
+ data = cipherBuffer;
+ offset = 0;
+ length = lastBytes.Length + BlockSizeInBytes;
+ }
+
+ private void TransformBlocks(ICryptoTransform transform, byte[] input, int inputLength, ref int currentRead, ref int currentWrite)
+ {
+ //This loop produces a invalid padding exception
+ //I'm leaving it here as a start point in case others need support for
+ //Platforms wheere !transfom.CanTransformMultipleBlocks
+ if (!transform.CanTransformMultipleBlocks)
+ {
+ while (inputLength - currentRead > BlockSizeInBytes)
+ {
+ int encryptedCount = transform.TransformBlock(input, currentRead, BlockSizeInBytes, cipherBuffer, currentWrite);
+ currentRead += encryptedCount;
+ currentWrite += encryptedCount;
+ }
+ }
+
+ byte[] lastBytes = transform.TransformFinalBlock(input, currentRead, inputLength - currentRead);
+ lastBytes.CopyTo(cipherBuffer, currentWrite);
+ currentWrite += lastBytes.Length;
+ }
+ }
+}
diff --git a/LibSample/AesEncryptionTest.cs b/LibSample/AesEncryptionTest.cs
new file mode 100644
index 00000000..e46e249d
--- /dev/null
+++ b/LibSample/AesEncryptionTest.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Linq;
+using System.Net;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace LibSample
+{
+ class AesEncryptionTest : IExample
+ {
+ public void Run() => AesLayerEncryptDecrypt();
+
+ private void AesLayerEncryptDecrypt()
+ {
+ IPEndPoint emptyEndPoint = null;
+
+ var keyGen = RandomNumberGenerator.Create();
+ byte[] key = new byte[AesEncryptLayer.KeySizeInBytes];
+ keyGen.GetBytes(key);
+ const string testData = "This text is long enough to need multiple blocks to encrypt";
+
+ var outboudLayer = new AesEncryptLayer(key);
+ byte[] outbound = Encoding.ASCII.GetBytes(testData);
+ int lengthOfPacket = outbound.Length;
+ int start = 0;
+ int length = outbound.Length;
+ outboudLayer.ProcessOutBoundPacket(ref emptyEndPoint, ref outbound, ref start, ref length);
+
+ int minLenth = lengthOfPacket + AesEncryptLayer.BlockSizeInBytes;
+ int maxLength = lengthOfPacket + outboudLayer.ExtraPacketSizeForLayer;
+ if (length < minLenth || length > maxLength)
+ {
+ throw new Exception("Packet length out of bounds");
+ }
+
+ var inboundLayer = new AesEncryptLayer(key);
+ //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 length);
+
+ Console.WriteLine(Encoding.ASCII.GetString(inboundData, 0, length));
+ byte[] expectedPlaintext = Encoding.ASCII.GetBytes(testData);
+
+ var isEqualLength = expectedPlaintext.Length == length;
+ var areContentEqual = expectedPlaintext.SequenceEqual(inboundData);
+ if (isEqualLength && areContentEqual)
+ {
+ Console.WriteLine("Test complete");
+ }
+ else
+ {
+ throw new Exception("Test failed, decrypted data not equal to original");
+ }
+ }
+ }
+}
diff --git a/LibSample/BroadcastTest.cs b/LibSample/BroadcastTest.cs
index 2b6973fd..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;
@@ -7,7 +8,7 @@
namespace LibSample
{
- class BroadcastTest
+ class BroadcastTest : IExample
{
private class ClientListener : INetEventListener
{
@@ -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)
@@ -28,7 +29,7 @@ public void OnNetworkError(IPEndPoint endPoint, SocketError error)
Console.WriteLine("[Client] error! " + error);
}
- public void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod)
+ public void OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channel, DeliveryMethod deliveryMethod)
{
}
@@ -50,7 +51,7 @@ public void OnNetworkLatencyUpdate(NetPeer peer, int latency)
public void OnConnectionRequest(ConnectionRequest request)
{
- request.AcceptIfKey("key");
+ request.Reject();
}
}
@@ -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.GetPeers(ConnectionState.Connected);
- 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)
@@ -78,7 +81,7 @@ public void OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode)
Console.WriteLine("[Server] error: " + socketErrorCode);
}
- public void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod)
+ public void OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod)
{
}
@@ -86,9 +89,9 @@ public void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMetho
public void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType)
{
Console.WriteLine("[Server] ReceiveUnconnected {0}. From: {1}. Data: {2}", messageType, remoteEndPoint, reader.GetString(100));
- NetDataWriter wrtier = new NetDataWriter();
- wrtier.Put("SERVER DISCOVERY RESPONSE");
- Server.SendUnconnectedMessage(wrtier, remoteEndPoint);
+ NetDataWriter writer = new NetDataWriter();
+ writer.Put("SERVER DISCOVERY RESPONSE");
+ Server.SendUnconnectedMessage(writer, remoteEndPoint);
}
public void OnNetworkLatencyUpdate(NetPeer peer, int latency)
@@ -98,7 +101,7 @@ public void OnNetworkLatencyUpdate(NetPeer peer, int latency)
public void OnConnectionRequest(ConnectionRequest request)
{
-
+ request.AcceptIfKey("key");
}
}
@@ -112,8 +115,11 @@ public void Run()
//Server
_serverListener = new ServerListener();
- NetManager server = new NetManager(_serverListener);
- server.BroadcastReceiveEnabled = true;
+ NetManager server = new NetManager(_serverListener)
+ {
+ BroadcastReceiveEnabled = true,
+ IPv6Enabled = true
+ };
if (!server.Start(9050))
{
Console.WriteLine("Server start failed");
@@ -125,10 +131,14 @@ public void Run()
//Client
_clientListener1 = new ClientListener();
- NetManager client1 = new NetManager(_clientListener1);
+ NetManager client1 = new NetManager(_clientListener1)
+ {
+ UnconnectedMessagesEnabled = true,
+ SimulateLatency = true,
+ SimulationMaxLatency = 1500,
+ IPv6Enabled = true
+ };
_clientListener1.Client = client1;
- client1.SimulateLatency = true;
- client1.SimulationMaxLatency = 1500;
if (!client1.Start())
{
Console.WriteLine("Client1 start failed");
@@ -137,10 +147,15 @@ public void Run()
}
_clientListener2 = new ClientListener();
- NetManager client2 = new NetManager(_clientListener2);
+ NetManager client2 = new NetManager(_clientListener2)
+ {
+ UnconnectedMessagesEnabled = true,
+ SimulateLatency = true,
+ SimulationMaxLatency = 1500,
+ IPv6Enabled = true
+ };
+
_clientListener2.Client = client2;
- client2.SimulateLatency = true;
- client2.SimulationMaxLatency = 1500;
client2.Start();
//Send broadcast
diff --git a/LibSample/EchoMessagesTest.cs b/LibSample/EchoMessagesTest.cs
index 5adb0755..7a8dc9b9 100644
--- a/LibSample/EchoMessagesTest.cs
+++ b/LibSample/EchoMessagesTest.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
@@ -7,7 +7,7 @@
namespace LibSample
{
- class EchoMessagesTest
+ class EchoMessagesTest : IExample
{
private static int _messagesReceivedCount = 0;
@@ -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++)
@@ -63,13 +63,13 @@ public void OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode)
Console.WriteLine("[Client] error! " + socketErrorCode);
}
- public void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod)
+ public void OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod)
{
if (reader.AvailableBytes == 13218)
{
- Console.WriteLine("[{0}] TestFrag: {1}, {2}",
- peer.NetManager.LocalPort,
- reader.RawData[reader.UserDataOffset],
+ Console.WriteLine("[{0}] TestFrag: {1}, {2}",
+ peer.NetManager.LocalPort,
+ reader.RawData[reader.UserDataOffset],
reader.RawData[reader.UserDataOffset + 13217]);
}
else
@@ -88,7 +88,7 @@ public void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketRead
public void OnNetworkLatencyUpdate(NetPeer peer, int latency)
{
-
+
}
public void OnConnectionRequest(ConnectionRequest request)
@@ -103,17 +103,17 @@ private class ServerListener : INetEventListener
public void OnPeerConnected(NetPeer peer)
{
- Console.WriteLine("[Server] Peer connected: " + peer.EndPoint);
- var peers = Server.GetPeers(ConnectionState.Connected);
- foreach (var netPeer in peers)
+ Console.WriteLine("[Server] Peer connected: " + peer);
+ foreach (var netPeer in Server)
{
- Console.WriteLine("ConnectedPeersList: id={0}, ep={1}", netPeer.Id, netPeer.EndPoint);
+ if(netPeer.ConnectionState == ConnectionState.Connected)
+ 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)
@@ -121,7 +121,7 @@ public void OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode)
Console.WriteLine("[Server] error: " + socketErrorCode);
}
- public void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod)
+ public void OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod)
{
//echo
peer.Send(reader.GetRemainingBytes(), deliveryMethod);
@@ -165,6 +165,7 @@ public void Run()
_serverListener = new ServerListener();
NetManager server = new NetManager(_serverListener);
+
if (!server.Start(Port))
{
Console.WriteLine("Server start failed");
@@ -194,7 +195,7 @@ public void Run()
//SimulateLatency = true,
SimulationMaxLatency = 1500
};
-
+
client2.Start();
client2.Connect("::1", Port, "gamekey");
@@ -210,10 +211,10 @@ public void Run()
client2.Stop();
server.Stop();
Console.ReadKey();
- Console.WriteLine("ServStats:\n BytesReceived: {0}\n PacketsReceived: {1}\n BytesSent: {2}\n PacketsSent: {3}",
- server.Statistics.BytesReceived,
- server.Statistics.PacketsReceived,
- server.Statistics.BytesSent,
+ Console.WriteLine("ServStats:\n BytesReceived: {0}\n PacketsReceived: {1}\n BytesSent: {2}\n PacketsSent: {3}",
+ server.Statistics.BytesReceived,
+ server.Statistics.PacketsReceived,
+ server.Statistics.BytesSent,
server.Statistics.PacketsSent);
Console.WriteLine("Client1Stats:\n BytesReceived: {0}\n PacketsReceived: {1}\n BytesSent: {2}\n PacketsSent: {3}",
client1.Statistics.BytesReceived,
diff --git a/LibSample/GithubSample.cs b/LibSample/GithubSample.cs
index 7b570195..0a680449 100644
--- a/LibSample/GithubSample.cs
+++ b/LibSample/GithubSample.cs
@@ -15,7 +15,7 @@ public void Server()
listener.ConnectionRequestEvent += request =>
{
- if(server.PeersCount < 10 /* max connections */)
+ if(server.ConnectedPeersCount < 10 /* max connections */)
request.AcceptIfKey("SomeConnectionKey");
else
request.Reject();
@@ -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)
@@ -44,7 +44,7 @@ public void Client()
NetManager client = new NetManager(listener);
client.Start();
client.Connect("localhost" /* host ip or name */, 9050 /* port */, "SomeConnectionKey" /* text key or NetDataWriter */);
- listener.NetworkReceiveEvent += (fromPeer, dataReader, deliveryMethod) =>
+ listener.NetworkReceiveEvent += (fromPeer, dataReader, channel, deliveryMethod) =>
{
Console.WriteLine("We got: {0}", dataReader.GetString(100 /* max length of string */));
dataReader.Recycle();
diff --git a/LibSample/HolePunchServerTest.cs b/LibSample/HolePunchServerTest.cs
index 0a4f81a0..d7564d56 100644
--- a/LibSample/HolePunchServerTest.cs
+++ b/LibSample/HolePunchServerTest.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Net;
using System.Threading;
@@ -8,13 +8,13 @@ namespace LibSample
{
class WaitPeer
{
- public IPEndPoint InternalAddr { get; private set; }
- public IPEndPoint ExternalAddr { get; private set; }
+ public IPEndPoint InternalAddr { get; }
+ public IPEndPoint ExternalAddr { get; }
public DateTime RefreshTime { get; private set; }
public void Refresh()
{
- RefreshTime = DateTime.Now;
+ RefreshTime = DateTime.UtcNow;
}
public WaitPeer(IPEndPoint internalAddr, IPEndPoint externalAddr)
@@ -25,7 +25,7 @@ public WaitPeer(IPEndPoint internalAddr, IPEndPoint externalAddr)
}
}
- class HolePunchServerTest : INatPunchListener
+ class HolePunchServerTest : IExample, INatPunchListener
{
private const int ServerPort = 50010;
private const string ConnectionKey = "test_key";
@@ -39,8 +39,7 @@ class HolePunchServerTest : INatPunchListener
void INatPunchListener.OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, string token)
{
- WaitPeer wpeer;
- if (_waitingPeers.TryGetValue(token, out wpeer))
+ if (_waitingPeers.TryGetValue(token, out var wpeer))
{
if (wpeer.InternalAddr.Equals(localEndPoint) &&
wpeer.ExternalAddr.Equals(remoteEndPoint))
@@ -77,7 +76,7 @@ void INatPunchListener.OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndP
}
}
- void INatPunchListener.OnNatIntroductionSuccess(IPEndPoint targetEndPoint, string token)
+ void INatPunchListener.OnNatIntroductionSuccess(IPEndPoint targetEndPoint, NatAddressType type, string token)
{
//Ignore we are server
}
@@ -85,22 +84,22 @@ void INatPunchListener.OnNatIntroductionSuccess(IPEndPoint targetEndPoint, strin
public void Run()
{
Console.WriteLine("=== HolePunch Test ===");
-
- EventBasedNetListener netListener = new EventBasedNetListener();
+
+ EventBasedNetListener clientListener = new EventBasedNetListener();
EventBasedNatPunchListener natPunchListener1 = new EventBasedNatPunchListener();
EventBasedNatPunchListener natPunchListener2 = new EventBasedNatPunchListener();
- netListener.PeerConnectedEvent += peer =>
+ clientListener.PeerConnectedEvent += peer =>
{
- Console.WriteLine("PeerConnected: " + peer.EndPoint.ToString());
+ Console.WriteLine("PeerConnected: " + peer);
};
- netListener.ConnectionRequestEvent += request =>
+ clientListener.ConnectionRequestEvent += request =>
{
request.AcceptIfKey(ConnectionKey);
};
- netListener.PeerDisconnectedEvent += (peer, disconnectInfo) =>
+ clientListener.PeerDisconnectedEvent += (peer, disconnectInfo) =>
{
Console.WriteLine("PeerDisconnected: " + disconnectInfo.Reason);
if (disconnectInfo.AdditionalData.AvailableBytes > 0)
@@ -109,35 +108,44 @@ public void Run()
}
};
- natPunchListener1.NatIntroductionSuccess += (point, token) =>
+ natPunchListener1.NatIntroductionSuccess += (point, addrType, token) =>
{
var peer = _c1.Connect(point, ConnectionKey);
- Console.WriteLine("Success C1. Connecting to C2: {0}, connection created: {1}", point, peer != null);
+ Console.WriteLine($"NatIntroductionSuccess C1. Connecting to C2: {point}, type: {addrType}, connection created: {peer != null}");
};
- natPunchListener2.NatIntroductionSuccess += (point, token) =>
+ natPunchListener2.NatIntroductionSuccess += (point, addrType, token) =>
{
var peer = _c2.Connect(point, ConnectionKey);
- Console.WriteLine("Success C2. Connecting to C1: {0}, connection created: {1}", point, peer != null);
+ Console.WriteLine($"NatIntroductionSuccess C2. Connecting to C1: {point}, type: {addrType}, connection created: {peer != null}");
};
- _c1 = new NetManager(netListener);
- _c1.NatPunchEnabled = true;
+ _c1 = new NetManager(clientListener)
+ {
+ IPv6Enabled = true,
+ NatPunchEnabled = true
+ };
_c1.NatPunchModule.Init(natPunchListener1);
_c1.Start();
- _c2 = new NetManager(netListener);
- _c2.NatPunchEnabled = true;
+ _c2 = new NetManager(clientListener)
+ {
+ IPv6Enabled = true,
+ NatPunchEnabled = true
+ };
_c2.NatPunchModule.Init(natPunchListener2);
_c2.Start();
- _puncher = new NetManager(netListener);
+ _puncher = new NetManager(clientListener)
+ {
+ IPv6Enabled = true,
+ NatPunchEnabled = true
+ };
_puncher.Start(ServerPort);
- _puncher.NatPunchEnabled = true;
_puncher.NatPunchModule.Init(this);
- _c1.NatPunchModule.SendNatIntroduceRequest(NetUtils.MakeEndPoint("::1", ServerPort), "token1");
- _c2.NatPunchModule.SendNatIntroduceRequest(NetUtils.MakeEndPoint("::1", ServerPort), "token1");
+ _c1.NatPunchModule.SendNatIntroduceRequest("localhost", ServerPort, "token1");
+ _c2.NatPunchModule.SendNatIntroduceRequest("localhost", ServerPort, "token1");
// keep going until ESCAPE is pressed
Console.WriteLine("Press ESC to quit");
@@ -158,8 +166,8 @@ public void Run()
_c1.Stop();
}
}
-
- DateTime nowTime = DateTime.Now;
+
+ DateTime nowTime = DateTime.UtcNow;
_c1.NatPunchModule.PollEvents();
_c2.NatPunchModule.PollEvents();
diff --git a/LibSample/IExample.cs b/LibSample/IExample.cs
new file mode 100644
index 00000000..2ed2afdc
--- /dev/null
+++ b/LibSample/IExample.cs
@@ -0,0 +1,7 @@
+namespace LibSample
+{
+ public interface IExample
+ {
+ void Run();
+ }
+}
diff --git a/LibSample/LibSample.csproj b/LibSample/LibSample.csproj
index a2a5c6fc..f1d24079 100644
--- a/LibSample/LibSample.csproj
+++ b/LibSample/LibSample.csproj
@@ -1,66 +1,12 @@
-
-
-
+
+
- Debug
- AnyCPU
- {EF232CBE-04C8-4FD9-AE26-2F7EBBE81699}
Exe
- Properties
- LibSample
- LibSample
- v4.5
- 512
- true
-
+ net8.0;net9.0
-
- AnyCPU
- true
- full
- false
- bin\Debug\
- TRACE;DEBUG
- prompt
- 4
- false
-
-
- AnyCPU
- pdbonly
- true
- bin\Release\
- TRACE
- prompt
- 4
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
- {2f98f12c-b72a-48fe-a7e5-6d8b0c5abacb}
- LiteNetLib
-
+
-
-
-
\ No newline at end of file
+
+
diff --git a/LibSample/NtpTest.cs b/LibSample/NtpTest.cs
new file mode 100644
index 00000000..8c7f73f7
--- /dev/null
+++ b/LibSample/NtpTest.cs
@@ -0,0 +1,38 @@
+using System;
+using System.Threading;
+using LiteNetLib;
+
+namespace LibSample
+{
+ public class NtpTest : IExample
+ {
+ public void Run()
+ {
+ RequestNtpService("0.pool.ntp.org");
+ }
+
+ private void RequestNtpService(string ntpService)
+ {
+ Console.WriteLine($"Request time from \"{ntpService}\" service");
+ var ntpComplete = false;
+ EventBasedNetListener listener = new EventBasedNetListener();
+ listener.NtpResponseEvent += ntpPacket =>
+ {
+ if (ntpPacket != null)
+ Console.WriteLine("[MAIN] NTP time test offset: " + ntpPacket.CorrectionOffset);
+ else
+ Console.WriteLine("[MAIN] NTP time error");
+ ntpComplete = true;
+ };
+
+ NetManager nm = new NetManager(listener);
+ nm.Start();
+ nm.CreateNtpRequest(ntpService);
+
+ while (!ntpComplete)
+ {
+ Thread.Yield();
+ }
+ }
+ }
+}
diff --git a/LibSample/PacketProcessorExample.cs b/LibSample/PacketProcessorExample.cs
index 57da9c1a..46ad36ee 100644
--- a/LibSample/PacketProcessorExample.cs
+++ b/LibSample/PacketProcessorExample.cs
@@ -1,51 +1,87 @@
using System;
+using System.Collections.Generic;
using System.Threading;
using LiteNetLib;
using LiteNetLib.Utils;
namespace LibSample
{
+ struct CustomStruct : INetSerializable
+ {
+ public int X;
+ public int Y;
+
+ public void Serialize(NetDataWriter writer)
+ {
+ writer.Put(X);
+ writer.Put(Y);
+ }
+
+ public void Deserialize(NetDataReader reader)
+ {
+ X = reader.GetInt();
+ Y = reader.GetInt();
+ }
+ }
+
class ArgumentsForLogin
{
public string UserId { get; set; }
public string Password { get; set; }
public int SomeInt { get; set; }
+ public List SomeList { get; set; }
}
- class PacketProcessorExample
+ class PacketProcessorExample : IExample
{
private readonly NetPacketProcessor _netPacketProcessor = new NetPacketProcessor();
private NetManager _client;
private NetManager _server;
+ private NetDataWriter _writer = new NetDataWriter();
public void Run()
{
//setup netpacketprocessor
+ _netPacketProcessor.RegisterNestedType();
_netPacketProcessor.SubscribeReusable(Method1);
//setup events
EventBasedNetListener clientListener = new EventBasedNetListener();
EventBasedNetListener serverListener = new EventBasedNetListener();
- serverListener.ConnectionRequestEvent += request => request.AcceptIfKey("key");
+ serverListener.ConnectionRequestEvent += request =>
+ {
+ request.AcceptIfKey("key");
+ };
serverListener.NetworkReceiveEvent +=
- (peer, reader, method) => _netPacketProcessor.ReadAllPackets(reader, peer);
+ (peer, reader, channel, method) =>
+ {
+ _netPacketProcessor.ReadAllPackets(reader, peer);
+ };
+ clientListener.PeerConnectedEvent += peer =>
+ {
+ //send after connect
+ var testList = new List
+ {
+ new CustomStruct {X = 1, Y = -1},
+ new CustomStruct {X = 5, Y = -28},
+ new CustomStruct {X = -114, Y = 65535}
+ };
+ _writer.Reset();
+ _netPacketProcessor.Write(_writer, new ArgumentsForLogin { Password = "pass", SomeInt = 5, UserId = "someUser", SomeList = testList });
+ peer.Send(_writer, DeliveryMethod.ReliableOrdered);
+ };
//start client/server
_client = new NetManager(clientListener);
_server = new NetManager(serverListener);
_client.Start();
_server.Start(9050);
- var clientPeer = _client.Connect("localhost", 9050, "key");
-
- //send
- _netPacketProcessor.Send(
- clientPeer,
- new ArgumentsForLogin { Password = "pass", SomeInt = 5, UserId = "someUser"},
- DeliveryMethod.ReliableOrdered);
+ _client.Connect("localhost", 9050, "key");
while (!Console.KeyAvailable)
{
_server.PollEvents();
+ _client.PollEvents();
Thread.Sleep(10);
}
_client.Stop();
@@ -55,6 +91,11 @@ public void Run()
void Method1(ArgumentsForLogin args, NetPeer peer)
{
Console.WriteLine("Received: \n {0}\n {1}\n {2}", args.UserId, args.Password, args.SomeInt);
+ Console.WriteLine("List count: " + args.SomeList.Count);
+ for (int i = 0; i < args.SomeList.Count; i++)
+ {
+ Console.WriteLine($" X: {args.SomeList[i].X}, Y: {args.SomeList[i].Y}");
+ }
}
}
}
diff --git a/LibSample/Program.cs b/LibSample/Program.cs
index 80d00aee..87149fad 100644
--- a/LibSample/Program.cs
+++ b/LibSample/Program.cs
@@ -1,32 +1,106 @@
-using System;
-using LiteNetLib.Utils;
+using System;
+using System.Text;
namespace LibSample
{
- class Program
+ public static class Program
{
+ private static readonly IExample[] Examples = {
+ new EchoMessagesTest(),
+ new HolePunchServerTest(),
+ new BroadcastTest(),
+ new SerializerBenchmark(),
+ new SpeedBench(),
+ new PacketProcessorExample(),
+ new AesEncryptionTest(),
+ new NtpTest(),
+ };
+
static void Main(string[] args)
{
- //Test ntp
- NtpRequest ntpRequest = null;
- ntpRequest = NtpRequest.Create("pool.ntp.org", ntpPacket =>
+ AppendExampleMenu(MenuStringBuilder);
+ WriteAndClean(MenuStringBuilder);
+
+ do
{
- ntpRequest.Close();
- if (ntpPacket != null)
- Console.WriteLine("[MAIN] NTP time test offset: " + ntpPacket.CorrectionOffset);
+ Console.Write("Write command: ");
+ var input = Console.ReadLine();
+
+ if (input != null)
+ {
+ var lcInput = input.ToLower();
+
+ if (lcInput == "help" || lcInput == "h")
+ {
+ AppendFullHelpMenu(MenuStringBuilder);
+ WriteAndClean(MenuStringBuilder);
+ continue;
+ }
+
+ if (lcInput == "quit" || lcInput == "exit" || lcInput == "q" || lcInput == "e")
+ {
+ break;
+ }
+
+ if (int.TryParse(input, out var optionKey))
+ {
+ if (optionKey < 0 || optionKey >= Examples.Length)
+ {
+ PrintInvalidCommand(input);
+ continue;
+ }
+
+ ((IExample)Activator.CreateInstance(Examples[optionKey].GetType())).Run();
+ }
+ else
+ {
+ PrintInvalidCommand(input);
+ }
+ }
else
- Console.WriteLine("[MAIN] NTP time error");
- });
- ntpRequest.Send();
-
- new EchoMessagesTest().Run();
- //new HolePunchServerTest().Run();
- //new BroadcastTest().Run();
- //new BenchmarkTest.TestHost().Run();
- //new SerializerBenchmark().Run();
- //new SpeedBench().Run();
- //new PacketProcessorExample().Run();
+ {
+ PrintInvalidCommand(string.Empty);
+ }
+ } while (true);
+ }
+
+ private static void PrintInvalidCommand(string invalidInput)
+ {
+ AppendInvalidCommand(MenuStringBuilder, invalidInput);
+ WriteAndClean(MenuStringBuilder);
+ }
+
+ private static readonly StringBuilder MenuStringBuilder = new StringBuilder();
+
+ private static void WriteAndClean(StringBuilder sb)
+ {
+ Console.WriteLine(sb.ToString());
+ sb.Clear();
+ }
+
+ private static void AppendInvalidCommand(StringBuilder sb, string invalidInput)
+ {
+ sb.Append("Invalid input \"");
+ sb.Append(string.IsNullOrWhiteSpace(invalidInput) ? "[Whitespace/Empty Line]" : invalidInput);
+ sb.AppendLine("\" command. Write \"help\" command for more information.");
+ }
+
+ private static void AppendFullHelpMenu(StringBuilder sb)
+ {
+ sb.AppendLine();
+ sb.AppendLine("\"help/h\" - write helper text for this console menu.");
+ sb.AppendLine("\"exit/e/quit/q\" - close app");
+ AppendExampleMenu(sb);
+ sb.AppendLine();
+ }
+
+ private static void AppendExampleMenu(StringBuilder sb)
+ {
+ for (var i = 0; i < Examples.Length; i++)
+ {
+ var example = Examples[i];
+ sb.AppendLine($"\"{i}\" - Example of {example.GetType().Name}");
+ }
}
}
}
-
diff --git a/LibSample/Properties/AssemblyInfo.cs b/LibSample/Properties/AssemblyInfo.cs
deleted file mode 100644
index 9c9f10b8..00000000
--- a/LibSample/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,36 +0,0 @@
-using System.Reflection;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-
-// General Information about an assembly is controlled through the following
-// set of attributes. Change these attribute values to modify the information
-// associated with an assembly.
-[assembly: AssemblyTitle("LibSample")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("LibSample")]
-[assembly: AssemblyCopyright("Copyright © 2016")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// Setting ComVisible to false makes the types in this assembly not visible
-// to COM components. If you need to access a type in this assembly from
-// COM, set the ComVisible attribute to true on that type.
-[assembly: ComVisible(false)]
-
-// The following GUID is for the ID of the typelib if this project is exposed to COM
-[assembly: Guid("ef232cbe-04c8-4fd9-ae26-2f7ebbe81699")]
-
-// Version information for an assembly consists of the following four values:
-//
-// Major Version
-// Minor Version
-// Build Number
-// Revision
-//
-// You can specify all the values or you can default the Build and Revision Numbers
-// by using the '*' as shown below:
-// [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("1.0.0.0")]
-[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/LibSample/SerializerBenchmark.cs b/LibSample/SerializerBenchmark.cs
index f9ea1f49..31034ebb 100644
--- a/LibSample/SerializerBenchmark.cs
+++ b/LibSample/SerializerBenchmark.cs
@@ -7,8 +7,10 @@
namespace LibSample
{
- class SerializerBenchmark
+ class SerializerBenchmark : IExample
{
+ const int LoopLength = 100000;
+
[Serializable] //Just for test binary formatter
private struct SampleNetSerializable : INetSerializable
{
@@ -90,14 +92,45 @@ public static SomeVector2 Deserialize(NetDataReader reader)
}
}
+ private void NetSerializerTest(NetSerializer serializer, NetDataWriter netDataWriter, Stopwatch stopwatch, SamplePacket samplePacket)
+ {
+ netDataWriter.Reset();
+ stopwatch.Restart();
+ for (int i = 0; i < LoopLength; i++)
+ serializer.Serialize(netDataWriter, samplePacket);
+ stopwatch.Stop();
+ Console.WriteLine($"NetSerializer time: {stopwatch.ElapsedMilliseconds} ms, size: { netDataWriter.Length / LoopLength} bytes");
+ }
+
+ private void DataWriterTest(NetDataWriter netDataWriter, Stopwatch stopwatch, SamplePacket samplePacket)
+ {
+ netDataWriter.Reset();
+ stopwatch.Restart();
+ for (int i = 0; i < LoopLength; i++)
+ {
+ netDataWriter.Put(samplePacket.SomeString);
+ netDataWriter.Put(samplePacket.SomeFloat);
+ netDataWriter.PutArray(samplePacket.SomeIntArray);
+ SomeVector2.Serialize(netDataWriter, samplePacket.SomeVector2);
+ netDataWriter.Put((ushort)samplePacket.SomeVectors.Length);
+ for (int j = 0; j < samplePacket.SomeVectors.Length; j++)
+ {
+ SomeVector2.Serialize(netDataWriter, samplePacket.SomeVectors[j]);
+ }
+ netDataWriter.Put(samplePacket.EmptyString);
+ netDataWriter.Put(samplePacket.TestObj);
+ }
+ stopwatch.Stop();
+ Console.WriteLine($"Raw time: {stopwatch.ElapsedMilliseconds} ms, size: { netDataWriter.Length / LoopLength} bytes");
+ }
+
public void Run()
{
Console.WriteLine("=== Serializer benchmark ===");
-
- const int LoopLength = 100000;
+
+
//Test serializer performance
Stopwatch stopwatch = new Stopwatch();
- BinaryFormatter binaryFormatter = new BinaryFormatter();
MemoryStream memoryStream = new MemoryStream();
NetDataWriter netDataWriter = new NetDataWriter();
@@ -114,7 +147,7 @@ public void Run()
samplePacket.SomeVectors[i] = new SomeVector2(i, i);
}
- NetSerializer netSerializer = new NetSerializer();
+ var netSerializer = new NetSerializer();
netSerializer.RegisterNestedType();
netSerializer.RegisterNestedType( SomeVector2.Serialize, SomeVector2.Deserialize );
@@ -124,55 +157,16 @@ 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");
+ DataWriterTest(netDataWriter, stopwatch, samplePacket);
+ NetSerializerTest(netSerializer, netDataWriter, stopwatch, samplePacket);
- //Test NetSerializer
- stopwatch.Restart();
- for (int i = 0; i < LoopLength; i++)
- {
- netSerializer.Serialize(netDataWriter, samplePacket);
- }
- stopwatch.Stop();
- Console.WriteLine("NetSerializer first run time: " + stopwatch.ElapsedMilliseconds + " ms");
+ DataWriterTest(netDataWriter, stopwatch, samplePacket);
+ NetSerializerTest(netSerializer, netDataWriter, stopwatch, samplePacket);
- //Test NetSerializer
- netDataWriter.Reset();
- stopwatch.Restart();
- for (int i = 0; i < LoopLength; i++)
- {
- netSerializer.Serialize(netDataWriter, samplePacket);
- }
- stopwatch.Stop();
- Console.WriteLine("NetSerializer second run time: " + stopwatch.ElapsedMilliseconds + " ms");
+ DataWriterTest(netDataWriter, stopwatch, samplePacket);
+ NetSerializerTest(netSerializer, netDataWriter, stopwatch, samplePacket);
- //Test RAW
- netDataWriter.Reset();
- stopwatch.Restart();
- for (int i = 0; i < LoopLength; i++)
- {
- netDataWriter.Put(samplePacket.SomeFloat);
- netDataWriter.Put(samplePacket.SomeString);
- netDataWriter.PutArray(samplePacket.SomeIntArray);
- netDataWriter.Put(samplePacket.SomeVector2.X);
- netDataWriter.Put(samplePacket.SomeVector2.Y);
- netDataWriter.Put(samplePacket.SomeVectors.Length);
- for (int j = 0; j < samplePacket.SomeVectors.Length; j++)
- {
- netDataWriter.Put(samplePacket.SomeVectors[j].X);
- netDataWriter.Put(samplePacket.SomeVectors[j].Y);
- }
- netDataWriter.Put(samplePacket.EmptyString);
- netDataWriter.Put(samplePacket.TestObj.Value);
- }
- stopwatch.Stop();
- Console.WriteLine("DataWriter (raw put method calls) time: " + stopwatch.ElapsedMilliseconds + " ms");
+ Console.ReadKey();
}
}
}
diff --git a/LibSample/SpeedBench.cs b/LibSample/SpeedBench.cs
index e34512ec..09dcb5f4 100644
--- a/LibSample/SpeedBench.cs
+++ b/LibSample/SpeedBench.cs
@@ -8,32 +8,34 @@
namespace LibSample
{
- internal class SpeedBench
+ internal class SpeedBench : IExample
{
- public class Server : INetEventListener
+ public class Server : INetEventListener, IDisposable
{
public int ReliableReceived;
public int UnreliableReceived;
readonly NetManager _server;
+ public NetStatistics Stats => _server.Statistics;
+
public Server()
{
- _server = new NetManager(this);
- _server.AutoRecycle = true;
- _server.UpdateTime = 1;
- _server.SimulatePacketLoss = false;
- _server.SimulationPacketLossChance = 20;
+ _server = new NetManager(this)
+ {
+ AutoRecycle = true,
+ UpdateTime = 1,
+ SimulatePacketLoss = false,
+ SimulationPacketLossChance = 20,
+ EnableStatistics = true,
+ UnsyncedEvents = true
+ };
_server.Start(9050);
}
- public void PollEvents()
- {
- _server.PollEvents();
- }
-
void INetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode)
{
+ Console.WriteLine($"Server: error: {socketErrorCode}");
}
void INetEventListener.OnNetworkLatencyUpdate(NetPeer peer, int latency)
@@ -45,7 +47,7 @@ public void OnConnectionRequest(ConnectionRequest request)
request.AcceptIfKey("ConnKey");
}
- void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod)
+ void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod)
{
var isReliable = reader.GetBool();
var data = reader.GetString();
@@ -67,15 +69,21 @@ void INetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, Ne
void INetEventListener.OnPeerConnected(NetPeer peer)
{
-
+ Console.WriteLine($"Server: client connected: {peer}");
}
void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo)
{
+ Console.WriteLine($"Server: client disconnected: {disconnectInfo.Reason}");
+ }
+
+ public void Dispose()
+ {
+ _server.Stop();
}
}
- public class Client : INetEventListener
+ public class Client : INetEventListener, IDisposable
{
public int ReliableSent;
public int UnreliableSent;
@@ -84,20 +92,20 @@ public class Client : INetEventListener
readonly NetDataWriter _writer;
NetPeer _peer;
- public NetStatistics Stats
- {
- get { return _client.Statistics; }
- }
+ public NetStatistics Stats => _client.Statistics;
public Client()
{
_writer = new NetDataWriter();
- _client = new NetManager(this);
- _client.UnsyncedEvents = true;
- _client.AutoRecycle = true;
- _client.SimulatePacketLoss = false;
- _client.SimulationPacketLossChance = 20;
+ _client = new NetManager(this)
+ {
+ UnsyncedEvents = true,
+ AutoRecycle = true,
+ SimulatePacketLoss = false,
+ SimulationPacketLossChance = 20,
+ EnableStatistics = true
+ };
_client.Start();
}
@@ -106,7 +114,6 @@ public void SendUnreliable(string pData)
_writer.Reset();
_writer.Put(false);
_writer.Put(pData);
-
_peer.Send(_writer, DeliveryMethod.Unreliable);
UnreliableSent++;
}
@@ -116,7 +123,6 @@ public void SendReliable(string pData)
_writer.Reset();
_writer.Put(true);
_writer.Put(pData);
-
_peer.Send(_writer, DeliveryMethod.ReliableOrdered);
ReliableSent++;
}
@@ -128,6 +134,7 @@ public void Connect()
void INetEventListener.OnNetworkError(IPEndPoint endPoint, SocketError socketErrorCode)
{
+ Console.WriteLine($"Client: error: {socketErrorCode}");
}
void INetEventListener.OnNetworkLatencyUpdate(NetPeer peer, int latency)
@@ -136,10 +143,10 @@ void INetEventListener.OnNetworkLatencyUpdate(NetPeer peer, int latency)
public void OnConnectionRequest(ConnectionRequest request)
{
- request.AcceptIfKey("ConnKey");
+ request.RejectForce();
}
- void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod)
+ void INetEventListener.OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod)
{
}
@@ -151,17 +158,35 @@ void INetEventListener.OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, Ne
void INetEventListener.OnPeerConnected(NetPeer peer)
{
+ Console.WriteLine("Client: Connected");
+ for (int i = 0; i < MAX_LOOP_COUNT; i++)
+ {
+ for (int ui = 0; ui < UNRELIABLE_MESSAGES_PER_LOOP; ui++)
+ SendUnreliable(DATA);
+
+ for (int ri = 0; ri < RELIABLE_MESSAGES_PER_LOOP; ri++)
+ SendReliable(DATA);
+ }
+
+ int dataSize = MAX_LOOP_COUNT * Encoding.UTF8.GetByteCount(DATA) * (UNRELIABLE_MESSAGES_PER_LOOP + RELIABLE_MESSAGES_PER_LOOP);
+ Console.WriteLine("DataSize: {0}b, {1}kb, {2}mb", dataSize, dataSize / 1024, dataSize / 1024 / 1024);
}
void INetEventListener.OnPeerDisconnected(NetPeer peer, DisconnectInfo disconnectInfo)
{
+ Console.WriteLine($"Client: Disconnected {disconnectInfo.Reason}");
+ }
+
+ public void Dispose()
+ {
+ _client.Stop();
}
}
private const string DATA = "The quick brown fox jumps over the lazy dog";
- private static int MAX_LOOP_COUNT = 750;
- private static int UNRELIABLE_MESSAGES_PER_LOOP = 1000;
- private static int RELIABLE_MESSAGES_PER_LOOP = 350;
- private static bool CLIENT_RUNNING = true;
+ private const int MAX_LOOP_COUNT = 750;
+ private const int UNRELIABLE_MESSAGES_PER_LOOP = 1000;
+ private const int RELIABLE_MESSAGES_PER_LOOP = 350;
+ private static volatile bool CLIENT_RUNNING = true;
public void Run()
{
@@ -171,46 +196,33 @@ public void Run()
Thread clientThread = new Thread(StartClient);
clientThread.Start();
Console.WriteLine("Processing...");
- Console.ReadKey();
+ serverThread.Join();
+ clientThread.Join();
+ Console.WriteLine("Test is end.");
}
private static void StartServer()
{
- Server s = new Server();
-
- while (CLIENT_RUNNING)
+ using (Server s = new Server())
{
- s.PollEvents();
- Thread.Sleep(1);
- }
+ while (CLIENT_RUNNING)
+ Thread.Sleep(100);
- Thread.Sleep(10000);
- s.PollEvents();
- Console.WriteLine("SERVER RECEIVED -> Reliable: " + s.ReliableReceived + ", Unreliable: " + s.UnreliableReceived);
+ Console.WriteLine("SERVER RECEIVED -> Reliable: " + s.ReliableReceived + ", Unreliable: " + s.UnreliableReceived);
+ Console.WriteLine("SERVER STATS:\n" + s.Stats);
+ }
}
private static void StartClient()
{
- Client c = new Client();
- c.Connect();
-
- for (int i = 0; i < MAX_LOOP_COUNT; i++)
+ using (Client c = new Client())
{
- //for (int ui = 0; ui < UNRELIABLE_MESSAGES_PER_LOOP; ui++)
- // c.SendUnreliable(DATA);
-
- for (int ri = 0; ri < RELIABLE_MESSAGES_PER_LOOP; ri++)
- c.SendReliable(DATA);
+ c.Connect();
+ Thread.Sleep(10000);
+ CLIENT_RUNNING = false;
+ Console.WriteLine("CLIENT SENT -> Reliable: " + c.ReliableSent + ", Unreliable: " + c.UnreliableSent);
+ Console.WriteLine("CLIENT STATS:\n" + c.Stats);
}
-
- int dataSize = MAX_LOOP_COUNT * Encoding.UTF8.GetByteCount(DATA) * (UNRELIABLE_MESSAGES_PER_LOOP + RELIABLE_MESSAGES_PER_LOOP);
- Console.WriteLine("DataSize: {0}b, {1}kb, {2}mb", dataSize, dataSize/1024, dataSize/1024/1024);
-
- Thread.Sleep(10000);
- CLIENT_RUNNING = false;
-
- Console.WriteLine("CLIENT SENT -> Reliable: " + c.ReliableSent + ", Unreliable: " + c.UnreliableSent);
- Console.WriteLine("CLIENT STATS:\n" + c.Stats);
}
}
}
diff --git a/LiteNetLib.Tests/CRC32LayerTest.cs b/LiteNetLib.Tests/CRC32LayerTest.cs
new file mode 100644
index 00000000..c6b716e6
--- /dev/null
+++ b/LiteNetLib.Tests/CRC32LayerTest.cs
@@ -0,0 +1,89 @@
+using LiteNetLib.Layers;
+using LiteNetLib.Utils;
+using NUnit.Framework;
+using System;
+using System.Net;
+
+namespace LiteNetLib.Tests
+{
+ [TestFixture]
+ public class CRC32LayerTest
+ {
+ private Crc32cLayer _crc32Layer;
+ private IPEndPoint _dummyEndpoint;
+
+ [SetUp]
+ public void Setup()
+ {
+ NetDebug.Logger = null;
+ _crc32Layer = new Crc32cLayer();
+ _dummyEndpoint = new IPEndPoint(IPAddress.Loopback, 23456);
+ }
+
+ [Test]
+ public void ReturnsDataWithoutChecksum()
+ {
+ byte[] packet = GetTestPacketWithCrc();
+
+ int length = packet.Length;
+ _crc32Layer.ProcessInboundPacket(ref _dummyEndpoint, ref packet, ref length);
+
+ Assert.That(length, Is.EqualTo(packet.Length - CRC32C.ChecksumSize));
+ }
+
+ [Test]
+ public void ReturnsNilCountForBadChecksum()
+ {
+ byte[] packet = GetTestPacketWithCrc();
+
+ //Fake a change to the data to cause data/crc missmatch
+ packet[4] = 0;
+
+ int length = packet.Length;
+ _crc32Layer.ProcessInboundPacket(ref _dummyEndpoint, ref packet, ref length);
+
+ Assert.That(length, Is.EqualTo(0));
+ }
+
+ [Test]
+ public void ReturnsNilCountForTooShortMessage()
+ {
+ byte[] packet = new byte[2];
+
+ int length = packet.Length;
+
+ _crc32Layer.ProcessInboundPacket(ref _dummyEndpoint, ref packet, ref length);
+
+ Assert.That(length, Is.EqualTo(0));
+ }
+
+ [Test]
+ public void CanSendAndReceiveSameMessage()
+ {
+ byte[] message = GetTestMessageBytes();
+ //Process outbound adds bytes, so we need a larger array
+ byte[] package = new byte[message.Length + CRC32C.ChecksumSize];
+
+ Buffer.BlockCopy(message, 0, package, 0, message.Length);
+
+ int offset = 0;
+ int length = message.Length;
+ _crc32Layer.ProcessOutBoundPacket(ref _dummyEndpoint, ref package, ref offset, ref length);
+ _crc32Layer.ProcessInboundPacket(ref _dummyEndpoint, ref package, ref length);
+ }
+
+ private static byte[] GetTestPacketWithCrc()
+ {
+ byte[] testMsg = GetTestMessageBytes();
+ uint crc32 = CRC32C.Compute(testMsg, 0, testMsg.Length);
+
+ byte[] packet = new byte[testMsg.Length + CRC32C.ChecksumSize];
+ Buffer.BlockCopy(testMsg, 0, packet, 0, testMsg.Length);
+ FastBitConverter.GetBytes(packet, testMsg.Length, crc32);
+ return packet;
+ }
+
+ private static byte[] GetTestMessageBytes()
+ => System.Text.Encoding.ASCII.GetBytes("This is a test string with some length");
+ }
+}
diff --git a/LiteNetLib.Tests/CommunicationTest.cs b/LiteNetLib.Tests/CommunicationTest.cs
new file mode 100644
index 00000000..fcc8224f
--- /dev/null
+++ b/LiteNetLib.Tests/CommunicationTest.cs
@@ -0,0 +1,751 @@
+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 CommunicationTest
+ {
+ const int TestTimeout = 4000;
+ [SetUp]
+ public void Init()
+ {
+ NetDebug.Logger = new LibErrorChecker();
+ ManagerStack = new NetManagerStack(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 NetManagerStack 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, 0, DeliveryMethod.ReliableUnordered, testData);
+ };
+ ManagerStack.ServerListener(1).NetworkReceiveEvent += (peer, reader, channel, 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()
+ {
+ NetManager 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()
+ {
+ NetManager client = ManagerStack.Client(1);
+ NetManager server = ManagerStack.Server(1);
+
+ //Default 5 sec timeout for local network is too mach, set 1 for test
+ server.DisconnectTimeout = 1000;
+
+ NetPeer 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()
+ {
+ NetManager client = ManagerStack.Client(1);
+ NetManager client2 = ManagerStack.Client(2);
+ NetManager server = ManagerStack.Server(1);
+
+ NetPeer 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()
+ {
+ NetManager server = ManagerStack.Server(1);
+ NetManager 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()
+ {
+ EventBasedNetListener srvListener = new EventBasedNetListener();
+ EventBasedNetListener cliListener = new EventBasedNetListener();
+ NetManager srv = new NetManager(srvListener, new XorEncryptLayer("secret_key"));
+ NetManager cli = new NetManager(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()
+ {
+ NetManager server = ManagerStack.Server(1);
+
+ EventBasedNetListener listener = new EventBasedNetListener();
+ NetManager client = new NetManager(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()
+ {
+ NetManager server = ManagerStack.Server(1);
+ NetManager 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;
+ };
+
+ NetPeer 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(10000)]
+ public void ChannelsTest()
+ {
+ const int channelsCount = 64;
+ var server = ManagerStack.Server(1);
+ var client = ManagerStack.Client(1);
+ server.ChannelsCount = channelsCount;
+ client.ChannelsCount = channelsCount;
+
+ NetDataWriter writer = new NetDataWriter();
+ var methods = new[]
+ {
+ DeliveryMethod.Unreliable,
+ DeliveryMethod.Sequenced,
+ DeliveryMethod.ReliableOrdered,
+ DeliveryMethod.ReliableSequenced,
+ DeliveryMethod.ReliableUnordered
+ };
+
+ int messagesReceived = 0;
+ ManagerStack.ClientListener(1).PeerConnectedEvent += peer =>
+ {
+ for (int i = 0; i < channelsCount; i++)
+ {
+ foreach (var deliveryMethod in methods)
+ {
+ writer.Reset();
+ writer.Put((byte) deliveryMethod);
+ if (deliveryMethod == DeliveryMethod.ReliableOrdered ||
+ deliveryMethod == DeliveryMethod.ReliableUnordered)
+ writer.Put(new byte[506]);
+ peer.Send(writer, (byte) i, deliveryMethod);
+ }
+ }
+ };
+ ManagerStack.ServerListener(1).NetworkReceiveEvent += (peer, reader, channel, method) =>
+ {
+ Assert.That((DeliveryMethod)reader.GetByte(), Is.EqualTo(method));
+ messagesReceived++;
+ };
+ client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
+
+ while (messagesReceived != methods.Length*channelsCount)
+ {
+ server.PollEvents();
+ client.PollEvents();
+ Thread.Sleep(15);
+ }
+
+ Assert.That(messagesReceived, Is.EqualTo(methods.Length * channelsCount));
+ Assert.That(server.ConnectedPeersCount, Is.EqualTo(1));
+ Assert.That(client.ConnectedPeersCount, Is.EqualTo(1));
+ }
+
+ [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 EventBasedNetListener();
+ var server = new NetManager(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, channel, 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/ConnectionTest.cs b/LiteNetLib.Tests/ConnectionTest.cs
deleted file mode 100644
index 3c7c8fa7..00000000
--- a/LiteNetLib.Tests/ConnectionTest.cs
+++ /dev/null
@@ -1,502 +0,0 @@
-using System.Collections.Generic;
-using System.Net.Sockets;
-using System.Text;
-using System.Threading;
-
-using LiteNetLib.Tests.TestUtility;
-using LiteNetLib.Utils;
-
-using NUnit.Framework;
-
-namespace LiteNetLib.Tests
-{
- [TestFixture]
- [Category("Communication")]
- public class CommunicationTest
- {
- [SetUp]
- public void Init()
- {
- ManagerStack = new NetManagerStack(DefaultAppKey, DefaultPort);
- }
-
- [TearDown]
- public void TearDown()
- {
- ManagerStack?.Dispose();
- }
-
- private const int DefaultPort = 9050;
- private const string DefaultAppKey = "test_server";
-
- public NetManagerStack ManagerStack { get; set; }
-
- [Test, MaxTime(2000)]
- public void ConnectionByIpV4()
- {
- var server = ManagerStack.Server(1);
- var client = ManagerStack.Client(1);
- client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
-
- while (server.PeersCount != 1 || client.PeersCount != 1)
- {
- Thread.Sleep(15);
- server.PollEvents();
- }
-
- Assert.AreEqual(1, server.PeersCount);
- Assert.AreEqual(1, client.PeersCount);
- }
-
- [Test, MaxTime(2000)]
- 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.PeersCount != 1 || client.PeersCount != 1)
- {
- Thread.Sleep(15);
- server.PollEvents();
- }
- server.Stop(false);
- server.Start(DefaultPort);
- while (client.PeersCount == 1)
- {
- Thread.Sleep(15);
- }
- client.PollEvents();
-
- Assert.AreEqual(0, server.PeersCount);
- Assert.AreEqual(0, client.PeersCount);
- Assert.IsTrue(disconnectInfo.HasValue);
- Assert.AreEqual(DisconnectReason.RemoteConnectionClose, disconnectInfo.Value.Reason);
- }
-
- [Test, MaxTime(10000)]
- public void ConnectionFailedTest()
- {
- NetManager client = ManagerStack.Client(1);
-
- var result = false;
- DisconnectInfo disconnectInfo = default(DisconnectInfo);
-
- ManagerStack.ClientListener(1).PeerConnectedEvent += peer =>
- {
- result = true;
- };
- 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.True(result);
- Assert.AreEqual(DisconnectReason.ConnectionFailed, disconnectInfo.Reason);
- }
-
- [Test, MaxTime(10000)]
- public void NetPeerDisconnectTimeout()
- {
- NetManager client = ManagerStack.Client(1);
- NetManager server = ManagerStack.Server(1);
-
- //Default 5 sec timeout for local network is too mach, set 1 for test
- server.DisconnectTimeout = 1000;
-
- NetPeer clientServerPeer = client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
-
- while (clientServerPeer.ConnectionState != ConnectionState.Connected)
- {
- Thread.Sleep(15);
- server.PollEvents();
- client.PollEvents();
- }
-
- Assert.AreEqual(ConnectionState.Connected, clientServerPeer.ConnectionState);
- Assert.True(server.PeersCount == 1);
-
- ManagerStack.ClientListener(1).PeerDisconnectedEvent += (peer, info) =>
- {
- Assert.AreEqual(clientServerPeer, peer);
- Assert.AreEqual(DisconnectReason.Timeout, info.Reason);
- };
-
- server.Stop();
-
- Assert.True(server.PeersCount == 0);
- while (client.PeersCount == 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(10123);
- client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
-
- while (connectCount < 2)
- {
- if (connectCount == 1 && !reconnected)
- {
- client.Stop();
- Thread.Sleep(500);
- client.Start(10123);
- client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
- reconnected = true;
- }
- client.PollEvents();
- server.PollEvents();
- Thread.Sleep(15);
- }
- Assert.AreEqual(2, connectCount);
- }
-
- [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.AreEqual(true, info.Reason == DisconnectReason.ConnectionRejected);
- Assert.AreEqual("reject_test", Encoding.UTF8.GetString(info.AdditionalData.GetRemainingBytes()));
- rejectReceived = true;
- };
-
- client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
-
- while (!rejectReceived)
- {
- client.PollEvents();
- server.PollEvents();
- Thread.Sleep(15);
- }
-
- Assert.AreEqual(0, server.PeersCount);
- Assert.AreEqual(0, client.PeersCount);
- }
-
- [Test, MaxTime(10000)]
- public void NetPeerDisconnectAll()
- {
- NetManager client = ManagerStack.Client(1);
- NetManager client2 = ManagerStack.Client(2);
- NetManager server = ManagerStack.Server(1);
-
- NetPeer 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.AreEqual(ConnectionState.Connected, clientServerPeer.ConnectionState);
- Assert.AreEqual(2, server.GetPeersCount(ConnectionState.Connected));
-
- 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);
- };
-
- server.DisconnectAll(new byte[]{1, 2, 3, 4}, 0, 4);
-
- Assert.AreEqual(0, server.GetPeersCount(ConnectionState.Connected));
-
- while (client.GetPeersCount(ConnectionState.Connected) != 0)
- {
- Thread.Sleep(15);
- client.PollEvents();
- server.PollEvents();
- }
-
- //Wait for client 'ShutdownOk' response
- Thread.Sleep(100);
-
- Assert.AreEqual(0, server.PeersCount);
- Assert.AreEqual(ConnectionState.Disconnected, clientServerPeer.ConnectionState);
- }
-
- [Test, MaxTime(2000)]
- public void DisconnectFromServerTest()
- {
- NetManager server = ManagerStack.Server(1);
- NetManager 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.PeersCount != 1)
- {
- Thread.Sleep(15);
- server.PollEvents();
- }
-
- server.DisconnectPeer(server.FirstPeer);
-
- while (!(clientDisconnected && serverDisconnected))
- {
- Thread.Sleep(15);
- client.PollEvents();
- server.PollEvents();
- }
-
- // Wait that server remove disconnected peers
- Thread.Sleep(100);
-
- Assert.True(clientDisconnected);
- Assert.True(serverDisconnected);
- Assert.AreEqual(0, server.PeersCount);
- Assert.AreEqual(0, client.PeersCount);
- }
-
- [Test, MaxTime(5000)]
- public void ConnectAfterDisconnectWithSamePort()
- {
- NetManager server = ManagerStack.Server(1);
-
- EventBasedNetListener listener = new EventBasedNetListener();
- NetManager client = new NetManager(listener);
- Assert.True(client.Start(9049));
- client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
- while (server.PeersCount != 1)
- {
- Thread.Sleep(15);
- server.PollEvents();
- }
- client.Stop();
-
- var connected = false;
- listener.PeerConnectedEvent += (peer) =>
- {
- connected = true;
- };
- Assert.True(client.Start(9049));
- client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
-
- while (!connected)
- {
- Thread.Sleep(15);
- server.PollEvents();
- client.PollEvents();
- }
-
- Assert.True(connected);
- Assert.AreEqual(1, server.PeersCount);
- Assert.AreEqual(1, client.PeersCount);
- }
-
- [Test, MaxTime(2000)]
- public void DisconnectFromClientTest()
- {
- NetManager server = ManagerStack.Server(1);
- NetManager 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; };
-
- NetPeer serverPeer = client.Connect("127.0.0.1", DefaultPort, DefaultAppKey);
- while (server.PeersCount != 1)
- {
- Thread.Sleep(15);
- server.PollEvents();
- }
-
- //User server peer from client
- serverPeer.Disconnect();
-
- while (!(clientDisconnected && serverDisconnected))
- {
- Thread.Sleep(15);
- client.PollEvents();
- server.PollEvents();
- }
-
- // Wait that server remove disconnected peers
- Thread.Sleep(100);
-
- Assert.True(clientDisconnected);
- Assert.True(serverDisconnected);
- Assert.AreEqual(0, server.PeersCount);
- Assert.AreEqual(0, client.PeersCount);
- }
-
- [Test, MaxTime(2000)]
- public void ConnectionByIpV6()
- {
- var server = ManagerStack.Server(1);
- var client = ManagerStack.Client(1);
- client.Connect("::1", DefaultPort, DefaultAppKey);
-
- while (server.PeersCount != 1 || client.PeersCount != 1)
- {
- Thread.Sleep(15);
- server.PollEvents();
- }
-
- Assert.AreEqual(1, server.PeersCount);
- Assert.AreEqual(1, client.PeersCount);
- }
-
- [Test, MaxTime(2000)]
- 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.AreEqual(type, UnconnectedMessageType.BasicMessage);
- Assert.AreEqual("Server response", reader.GetString());
- ManagerStack.Client(cache).Connect(point, DefaultAppKey);
- };
- }
-
- ManagerStack.ClientForeach((i, manager, l) => manager.SendBroadcast(writer, DefaultPort));
-
- while (server.PeersCount < clientCount)
- {
- server.PollEvents();
- ManagerStack.ClientForeach((i, manager, l) => manager.PollEvents());
-
- Thread.Sleep(15);
- }
-
- Assert.AreEqual(clientCount, server.PeersCount);
- ManagerStack.ClientForeach(
- (i, manager, l) =>
- {
- Assert.AreEqual(manager.PeersCount, 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.AreSame(ManagerStack.Server(1), ManagerStack.Server(1));
- Assert.AreNotSame(ManagerStack.Server(1), ManagerStack.Client(1));
- Assert.AreNotSame(ManagerStack.Server(1), ManagerStack.Client(2));
- }
-
- [Test]
- 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.PeersCount < clientCount)
- {
- Thread.Sleep(15);
- server.PollEvents();
- }
-
- Assert.AreEqual(server.PeersCount, clientCount);
- Thread.Sleep(100);
- ManagerStack.ClientForeach((i, manager, l) => Assert.AreEqual(manager.PeersCount, 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.AreEqual(dataStack.Count, clientCount);
-
- Assert.AreEqual(server.PeersCount, clientCount);
- for (ushort i = 1; i <= clientCount; i++)
- {
- Assert.AreEqual(ManagerStack.Client(i).PeersCount, 1);
- Assert.That(data, Is.EqualTo(dataStack.Pop()).AsCollection);
- }
- }
- }
-}
\ No newline at end of file
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 6492c448..b0bb097e 100644
--- a/LiteNetLib.Tests/LiteNetLib.Tests.csproj
+++ b/LiteNetLib.Tests/LiteNetLib.Tests.csproj
@@ -2,23 +2,22 @@
Library
- net45;netcoreapp2.0
+ net8.0;net9.0
-
-
-
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
-
-
-
-
diff --git a/LiteNetLib.Tests/NetSerializerTest.cs b/LiteNetLib.Tests/NetSerializerTest.cs
index 989cde12..a8bd036a 100644
--- a/LiteNetLib.Tests/NetSerializerTest.cs
+++ b/LiteNetLib.Tests/NetSerializerTest.cs
@@ -1,4 +1,8 @@
-using LiteNetLib.Utils;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.Serialization;
+using LiteNetLib.Utils;
using NUnit.Framework;
@@ -14,21 +18,31 @@ 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 } }
+ 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
};
_packetProcessor = new NetPacketProcessor();
_packetProcessor.RegisterNestedType();
_packetProcessor.RegisterNestedType(() => new SampleClass());
_packetProcessor.RegisterNestedType(SomeVector2.Serialize, SomeVector2.Deserialize);
+ _packetProcessor.RegisterNestedType(SomeFastVector3.Serialize, SomeFastVector3.Deserialize);
}
private SamplePacket _samplePacket;
@@ -60,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;
@@ -78,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()
@@ -100,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,
@@ -114,12 +185,20 @@ 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; }
}
private static bool AreSame(string s1, string s2)
@@ -131,13 +210,22 @@ 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()
{
var writer = new NetDataWriter();
_packetProcessor.Write(writer, _samplePacket);
- var reader = new NetDataReader(writer.CopyData());
+ var reader = new NetDataReader(writer);
SamplePacket readPackage = null;
_packetProcessor.SubscribeReusable(
@@ -148,18 +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.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.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.That(readPackage.SampleClassArray, Is.EqualTo(_samplePacket.SampleClassArray).AsCollection);
+ Assert.That(readPackage.SampleClassList, Is.EqualTo(_samplePacket.SampleClassList).AsCollection);
}
}
-}
\ No newline at end of file
+}
diff --git a/LiteNetLib.Tests/ReaderWriterSimpleDataTest.cs b/LiteNetLib.Tests/ReaderWriterSimpleDataTest.cs
index e1185eda..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));
}
}
-}
\ No newline at end of file
+}
diff --git a/LiteNetLib.Tests/TestUtility/NetManagerStack.cs b/LiteNetLib.Tests/TestUtility/NetManagerStack.cs
index 36e23026..4ade127c 100644
--- a/LiteNetLib.Tests/TestUtility/NetManagerStack.cs
+++ b/LiteNetLib.Tests/TestUtility/NetManagerStack.cs
@@ -1,41 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
-
+using LiteNetLib.Layers;
using NUnit.Framework;
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,43 +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;
+ var (netManager, listener) = CreateNetworkManager();
if (isClient)
{
- netManager = new NetManager(listener);
if (!netManager.Start())
- {
Assert.Fail($"Client {id} start failed");
- }
}
else
{
- netManager = new NetManager(listener);
if (!netManager.Start(_serverPort))
- {
Assert.Fail($"Server {id} on port{_serverPort} start failed");
- }
}
container = new NetContainer(netManager, listener);
@@ -126,4 +112,32 @@ private NetContainer GetNetworkManager(ushort id, bool isClient)
return container;
}
}
-}
\ No newline at end of file
+
+ 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.nuspec b/LiteNetLib.nuspec
deleted file mode 100644
index 9ae8c981..00000000
--- a/LiteNetLib.nuspec
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
- LiteNetLib
- LiteNetLib
- 0.8.3
- Ruslan Pyrch
- RevenantX
- https://github.com/RevenantX/LiteNetLib/blob/master/LICENSE.txt
- https://github.com/RevenantX/LiteNetLib
- false
- Lite reliable UDP library for .NET, Mono, and .NET Core.
- Copyright 2017 Ruslan Pyrch
- udp reliable-udp network
- https://github.com/RevenantX/LiteNetLib/releases/tag/v0.8.3
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/LiteNetLib.sln b/LiteNetLib.sln
index e0396dce..853c8778 100644
--- a/LiteNetLib.sln
+++ b/LiteNetLib.sln
@@ -1,10 +1,8 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio 15
-VisualStudioVersion = 15.0.27130.2024
+# Visual Studio Version 17
+VisualStudioVersion = 17.7.34031.279
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LibSample", "LibSample\LibSample.csproj", "{EF232CBE-04C8-4FD9-AE26-2F7EBBE81699}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiteNetLib.Tests", "LiteNetLib.Tests\LiteNetLib.Tests.csproj", "{6B591E33-E71F-4A37-A08D-B56DD47EDE3F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiteNetLib", "LiteNetLib\LiteNetLib.csproj", "{2F98F12C-B72A-48FE-A7E5-6D8B0C5ABACB}"
@@ -14,34 +12,26 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig
EndProjectSection
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
Debug|ARM = Debug|ARM
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
+ DebugUnsafe|Any CPU = DebugUnsafe|Any CPU
+ DebugUnsafe|ARM = DebugUnsafe|ARM
+ DebugUnsafe|x64 = DebugUnsafe|x64
+ DebugUnsafe|x86 = DebugUnsafe|x86
Release|Any CPU = Release|Any CPU
Release|ARM = Release|ARM
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {EF232CBE-04C8-4FD9-AE26-2F7EBBE81699}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {EF232CBE-04C8-4FD9-AE26-2F7EBBE81699}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {EF232CBE-04C8-4FD9-AE26-2F7EBBE81699}.Debug|ARM.ActiveCfg = Debug|Any CPU
- {EF232CBE-04C8-4FD9-AE26-2F7EBBE81699}.Debug|ARM.Build.0 = Debug|Any CPU
- {EF232CBE-04C8-4FD9-AE26-2F7EBBE81699}.Debug|x64.ActiveCfg = Debug|Any CPU
- {EF232CBE-04C8-4FD9-AE26-2F7EBBE81699}.Debug|x64.Build.0 = Debug|Any CPU
- {EF232CBE-04C8-4FD9-AE26-2F7EBBE81699}.Debug|x86.ActiveCfg = Debug|Any CPU
- {EF232CBE-04C8-4FD9-AE26-2F7EBBE81699}.Debug|x86.Build.0 = Debug|Any CPU
- {EF232CBE-04C8-4FD9-AE26-2F7EBBE81699}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {EF232CBE-04C8-4FD9-AE26-2F7EBBE81699}.Release|Any CPU.Build.0 = Release|Any CPU
- {EF232CBE-04C8-4FD9-AE26-2F7EBBE81699}.Release|ARM.ActiveCfg = Release|Any CPU
- {EF232CBE-04C8-4FD9-AE26-2F7EBBE81699}.Release|ARM.Build.0 = Release|Any CPU
- {EF232CBE-04C8-4FD9-AE26-2F7EBBE81699}.Release|x64.ActiveCfg = Release|Any CPU
- {EF232CBE-04C8-4FD9-AE26-2F7EBBE81699}.Release|x64.Build.0 = Release|Any CPU
- {EF232CBE-04C8-4FD9-AE26-2F7EBBE81699}.Release|x86.ActiveCfg = Release|Any CPU
- {EF232CBE-04C8-4FD9-AE26-2F7EBBE81699}.Release|x86.Build.0 = Release|Any CPU
{6B591E33-E71F-4A37-A08D-B56DD47EDE3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6B591E33-E71F-4A37-A08D-B56DD47EDE3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6B591E33-E71F-4A37-A08D-B56DD47EDE3F}.Debug|ARM.ActiveCfg = Debug|Any CPU
@@ -50,6 +40,14 @@ Global
{6B591E33-E71F-4A37-A08D-B56DD47EDE3F}.Debug|x64.Build.0 = Debug|Any CPU
{6B591E33-E71F-4A37-A08D-B56DD47EDE3F}.Debug|x86.ActiveCfg = Debug|Any CPU
{6B591E33-E71F-4A37-A08D-B56DD47EDE3F}.Debug|x86.Build.0 = Debug|Any CPU
+ {6B591E33-E71F-4A37-A08D-B56DD47EDE3F}.DebugUnsafe|Any CPU.ActiveCfg = Debug|Any CPU
+ {6B591E33-E71F-4A37-A08D-B56DD47EDE3F}.DebugUnsafe|Any CPU.Build.0 = Debug|Any CPU
+ {6B591E33-E71F-4A37-A08D-B56DD47EDE3F}.DebugUnsafe|ARM.ActiveCfg = Debug|Any CPU
+ {6B591E33-E71F-4A37-A08D-B56DD47EDE3F}.DebugUnsafe|ARM.Build.0 = Debug|Any CPU
+ {6B591E33-E71F-4A37-A08D-B56DD47EDE3F}.DebugUnsafe|x64.ActiveCfg = Debug|Any CPU
+ {6B591E33-E71F-4A37-A08D-B56DD47EDE3F}.DebugUnsafe|x64.Build.0 = Debug|Any CPU
+ {6B591E33-E71F-4A37-A08D-B56DD47EDE3F}.DebugUnsafe|x86.ActiveCfg = Debug|Any CPU
+ {6B591E33-E71F-4A37-A08D-B56DD47EDE3F}.DebugUnsafe|x86.Build.0 = Debug|Any CPU
{6B591E33-E71F-4A37-A08D-B56DD47EDE3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6B591E33-E71F-4A37-A08D-B56DD47EDE3F}.Release|Any CPU.Build.0 = Release|Any CPU
{6B591E33-E71F-4A37-A08D-B56DD47EDE3F}.Release|ARM.ActiveCfg = Release|Any CPU
@@ -66,6 +64,14 @@ Global
{2F98F12C-B72A-48FE-A7E5-6D8B0C5ABACB}.Debug|x64.Build.0 = Debug|Any CPU
{2F98F12C-B72A-48FE-A7E5-6D8B0C5ABACB}.Debug|x86.ActiveCfg = Debug|Any CPU
{2F98F12C-B72A-48FE-A7E5-6D8B0C5ABACB}.Debug|x86.Build.0 = Debug|Any CPU
+ {2F98F12C-B72A-48FE-A7E5-6D8B0C5ABACB}.DebugUnsafe|Any CPU.ActiveCfg = DebugUnsafe|Any CPU
+ {2F98F12C-B72A-48FE-A7E5-6D8B0C5ABACB}.DebugUnsafe|Any CPU.Build.0 = DebugUnsafe|Any CPU
+ {2F98F12C-B72A-48FE-A7E5-6D8B0C5ABACB}.DebugUnsafe|ARM.ActiveCfg = DebugUnsafe|Any CPU
+ {2F98F12C-B72A-48FE-A7E5-6D8B0C5ABACB}.DebugUnsafe|ARM.Build.0 = DebugUnsafe|Any CPU
+ {2F98F12C-B72A-48FE-A7E5-6D8B0C5ABACB}.DebugUnsafe|x64.ActiveCfg = DebugUnsafe|Any CPU
+ {2F98F12C-B72A-48FE-A7E5-6D8B0C5ABACB}.DebugUnsafe|x64.Build.0 = DebugUnsafe|Any CPU
+ {2F98F12C-B72A-48FE-A7E5-6D8B0C5ABACB}.DebugUnsafe|x86.ActiveCfg = DebugUnsafe|Any CPU
+ {2F98F12C-B72A-48FE-A7E5-6D8B0C5ABACB}.DebugUnsafe|x86.Build.0 = DebugUnsafe|Any CPU
{2F98F12C-B72A-48FE-A7E5-6D8B0C5ABACB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2F98F12C-B72A-48FE-A7E5-6D8B0C5ABACB}.Release|Any CPU.Build.0 = Release|Any CPU
{2F98F12C-B72A-48FE-A7E5-6D8B0C5ABACB}.Release|ARM.ActiveCfg = Release|Any CPU
@@ -74,6 +80,54 @@ Global
{2F98F12C-B72A-48FE-A7E5-6D8B0C5ABACB}.Release|x64.Build.0 = Release|Any CPU
{2F98F12C-B72A-48FE-A7E5-6D8B0C5ABACB}.Release|x86.ActiveCfg = Release|Any CPU
{2F98F12C-B72A-48FE-A7E5-6D8B0C5ABACB}.Release|x86.Build.0 = Release|Any CPU
+ {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.Debug|ARM.Build.0 = Debug|Any CPU
+ {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.Debug|x64.Build.0 = Debug|Any CPU
+ {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.Debug|x86.Build.0 = Debug|Any CPU
+ {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.DebugUnsafe|Any CPU.ActiveCfg = Debug|Any CPU
+ {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.DebugUnsafe|Any CPU.Build.0 = Debug|Any CPU
+ {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.DebugUnsafe|ARM.ActiveCfg = Debug|Any CPU
+ {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.DebugUnsafe|ARM.Build.0 = Debug|Any CPU
+ {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.DebugUnsafe|x64.ActiveCfg = Debug|Any CPU
+ {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.DebugUnsafe|x64.Build.0 = Debug|Any CPU
+ {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.DebugUnsafe|x86.ActiveCfg = Debug|Any CPU
+ {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.DebugUnsafe|x86.Build.0 = Debug|Any CPU
+ {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.Release|ARM.ActiveCfg = Release|Any CPU
+ {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.Release|ARM.Build.0 = Release|Any CPU
+ {6499F9B4-0814-42EE-90A5-119AEA07E7EE}.Release|x64.ActiveCfg = Release|Any CPU
+ {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 c734413f..1dd969c5 100644
--- a/LiteNetLib/BaseChannel.cs
+++ b/LiteNetLib/BaseChannel.cs
@@ -1,31 +1,83 @@
using System.Collections.Generic;
+using System.Threading;
namespace LiteNetLib
{
+ ///
+ /// Base class for reliable and sequenced communication channels.
+ /// Handles the queuing and scheduling of outgoing packets.
+ ///
internal abstract class BaseChannel
{
- public BaseChannel Next;
- protected readonly NetPeer Peer;
- protected readonly Queue OutgoingQueue;
+ ///
+ /// 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;
- protected BaseChannel(NetPeer peer)
- {
+ ///
+ /// Gets the number of packets currently residing in the outgoing queue.
+ ///
+ public int PacketsInQueue => OutgoingQueue.Count;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The peer that owns this channel.
+ protected BaseChannel(LiteNetPeer peer) =>
Peer = peer;
- OutgoingQueue = new Queue(64);
+
+ ///
+ /// 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)
+ {
+ OutgoingQueue.Enqueue(packet);
+ }
+ AddToPeerChannelSendQueue();
}
- public int PacketsInQueue
+ ///
+ /// Thread-safely marks this channel as having pending data and adds it to the peer's update list.
+ ///
+ protected void AddToPeerChannelSendQueue()
{
- get { return OutgoingQueue.Count; }
+ if (Interlocked.CompareExchange(ref _isAddedToPeerChannelSendQueue, 1, 0) == 0)
+ Peer.AddToReliableChannelSendQueue(this);
}
- public void AddToQueue(NetPacket packet)
+ ///
+ /// 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()
{
- lock (OutgoingQueue)
- OutgoingQueue.Enqueue(packet);
+ bool hasPacketsToSend = SendNextPackets();
+ if (!hasPacketsToSend)
+ Interlocked.Exchange(ref _isAddedToPeerChannelSendQueue, 0);
+
+ return hasPacketsToSend;
}
- public abstract void 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 8bdbc7d4..8fb15cf5 100644
--- a/LiteNetLib/ConnectionRequest.cs
+++ b/LiteNetLib/ConnectionRequest.cs
@@ -4,76 +4,80 @@
namespace LiteNetLib
{
- public enum ConnectionRequestType
- {
- Incoming,
- PeerToPeer
- }
-
internal enum ConnectionRequestResult
{
None,
Accept,
- Reject
+ Reject,
+ RejectForce
}
- internal interface IConnectionRequestListener
+ public class LiteConnectionRequest
{
- void OnConnectionSolved(ConnectionRequest request, byte[] rejectData, int start, int length);
- }
-
- public class ConnectionRequest
- {
- private readonly IConnectionRequestListener _listener;
+ private readonly LiteNetManager _listener;
private int _used;
- public IPEndPoint RemoteEndPoint { get { return Peer.EndPoint; } }
- public readonly NetDataReader Data;
- public ConnectionRequestType Type { get; private set; }
+ ///
+ /// Data sent by the remote peer in the connection request.
+ ///
+ public NetDataReader Data => InternalPacket.Data;
internal ConnectionRequestResult Result { get; private set; }
- internal readonly long ConnectionId;
- internal readonly byte ConnectionNumber;
- internal readonly NetPeer Peer;
+ internal NetConnectRequestPacket InternalPacket;
+
+ ///
+ /// The remote endpoint (IP and Port) of the peer requesting the connection.
+ ///
+ public readonly IPEndPoint RemoteEndPoint;
- private bool TryActivate()
+ internal void UpdateRequest(NetConnectRequestPacket connectRequest)
{
- return Interlocked.CompareExchange(ref _used, 1, 0) == 0;
+ //old request
+ if (connectRequest.ConnectionTime < InternalPacket.ConnectionTime)
+ return;
+
+ if (connectRequest.ConnectionTime == InternalPacket.ConnectionTime &&
+ connectRequest.ConnectionNumber == InternalPacket.ConnectionNumber)
+ return;
+
+ InternalPacket = connectRequest;
}
- internal ConnectionRequest(
- long connectionId,
- byte connectionNumber,
- ConnectionRequestType type,
- NetDataReader netDataReader,
- NetPeer peer,
- IConnectionRequestListener listener)
+ private bool TryActivate() =>
+ Interlocked.CompareExchange(ref _used, 1, 0) == 0;
+
+ internal LiteConnectionRequest(IPEndPoint remoteEndPoint, NetConnectRequestPacket requestPacket, LiteNetManager listener)
{
- ConnectionId = connectionId;
- ConnectionNumber = connectionNumber;
- Type = type;
- Peer = peer;
- Data = netDataReader;
+ 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;
try
{
if (Data.GetString() == key)
- {
Result = ConnectionRequestResult.Accept;
- _listener.OnConnectionSolved(this, null, 0, 0);
- return Peer;
- }
}
catch
{
NetDebug.WriteError("[AC] Invalid incoming data");
}
+ if (Result == ConnectionRequestResult.Accept)
+ return _listener.OnConnectionSolved(this, null, 0, 0);
+
Result = ConnectionRequestResult.Reject;
_listener.OnConnectionSolved(this, null, 0, 0);
return null;
@@ -83,36 +87,102 @@ 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;
- _listener.OnConnectionSolved(this, null, 0, 0);
- return Peer;
+ return _listener.OnConnectionSolved(this, null, 0, 0);
}
- public void Reject(byte[] rejectData, int start, int length)
+ ///
+ /// 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)
{
if (!TryActivate())
return;
- Result = ConnectionRequestResult.Reject;
+ Result = force ? ConnectionRequestResult.RejectForce : ConnectionRequestResult.Reject;
_listener.OnConnectionSolved(this, rejectData, start, length);
}
- public void Reject()
- {
- Reject(null, 0, 0);
- }
+ ///
+ /// 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(rejectData, start, length, false);
- public void Reject(byte[] rejectData)
- {
- Reject(rejectData, 0, rejectData.Length);
- }
+ ///
+ /// 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(rejectData, start, length, true);
+
+ ///
+ /// Rejects the connection immediately without sending any packet.
+ ///
+ public void RejectForce() =>
+ Reject(null, 0, 0, true);
+
+ ///
+ /// Rejects the connection immediately without reliability.
+ ///
+ /// Data to send with the rejection.
+ public void RejectForce(byte[] rejectData) =>
+ Reject(rejectData, 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.Data, 0, rejectData.Length, true);
- public void Reject(NetDataWriter rejectData)
+ ///
+ /// Rejects the connection reliably without additional data.
+ ///
+ public void Reject() =>
+ Reject(null, 0, 0, false);
+
+ ///
+ /// Rejects the connection reliably.
+ ///
+ /// Data to send with the rejection.
+ public void Reject(byte[] rejectData) =>
+ Reject(rejectData, 0, rejectData.Length, false);
+
+ ///
+ /// Rejects the connection reliably using data from a .
+ ///
+ /// Writer containing the data to send.
+ public void Reject(NetDataWriter rejectData) =>
+ Reject(rejectData.Data, 0, rejectData.Length, false);
+ }
+
+ public class ConnectionRequest : LiteConnectionRequest
+ {
+ internal ConnectionRequest(IPEndPoint remoteEndPoint, NetConnectRequestPacket requestPacket, LiteNetManager listener) : base(remoteEndPoint, requestPacket, listener)
{
- Reject(rejectData.Data, 0, rejectData.Length);
}
+
+ ///
+ 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 7589041a..17891c6e 100644
--- a/LiteNetLib/INetEventListener.cs
+++ b/LiteNetLib/INetEventListener.cs
@@ -1,5 +1,6 @@
-using System.Net;
+using System.Net;
using System.Net.Sockets;
+using LiteNetLib.Utils;
namespace LiteNetLib
{
@@ -17,14 +18,39 @@ 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
+ UnknownHost,
+ Reconnect,
+ PeerToPeerConnection,
+ PeerNotFound
}
///
@@ -48,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
{
///
@@ -75,15 +104,16 @@ public interface INetEventListener
///
/// From peer
/// DataReader containing all received data
+ /// Number of channel at which packet arrived
/// Type of received packet
- void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod);
+ void OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelNumber, DeliveryMethod deliveryMethod);
///
/// Received unconnected message
///
/// From address (IP and Port)
/// Message data
- /// Message type (simple, discovery request or responce)
+ /// Message type (simple, discovery request or response)
void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType);
///
@@ -98,102 +128,397 @@ public interface INetEventListener
///
/// Request information (EndPoint, internal id, additional data)
void OnConnectionRequest(ConnectionRequest request);
+
+ ///
+ /// On reliable message delivered
+ ///
+ ///
+ ///
+ void OnMessageDelivered(NetPeer peer, object userData) { }
+
+ ///
+ /// Ntp response
+ ///
+ ///
+ 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) { }
+ }
+
+ ///
+ /// 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(LiteNetPeer peer, IPEndPoint previousAddress) { }
+
+ ///
+ /// On reliable message delivered
+ ///
+ ///
+ ///
+ void OnMessageDelivered(LiteNetPeer peer, object userData) { }
}
+ ///
+ /// 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);
- public delegate void OnNetworkReceive(NetPeer peer, NetPacketReader reader, DeliveryMethod deliveryMethod);
+ ///
+ /// 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;
+
+ /// 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);
- 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;
- }
-
- 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, DeliveryMethod deliveryMethod)
- {
- if (NetworkReceiveEvent != null)
- NetworkReceiveEvent(peer, reader, 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 ILiteNetEventListener.OnPeerAddressChanged(LiteNetPeer peer, IPEndPoint previousAddress) =>
+ PeerAddressChangedEvent?.Invoke(peer, previousAddress);
}
}
diff --git a/LiteNetLib/InternalPackets.cs b/LiteNetLib/InternalPackets.cs
new file mode 100644
index 00000000..6cd9a8db
--- /dev/null
+++ b/LiteNetLib/InternalPackets.cs
@@ -0,0 +1,131 @@
+using System;
+using System.Net;
+using LiteNetLib.Utils;
+
+namespace LiteNetLib
+{
+ public sealed class NetConnectRequestPacket
+ {
+ public const int HeaderSize = 18;
+ public readonly long ConnectionTime;
+ public byte ConnectionNumber;
+ public readonly byte[] TargetAddress;
+ public readonly NetDataReader Data;
+ public readonly int PeerId;
+
+ private NetConnectRequestPacket(long connectionTime, byte connectionNumber, int localId, byte[] targetAddress, NetDataReader data)
+ {
+ ConnectionTime = connectionTime;
+ ConnectionNumber = connectionNumber;
+ TargetAddress = targetAddress;
+ Data = data;
+ PeerId = localId;
+ }
+
+ internal static int GetProtocolId(NetPacket packet) =>
+ BitConverter.ToInt32(packet.RawData, 1);
+
+ internal static NetConnectRequestPacket FromData(NetPacket packet)
+ {
+ if (packet.ConnectionNumber >= NetConstants.MaxConnectionNumber)
+ return null;
+
+ //Getting connection time for peer
+ long connectionTime = BitConverter.ToInt64(packet.RawData, 5);
+
+ //Get peer id
+ int peerId = BitConverter.ToInt32(packet.RawData, 13);
+
+ //Get target address
+ int addrSize = packet.RawData[HeaderSize-1];
+ if (addrSize != 16 && addrSize != 28)
+ return null;
+ byte[] addressBytes = new byte[addrSize];
+ Buffer.BlockCopy(packet.RawData, HeaderSize, addressBytes, 0, addrSize);
+
+ // Read data and create request
+ var reader = new NetDataReader(null, 0, 0);
+ if (packet.Size > HeaderSize+addrSize)
+ reader.SetSource(packet.RawData, HeaderSize + addrSize, packet.Size);
+
+ return new NetConnectRequestPacket(connectionTime, packet.ConnectionNumber, peerId, addressBytes, reader);
+ }
+
+ 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);
+
+ //Add data
+ 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;
+ for (int i = 0; i < addressBytes.Size; i++)
+ packet.RawData[HeaderSize + i] = addressBytes[i];
+ connectData.CopyTo(packet.RawData.AsSpan(HeaderSize + addressBytes.Size));
+ return packet;
+ }
+ }
+
+ internal sealed class NetConnectAcceptPacket
+ {
+ public const int Size = 15;
+ public readonly long ConnectionTime;
+ public readonly byte ConnectionNumber;
+ public readonly int PeerId;
+ public readonly bool PeerNetworkChanged;
+
+ private NetConnectAcceptPacket(long connectionTime, byte connectionNumber, int peerId, bool peerNetworkChanged)
+ {
+ ConnectionTime = connectionTime;
+ ConnectionNumber = connectionNumber;
+ PeerId = peerId;
+ PeerNetworkChanged = peerNetworkChanged;
+ }
+
+ public static NetConnectAcceptPacket FromData(NetPacket packet)
+ {
+ if (packet.Size != Size)
+ return null;
+
+ long connectionId = BitConverter.ToInt64(packet.RawData, 1);
+
+ //check connect num
+ byte connectionNumber = packet.RawData[9];
+ if (connectionNumber >= NetConstants.MaxConnectionNumber)
+ return null;
+
+ //check reused flag
+ byte isReused = packet.RawData[10];
+ if (isReused > 1)
+ return null;
+
+ //get remote peer id
+ int peerId = BitConverter.ToInt32(packet.RawData, 11);
+ if (peerId < 0)
+ return null;
+
+ return new NetConnectAcceptPacket(connectionId, connectionNumber, peerId, isReused == 1);
+ }
+
+ public static NetPacket Make(long connectTime, byte connectNum, int localPeerId)
+ {
+ var packet = new NetPacket(PacketProperty.ConnectAccept, 0);
+ FastBitConverter.GetBytes(packet.RawData, 1, connectTime);
+ packet.RawData[9] = connectNum;
+ FastBitConverter.GetBytes(packet.RawData, 11, localPeerId);
+ return packet;
+ }
+
+ public static NetPacket MakeNetworkChanged(LiteNetPeer peer)
+ {
+ var packet = new NetPacket(PacketProperty.PeerNotFound, Size-1);
+ FastBitConverter.GetBytes(packet.RawData, 1, peer.ConnectTime);
+ packet.RawData[9] = peer.ConnectionNum;
+ packet.RawData[10] = 1;
+ FastBitConverter.GetBytes(packet.RawData, 11, peer.RemoteId);
+ return packet;
+ }
+ }
+}
diff --git a/LiteNetLib/Layers/Crc32cLayer.cs b/LiteNetLib/Layers/Crc32cLayer.cs
new file mode 100644
index 00000000..dc07fe19
--- /dev/null
+++ b/LiteNetLib/Layers/Crc32cLayer.cs
@@ -0,0 +1,41 @@
+using LiteNetLib.Utils;
+using System;
+using System.Net;
+
+namespace LiteNetLib.Layers
+{
+ public sealed class Crc32cLayer : PacketLayerBase
+ {
+ public Crc32cLayer() : base(CRC32C.ChecksumSize)
+ {
+
+ }
+
+ public override void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int length)
+ {
+ if (length < NetConstants.HeaderSize + CRC32C.ChecksumSize)
+ {
+ NetDebug.WriteError("[NM] DataReceived size: bad!");
+ //Set length to 0 to have netManager drop the packet.
+ length = 0;
+ return;
+ }
+
+ int checksumPoint = length - CRC32C.ChecksumSize;
+ 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.
+ length = 0;
+ return;
+ }
+ length -= CRC32C.ChecksumSize;
+ }
+
+ public override void ProcessOutBoundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int offset, ref int length)
+ {
+ FastBitConverter.GetBytes(data, length, CRC32C.Compute(data, offset, length));
+ length += CRC32C.ChecksumSize;
+ }
+ }
+}
diff --git a/LiteNetLib/Layers/PacketLayerBase.cs b/LiteNetLib/Layers/PacketLayerBase.cs
new file mode 100644
index 00000000..f2e4c881
--- /dev/null
+++ b/LiteNetLib/Layers/PacketLayerBase.cs
@@ -0,0 +1,17 @@
+using System.Net;
+
+namespace LiteNetLib.Layers
+{
+ public abstract class PacketLayerBase
+ {
+ public readonly int ExtraPacketSizeForLayer;
+
+ protected PacketLayerBase(int extraPacketSizeForLayer)
+ {
+ ExtraPacketSizeForLayer = extraPacketSizeForLayer;
+ }
+
+ 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
new file mode 100644
index 00000000..a3a130bf
--- /dev/null
+++ b/LiteNetLib/Layers/XorEncryptLayer.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Net;
+using System.Text;
+
+namespace LiteNetLib.Layers
+{
+ public class XorEncryptLayer : PacketLayerBase
+ {
+ private byte[] _byteKey;
+
+ public XorEncryptLayer() : base(0)
+ {
+
+ }
+
+ public XorEncryptLayer(byte[] key) : this()
+ {
+ SetKey(key);
+ }
+
+ public XorEncryptLayer(string key) : this()
+ {
+ SetKey(key);
+ }
+
+ public void SetKey(string key)
+ {
+ _byteKey = Encoding.UTF8.GetBytes(key);
+ }
+
+ public void SetKey(byte[] key)
+ {
+ if (_byteKey == null || _byteKey.Length != key.Length)
+ _byteKey = new byte[key.Length];
+ Buffer.BlockCopy(key, 0, _byteKey, 0, key.Length);
+ }
+
+ public override void ProcessInboundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int length)
+ {
+ if (_byteKey == null)
+ return;
+ for (int i = 0; i < length; i++)
+ {
+ data[i] = (byte)(data[i] ^ _byteKey[i % _byteKey.Length]);
+ }
+ }
+
+ public override void ProcessOutBoundPacket(ref IPEndPoint endPoint, ref byte[] data, ref int offset, ref int length)
+ {
+ if (_byteKey == null)
+ return;
+ 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.asmdef b/LiteNetLib/LiteNetLib.asmdef
new file mode 100644
index 00000000..530c72ed
--- /dev/null
+++ b/LiteNetLib/LiteNetLib.asmdef
@@ -0,0 +1,13 @@
+{
+ "name": "LiteNetLib",
+ "references": [],
+ "includePlatforms": [],
+ "excludePlatforms": [],
+ "allowUnsafeCode": true,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": [],
+ "versionDefines": [],
+ "noEngineReferences": false
+}
\ No newline at end of file
diff --git a/LiteNetLib/LiteNetLib.csproj b/LiteNetLib/LiteNetLib.csproj
index 67493698..fa18ce06 100644
--- a/LiteNetLib/LiteNetLib.csproj
+++ b/LiteNetLib/LiteNetLib.csproj
@@ -3,27 +3,58 @@
LiteNetLib
LiteNetLib
- 4
- net35;netstandard2.0;netcoreapp2.0
+ net8.0;netstandard2.1
+ true
+ Library
+ 8
+
+ true
+ 1701;1702;1705;1591
+ 2.1.3
+ Lite reliable UDP library for Mono and .NET
+ true
+ true
+ 2.1.3
-
+
TRACE;DEBUG
- 1701;1702;1705;1591
- 4
-
+
TRACE
- bin\Release\net35\LiteNetLib.xml
- 1701;1702;1705;1591
-
-
-
-
-
-
+
+ $(DefineConstants);SIMULATE_NETWORK
+
+
+
+ true
+ $(DefineConstants);LITENETLIB_UNSAFE
+ udp reliable-udp network
+ https://github.com/RevenantX/LiteNetLib/releases/tag/2.1.3
+ git
+ https://github.com/RevenantX/LiteNetLib
+ https://github.com/RevenantX/LiteNetLib
+ MIT
+ True
+ Ruslan Pyrch
+ Copyright 2026 Ruslan Pyrch
+ Lite reliable UDP library for .NET, Mono, and .NET Core
+ LNL.png
+ README.md
+
+
+
+
+ True
+ \
+
+
+ True
+ \
+
+
\ No newline at end of file
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/LiteNetManager.PacketPool.cs b/LiteNetLib/LiteNetManager.PacketPool.cs
new file mode 100644
index 00000000..fd8520f6
--- /dev/null
+++ b/LiteNetLib/LiteNetManager.PacketPool.cs
@@ -0,0 +1,82 @@
+using System;
+
+namespace LiteNetLib
+{
+ public partial class LiteNetManager
+ {
+ private NetPacket _poolHead;
+ private int _poolCount;
+ private readonly object _poolLock = new object();
+
+ ///
+ /// Maximum packet pool size (increase if you have tons of packets sending)
+ ///
+ public int PacketPoolSize = 1000;
+
+ public int PoolCount => _poolCount;
+
+ private NetPacket PoolGetWithData(PacketProperty property, byte[] data, int start, int length)
+ {
+ int headerSize = NetPacket.GetHeaderSize(property);
+ NetPacket packet = PoolGetPacket(length + headerSize);
+ packet.Property = property;
+ Buffer.BlockCopy(data, start, packet.RawData, headerSize, length);
+ return packet;
+ }
+
+ //Get packet with size
+ private NetPacket PoolGetWithProperty(PacketProperty property, int size)
+ {
+ NetPacket packet = PoolGetPacket(size + NetPacket.GetHeaderSize(property));
+ packet.Property = property;
+ return packet;
+ }
+
+ private NetPacket PoolGetWithProperty(PacketProperty property)
+ {
+ NetPacket packet = PoolGetPacket(NetPacket.GetHeaderSize(property));
+ packet.Property = property;
+ return packet;
+ }
+
+ internal NetPacket PoolGetPacket(int size)
+ {
+ if (size > NetConstants.MaxPacketSize)
+ return new NetPacket(size);
+
+ NetPacket packet;
+ lock (_poolLock)
+ {
+ packet = _poolHead;
+ if (packet == null)
+ return new NetPacket(size);
+
+ _poolHead = _poolHead.Next;
+ _poolCount--;
+ }
+
+ packet.Size = size;
+ if (packet.RawData.Length < size)
+ packet.RawData = new byte[size];
+ return packet;
+ }
+
+ internal void PoolRecycle(NetPacket packet)
+ {
+ if (packet.RawData.Length > NetConstants.MaxPacketSize || _poolCount >= PacketPoolSize)
+ {
+ //Don't pool big packets. Save memory
+ return;
+ }
+
+ //Clean fragmented flag
+ packet.RawData[0] = 0;
+ lock (_poolLock)
+ {
+ packet.Next = _poolHead;
+ _poolHead = packet;
+ _poolCount++;
+ }
+ }
+ }
+}
diff --git a/LiteNetLib/LiteNetManager.Socket.cs b/LiteNetLib/LiteNetManager.Socket.cs
new file mode 100644
index 00000000..2f1e35a3
--- /dev/null
+++ b/LiteNetLib/LiteNetManager.Socket.cs
@@ -0,0 +1,736 @@
+#if UNITY_2018_3_OR_NEWER
+#define UNITY_SOCKET_FIX
+#endif
+using System.Runtime.InteropServices;
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using LiteNetLib.Utils;
+
+namespace LiteNetLib
+{
+ public partial class LiteNetManager
+ {
+ protected Socket _udpSocketv4;
+ private Socket _udpSocketv6;
+ private Thread _receiveThread;
+ private IPEndPoint _bufferEndPointv4;
+ private IPEndPoint _bufferEndPointv6;
+#if UNITY_SOCKET_FIX
+ private PausedSocketFix _pausedSocketFix;
+ private bool _useSocketFix;
+#endif
+
+#if NET8_0_OR_GREATER
+ private readonly SocketAddress _sockAddrCacheV4 = new SocketAddress(AddressFamily.InterNetwork);
+ private readonly SocketAddress _sockAddrCacheV6 = new SocketAddress(AddressFamily.InterNetworkV6);
+#endif
+
+ 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;
+
+ // 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
+ {
+#if UNITY_SWITCH
+ return 0;
+#else
+ return _udpSocketv4.Ttl;
+#endif
+ }
+ internal set
+ {
+#if !UNITY_SWITCH
+ _udpSocketv4.Ttl = value;
+#endif
+ }
+ }
+
+ static LiteNetManager()
+ {
+#if DISABLE_IPV6
+ IPv6Support = false;
+#elif !UNITY_2019_1_OR_NEWER && !UNITY_2018_4_OR_NEWER && (!UNITY_EDITOR && ENABLE_IL2CPP)
+ string version = UnityEngine.Application.unityVersion;
+ IPv6Support = Socket.OSSupportsIPv6 && int.Parse(version.Remove(version.IndexOf('f')).Split('.')[2]) >= 6;
+#else
+ IPv6Support = Socket.OSSupportsIPv6;
+#endif
+ }
+
+ private bool ProcessError(SocketException ex)
+ {
+ switch (ex.SocketErrorCode)
+ {
+ case SocketError.NotConnected:
+ NotConnected = true;
+ return true;
+ case SocketError.Interrupted:
+ case SocketError.NotSocket:
+ case SocketError.OperationAborted:
+ return true;
+ case SocketError.ConnectionReset:
+ case SocketError.MessageSize:
+ case SocketError.TimedOut:
+ case SocketError.NetworkReset:
+ case SocketError.WouldBlock:
+ //NetDebug.Write($"[R]Ignored error: {(int)ex.SocketErrorCode} - {ex}");
+ break;
+ default:
+ NetDebug.WriteError($"[R]Error code: {(int)ex.SocketErrorCode} - {ex}");
+ CreateEvent(NetEvent.EType.Error, errorCode: ex.SocketErrorCode);
+ break;
+ }
+ return false;
+ }
+
+ private void ManualReceive(Socket socket, EndPoint bufferEndPoint)
+ {
+ //Reading data
+ try
+ {
+ int count = MaxPacketPerManualReceive;
+ while (socket.Available > 0 && (count > 0 || MaxPacketPerManualReceive == 0))
+ {
+ if(ReceiveFrom(socket, ref bufferEndPoint) > 0)
+ count--;
+ else
+ break;
+ }
+ }
+ catch (SocketException ex)
+ {
+ ProcessError(ex);
+ }
+ catch (ObjectDisposedException)
+ {
+
+ }
+ catch (Exception e)
+ {
+ //protects socket receive thread
+ NetDebug.WriteError("[NM] SocketReceiveThread error: " + e);
+ }
+ }
+
+ 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];
+ 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)
+ {
+ 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)
+ {
+ if (ProcessError(ex))
+ return;
+ }
+ catch (ObjectDisposedException)
+ {
+ //socket closed
+ return;
+ }
+ catch (ThreadAbortException)
+ {
+ //thread closed
+ return;
+ }
+ catch (Exception e)
+ {
+ //protects socket receive thread
+ 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 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()
+ {
+ EndPoint bufferEndPoint4 = new IPEndPoint(IPAddress.Any, 0);
+ EndPoint bufferEndPoint6 = new IPEndPoint(IPAddress.IPv6Any, 0);
+ var selectReadList = new List(2);
+ var socketv4 = _udpSocketv4;
+ var socketV6 = _udpSocketv6;
+
+ while (_isRunning)
+ {
+ //Reading data
+ try
+ {
+ if (socketV6 == null)
+ {
+ if (socketv4.Available == 0 && !socketv4.Poll(ReceivePollingTime, SelectMode.SelectRead))
+ continue;
+ ReceiveFrom(socketv4, ref bufferEndPoint4);
+ }
+ else
+ {
+ bool messageReceived = false;
+ if (socketv4.Available != 0 || selectReadList.Contains(socketv4))
+ {
+ ReceiveFrom(socketv4, ref bufferEndPoint4);
+ messageReceived = true;
+ }
+ if (socketV6.Available != 0 || selectReadList.Contains(socketV6))
+ {
+ ReceiveFrom(socketV6, ref bufferEndPoint6);
+ messageReceived = true;
+ }
+
+ selectReadList.Clear();
+
+ if (messageReceived)
+ continue;
+
+ selectReadList.Add(socketv4);
+ selectReadList.Add(socketV6);
+ Socket.Select(selectReadList, null, null, ReceivePollingTime);
+ }
+ //NetDebug.Write(NetLogLevel.Trace, $"[R]Received data from {bufferEndPoint}, result: {packet.Size}");
+ }
+ catch (SocketException ex)
+ {
+ if (ProcessError(ex))
+ return;
+ }
+ catch (ObjectDisposedException)
+ {
+ //socket closed
+ return;
+ }
+ catch (ThreadAbortException)
+ {
+ //thread closed
+ return;
+ }
+ catch (Exception e)
+ {
+ //protects socket receive thread
+ 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
+ ///
+ /// 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)
+ return false;
+
+ NotConnected = false;
+ _manualMode = manualMode;
+ UseNativeSockets = UseNativeSockets && NativeSocket.IsSupported;
+ _udpSocketv4 = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
+ if (!BindSocket(_udpSocketv4, new IPEndPoint(addressIPv4, port)))
+ return false;
+
+ LocalPort = ((IPEndPoint)_udpSocketv4.LocalEndPoint).Port;
+
+#if UNITY_SOCKET_FIX
+ if (_useSocketFix && _pausedSocketFix == null)
+ _pausedSocketFix = new PausedSocketFix(this, addressIPv4, addressIPv6, port, manualMode);
+#endif
+
+ _isRunning = true;
+ if (_manualMode)
+ {
+ _bufferEndPointv4 = new IPEndPoint(IPAddress.Any, 0);
+ }
+
+ //Check IPv6 support
+ if (IPv6Support && IPv6Enabled)
+ {
+ _udpSocketv6 = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
+ //Use one port for two sockets
+ if (BindSocket(_udpSocketv6, new IPEndPoint(addressIPv6, LocalPort)))
+ {
+ if (_manualMode)
+ _bufferEndPointv6 = new IPEndPoint(IPAddress.IPv6Any, 0);
+ }
+ else
+ {
+ _udpSocketv6 = null;
+ }
+ }
+
+ if (!manualMode)
+ {
+ ThreadStart ts = ReceiveLogic;
+ if (UseNativeSockets)
+ ts = NativeReceiveLogic;
+ _receiveThread = new Thread(ts)
+ {
+ Name = $"ReceiveThread({LocalPort})",
+ IsBackground = true
+ };
+ _receiveThread.Start();
+ if (_logicThread == null)
+ {
+ _logicThread = new Thread(UpdateLogic) { Name = "LogicThread", IsBackground = true };
+ _logicThread.Start();
+ }
+ }
+
+ return true;
+ }
+
+ private bool BindSocket(Socket socket, IPEndPoint ep)
+ {
+ //Setup socket
+ socket.ReceiveTimeout = 500;
+ socket.SendTimeout = 500;
+ socket.ReceiveBufferSize = NetConstants.SocketBufferSize;
+ socket.SendBufferSize = NetConstants.SocketBufferSize;
+ socket.Blocking = true;
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ try
+ {
+ socket.IOControl(SioUdpConnreset, new byte[] { 0 }, null);
+ }
+ catch
+ {
+ //ignored
+ }
+ }
+
+ try
+ {
+ socket.ExclusiveAddressUse = !ReuseAddress;
+ socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, ReuseAddress);
+ socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontRoute, DontRoute);
+ }
+ catch
+ {
+ //Unity with IL2CPP throws an exception here, it doesn't matter in most cases so just ignore it
+ }
+ if (ep.AddressFamily == AddressFamily.InterNetwork)
+ {
+ Ttl = NetConstants.SocketTTL;
+
+ try { socket.EnableBroadcast = true; }
+ catch (SocketException e)
+ {
+ NetDebug.WriteError($"[B]Broadcast error: {e.SocketErrorCode}");
+ }
+
+ if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ try { socket.DontFragment = true; }
+ catch (SocketException e)
+ {
+ NetDebug.WriteError($"[B]DontFragment error: {e.SocketErrorCode}");
+ }
+ }
+ }
+ //Bind
+ try
+ {
+ socket.Bind(ep);
+ NetDebug.Write(NetLogLevel.Trace, $"[B]Successfully binded to port: {((IPEndPoint)socket.LocalEndPoint).Port}, AF: {socket.AddressFamily}");
+
+ //join multicast
+ if (ep.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ try
+ {
+#if !UNITY_SOCKET_FIX
+ socket.SetSocketOption(
+ SocketOptionLevel.IPv6,
+ SocketOptionName.AddMembership,
+ new IPv6MulticastOption(MulticastAddressV6));
+#endif
+ }
+ catch (Exception)
+ {
+ // Unity3d throws exception - ignored
+ }
+ }
+ }
+ catch (SocketException bindException)
+ {
+ switch (bindException.SocketErrorCode)
+ {
+ //IPv6 bind fix
+ case SocketError.AddressAlreadyInUse:
+ if (socket.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ try
+ {
+ //Set IPv6Only
+ socket.DualMode = false;
+ socket.Bind(ep);
+ }
+ catch (SocketException ex)
+ {
+ //because its fixed in 2018_3
+ NetDebug.WriteError($"[B]Bind exception: {ex}, errorCode: {ex.SocketErrorCode}");
+ return false;
+ }
+ return true;
+ }
+ break;
+ //hack for iOS (Unity3D)
+ case SocketError.AddressFamilyNotSupported:
+ return true;
+ }
+ NetDebug.WriteError($"[B]Bind exception: {bindException}, errorCode: {bindException.SocketErrorCode}");
+ return false;
+ }
+ return true;
+ }
+
+ internal int SendRawAndRecycle(NetPacket packet, IPEndPoint remoteEndPoint)
+ {
+ int result = SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint);
+ PoolRecycle(packet);
+ return result;
+ }
+
+ 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)
+ return 0;
+
+ NetPacket expandedPacket = null;
+ if (_extraPacketLayer != null)
+ {
+ expandedPacket = PoolGetPacket(length + _extraPacketLayer.ExtraPacketSizeForLayer);
+ Buffer.BlockCopy(message, start, expandedPacket.RawData, 0, length);
+ start = 0;
+ _extraPacketLayer.ProcessOutBoundPacket(ref remoteEndPoint, ref expandedPacket.RawData, ref start, ref length);
+ 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
+
+ return SendRawCoreWithCleanup(message, start, length, remoteEndPoint, expandedPacket);
+ }
+
+ private int SendRawCoreWithCleanup(byte[] message, int start, int length, IPEndPoint remoteEndPoint, NetPacket expandedPacket)
+ {
+ try
+ {
+ return SendRawCore(message, start, length, remoteEndPoint);
+ }
+ finally
+ {
+ if (expandedPacket != null)
+ PoolRecycle(expandedPacket);
+ }
+ }
+
+ // 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)
+ {
+ socket = _udpSocketv6;
+ if (socket == null)
+ return 0;
+ }
+
+ int result;
+ try
+ {
+ if (UseNativeSockets && remoteEndPoint is LiteNetPeer peer)
+ {
+ unsafe
+ {
+ fixed (byte* dataWithOffset = &message[start])
+ result = NativeSocket.SendTo(socket.Handle, dataWithOffset, length, peer.NativeAddress, peer.NativeAddress.Length);
+ }
+ 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);
+ }
+ catch (SocketException ex)
+ {
+ switch (ex.SocketErrorCode)
+ {
+ case SocketError.NoBufferSpaceAvailable:
+ case SocketError.Interrupted:
+ return 0;
+ case SocketError.MessageSize:
+ NetDebug.Write(NetLogLevel.Trace, $"[SRD] 10040, datalen: {length}");
+ return 0;
+
+ case SocketError.HostUnreachable:
+ case SocketError.NetworkUnreachable:
+ if (DisconnectOnUnreachable && remoteEndPoint is LiteNetPeer peer)
+ {
+ DisconnectPeerForce(
+ peer,
+ ex.SocketErrorCode == SocketError.HostUnreachable
+ ? DisconnectReason.HostUnreachable
+ : DisconnectReason.NetworkUnreachable,
+ ex.SocketErrorCode,
+ null);
+ }
+
+ CreateEvent(NetEvent.EType.Error, remoteEndPoint: remoteEndPoint, errorCode: ex.SocketErrorCode);
+ return -1;
+
+ case SocketError.Shutdown:
+ CreateEvent(NetEvent.EType.Error, remoteEndPoint: remoteEndPoint, errorCode: ex.SocketErrorCode);
+ return -1;
+
+ default:
+ NetDebug.WriteError($"[S] {ex}");
+ return -1;
+ }
+ }
+ catch (Exception ex)
+ {
+ NetDebug.WriteError($"[S] {ex}");
+ return 0;
+ }
+
+ if (result <= 0)
+ return 0;
+
+ if (EnableStatistics)
+ {
+ Statistics.IncrementPacketsSent();
+ Statistics.AddBytesSent(length);
+ }
+
+ return result;
+ }
+
+ public bool SendBroadcast(NetDataWriter writer, int port) =>
+ SendBroadcast(writer.Data, 0, writer.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)
+ {
+ if (!IsRunning)
+ return false;
+
+ NetPacket packet;
+ if (_extraPacketLayer != null)
+ {
+ var headerSize = NetPacket.GetHeaderSize(PacketProperty.Broadcast);
+ packet = PoolGetPacket(headerSize + length + _extraPacketLayer.ExtraPacketSizeForLayer);
+ packet.Property = PacketProperty.Broadcast;
+ Buffer.BlockCopy(data, start, packet.RawData, headerSize, length);
+ var checksumComputeStart = 0;
+ int preCrcLength = length + headerSize;
+ IPEndPoint emptyEp = null;
+ _extraPacketLayer.ProcessOutBoundPacket(ref emptyEp, ref packet.RawData, ref checksumComputeStart, ref preCrcLength);
+ }
+ else
+ {
+ packet = PoolGetWithData(PacketProperty.Broadcast, data, start, length);
+ }
+
+ bool broadcastSuccess = false;
+ bool multicastSuccess = false;
+ try
+ {
+ broadcastSuccess = _udpSocketv4.SendTo(
+ packet.RawData,
+ 0,
+ packet.Size,
+ SocketFlags.None,
+ new IPEndPoint(IPAddress.Broadcast, port)) > 0;
+
+ if (_udpSocketv6 != null)
+ {
+ multicastSuccess = _udpSocketv6.SendTo(
+ packet.RawData,
+ 0,
+ packet.Size,
+ SocketFlags.None,
+ 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}");
+ return broadcastSuccess;
+ }
+ finally
+ {
+ PoolRecycle(packet);
+ }
+
+ return broadcastSuccess || multicastSuccess;
+ }
+
+ private void CloseSocket()
+ {
+ _isRunning = false;
+ if (_receiveThread != null && _receiveThread != Thread.CurrentThread)
+ _receiveThread.Join();
+ _receiveThread = null;
+ _udpSocketv4?.Close();
+ _udpSocketv6?.Close();
+ _udpSocketv4 = null;
+ _udpSocketv6 = null;
+ }
+ }
+}
diff --git a/LiteNetLib/LiteNetManager.cs b/LiteNetLib/LiteNetManager.cs
new file mode 100644
index 00000000..2903e8d8
--- /dev/null
+++ b/LiteNetLib/LiteNetManager.cs
@@ -0,0 +1,1670 @@
+#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;
+ }
+
+ private struct IncomingData
+ {
+ public NetPacket Data;
+ public IPEndPoint EndPoint;
+ public DateTime TimeWhenGet;
+ }
+ private readonly List _pingSimulationList = new List();
+
+#if DEBUG || SIMULATE_NETWORK
+ 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, null, 0, 0, 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.
+ /// Offset in the array.
+ /// Number of bytes to send from the array.
+ /// Internal packet data associated with the disconnect event.
+ private void DisconnectPeer(
+ LiteNetPeer peer,
+ DisconnectReason reason,
+ SocketError socketErrorCode,
+ bool force,
+ byte[] data,
+ int start,
+ int count,
+ NetPacket eventData)
+ {
+ var shutdownResult = peer.Shutdown(data, start, count, force);
+ if (shutdownResult == ShutdownResult.None)
+ return;
+ if (shutdownResult == ShutdownResult.WasConnected)
+ Interlocked.Decrement(ref _connectedPeersCount);
+ CreateEvent(
+ NetEvent.EType.Disconnect,
+ peer,
+ errorCode: socketErrorCode,
+ disconnectReason: reason,
+ readerSource: eventData);
+ }
+
+ private void CreateEvent(
+ NetEvent.EType type,
+ 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;
+
+ 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--;
+ }
+ }
+ }
+
+#if DEBUG || SIMULATE_NETWORK
+ 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, byte[] rejectData, int start, int length)
+ {
+ LiteNetPeer 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);
+ }
+ 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, start, length);
+ 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;
+ }
+
+ 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;
+ }
+ }
+
+ [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()
+ {
+ bool shouldDrop = SimulatePacketLoss && _randomGenerator.NextDouble() * 100 < SimulationPacketLossChance;
+ return shouldDrop;
+ }
+#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");
+
+ //Send last disconnect
+ for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
+ netPeer.Shutdown(null, 0, 0, !sendDisconnectMessages);
+
+ //Stop
+ CloseSocket();
+
+#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;
+
+ ClearPingSimulationList();
+ ClearOutboundSimulationList();
+
+ _connectedPeersCount = 0;
+ _pendingEventHead = null;
+ _pendingEventTail = null;
+ }
+
+ [Conditional("DEBUG"), Conditional("SIMULATE_NETWORK")]
+ private void ClearPingSimulationList()
+ {
+ lock (_pingSimulationList)
+ _pingSimulationList.Clear();
+ }
+
+ [Conditional("DEBUG"), Conditional("SIMULATE_NETWORK")]
+ private void ClearOutboundSimulationList()
+ {
+#if DEBUG || SIMULATE_NETWORK
+ lock (_outboundSimulationList)
+ _outboundSimulationList.Clear();
+#endif
+ }
+
+ ///
+ /// 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(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(LiteNetPeer peer) =>
+ DisconnectPeerForce(peer, DisconnectReason.DisconnectPeerCalled, 0, null);
+
+ ///
+ /// Disconnect peer from server
+ ///
+ /// peer to disconnect
+ public void DisconnectPeer(LiteNetPeer 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(LiteNetPeer 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(LiteNetPeer 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(LiteNetPeer peer, byte[] data, int start, int count)
+ {
+ DisconnectPeer(
+ peer,
+ DisconnectReason.DisconnectPeerCalled,
+ 0,
+ false,
+ data,
+ start,
+ count,
+ 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..78ba2e9d
--- /dev/null
+++ b/LiteNetLib/LiteNetPeer.cs
@@ -0,0 +1,1356 @@
+#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, 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) =>
+ _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() =>
+ 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.
+ /// Offset in the array.
+ /// Length of the data to send.
+ ///
+ /// 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(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, 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 a49146ed..3259384b 100644
--- a/LiteNetLib/NatPunchModule.cs
+++ b/LiteNetLib/NatPunchModule.cs
@@ -1,23 +1,71 @@
-using System.Collections.Generic;
+using System.Collections.Concurrent;
+using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Sockets;
using LiteNetLib.Utils;
-//Some code parts taken from lidgren-network-gen3
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);
- void OnNatIntroductionSuccess(IPEndPoint targetEndPoint, 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);
- public delegate void OnNatIntroductionSuccess(IPEndPoint targetEndPoint, 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)
@@ -26,10 +74,10 @@ void INatPunchListener.OnNatIntroductionRequest(IPEndPoint localEndPoint, IPEndP
NatIntroductionRequest(localEndPoint, remoteEndPoint, token);
}
- void INatPunchListener.OnNatIntroductionSuccess(IPEndPoint targetEndPoint, string token)
+ void INatPunchListener.OnNatIntroductionSuccess(IPEndPoint targetEndPoint, NatAddressType type, string token)
{
if (NatIntroductionSuccess != null)
- NatIntroductionSuccess(targetEndPoint, token);
+ NatIntroductionSuccess(targetEndPoint, type, token);
}
}
@@ -48,30 +96,95 @@ struct RequestEventData
struct SuccessEventData
{
public IPEndPoint TargetEndPoint;
+ public NatAddressType Type;
public string Token;
}
- private readonly NetSocket _socket;
- private readonly Queue _requestEvents;
- private readonly Queue _successEvents;
- private const byte HostByte = 1;
- private const byte ClientByte = 0;
- public const int MaxTokenLength = 256;
+ class NatIntroduceRequestPacket
+ {
+ public IPEndPoint Internal { [Preserve] get; [Preserve] set; }
+ public string Token { [Preserve] get; [Preserve] set; }
+ }
+ class NatIntroduceResponsePacket
+ {
+ public IPEndPoint Internal { [Preserve] get; [Preserve] set; }
+ public IPEndPoint External { [Preserve] get; [Preserve] set; }
+ public string Token { [Preserve] get; [Preserve] set; }
+ }
+
+ class NatPunchPacket
+ {
+ public string Token { [Preserve] get; [Preserve] set; }
+ public bool IsExternal { [Preserve] get; [Preserve] set; }
+ }
+
+ 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;
- internal NatPunchModule(NetSocket socket)
+ ///
+ /// Events automatically will be called without PollEvents method from another thread
+ ///
+ public bool UnsyncedEvents = false;
+
+ internal NatPunchModule(LiteNetManager socket)
{
_socket = socket;
- _requestEvents = new Queue();
- _successEvents = new Queue();
+ _netPacketProcessor.SubscribeReusable(OnNatIntroductionResponse);
+ _netPacketProcessor.SubscribeReusable(OnNatIntroductionRequest);
+ _netPacketProcessor.SubscribeReusable(OnNatPunch);
+ }
+
+ internal void ProcessMessage(IPEndPoint senderEndPoint, NetPacket packet)
+ {
+ lock (_cacheReader)
+ {
+ _cacheReader.SetSource(packet.RawData, NetConstants.HeaderSize, packet.Size);
+ _netPacketProcessor.ReadAllPackets(_cacheReader, senderEndPoint);
+ }
}
+ ///
+ /// 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<
+#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);
+ _netPacketProcessor.Write(_cacheWriter, packet);
+ _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,
@@ -79,160 +192,149 @@ public void NatIntroduce(
IPEndPoint clientExternal,
string additionalInfo)
{
- NetDataWriter dw = new NetDataWriter();
-
- //First packet (server)
- //send to client
- dw.Put((byte)PacketProperty.NatIntroduction);
- dw.Put(ClientByte);
- dw.Put(hostInternal);
- dw.Put(hostExternal);
- dw.Put(additionalInfo, MaxTokenLength);
- SocketError errorCode = 0;
- _socket.SendTo(dw.Data, 0, dw.Length, clientExternal, ref errorCode);
-
- //Second packet (client)
- //send to server
- dw.Reset();
- dw.Put((byte)PacketProperty.NatIntroduction);
- dw.Put(HostByte);
- dw.Put(clientInternal);
- dw.Put(clientExternal);
- dw.Put(additionalInfo, MaxTokenLength);
- _socket.SendTo(dw.Data, 0, dw.Length, hostExternal, ref errorCode);
+ var req = new NatIntroduceResponsePacket
+ {
+ Token = additionalInfo
+ };
+
+ //First packet (server) send to client
+ req.Internal = hostInternal;
+ req.External = hostExternal;
+ Send(req, clientExternal);
+
+ //Second packet (client) send to server
+ req.Internal = clientInternal;
+ req.External = clientExternal;
+ 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 (_natPunchListener == null)
+ if (UnsyncedEvents)
+ return;
+
+ if (_natPunchListener == null || (_successEvents.IsEmpty && _requestEvents.IsEmpty))
return;
- lock (_successEvents)
+
+ while (_successEvents.TryDequeue(out var evt))
{
- while (_successEvents.Count > 0)
- {
- var evt = _successEvents.Dequeue();
- _natPunchListener.OnNatIntroductionSuccess(evt.TargetEndPoint, evt.Token);
- }
+ _natPunchListener.OnNatIntroductionSuccess(
+ evt.TargetEndPoint,
+ evt.Type,
+ evt.Token);
}
- lock (_requestEvents)
+
+ while (_requestEvents.TryDequeue(out var evt))
{
- while (_requestEvents.Count > 0)
- {
- var evt = _requestEvents.Dequeue();
- _natPunchListener.OnNatIntroductionRequest(evt.LocalEndPoint, evt.RemoteEndPoint, evt.Token);
- }
+ _natPunchListener.OnNatIntroductionRequest(evt.LocalEndPoint, evt.RemoteEndPoint, evt.Token);
}
}
+ ///
+ /// 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
- NetDataWriter dw = new NetDataWriter();
string networkIp = NetUtils.GetLocalIp(LocalAddrType.IPv4);
- if (string.IsNullOrEmpty(networkIp))
+ if (string.IsNullOrEmpty(networkIp) || masterServerEndPoint.AddressFamily == AddressFamily.InterNetworkV6)
{
networkIp = NetUtils.GetLocalIp(LocalAddrType.IPv6);
}
- IPEndPoint localEndPoint = NetUtils.MakeEndPoint(networkIp, _socket.LocalPort);
- dw.Put((byte)PacketProperty.NatIntroductionRequest);
- dw.Put(localEndPoint);
- dw.Put(additionalInfo, MaxTokenLength);
- //prepare packet
- SocketError errorCode = 0;
- _socket.SendTo(dw.Data, 0, dw.Length, masterServerEndPoint, ref errorCode);
+ Send(
+ new NatIntroduceRequestPacket
+ {
+ Internal = NetUtils.MakeEndPoint(networkIp, _socket.LocalPort),
+ Token = additionalInfo
+ },
+ masterServerEndPoint);
}
- private void HandleNatPunch(IPEndPoint senderEndPoint, NetDataReader dr)
+ //We got request and must introduce
+ private void OnNatIntroductionRequest(NatIntroduceRequestPacket req, IPEndPoint senderEndPoint)
{
- byte fromHostByte = dr.GetByte();
- if (fromHostByte != HostByte && fromHostByte != ClientByte)
+ if (UnsyncedEvents)
{
- //garbage
- return;
+ _natPunchListener.OnNatIntroductionRequest(
+ req.Internal,
+ senderEndPoint,
+ req.Token);
}
-
- //Read info
- string additionalInfo = dr.GetString(MaxTokenLength);
- NetDebug.Write(NetLogLevel.Trace, "[NAT] punch received from {0} - additional info: {1}", senderEndPoint, additionalInfo);
-
- //Release punch success to client; enabling him to Connect() to msg.Sender if token is ok
- lock (_successEvents)
+ else
{
- _successEvents.Enqueue(new SuccessEventData { TargetEndPoint = senderEndPoint, Token = additionalInfo });
+ _requestEvents.Enqueue(new RequestEventData
+ {
+ LocalEndPoint = req.Internal,
+ RemoteEndPoint = senderEndPoint,
+ Token = req.Token
+ });
}
}
- private void HandleNatIntroduction(NetDataReader dr)
+ //We got introduce and must punch
+ private void OnNatIntroductionResponse(NatIntroduceResponsePacket req)
{
- // read intro
- byte hostByte = dr.GetByte();
- IPEndPoint remoteInternal = dr.GetNetEndPoint();
- IPEndPoint remoteExternal = dr.GetNetEndPoint();
- string token = dr.GetString(MaxTokenLength);
-
- NetDebug.Write(NetLogLevel.Trace, "[NAT] introduction received; we are designated " + (hostByte == HostByte ? "host" : "client"));
- NetDataWriter writer = new NetDataWriter();
+ NetDebug.Write(NetLogLevel.Trace, "[NAT] introduction received");
// send internal punch
- writer.Put((byte)PacketProperty.NatPunchMessage);
- writer.Put(hostByte);
- writer.Put(token);
- SocketError errorCode = 0;
- _socket.SendTo(writer.Data, 0, writer.Length, remoteInternal, ref errorCode);
- NetDebug.Write(NetLogLevel.Trace, "[NAT] internal punch sent to " + remoteInternal);
+ var punchPacket = new NatPunchPacket {Token = req.Token};
+ Send(punchPacket, req.Internal);
+ NetDebug.Write(NetLogLevel.Trace, $"[NAT] internal punch sent to {req.Internal}");
+
+ // hack for some routers
+ _socket.Ttl = 2;
+ _socket.SendRaw(new[] { (byte)PacketProperty.Empty }, 0, 1, req.External);
// send external punch
- writer.Reset();
- writer.Put((byte)PacketProperty.NatPunchMessage);
- writer.Put(hostByte);
- writer.Put(token);
- if (hostByte == HostByte)
- {
- _socket.Ttl = 2;
- _socket.SendTo(writer.Data, 0, writer.Length, remoteExternal, ref errorCode);
- _socket.Ttl = NetConstants.SocketTTL;
- }
- else
- {
- _socket.SendTo(writer.Data, 0, writer.Length, remoteExternal, ref errorCode);
- }
-
- NetDebug.Write(NetLogLevel.Trace, "[NAT] external punch sent to " + remoteExternal);
+ _socket.Ttl = NetConstants.SocketTTL;
+ punchPacket.IsExternal = true;
+ Send(punchPacket, req.External);
+ NetDebug.Write(NetLogLevel.Trace, $"[NAT] external punch sent to {req.External}");
}
- private void HandleNatIntroductionRequest(IPEndPoint senderEndPoint, NetDataReader dr)
+ //We got punch and can connect
+ private void OnNatPunch(NatPunchPacket req, IPEndPoint senderEndPoint)
{
- IPEndPoint localEp = dr.GetNetEndPoint();
- string token = dr.GetString(MaxTokenLength);
- lock (_requestEvents)
+ //Read info
+ NetDebug.Write(NetLogLevel.Trace, $"[NAT] punch received from {senderEndPoint} - additional info: {req.Token}");
+
+ //Release punch success to client; enabling him to Connect() to Sender if token is ok
+ if(UnsyncedEvents)
{
- _requestEvents.Enqueue(new RequestEventData
- {
- LocalEndPoint = localEp,
- RemoteEndPoint = senderEndPoint,
- Token = token
- });
+ _natPunchListener.OnNatIntroductionSuccess(
+ senderEndPoint,
+ req.IsExternal ? NatAddressType.External : NatAddressType.Internal,
+ req.Token
+ );
}
- }
-
- internal void ProcessMessage(IPEndPoint senderEndPoint, NetPacket packet)
- {
- var dr = new NetDataReader(packet.RawData, NetConstants.HeaderSize, packet.Size);
- switch (packet.Property)
+ else
{
- case PacketProperty.NatIntroductionRequest:
- //We got request and must introduce
- HandleNatIntroductionRequest(senderEndPoint, dr);
- break;
- case PacketProperty.NatIntroduction:
- //We got introduce and must punch
- HandleNatIntroduction(dr);
- break;
- case PacketProperty.NatPunchMessage:
- //We got punch and can connect
- HandleNatPunch(senderEndPoint, dr);
- break;
+ _successEvents.Enqueue(new SuccessEventData
+ {
+ TargetEndPoint = senderEndPoint,
+ Type = req.IsExternal ? NatAddressType.External : NatAddressType.Internal,
+ Token = req.Token
+ });
}
}
}
diff --git a/LiteNetLib/NativeSocket.cs b/LiteNetLib/NativeSocket.cs
new file mode 100644
index 00000000..84ecedc4
--- /dev/null
+++ b/LiteNetLib/NativeSocket.cs
@@ -0,0 +1,222 @@
+using System;
+using System.Collections.Generic;
+using System.Net;
+using System.Net.Sockets;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace LiteNetLib
+{
+ internal static class NativeSocket
+ {
+ static unsafe class WinSock
+ {
+ private const string LibName = "ws2_32.dll";
+
+ [DllImport(LibName, SetLastError = true)]
+ public static extern int recvfrom(
+ IntPtr socketHandle,
+ [In, Out] byte[] pinnedBuffer,
+ [In] int len,
+ [In] SocketFlags socketFlags,
+ [Out] byte[] socketAddress,
+ [In, Out] ref int socketAddressSize);
+
+ [DllImport(LibName, SetLastError = true)]
+ internal static extern int sendto(
+ IntPtr socketHandle,
+ byte* pinnedBuffer,
+ [In] int len,
+ [In] SocketFlags socketFlags,
+ [In] byte[] socketAddress,
+ [In] int socketAddressSize);
+ }
+
+ static unsafe class UnixSock
+ {
+ private const string LibName = "libc";
+
+ [DllImport(LibName, SetLastError = true)]
+ public static extern int recvfrom(
+ IntPtr socketHandle,
+ [In, Out] byte[] pinnedBuffer,
+ [In] int len,
+ [In] SocketFlags socketFlags,
+ [Out] byte[] socketAddress,
+ [In, Out] ref int socketAddressSize);
+
+ [DllImport(LibName, SetLastError = true)]
+ internal static extern int sendto(
+ IntPtr socketHandle,
+ byte* pinnedBuffer,
+ [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
+ {
+ { 13, SocketError.AccessDenied }, //EACCES
+ { 98, SocketError.AddressAlreadyInUse }, //EADDRINUSE
+ { 99, SocketError.AddressNotAvailable }, //EADDRNOTAVAIL
+ { 97, SocketError.AddressFamilyNotSupported }, //EAFNOSUPPORT
+ { 11, SocketError.WouldBlock }, //EAGAIN
+ { 114, SocketError.AlreadyInProgress }, //EALREADY
+ { 9, SocketError.OperationAborted }, //EBADF
+ { 125, SocketError.OperationAborted }, //ECANCELED
+ { 103, SocketError.ConnectionAborted }, //ECONNABORTED
+ { 111, SocketError.ConnectionRefused }, //ECONNREFUSED
+ { 104, SocketError.ConnectionReset }, //ECONNRESET
+ { 89, SocketError.DestinationAddressRequired }, //EDESTADDRREQ
+ { 14, SocketError.Fault }, //EFAULT
+ { 112, SocketError.HostDown }, //EHOSTDOWN
+ { 6, SocketError.HostNotFound }, //ENXIO
+ { 113, SocketError.HostUnreachable }, //EHOSTUNREACH
+ { 115, SocketError.InProgress }, //EINPROGRESS
+ { 4, SocketError.Interrupted }, //EINTR
+ { 22, SocketError.InvalidArgument }, //EINVAL
+ { 106, SocketError.IsConnected }, //EISCONN
+ { 24, SocketError.TooManyOpenSockets }, //EMFILE
+ { 90, SocketError.MessageSize }, //EMSGSIZE
+ { 100, SocketError.NetworkDown }, //ENETDOWN
+ { 102, SocketError.NetworkReset }, //ENETRESET
+ { 101, SocketError.NetworkUnreachable }, //ENETUNREACH
+ { 23, SocketError.TooManyOpenSockets }, //ENFILE
+ { 105, SocketError.NoBufferSpaceAvailable }, //ENOBUFS
+ { 61, SocketError.NoData }, //ENODATA
+ { 2, SocketError.AddressNotAvailable }, //ENOENT
+ { 92, SocketError.ProtocolOption }, //ENOPROTOOPT
+ { 107, SocketError.NotConnected }, //ENOTCONN
+ { 88, SocketError.NotSocket }, //ENOTSOCK
+ { 3440, SocketError.OperationNotSupported }, //ENOTSUP
+ { 1, SocketError.AccessDenied }, //EPERM
+ { 32, SocketError.Shutdown }, //EPIPE
+ { 96, SocketError.ProtocolFamilyNotSupported }, //EPFNOSUPPORT
+ { 93, SocketError.ProtocolNotSupported }, //EPROTONOSUPPORT
+ { 91, SocketError.ProtocolType }, //EPROTOTYPE
+ { 94, SocketError.SocketNotSupported }, //ESOCKTNOSUPPORT
+ { 108, SocketError.Disconnecting }, //ESHUTDOWN
+ { 110, SocketError.TimedOut }, //ETIMEDOUT
+ { 0, SocketError.Success }
+ };
+
+ static NativeSocket()
+ {
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ IsSupported = true;
+ UnixMode = true;
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ IsSupported = true;
+ }
+ }
+
+ ///
+ /// 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) =>
+ 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 static unsafe int SendTo(
+ IntPtr socketHandle,
+ byte* pinnedBuffer,
+ int len,
+ byte[] socketAddress,
+ 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();
+ if (UnixMode)
+ return NativeErrorToSocketError.TryGetValue(error, out var err)
+ ? err
+ : SocketError.SocketError;
+ 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();
+ if (UnixMode)
+ return NativeErrorToSocketError.TryGetValue(error, out var err)
+ ? new SocketException((int)err)
+ : new SocketException((int)SocketError.SocketError);
+ 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) =>
+ UnixMode
+ ? (short)(remoteEndPoint.AddressFamily == AddressFamily.InterNetwork ? AF_INET : AF_INET6)
+ : (short)remoteEndPoint.AddressFamily;
+ }
+}
diff --git a/LiteNetLib/NetConstants.cs b/LiteNetLib/NetConstants.cs
index 26bfc476..947cc865 100644
--- a/LiteNetLib/NetConstants.cs
+++ b/LiteNetLib/NetConstants.cs
@@ -1,34 +1,35 @@
-namespace LiteNetLib
+namespace LiteNetLib
{
///
/// Sending method type
///
- public enum DeliveryMethod
+ public enum DeliveryMethod : byte
{
///
- /// Unreliable. Packets can be dropped, duplicated or arrive without order
+ /// Unreliable. Packets can be dropped, can be duplicated, can arrive without order.
///
- Unreliable,
+ Unreliable = 4,
///
- /// Reliable. All packets will be sent and received, but without order
+ /// Reliable. Packets won't be dropped, won't be duplicated, can arrive without order.
///
- ReliableUnordered,
+ ReliableUnordered = 0,
///
- /// Unreliable. Packets can be dropped, but never duplicated and arrive in order
+ /// Unreliable. Packets can be dropped, won't be duplicated, will arrive in order.
///
- Sequenced,
+ Sequenced = 1,
///
- /// Reliable and ordered. All packets will be sent and received in order
+ /// Reliable and ordered. Packets won't be dropped, won't be duplicated, will arrive in order.
///
- ReliableOrdered,
+ ReliableOrdered = 2,
///
- /// Reliable only last packet
+ /// Reliable only last packet. Packets can be dropped (except the last one), won't be duplicated, will arrive in order.
+ /// Cannot be fragmented
///
- ReliableSequenced
+ ReliableSequenced = 3
}
///
@@ -36,47 +37,59 @@ public enum DeliveryMethod
///
public static class NetConstants
{
- internal static byte ChannelNumberToId(DeliveryMethod method, byte channelNumber, byte channelsCount)
- {
- int multiplier = 0;
- switch (method)
- {
- case DeliveryMethod.Sequenced: multiplier = 1; break;
- case DeliveryMethod.ReliableOrdered: multiplier = 2; break;
- case DeliveryMethod.ReliableSequenced: multiplier = 3; break;
- }
- return (byte)(channelNumber + multiplier * channelsCount);
- }
-
- internal static DeliveryMethod ChannelIdToDeliveryMethod(byte channelId, byte channelsCount)
- {
- switch (channelId / channelsCount)
- {
- case 1: return DeliveryMethod.Sequenced;
- case 2: return DeliveryMethod.ReliableOrdered;
- case 3: return DeliveryMethod.ReliableSequenced;
- }
- return DeliveryMethod.ReliableUnordered;
- }
-
- //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 = 9;
+ 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
+ //576 - MaxUdpHeaderSize minimal (RFC 1191)
+ 1024, //most games standard
1232 - MaxUdpHeaderSize,
1460 - MaxUdpHeaderSize, //google cloud
1472 - MaxUdpHeaderSize, //VPN
@@ -84,11 +97,28 @@ internal static DeliveryMethod ChannelIdToDeliveryMethod(byte channelId, byte ch
1500 - MaxUdpHeaderSize //Ethernet II (RFC 1191)
};
- internal static readonly int MaxPacketSize = PossibleMtu[PossibleMtu.Length - 1];
+ ///
+ /// 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;
-
- public const int PacketPoolSize = 1000;
}
}
diff --git a/LiteNetLib/NetDebug.cs b/LiteNetLib/NetDebug.cs
index bebd8d1e..44cb6f3e 100644
--- a/LiteNetLib/NetDebug.cs
+++ b/LiteNetLib/NetDebug.cs
@@ -3,6 +3,20 @@
namespace LiteNetLib
{
+ public class InvalidPacketException : ArgumentException
+ {
+ public InvalidPacketException(string message) : base(message)
+ {
+ }
+ }
+
+ public class TooBigPacketException : InvalidPacketException
+ {
+ public TooBigPacketException(string message) : base(message)
+ {
+ }
+ }
+
public enum NetLogLevel
{
Warning,
@@ -33,7 +47,7 @@ private static void WriteLogic(NetLogLevel logLevel, string str, params object[]
{
if (Logger == null)
{
-#if UNITY_4 || UNITY_5 || UNITY_5_3_OR_NEWER
+#if UNITY_5_3_OR_NEWER
UnityEngine.Debug.Log(string.Format(str, args));
#else
Console.WriteLine(str, args);
@@ -47,32 +61,32 @@ private static void WriteLogic(NetLogLevel logLevel, string str, params object[]
}
[Conditional("DEBUG_MESSAGES")]
- internal static void Write(string str, params object[] args)
+ internal static void Write(string str)
{
- WriteLogic(NetLogLevel.Trace, str, args);
+ WriteLogic(NetLogLevel.Trace, str);
}
[Conditional("DEBUG_MESSAGES")]
- internal static void Write(NetLogLevel level, string str, params object[] args)
+ internal static void Write(NetLogLevel level, string str)
{
- WriteLogic(level, str, args);
+ WriteLogic(level, str);
}
[Conditional("DEBUG_MESSAGES"), Conditional("DEBUG")]
- internal static void WriteForce(string str, params object[] args)
+ internal static void WriteForce(string str)
{
- WriteLogic(NetLogLevel.Trace, str, args);
+ WriteLogic(NetLogLevel.Trace, str);
}
[Conditional("DEBUG_MESSAGES"), Conditional("DEBUG")]
- internal static void WriteForce(NetLogLevel level, string str, params object[] args)
+ internal static void WriteForce(NetLogLevel level, string str)
{
- WriteLogic(level, str, args);
+ WriteLogic(level, str);
}
- internal static void WriteError(string str, params object[] args)
+ internal static void WriteError(string str)
{
- WriteLogic(NetLogLevel.Error, str, args);
+ WriteLogic(NetLogLevel.Error, str);
}
}
}
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/NetExceptions.cs b/LiteNetLib/NetExceptions.cs
deleted file mode 100644
index db999a64..00000000
--- a/LiteNetLib/NetExceptions.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using System;
-namespace LiteNetLib
-{
- public class InvalidPacketException: ArgumentException
- {
- public InvalidPacketException()
- {
- }
-
- public InvalidPacketException(string message): base(message)
- {
- }
-
- public InvalidPacketException(string message, Exception innerException): base(message, innerException)
- {
- }
- }
-
- public class TooBigPacketException : InvalidPacketException
- {
- public TooBigPacketException()
- {
- }
-
- public TooBigPacketException(string message) : base(message)
- {
- }
-
- public TooBigPacketException(string message, Exception innerException) : base(message, innerException)
- {
- }
- }
-}
\ No newline at end of file
diff --git a/LiteNetLib/NetManager.cs b/LiteNetLib/NetManager.cs
index 5c73a3c2..fc02a659 100644
--- a/LiteNetLib/NetManager.cs
+++ b/LiteNetLib/NetManager.cs
@@ -1,483 +1,154 @@
-#if DEBUG
-#define STATS_ENABLED
-#endif
-
-using System;
-using System.Collections;
+using System;
+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 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)
- {
- if (packet == null)
- return;
- _packet = packet;
- SetSource(packet.RawData, packet.GetHeaderSize(), packet.Size);
- }
-
- public void Recycle()
- {
- Clear();
- if (_packet != null)
- _manager.NetPacketPool.Recycle(_packet);
- _packet = null;
- _manager.RecycleEvent(_evt);
- }
- }
-
- internal sealed class NetEvent
- {
- public enum EType
- {
- Connect,
- Disconnect,
- Receive,
- ReceiveUnconnected,
- Error,
- ConnectionLatencyUpdated,
- Broadcast,
- ConnectionRequest
- }
- public EType Type;
-
- public NetPeer Peer;
- public IPEndPoint RemoteEndPoint;
- public int Latency;
- public SocketError ErrorCode;
- public DisconnectReason DisconnectReason;
- public ConnectionRequest ConnectionRequest;
- public DeliveryMethod DeliveryMethod;
- public readonly NetPacketReader DataReader;
-
- public NetEvent(NetManager manager)
- {
- DataReader = new NetPacketReader(manager, this);
- }
- }
-
///
- /// Main class for all network operations. Can be used as client and/or server.
+ /// More feature rich network manager with adjustable channels count
///
- public class NetManager : INetSocketListener, IConnectionRequestListener, IEnumerable
+ public class NetManager : LiteNetManager, IEnumerable
{
- private class IPEndPointComparer : IEqualityComparer
- {
- public bool Equals(IPEndPoint x, IPEndPoint y)
- {
- return x.Equals(y);
- }
-
- public int GetHashCode(IPEndPoint obj)
- {
- return obj.GetHashCode();
- }
- }
-
-#if DEBUG
- private struct IncomingData
- {
- public byte[] Data;
- public IPEndPoint EndPoint;
- public DateTime TimeWhenGet;
- }
- private readonly List _pingSimulationList = new List();
- private readonly Random _randomGenerator = new Random();
- private const int MinLatencyThreshold = 5;
-#endif
-
- private readonly NetSocket _socket;
- private Thread _logicThread;
-
- private readonly Queue _netEventsQueue;
- private readonly Stack _netEventsPool;
private readonly INetEventListener _netEventListener;
-
- private readonly Dictionary _peersDict;
- private readonly ReaderWriterLockSlim _peersLock;
- private volatile NetPeer _headPeer;
- private volatile int _connectedPeersCount;
- private readonly List _connectedPeerListCache;
- private int _lastPeerId;
- private readonly Queue _peerIds;
private byte _channelsCount = 1;
-
- internal readonly NetPacketPool NetPacketPool;
-
- //config section
- ///
- /// Enable messages receiving without connection. (with SendUnconnectedMessage method)
- ///
- public bool UnconnectedMessagesEnabled = false;
-
- ///
- /// Enable nat punch messages
- ///
- public bool NatPunchEnabled = false;
-
- ///
- /// Library logic update and send period in milliseconds
- ///
- public int UpdateTime = 15;
-
- ///
- /// Interval for latency detection and checking connection
- ///
- public int PingInterval = 1000;
-
- ///
- /// If NetManager doesn't receive any packet from remote peer during this time then connection will be closed
- /// (including library internal keepalive packets)
- ///
- public int DisconnectTimeout = 5000;
-
- ///
- /// Simulate packet loss by dropping random amout of packets. (Works only in DEBUG mode)
- ///
- public bool SimulatePacketLoss = false;
-
- ///
- /// Simulate latency by holding packets for random time. (Works only in DEBUG mode)
- ///
- public bool SimulateLatency = false;
-
- ///
- /// Chance of packet loss when simulation enabled. value in percents (1 - 100).
- ///
- public int SimulationPacketLossChance = 10;
-
- ///
- /// Minimum simulated latency
- ///
- public int SimulationMinLatency = 30;
-
- ///
- /// Maximum simulated latency
- ///
- public int SimulationMaxLatency = 100;
-
- ///
- /// Experimental feature. Events automatically will be called without PollEvents method from another thread
- ///
- public bool UnsyncedEvents = false;
-
- ///
- /// Allows receive broadcast packets
- ///
- public bool BroadcastReceiveEnabled = false;
-
- ///
- /// Delay betwen initial connection attempts
- ///
- public int ReconnectDelay = 500;
-
- ///
- /// Maximum connection attempts before client stops and call disconnect event.
- ///
- public int MaxConnectAttempts = 10;
-
- ///
- /// Enables socket option "ReuseAddress" for specific purposes
- ///
- public bool ReuseAddress = false;
-
- ///
- /// Statistics of all connections
- ///
- public readonly NetStatistics Statistics;
-
- //modules
- ///
- /// 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 { return _socket.LocalPort; } }
-
- ///
- /// Automatically recycle NetPacketReader after OnReceive event
- ///
- public bool AutoRecycle;
-
- ///
- /// IPv6 support
- ///
- public bool IPv6Enabled = true;
-
- ///
- /// First peer. Useful for Client mode
- ///
- public NetPeer FirstPeer
- {
- get { return _headPeer; }
- }
+ private readonly ConcurrentDictionary _ntpRequests = new ConcurrentDictionary();
///
/// QoS channel count per message type (value must be between 1 and 64 channels)
///
public byte ChannelsCount
{
- get { return _channelsCount; }
+ get => _channelsCount;
set
{
- if(value < 1 || value > 64)
+ if (value < 1 || value > 64)
throw new ArgumentException("Channels count must be between 1 and 64");
_channelsCount = value;
}
}
///
- /// Returns connected peers list (with internal cached list)
+ /// First peer. Useful for Client mode
///
- public List ConnectedPeerList
- {
- get
- {
- _connectedPeerListCache.Clear();
- for(var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
- {
- if ((netPeer.ConnectionState & ConnectionState.Connected) != 0)
- _connectedPeerListCache.Add(netPeer);
- }
- return _connectedPeerListCache;
- }
- }
+ public new NetPeer FirstPeer => (NetPeer)_headPeer;
///
- /// Returns connected peers count
+ /// Get copy of peers (without allocations)
///
- public int PeersCount { get { return _connectedPeersCount; } }
-
- private bool TryGetPeer(IPEndPoint endPoint, out NetPeer peer)
+ /// List that will contain result
+ /// State of peers
+ public void GetPeers(List peers, ConnectionState peerState)
{
+ peers.Clear();
_peersLock.EnterReadLock();
- bool result = _peersDict.TryGetValue(endPoint, out peer);
- _peersLock.ExitReadLock();
- return result;
- }
-
- private NetPeer TryAddPeer(NetPeer peer)
- {
- _peersLock.EnterUpgradeableReadLock();
- NetPeer existingPeer;
- if (_peersDict.TryGetValue(peer.EndPoint, out existingPeer))
- {
- //return back unused peerId
- lock (_peerIds)
- _peerIds.Enqueue(peer.Id);
-
- _peersLock.ExitUpgradeableReadLock();
- return existingPeer;
- }
- _peersLock.EnterWriteLock();
- if (_headPeer != null)
+ for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
{
- peer.NextPeer = _headPeer;
- _headPeer.PrevPeer = peer;
+ if ((netPeer.ConnectionState & peerState) != 0)
+ peers.Add((NetPeer)netPeer);
}
- _headPeer = peer;
- _peersDict.Add(peer.EndPoint, peer);
- _peersLock.ExitWriteLock();
- _peersLock.ExitUpgradeableReadLock();
- return peer;
+ _peersLock.ExitReadLock();
}
- private void RemovePeer(NetPeer peer)
- {
- _peersLock.EnterWriteLock();
- RemovePeerInternal(peer);
- _peersLock.ExitWriteLock();
- }
+ ///
+ /// Get copy of connected peers (without allocations)
+ ///
+ /// List that will contain result
+ public void GetConnectedPeers(List peers) =>
+ GetPeers(peers, ConnectionState.Connected);
- private void RemovePeerInternal(NetPeer peer)
- {
- if (!_peersDict.Remove(peer.EndPoint))
- return;
- if (peer == _headPeer)
- _headPeer = peer.NextPeer;
- if (peer.PrevPeer != null)
- peer.PrevPeer.NextPeer = peer.NextPeer;
- if (peer.NextPeer != null)
- peer.NextPeer.PrevPeer = peer.PrevPeer;
- peer.PrevPeer = null;
- peer.NextPeer = null;
- lock(_peerIds)
- _peerIds.Enqueue(peer.Id);
- }
+ public NetManager(INetEventListener listener, PacketLayerBase extraPacketLayer = null) : base(null, extraPacketLayer) =>
+ _netEventListener = listener;
///
- /// NetManager constructor
+ /// Create the requests for NTP server
///
- /// Network events listener
- public NetManager(INetEventListener listener)
- {
- _socket = new NetSocket(this);
- _netEventListener = listener;
- _netEventsQueue = new Queue();
- _netEventsPool = new Stack();
- NetPacketPool = new NetPacketPool();
- NatPunchModule = new NatPunchModule(_socket);
- Statistics = new NetStatistics();
- _connectedPeerListCache = new List();
- _peersDict = new Dictionary(new IPEndPointComparer());
- _peersLock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
- _peerIds = new Queue();
- }
+ /// NTP Server address.
+ public void CreateNtpRequest(IPEndPoint endPoint) =>
+ _ntpRequests.TryAdd(endPoint, new NtpRequest(endPoint));
- internal void ConnectionLatencyUpdated(NetPeer fromPeer, int latency)
+ ///
+ /// Create the requests for NTP server
+ ///
+ /// NTP Server address.
+ /// port
+ public void CreateNtpRequest(string ntpServerAddress, int port)
{
- CreateEvent(NetEvent.EType.ConnectionLatencyUpdated, fromPeer, latency: latency);
+ var endPoint = NetUtils.MakeEndPoint(ntpServerAddress, port);
+ _ntpRequests.TryAdd(endPoint, new NtpRequest(endPoint));
}
- internal int SendRawAndRecycle(NetPacket packet, IPEndPoint remoteEndPoint)
+ ///
+ /// Create the requests for NTP server (default port)
+ ///
+ /// NTP Server address.
+ public void CreateNtpRequest(string ntpServerAddress)
{
- var result = SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint);
- NetPacketPool.Recycle(packet);
- return result;
+ var endPoint = NetUtils.MakeEndPoint(ntpServerAddress, NtpRequest.DefaultPort);
+ _ntpRequests.TryAdd(endPoint, new NtpRequest(endPoint));
}
- internal int SendRaw(NetPacket packet, IPEndPoint remoteEndPoint)
+ internal override bool CustomMessageHandle(NetPacket packet, IPEndPoint remoteEndPoint)
{
- return SendRaw(packet.RawData, 0, packet.Size, remoteEndPoint);
- }
+ 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 int SendRaw(byte[] message, int start, int length, IPEndPoint remoteEndPoint)
- {
- if (!IsRunning)
- return 0;
+ 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;
+ }
- SocketError errorCode = 0;
- int result = _socket.SendTo(message, start, length, remoteEndPoint, ref errorCode);
- NetPeer fromPeer;
- switch (errorCode)
- {
- case SocketError.MessageSize:
- NetDebug.Write(NetLogLevel.Trace, "[SRD] 10040, datalen: {0}", length);
- return -1;
- case SocketError.HostUnreachable:
- if (TryGetPeer(remoteEndPoint, out fromPeer))
- DisconnectPeerForce(fromPeer, DisconnectReason.HostUnreachable, errorCode, null);
- CreateEvent(NetEvent.EType.Error, remoteEndPoint: remoteEndPoint, errorCode: errorCode);
- return -1;
- case SocketError.ConnectionReset: //connection reset (connection closed)
- if (TryGetPeer(remoteEndPoint, out fromPeer))
- DisconnectPeerForce(fromPeer, DisconnectReason.RemoteConnectionClose, errorCode, null);
- return -1;
+ if (ntpPacket != null)
+ {
+ _ntpRequests.TryRemove(remoteEndPoint, out _);
+ _netEventListener.OnNtpResponse(ntpPacket);
+ }
+ return true;
}
- if (result <= 0)
- return 0;
-#if STATS_ENABLED
- Statistics.PacketsSent++;
- Statistics.BytesSent += (uint)length;
-#endif
- return result;
- }
- internal void DisconnectPeerForce(NetPeer peer,
- DisconnectReason reason,
- SocketError socketErrorCode,
- NetPacket eventData)
- {
- DisconnectPeer(peer, reason, socketErrorCode, true, null, 0, 0, eventData);
+ return false;
}
- private void DisconnectPeer(
- NetPeer peer,
- DisconnectReason reason,
- SocketError socketErrorCode,
- bool force,
- byte[] data,
- int start,
- int count,
- NetPacket eventData)
- {
- bool wasConnected = peer.ConnectionState == ConnectionState.Connected;
- if (!peer.Shutdown(data, start, count, force))
- return;
- if(wasConnected)
- _connectedPeersCount--;
- CreateEvent(
- NetEvent.EType.Disconnect,
- peer,
- errorCode: socketErrorCode,
- disconnectReason: reason,
- readerSource: eventData);
- }
+ //connect to
+ protected override LiteNetPeer CreateOutgoingPeer(IPEndPoint remoteEndPoint, int id, byte connectNum, ReadOnlySpan connectData) =>
+ new NetPeer(this, remoteEndPoint, id, connectNum, connectData);
- private void CreateEvent(
- NetEvent.EType type,
- NetPeer peer = null,
- IPEndPoint remoteEndPoint = null,
- SocketError errorCode = 0,
- int latency = 0,
- DisconnectReason disconnectReason = DisconnectReason.ConnectionFailed,
- ConnectionRequest connectionRequest = null,
- DeliveryMethod deliveryMethod = DeliveryMethod.Unreliable,
- NetPacket readerSource = null)
- {
- NetEvent evt = null;
- if (type == NetEvent.EType.Connect)
- _connectedPeersCount++;
- lock (_netEventsPool)
- {
- if (_netEventsPool.Count > 0)
- evt = _netEventsPool.Pop();
- }
- if(evt == null)
- evt = new NetEvent(this);
- evt.Type = type;
- evt.DataReader.SetSource(readerSource);
- evt.Peer = peer;
- evt.RemoteEndPoint = remoteEndPoint;
- evt.Latency = latency;
- evt.ErrorCode = errorCode;
- evt.DisconnectReason = disconnectReason;
- evt.ConnectionRequest = connectionRequest;
- evt.DeliveryMethod = deliveryMethod;
- if (UnsyncedEvents)
- {
- ProcessEvent(evt);
- }
- else
- {
- lock (_netEventsQueue)
- _netEventsQueue.Enqueue(evt);
- }
- }
+ //accept
+ protected override LiteNetPeer CreateIncomingPeer(LiteConnectionRequest request, int id) =>
+ new NetPeer(this, request, id);
+
+ //reject
+ protected override LiteNetPeer CreateRejectPeer(IPEndPoint remoteEndPoint, int id) =>
+ new NetPeer(this, remoteEndPoint, id);
+
+ //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
@@ -486,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.DeliveryMethod);
+ _netEventListener.OnNetworkReceive(netPeer, evt.DataReader, evt.ChannelNumber, evt.DeliveryMethod);
break;
case NetEvent.EType.ReceiveUnconnected:
_netEventListener.OnNetworkReceiveUnconnected(evt.RemoteEndPoint, evt.DataReader, UnconnectedMessageType.BasicMessage);
@@ -501,411 +172,60 @@ 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);
- break;
- }
- //Recycle if not message
- if (emptyData)
- RecycleEvent(evt);
- else if (AutoRecycle)
- evt.DataReader.Recycle();
- }
-
- internal void RecycleEvent(NetEvent evt)
- {
- evt.Peer = null;
- evt.ErrorCode = 0;
- evt.RemoteEndPoint = null;
- evt.ConnectionRequest = null;
- lock (_netEventsPool)
- _netEventsPool.Push(evt);
- }
-
- //Update function
- private void UpdateLogic()
- {
- var peersToRemove = new List();
- var stopwatch = new Stopwatch();
- stopwatch.Start();
-
- while (IsRunning)
- {
-#if DEBUG
- if (SimulateLatency)
- {
- var time = DateTime.UtcNow;
- lock (_pingSimulationList)
- {
- for (int i = 0; i < _pingSimulationList.Count; i++)
- {
- var incomingData = _pingSimulationList[i];
- if (incomingData.TimeWhenGet <= time)
- {
- DataReceived(incomingData.Data, incomingData.Data.Length, incomingData.EndPoint);
- _pingSimulationList.RemoveAt(i);
- i--;
- }
- }
- }
- }
-#endif
-
-#if STATS_ENABLED
- ulong totalPacketLoss = 0;
-#endif
- int elapsed = (int)stopwatch.ElapsedMilliseconds;
- if (elapsed <= 0)
- elapsed = 1;
- 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 STATS_ENABLED
- totalPacketLoss += netPeer.Statistics.PacketLoss;
-#endif
- }
- }
- if (peersToRemove.Count > 0)
- {
- _peersLock.EnterWriteLock();
- for (int i = 0; i < peersToRemove.Count; i++)
- RemovePeerInternal(peersToRemove[i]);
- _peersLock.ExitWriteLock();
- peersToRemove.Clear();
- }
-#if STATS_ENABLED
- Statistics.PacketLoss = totalPacketLoss;
-#endif
- int sleepTime = UpdateTime - (int)(stopwatch.ElapsedMilliseconds - elapsed);
- stopwatch.Reset();
- stopwatch.Start();
- if (sleepTime > 0)
- Thread.Sleep(sleepTime);
- }
- stopwatch.Stop();
- }
-
- void INetSocketListener.OnMessageReceived(byte[] data, int length, SocketError errorCode, IPEndPoint remoteEndPoint)
- {
- if (errorCode != 0)
- {
- CreateEvent(NetEvent.EType.Error, errorCode: errorCode);
- NetDebug.WriteError("[NM] Receive error: {0}", errorCode);
- return;
- }
-#if DEBUG
- if (SimulatePacketLoss && _randomGenerator.NextDouble() * 100 < SimulationPacketLossChance)
- {
- //drop packet
- return;
- }
- if (SimulateLatency)
- {
- int latency = _randomGenerator.Next(SimulationMinLatency, SimulationMaxLatency);
- if (latency > MinLatencyThreshold)
- {
- byte[] holdedData = new byte[length];
- Buffer.BlockCopy(data, 0, holdedData, 0, length);
-
- lock (_pingSimulationList)
- {
- _pingSimulationList.Add(new IncomingData
- {
- Data = holdedData,
- EndPoint = remoteEndPoint,
- TimeWhenGet = DateTime.UtcNow.AddMilliseconds(latency)
- });
- }
- //hold packet
- return;
- }
- }
-#endif
- try
- {
- //ProcessEvents
- DataReceived(data, length, remoteEndPoint);
- }
- catch(Exception e)
- {
- //protects socket receive thread
- NetDebug.WriteError("[NM] SocketReceiveThread error: " + e );
- }
- }
-
- void IConnectionRequestListener.OnConnectionSolved(ConnectionRequest request, byte[] rejectData, int start, int length)
- {
- if (request.Result == ConnectionRequestResult.Reject)
- {
- NetDebug.Write(NetLogLevel.Trace, "[NM] Peer connect reject.");
- request.Peer.Reject(request.ConnectionId, request.ConnectionNumber, rejectData, start, length);
- }
- else
- {
- //Accept
- request.Peer.Accept(request.ConnectionId, request.ConnectionNumber);
- //TODO: sync
- //Add event
- CreateEvent(NetEvent.EType.Connect, request.Peer);
-
- NetDebug.Write(NetLogLevel.Trace, "[NM] Received peer connection Id: {0}, EP: {1}",
- request.Peer.ConnectTime, request.Peer.EndPoint);
- }
- }
-
- private int GetNextPeerId()
- {
- lock (_peerIds)
- {
- if (_peerIds.Count == 0)
- return _lastPeerId++;
- return _peerIds.Dequeue();
- }
- }
-
- private void ProcessConnectRequest(
- IPEndPoint remoteEndPoint,
- NetPeer netPeer,
- NetConnectRequestPacket connRequest)
- {
- byte connectionNumber = connRequest.ConnectionNumber;
-
- //if we have peer
- if (netPeer != null)
- {
- NetDebug.Write("ConnectRequest LastId: {0}, NewId: {1}, EP: {2}", netPeer.ConnectTime, connRequest.ConnectionTime, remoteEndPoint);
- var processResult = netPeer.ProcessConnectRequest(connRequest);
- switch (processResult)
- {
- case ConnectRequestResult.Reconnection:
- DisconnectPeerForce(netPeer, DisconnectReason.RemoteConnectionClose, 0, null);
- RemovePeer(netPeer);
- //go to new connection
- break;
- case ConnectRequestResult.NewConnection:
- RemovePeer(netPeer);
- //go to new connection
- break;
- case ConnectRequestResult.P2PConnection:
- CreateEvent(
- NetEvent.EType.ConnectionRequest,
- connectionRequest: new ConnectionRequest(
- netPeer.ConnectTime,
- connectionNumber,
- ConnectionRequestType.PeerToPeer,
- connRequest.Data,
- netPeer,
- this)
- );
- return;
- default:
- //no operations needed
- return;
- }
- //ConnectRequestResult.NewConnection
- //Set next connection number
- connectionNumber = (byte)((netPeer.ConnectionNum + 1) % NetConstants.MaxConnectionNumber);
- //To reconnect peer
- }
- else
- {
- NetDebug.Write("ConnectRequest Id: {0}, EP: {1}", connRequest.ConnectionTime, remoteEndPoint);
- }
- //Add new peer and craete ConnectRequest event
- NetDebug.Write("[NM] Creating request event: " + connRequest.ConnectionTime);
- netPeer = new NetPeer(this, remoteEndPoint, GetNextPeerId());
- if (TryAddPeer(netPeer) == netPeer)
- {
- CreateEvent(NetEvent.EType.ConnectionRequest, connectionRequest: new ConnectionRequest(
- connRequest.ConnectionTime,
- connectionNumber,
- ConnectionRequestType.Incoming,
- connRequest.Data,
- netPeer,
- this));
- }
- }
-
- private void DataReceived(byte[] reusableBuffer, int count, IPEndPoint remoteEndPoint)
- {
-#if STATS_ENABLED
- Statistics.PacketsReceived++;
- Statistics.BytesReceived += (uint) count;
-#endif
- //Try read packet
- NetPacket packet = NetPacketPool.GetPacket(count, false);
- if (!packet.FromBytes(reusableBuffer, 0, count))
- {
- NetPacketPool.Recycle(packet);
- NetDebug.WriteError("[NM] DataReceived: bad!");
- return;
- }
-
- //get peer
- //Check normal packets
- NetPeer netPeer;
- //old packets protection
- bool peerFound = TryGetPeer(remoteEndPoint, out netPeer);
-
- //Check unconnected
- switch (packet.Property)
- {
- case PacketProperty.PeerNotFound:
- if (peerFound)
- {
- if (netPeer.ConnectionState != ConnectionState.Connected)
- return;
- if (packet.Size == 1)
- {
- //first reply
- var p = NetPacketPool.GetWithProperty(PacketProperty.PeerNotFound, 9);
- p.RawData[1] = 0;
- FastBitConverter.GetBytes(p.RawData, 2, netPeer.ConnectTime);
- SendRawAndRecycle(p, remoteEndPoint);
- NetDebug.Write("PeerNotFound sending connectId: {0}", netPeer.ConnectTime);
- }
- else if (packet.Size == 10 && packet.RawData[1] == 1 && BitConverter.ToInt64(packet.RawData, 2) == netPeer.ConnectTime)
- {
- //second reply
- NetDebug.Write("PeerNotFound received our connectId: {0}", netPeer.ConnectTime);
- DisconnectPeerForce(netPeer, DisconnectReason.RemoteConnectionClose, 0, null);
- }
- }
- else if (packet.Size == 10 && packet.RawData[1] == 0)
- {
- //send reply back
- packet.RawData[1] = 1;
- SendRawAndRecycle(packet, remoteEndPoint);
- }
- break;
- case PacketProperty.Broadcast:
- if (!BroadcastReceiveEnabled)
- break;
- CreateEvent(NetEvent.EType.Broadcast, remoteEndPoint: remoteEndPoint, readerSource: packet);
- break;
-
- case PacketProperty.UnconnectedMessage:
- if (!UnconnectedMessagesEnabled)
- break;
- CreateEvent(NetEvent.EType.ReceiveUnconnected, remoteEndPoint: remoteEndPoint, readerSource: packet);
- break;
-
- case PacketProperty.NatIntroduction:
- case PacketProperty.NatIntroductionRequest:
- case PacketProperty.NatPunchMessage:
- if (NatPunchEnabled)
- NatPunchModule.ProcessMessage(remoteEndPoint, packet);
- break;
- case PacketProperty.InvalidProtocol:
- if (peerFound && netPeer.ConnectionState == ConnectionState.Outcoming)
- DisconnectPeerForce(netPeer, DisconnectReason.InvalidProtocol, 0, null);
- break;
- case PacketProperty.Disconnect:
- if (peerFound)
- {
- var disconnectResult = netPeer.ProcessDisconnect(packet);
- if (disconnectResult == DisconnectResult.None)
- {
- NetPacketPool.Recycle(packet);
- return;
- }
- DisconnectPeerForce(
- netPeer,
- disconnectResult == DisconnectResult.Disconnect
- ? DisconnectReason.RemoteConnectionClose
- : DisconnectReason.ConnectionRejected,
- 0, packet);
- }
- else
- {
- NetPacketPool.Recycle(packet);
- }
- //Send shutdown
- SendRawAndRecycle(NetPacketPool.GetWithProperty(PacketProperty.ShutdownOk, 0), remoteEndPoint);
+ _netEventListener.OnConnectionRequest((ConnectionRequest)evt.ConnectionRequest);
break;
-
- case PacketProperty.ConnectAccept:
- var connAccept = NetConnectAcceptPacket.FromData(packet);
- if (connAccept != null && peerFound && netPeer.ProcessConnectAccept(connAccept))
- CreateEvent(NetEvent.EType.Connect, netPeer);
+ case NetEvent.EType.MessageDelivered:
+ _netEventListener.OnMessageDelivered(netPeer, evt.UserData);
break;
- case PacketProperty.ConnectRequest:
- if (NetConnectRequestPacket.GetProtocolId(packet) != NetConstants.ProtocolId)
+ case NetEvent.EType.PeerAddressChanged:
+ _peersLock.EnterUpgradeableReadLock();
+ IPEndPoint previousAddress = null;
+ if (ContainsPeer(evt.Peer))
{
- SendRawAndRecycle(NetPacketPool.GetWithProperty(PacketProperty.InvalidProtocol, 0), remoteEndPoint);
- break;
+ _peersLock.EnterWriteLock();
+ RemovePeerFromSet(evt.Peer);
+ previousAddress = new IPEndPoint(evt.Peer.Address, evt.Peer.Port);
+ evt.Peer.FinishEndPointChange(evt.RemoteEndPoint);
+ AddPeerToSet(evt.Peer);
+ _peersLock.ExitWriteLock();
}
- var connRequest = NetConnectRequestPacket.FromData(packet);
- if (connRequest != null)
- ProcessConnectRequest(remoteEndPoint, netPeer, connRequest);
- break;
- default:
- if(peerFound)
- netPeer.ProcessPacket(packet);
- else
- SendRawAndRecycle(NetPacketPool.GetWithProperty(PacketProperty.PeerNotFound, 0), remoteEndPoint);
+ _peersLock.ExitUpgradeableReadLock();
+ if (previousAddress != null)
+ _netEventListener.OnPeerAddressChanged(netPeer, previousAddress);
break;
}
+ //Recycle if not message
+ if (emptyData)
+ RecycleEvent(evt);
+ else if (AutoRecycle)
+ evt.DataReader.RecycleInternal();
}
- internal void ReceiveFromPeer(NetPacket packet, IPEndPoint remoteEndPoint)
+ protected override void ProcessNtpRequests(float elapsedMilliseconds)
{
- NetPeer fromPeer;
- if (!TryGetPeer(remoteEndPoint, out fromPeer))
+ if (_ntpRequests.IsEmpty)
return;
- NetDebug.Write(NetLogLevel.Trace, "[NM] Received message");
- DeliveryMethod deliveryMethod;
- switch (packet.Property)
+ List requestsToRemove = null;
+ foreach (var ntpRequest in _ntpRequests)
{
- default: //PacketProperty.Unreliable
- deliveryMethod = DeliveryMethod.Unreliable;
- break;
- case PacketProperty.Channeled:
- deliveryMethod = NetConstants.ChannelIdToDeliveryMethod(packet.ChannelId, _channelsCount);
- break;
+ ntpRequest.Value.Send(_udpSocketv4, elapsedMilliseconds);
+ if (ntpRequest.Value.NeedToKill)
+ {
+ if (requestsToRemove == null)
+ requestsToRemove = new List();
+ requestsToRemove.Add(ntpRequest.Key);
+ }
}
- CreateEvent(NetEvent.EType.Receive, fromPeer, deliveryMethod: deliveryMethod, readerSource: packet);
- }
-
- ///
- /// Send data to all connected peers (channel - 0)
- ///
- /// DataWriter with data
- /// Send options (reliable, unreliable, etc.)
- public void SendToAll(NetDataWriter writer, DeliveryMethod options)
- {
- SendToAll(writer.Data, 0, writer.Length, options);
- }
-
- ///
- /// Send data to all connected peers (channel - 0)
- ///
- /// Data
- /// Send options (reliable, unreliable, etc.)
- public void SendToAll(byte[] data, DeliveryMethod options)
- {
- SendToAll(data, 0, data.Length, options);
- }
- ///
- /// Send data to all connected peers (channel - 0)
- ///
- /// Data
- /// Start of data
- /// Length of data
- /// Send options (reliable, unreliable, etc.)
- public void SendToAll(byte[] data, int start, int length, DeliveryMethod options)
- {
- SendToAll(data, start, length, 0, options);
+ if (requestsToRemove != null)
+ {
+ foreach (var ipEndPoint in requestsToRemove)
+ _ntpRequests.TryRemove(ipEndPoint, out _);
+ }
}
///
@@ -914,10 +234,8 @@ 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)
- {
+ public void SendToAll(NetDataWriter writer, byte channelNumber, DeliveryMethod options) =>
SendToAll(writer.Data, 0, writer.Length, channelNumber, options);
- }
///
/// Send data to all connected peers
@@ -925,70 +243,75 @@ 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)
- {
+ public void SendToAll(byte[] data, byte channelNumber, DeliveryMethod options) =>
SendToAll(data, 0, data.Length, channelNumber, options);
- }
///
/// Send data to all connected peers
///
- /// Data
- /// Start of data
- /// Length of data
- /// Number of channel (from 0 to channelsCount - 1)
- /// Send options (reliable, unreliable, etc.)
- public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options)
- {
- for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
- netPeer.Send(data, start, length, channelNumber, options);
- }
-
- ///
- /// Send data to all connected peers (channel - 0)
- ///
/// DataWriter with data
+ /// Number of channel (from 0 to channelsCount - 1)
/// 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);
- }
+ 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 (channel - 0)
+ /// Send data to all connected peers
///
/// Data
+ /// Number of channel (from 0 to channelsCount - 1)
/// Send options (reliable, unreliable, etc.)
/// Excluded peer
- public void SendToAll(byte[] data, DeliveryMethod options, NetPeer excludePeer)
- {
- SendToAll(data, 0, data.Length, 0, options, excludePeer);
- }
+ 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 (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.)
/// Excluded peer
- public void SendToAll(byte[] data, int start, int length, DeliveryMethod options, NetPeer excludePeer)
+ public void SendToAll(byte[] data, int start, int length, byte channelNumber, DeliveryMethod options, LiteNetPeer excludePeer)
{
- SendToAll(data, start, length, 0, options, excludePeer);
+ try
+ {
+ _peersLock.EnterReadLock();
+ for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
+ {
+ if (netPeer != excludePeer)
+ ((NetPeer)netPeer).Send(data, start, length, channelNumber, options);
+ }
+ }
+ finally
+ {
+ _peersLock.ExitReadLock();
+ }
}
///
/// Send data to all connected peers
///
- /// DataWriter with data
+ /// 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(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();
+ }
}
///
@@ -997,165 +320,30 @@ 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)
- {
- for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
- {
- if (netPeer != excludePeer)
- netPeer.Send(data, start, length, channelNumber, options);
- }
- }
-
- ///
- /// Start logic thread and listening on available port
- ///
- public bool Start()
- {
- return Start(0);
- }
-
- ///
- /// Start logic thread and listening on selected port
- ///
- /// bind to specific ipv4 address
- /// bind to specific ipv6 address
- /// port to listen
- public bool Start(IPAddress addressIPv4, IPAddress addressIPv6, int port)
- {
- if (IsRunning)
- return false;
- if (!_socket.Bind(addressIPv4, addressIPv6, port, ReuseAddress, IPv6Enabled))
- return false;
- IsRunning = true;
- _logicThread = new Thread(UpdateLogic) { Name = "LogicThread", IsBackground = true };
- _logicThread.Start();
- return true;
- }
-
- ///
- /// Start logic thread and listening on selected port
- ///
- /// bind to specific ipv4 address
- /// bind to specific ipv6 address
- /// port to listen
- public bool Start(string addressIPv4, string addressIPv6, int port)
- {
- IPAddress ipv4 = NetUtils.ResolveAddress(addressIPv4);
- IPAddress ipv6 = NetUtils.ResolveAddress(addressIPv6);
- return Start(ipv4, ipv6, port);
- }
-
- ///
- /// Start logic thread and listening on selected port
- ///
- /// port to listen
- public bool Start(int port)
- {
- return Start(IPAddress.Any, IPAddress.IPv6Any, port);
- }
-
- ///
- /// Send message without connection
- ///
- /// Raw data
- /// Packet destination
- /// Operation result
- public bool SendUnconnectedMessage(byte[] message, IPEndPoint remoteEndPoint)
- {
- return SendUnconnectedMessage(message, 0, message.Length, remoteEndPoint);
- }
-
- ///
- /// Send message without connection
- ///
- /// Data serializer
- /// Packet destination
- /// Operation result
- public bool SendUnconnectedMessage(NetDataWriter writer, IPEndPoint remoteEndPoint)
- {
- return SendUnconnectedMessage(writer.Data, 0, writer.Length, remoteEndPoint);
- }
-
- ///
- /// Send message without connection
- ///
- /// Raw data
- /// data start
- /// data length
- /// Packet destination
- /// Operation result
- public bool SendUnconnectedMessage(byte[] message, int start, int length, IPEndPoint remoteEndPoint)
- {
- if (!IsRunning)
- return false;
- var packet = NetPacketPool.GetWithData(PacketProperty.UnconnectedMessage, message, start, length);
- bool result = SendRawAndRecycle(packet, remoteEndPoint) > 0;
- return result;
- }
-
- public bool SendBroadcast(NetDataWriter writer, int port)
- {
- return SendBroadcast(writer.Data, 0, writer.Length, port);
- }
-
- public bool SendBroadcast(byte[] data, int port)
- {
- return SendBroadcast(data, 0, data.Length, port);
- }
-
- public bool SendBroadcast(byte[] data, int start, int length, int port)
- {
- if (!IsRunning)
- return false;
- var packet = NetPacketPool.GetWithData(PacketProperty.Broadcast, data, start, length);
- bool result = _socket.SendBroadcast(packet.RawData, 0, packet.Size, port);
- NetPacketPool.Recycle(packet);
- return result;
- }
-
- ///
- /// Flush all queued packets of all peers
- ///
- public void Flush()
- {
- for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
- netPeer.Flush();
- }
-
- ///
- /// Receive all pending events. Call this in game update code
- ///
- public void PollEvents()
+ public void SendToAll(ReadOnlySpan data, byte channelNumber, DeliveryMethod options, LiteNetPeer excludePeer)
{
- if (UnsyncedEvents)
- return;
- while (true)
+ try
{
- NetEvent evt;
- lock (_netEventsQueue)
+ _peersLock.EnterReadLock();
+ for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
{
- if (_netEventsQueue.Count > 0)
- evt = _netEventsQueue.Dequeue();
- else
- return;
+ if (netPeer != excludePeer)
+ ((NetPeer)netPeer).Send(data, channelNumber, options);
}
- ProcessEvent(evt);
+ }
+ finally
+ {
+ _peersLock.ExitReadLock();
}
}
@@ -1165,12 +353,10 @@ public void PollEvents()
/// Server IP or hostname
/// Server Port
/// Connection key
- /// New NetPeer if new connection, Old NetPeer if already connected
- /// Manager is not running. Call
- public NetPeer Connect(string address, int port, string key)
- {
- return Connect(address, port, NetDataWriter.FromString(key));
- }
+ /// 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(string address, int port, string key) =>
+ Connect(address, port, NetDataWriter.FromString(key));
///
/// Connect to remote host
@@ -1178,266 +364,45 @@ public NetPeer Connect(string address, int port, string key)
/// Server IP or hostname
/// Server Port
/// Additional data for remote peer
- /// New NetPeer if new connection, Old NetPeer if already connected
- /// 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);
- }
+ /// 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(string address, int port, NetDataWriter connectionData) =>
+ (NetPeer)base.Connect(address, port, connectionData);
///
/// Connect to remote host
///
/// Server end point (ip and port)
/// Connection key
- /// New NetPeer if new connection, Old NetPeer if already connected
- /// Manager is not running. Call
- public NetPeer Connect(IPEndPoint target, string key)
- {
- return Connect(target, NetDataWriter.FromString(key));
- }
+ /// 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, string key) =>
+ (NetPeer)base.Connect(target, 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
- /// Manager is not running. Call
- public NetPeer Connect(IPEndPoint target, NetDataWriter connectionData)
- {
- if (!IsRunning)
- throw new InvalidOperationException("Client is not running");
-
- NetPeer peer;
- byte connectionNumber = 0;
- if (TryGetPeer(target, out peer))
- {
- switch (peer.ConnectionState)
- {
- //just return already connected peer
- case ConnectionState.Connected:
- case ConnectionState.Outcoming:
- case ConnectionState.Incoming:
- return peer;
- }
- //else reconnect
- connectionNumber = (byte)((peer.ConnectionNum + 1) % NetConstants.MaxConnectionNumber);
- RemovePeer(peer);
- }
- //Create reliable connection
- //And send connection request
- return TryAddPeer(new NetPeer(this, target, GetNextPeerId(), connectionNumber, 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");
-
- //Send last disconnect
- for(var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
- netPeer.Shutdown(null, 0, 0, !sendDisconnectMessages);
-
- //For working send
- IsRunning = false;
-
- //Stop
- _logicThread.Join();
- _logicThread = null;
- _socket.Close();
-
- //clear peers
- _peersLock.EnterWriteLock();
- _headPeer = null;
- _peersDict.Clear();
- _peersLock.ExitWriteLock();
-#if DEBUG
- lock (_pingSimulationList)
- _pingSimulationList.Clear();
-#endif
- _connectedPeersCount = 0;
- lock(_netEventsQueue)
- _netEventsQueue.Clear();
- }
-
- ///
- /// Return peers count with connection state
- ///
- /// peer connection state (you can use as bit flags)
- /// peers count
- public int GetPeersCount(ConnectionState peerState)
- {
- int count = 0;
- for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
- {
- if ((netPeer.ConnectionState & peerState) != 0)
- count++;
- }
- return count;
- }
-
- ///
- /// Get copy of current connected peers (slow! use GetPeersNonAlloc for best performance)
- ///
- /// Array with connected peers
- [Obsolete("Use GetPeers(ConnectionState peerState)")]
- public NetPeer[] GetPeers()
- {
- return GetPeers(ConnectionState.Connected | ConnectionState.Outcoming);
- }
-
- ///
- /// Get copy of current connected peers (slow! use GetPeersNonAlloc for best performance)
- ///
- /// Array with connected peers
- public NetPeer[] GetPeers(ConnectionState peerState)
- {
- List peersList = new List();
- GetPeersNonAlloc(peersList, peerState);
- return peersList.ToArray();
- }
-
- ///
- /// Get copy of peers (without allocations)
- ///
- /// List that will contain result
- /// State of peers
- public void GetPeersNonAlloc(List peers, ConnectionState peerState)
- {
- peers.Clear();
- for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
- {
- if ((netPeer.ConnectionState & peerState) != 0)
- peers.Add(netPeer);
- }
- }
-
- ///
- /// 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
- for (var netPeer = _headPeer; netPeer != null; netPeer = netPeer.NextPeer)
- {
- DisconnectPeer(
- netPeer,
- DisconnectReason.DisconnectPeerCalled,
- 0,
- false,
- data,
- start,
- count,
- null);
- }
- }
-
- ///
- /// 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);
- }
+ /// 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, NetDataWriter connectionData) =>
+ (NetPeer)base.Connect(target, connectionData);
///
- /// 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)
+ /// Connect to remote host
///
- /// 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);
- }
+ /// 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);
- public IEnumerator GetEnumerator()
- {
- var peer = _headPeer;
- while (peer != null)
- {
- yield return peer;
- peer = peer.NextPeer;
- }
- }
+ public new NetPeerEnumerator GetEnumerator() =>
+ new NetPeerEnumerator((NetPeer)_headPeer);
- IEnumerator IEnumerable.GetEnumerator()
- {
- return GetEnumerator();
- }
+ IEnumerator IEnumerable.GetEnumerator() =>
+ new NetPeerEnumerator((NetPeer)_headPeer);
}
}
diff --git a/LiteNetLib/NetPacket.cs b/LiteNetLib/NetPacket.cs
index 2ff8e270..d0ecdd6a 100644
--- a/LiteNetLib/NetPacket.cs
+++ b/LiteNetLib/NetPacket.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using LiteNetLib.Utils;
namespace LiteNetLib
@@ -7,6 +7,7 @@ internal enum PacketProperty : byte
{
Unreliable,
Channeled,
+ ReliableMerged,
Ack,
Ping,
Pong,
@@ -14,238 +15,207 @@ internal enum PacketProperty : byte
ConnectAccept,
Disconnect,
UnconnectedMessage,
- NatIntroductionRequest,
- NatIntroduction,
- NatPunchMessage,
MtuCheck,
MtuOk,
Broadcast,
Merged,
ShutdownOk,
PeerNotFound,
- InvalidProtocol
+ InvalidProtocol,
+ NatMessage,
+ Empty,
+ Total
}
internal sealed class NetPacket
{
- private static readonly int LastProperty = Enum.GetValues(typeof(PacketProperty)).Length;
- //Header
+ private static readonly int PropertiesCount = (int)PacketProperty.Total;
+ private static readonly int[] HeaderSizes;
+
+ static NetPacket()
+ {
+ HeaderSizes = NetUtils.AllocatePinnedUninitializedArray(PropertiesCount);
+ for (int i = 0; i < HeaderSizes.Length; i++)
+ {
+ switch ((PacketProperty)i)
+ {
+ case PacketProperty.Channeled:
+ case PacketProperty.Ack:
+ case PacketProperty.ReliableMerged:
+ HeaderSizes[i] = NetConstants.ChanneledHeaderSize;
+ break;
+ case PacketProperty.Ping:
+ HeaderSizes[i] = NetConstants.HeaderSize + 2;
+ break;
+ case PacketProperty.ConnectRequest:
+ HeaderSizes[i] = NetConnectRequestPacket.HeaderSize;
+ break;
+ case PacketProperty.ConnectAccept:
+ HeaderSizes[i] = NetConnectAcceptPacket.Size;
+ break;
+ case PacketProperty.Disconnect:
+ HeaderSizes[i] = NetConstants.HeaderSize + 8;
+ break;
+ case PacketProperty.Pong:
+ HeaderSizes[i] = NetConstants.HeaderSize + 10;
+ break;
+ default:
+ HeaderSizes[i] = NetConstants.HeaderSize;
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the packet property (type).
+ /// Stored in the first 5 bits of the first byte (0x1F mask).
+ ///
public PacketProperty Property
{
- get { return (PacketProperty)(RawData[0] & 0x1F); }
- set { RawData[0] = (byte)((RawData[0] & 0xE0) | (byte)value); }
+ 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 { return (byte)((RawData[0] & 0x60) >> 5); }
- set { RawData[0] = (byte) ((RawData[0] & 0x9F) | (value << 5)); }
+ get => (byte)((RawData[0] & 0x60) >> 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 { return BitConverter.ToUInt16(RawData, 1); }
- set { FastBitConverter.GetBytes(RawData, 1, value); }
+ get => BitConverter.ToUInt16(RawData, 1);
+ set => FastBitConverter.GetBytes(RawData, 1, value);
}
- public bool IsFragmented
- {
- get { return (RawData[0] & 0x80) != 0; }
- }
+ ///
+ /// 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 { return RawData[3]; }
- set { RawData[3] = value; }
+ 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 { return BitConverter.ToUInt16(RawData, 4); }
- set { FastBitConverter.GetBytes(RawData, 4, value); }
+ 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 { return BitConverter.ToUInt16(RawData, 6); }
- set { FastBitConverter.GetBytes(RawData, 6, value); }
+ 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 { return BitConverter.ToUInt16(RawData, 8); }
- set { FastBitConverter.GetBytes(RawData, 8, value); }
+ 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;
+ ///
+ /// Custom user data associated with this packet. Used for delivery notifications.
+ ///
+ public object UserData;
+
+ ///
+ /// 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;
+ Size = totalSize;
}
- public void Realloc(int toSize, bool clear)
- {
- Size = toSize;
- if (RawData.Length < toSize)
- {
- RawData = new byte[toSize];
- return;
- }
- if (clear) //clear not reallocated
- Array.Clear(RawData, 0, toSize);
- }
+ ///
+ /// Gets the fixed header size for a specific .
+ ///
+ /// The packet type.
+ /// Header size in bytes.
+ public static int GetHeaderSize(PacketProperty property) => HeaderSizes[(int)property];
- public static int GetHeaderSize(PacketProperty property)
- {
- switch (property)
- {
- case PacketProperty.Channeled:
- case PacketProperty.Ack:
- return NetConstants.ChanneledHeaderSize;
- case PacketProperty.Ping:
- return NetConstants.HeaderSize + 2;
- case PacketProperty.ConnectRequest:
- return NetConnectRequestPacket.HeaderSize;
- case PacketProperty.ConnectAccept:
- return NetConnectAcceptPacket.Size;
- case PacketProperty.Disconnect:
- return NetConstants.HeaderSize + 8;
- case PacketProperty.Pong:
- return NetConstants.HeaderSize + 10;
- default:
- return NetConstants.HeaderSize;
- }
- }
+ ///
+ /// Gets the header size of the current packet based on its property bits.
+ ///
+ public int HeaderSize => HeaderSizes[RawData[0] & 0x1F];
- public int GetHeaderSize()
+ ///
+ /// 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()
{
- return GetHeaderSize(Property);
- }
-
- //Packet contstructor from byte array
- public bool FromBytes(byte[] data, int start, int packetSize)
- {
- //Reading property
- byte property = (byte)(data[start] & 0x1F);
- bool fragmented = (data[start] & 0x80) != 0;
- int headerSize = GetHeaderSize((PacketProperty) property);
-
- if (property > LastProperty || packetSize < headerSize ||
- (fragmented && packetSize < headerSize + NetConstants.FragmentHeaderSize))
- {
+ byte property = (byte)(RawData[0] & 0x1F);
+ if (property >= PropertiesCount)
return false;
- }
-
- Buffer.BlockCopy(data, start, RawData, 0, packetSize);
- Size = packetSize;
- return true;
- }
- }
-
- internal sealed class NetConnectRequestPacket
- {
- public const int HeaderSize = 13;
- public readonly long ConnectionTime;
- public readonly byte ConnectionNumber;
- public readonly NetDataReader Data;
-
- private NetConnectRequestPacket(long connectionId, byte connectionNumber, NetDataReader data)
- {
- ConnectionTime = connectionId;
- ConnectionNumber = connectionNumber;
- Data = data;
- }
-
- public static int GetProtocolId(NetPacket packet)
- {
- return BitConverter.ToInt32(packet.RawData, 1);
- }
-
- public static NetConnectRequestPacket FromData(NetPacket packet)
- {
- if (packet.ConnectionNumber >= NetConstants.MaxConnectionNumber)
- return null;
-
- //Getting new id for peer
- long connectionId = BitConverter.ToInt64(packet.RawData, 5);
-
- // Read data and create request
- var reader = new NetDataReader(null, 0, 0);
- if (packet.Size > HeaderSize)
- reader.SetSource(packet.RawData, HeaderSize, packet.Size);
-
- return new NetConnectRequestPacket(connectionId, packet.ConnectionNumber, reader);
- }
-
- public static NetPacket Make(NetDataWriter connectData, long connectId)
- {
- //Make initial packet
- var packet = new NetPacket(PacketProperty.ConnectRequest, connectData.Length);
-
- //Add data
- FastBitConverter.GetBytes(packet.RawData, 1, NetConstants.ProtocolId);
- FastBitConverter.GetBytes(packet.RawData, 5, connectId);
- Buffer.BlockCopy(connectData.Data, 0, packet.RawData, HeaderSize, connectData.Length);
- return packet;
- }
- }
-
- internal sealed class NetConnectAcceptPacket
- {
- public const int Size = 11;
- public readonly long ConnectionId;
- public readonly byte ConnectionNumber;
- public readonly bool IsReusedPeer;
-
- private NetConnectAcceptPacket(long connectionId, byte connectionNumber, bool isReusedPeer)
- {
- ConnectionId = connectionId;
- ConnectionNumber = connectionNumber;
- IsReusedPeer = isReusedPeer;
- }
-
- public static NetConnectAcceptPacket FromData(NetPacket packet)
- {
- if (packet.Size > Size)
- return null;
-
- long connectionId = BitConverter.ToInt64(packet.RawData, 1);
- //check connect num
- byte connectionNumber = packet.RawData[9];
- if (connectionNumber >= NetConstants.MaxConnectionNumber)
- return null;
- //check reused flag
- byte isReused = packet.RawData[10];
- if (isReused > 1)
- return null;
-
- return new NetConnectAcceptPacket(connectionId, connectionNumber, isReused == 1);
- }
-
- public static NetPacket Make(long connectId, byte connectNum, bool reusedPeer)
- {
- var packet = new NetPacket(PacketProperty.ConnectAccept, 0);
- FastBitConverter.GetBytes(packet.RawData, 1, connectId);
- packet.RawData[9] = connectNum;
- packet.RawData[10] = (byte)(reusedPeer ? 1 : 0);
- return packet;
+ int headerSize = HeaderSizes[property];
+ bool fragmented = (RawData[0] & 0x80) != 0;
+ return Size >= headerSize && (!fragmented || Size >= headerSize + NetConstants.FragmentHeaderSize);
}
}
}
diff --git a/LiteNetLib/NetPacketPool.cs b/LiteNetLib/NetPacketPool.cs
deleted file mode 100644
index 57a39996..00000000
--- a/LiteNetLib/NetPacketPool.cs
+++ /dev/null
@@ -1,81 +0,0 @@
-using System;
-using System.Threading;
-
-namespace LiteNetLib
-{
- internal sealed class NetPacketPool
- {
- private readonly NetPacket[] _pool = new NetPacket[NetConstants.PacketPoolSize];
- private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
- private int _count;
-
- public NetPacket GetWithData(PacketProperty property, byte[] data, int start, int length)
- {
- var packet = GetWithProperty(property, length);
- Buffer.BlockCopy(data, start, packet.RawData, NetPacket.GetHeaderSize(property), length);
- return packet;
- }
-
- public NetPacket GetPacket(int size, bool clear)
- {
- NetPacket packet = null;
- if (size <= NetConstants.MaxPacketSize)
- {
- _lock.EnterUpgradeableReadLock();
- if (_count > 0)
- {
- _lock.EnterWriteLock();
- _count--;
- packet = _pool[_count];
- _pool[_count] = null;
- _lock.ExitWriteLock();
- }
- _lock.ExitUpgradeableReadLock();
- }
- if (packet == null)
- {
- //allocate new packet
- packet = new NetPacket(size);
- }
- else
- {
- //reallocate packet data if packet not fits
- packet.Realloc(size, clear);
- }
- return packet;
- }
-
- //Get packet with size
- public NetPacket GetWithProperty(PacketProperty property, int size)
- {
- size += NetPacket.GetHeaderSize(property);
- NetPacket packet = GetPacket(size, true);
- packet.Property = property;
- return packet;
- }
-
- public void Recycle(NetPacket packet)
- {
- if (packet.RawData.Length > NetConstants.MaxPacketSize)
- {
- //Dont pool big packets. Save memory
- return;
- }
-
- //Clean fragmented flag
- packet.RawData[0] = 0;
-
- _lock.EnterUpgradeableReadLock();
- if (_count == NetConstants.PacketPoolSize)
- {
- _lock.ExitUpgradeableReadLock();
- return;
- }
- _lock.EnterWriteLock();
- _pool[_count] = packet;
- _count++;
- _lock.ExitWriteLock();
- _lock.ExitUpgradeableReadLock();
- }
- }
-}
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 951b4450..3be8844e 100644
--- a/LiteNetLib/NetPeer.cs
+++ b/LiteNetLib/NetPeer.cs
@@ -1,353 +1,141 @@
-#if DEBUG
-#define STATS_ENABLED
-#endif
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
+using System;
+using System.Collections.Concurrent;
using System.Net;
+using System.Threading;
using LiteNetLib.Utils;
namespace LiteNetLib
{
///
- /// Peer connection state
+ /// Improved LiteNetPeer with full multi-channel support
///
- [Flags]
- public enum ConnectionState : byte
+ public class NetPeer : LiteNetPeer
{
- Incoming = 1 << 1,
- Outcoming = 1 << 2,
- Connected = 1 << 3,
- ShutdownRequested = 1 << 4,
- Disconnected = 1 << 5,
- Any = Incoming | Outcoming | Connected | ShutdownRequested
- }
-
- internal enum ConnectRequestResult
- {
- None,
- P2PConnection, //when peer connecting
- Reconnection, //when peer was connected
- NewConnection //when peer was disconnected
- }
-
- internal enum DisconnectResult
- {
- None,
- Reject,
- Disconnect
- }
-
- ///
- /// 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 IPEndPoint _remoteEndPoint;
- private readonly NetManager _netManager;
- private readonly NetPacketPool _packetPool;
- private readonly object _flushLock = new object();
- private readonly object _sendLock = new object();
- private readonly object _shutdownLock = new object();
-
- internal NetPeer NextPeer;
- internal NetPeer PrevPeer;
-
- internal byte ConnectionNum
- {
- get { return _connectNum; }
- private set
- {
- _connectNum = value;
- _mergeData.ConnectionNumber = value;
- _pingPacket.ConnectionNumber = value;
- _pongPacket.ConnectionNumber = value;
- }
- }
-
- //Channels
- private readonly SimpleChannel _unreliableChannel;
+ private readonly ConcurrentQueue _channelSendQueue = new ConcurrentQueue();
private readonly BaseChannel[] _channels;
- private BaseChannel _headChannel;
- private readonly byte _channelsCount;
- private readonly int _channelsTotalCount;
- //MTU
- private int _mtu = NetConstants.PossibleMtu[0];
- 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();
+ protected override int ChannelsCount => ((NetManager)NetManager).ChannelsCount;
- //Fragment
- private class IncomingFragments
+ internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id) : base(netManager, remoteEndPoint, id)
{
- public NetPacket[] Fragments;
- public int ReceivedCount;
- public int TotalSize;
- }
- private ushort _fragmentId;
- private readonly Dictionary _holdedFragments;
-
- //Merging
- private readonly NetPacket _mergeData;
- private int _mergePos;
- private int _mergeCount;
-
- //Connection
- private int _connectAttempts;
- private int _connectTimer;
- private long _connectTime;
- private byte _connectNum;
- private ConnectionState _connectionState;
- private NetPacket _shutdownPacket;
- private const int ShutdownDelay = 300;
- private int _shutdownTimer;
- private readonly NetPacket _pingPacket;
- private readonly NetPacket _pongPacket;
- private readonly NetPacket _connectRequestPacket;
- private NetPacket _connectAcceptPacket;
- ///
- /// Current connection state
- ///
- public ConnectionState ConnectionState { get { return _connectionState; } }
-
- ///
- /// Connection time for internal purposes
- ///
- internal long ConnectTime { get { return _connectTime; } }
-
- ///
- /// Peer id can be used as key in your dictionary of peers
- ///
- public readonly int Id;
-
- ///
- /// Peer ip address and port
- ///
- public IPEndPoint EndPoint { get { return _remoteEndPoint; } }
-
- ///
- /// Current ping in milliseconds
- ///
- public int Ping { get { return _avgRtt/2; } }
-
- ///
- /// Current MTU - Maximum Transfer Unit ( maximum udp packet size without fragmentation )
- ///
- public int Mtu { get { return _mtu; } }
+ }
- ///
- /// Delta with remote time in ticks (not accurate)
- /// positive - remote time > our time
- ///
- public long RemoteTimeDelta
+ internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id, byte connectNum, ReadOnlySpan connectData) : base(netManager, remoteEndPoint, id, connectNum, connectData)
{
- get { return _remoteDelta; }
+ _channels = new BaseChannel[netManager.ChannelsCount * NetConstants.ChannelTypeCount];
}
- ///
- /// Remote UTC time (not accurate)
- ///
- public DateTime RemoteUtcTime
+ internal NetPeer(NetManager netManager, LiteConnectionRequest request, int id) : base(netManager, request, id)
{
- get { return new DateTime(DateTime.UtcNow.Ticks + _remoteDelta); }
+ _channels = new BaseChannel[netManager.ChannelsCount * NetConstants.ChannelTypeCount];
}
///
- /// Time since last packet received (including internal library packets)
- ///
- public int TimeSinceLastPacket { get { return _timeSinceLastPacket; } }
-
- ///
- /// Peer parent NetManager
+ /// Send data to peer
///
- public NetManager NetManager { get { return _netManager; } }
-
- internal double ResendDelay { get { return _resendDelay; } }
-
- ///
- /// Application defined object containing data about the connection
- ///
- public object Tag;
+ /// DataWriter with 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(NetDataWriter dataWriter, byte channelNumber, DeliveryMethod deliveryMethod) =>
+ SendInternal(dataWriter.AsReadOnlySpan(), channelNumber, deliveryMethod, null);
///
- /// Statistics of peer connection
+ /// Send data to peer with delivery event called
///
- public readonly NetStatistics Statistics;
-
- //incoming connection constructor
- internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id)
- {
- Id = id;
- Statistics = new NetStatistics();
- _packetPool = netManager.NetPacketPool;
- _netManager = netManager;
- _remoteEndPoint = remoteEndPoint;
- _connectionState = ConnectionState.Incoming;
- _mergeData = new NetPacket(PacketProperty.Merged, NetConstants.MaxPacketSize);
- _pongPacket = new NetPacket(PacketProperty.Pong, 0);
- _pingPacket = new NetPacket(PacketProperty.Ping, 0) {Sequence = 1};
-
- _unreliableChannel = new SimpleChannel(this);
- _headChannel = _unreliableChannel;
- _holdedFragments = new Dictionary();
-
- _channelsCount = netManager.ChannelsCount;
- _channelsTotalCount = (byte)(_channelsCount * 4);
- _channels = new BaseChannel[_channelsTotalCount];
- }
-
- private BaseChannel CreateChannel(byte idx)
- {
- BaseChannel newChannel = _channels[idx];
- if (newChannel != null)
- return newChannel;
- switch (NetConstants.ChannelIdToDeliveryMethod(idx, _channelsCount))
- {
- case DeliveryMethod.ReliableUnordered:
- newChannel = new ReliableChannel(this, false, idx);
- break;
- case DeliveryMethod.Sequenced:
- newChannel = new SequencedChannel(this, false, idx);
- break;
- case DeliveryMethod.ReliableOrdered:
- newChannel = new ReliableChannel(this, true, idx);
- break;
- case DeliveryMethod.ReliableSequenced:
- newChannel = new SequencedChannel(this, true, idx);
- break;
- }
- _channels[idx] = newChannel;
- newChannel.Next = _headChannel;
- _headChannel = newChannel;
- return newChannel;
- }
-
- //"Connect to" constructor
- internal NetPeer(NetManager netManager, IPEndPoint remoteEndPoint, int id, byte connectNum, NetDataWriter connectData)
- : this(netManager, remoteEndPoint, id)
- {
- _connectTime = DateTime.UtcNow.Ticks;
- _connectionState = ConnectionState.Outcoming;
- ConnectionNum = connectNum;
-
- //Make initial packet
- _connectRequestPacket = NetConnectRequestPacket.Make(connectData, _connectTime);
- _connectRequestPacket.ConnectionNumber = connectNum;
-
- //Send request
- _netManager.SendRaw(_connectRequestPacket, _remoteEndPoint);
-
- NetDebug.Write(NetLogLevel.Trace, "[CC] ConnectId: {0}, ConnectNum: {1}", _connectTime, connectNum);
- }
-
- //"Accept" incoming constructor
- internal void Accept(long connectId, byte connectNum)
- {
- _connectTime = connectId;
- _connectionState = ConnectionState.Connected;
- ConnectionNum = connectNum;
-
- //Make initial packet
- _connectAcceptPacket = NetConnectAcceptPacket.Make(_connectTime, connectNum, false);
- //Send
- _netManager.SendRaw(_connectAcceptPacket, _remoteEndPoint);
-
- NetDebug.Write(NetLogLevel.Trace, "[CC] ConnectId: {0}", _connectTime);
- }
-
- internal bool ProcessConnectAccept(NetConnectAcceptPacket packet)
+ /// 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(byte[] data, byte channelNumber, DeliveryMethod deliveryMethod, object userData)
{
- if (_connectionState != ConnectionState.Outcoming)
- return false;
-
- //check connection id
- if (packet.ConnectionId != _connectTime)
- {
- NetDebug.Write(NetLogLevel.Trace, "[NC] Invalid connectId: {0}", _connectTime);
- return false;
- }
- //check connect num
- ConnectionNum = packet.ConnectionNumber;
-
- NetDebug.Write(NetLogLevel.Trace, "[NC] Received connection accept");
- _timeSinceLastPacket = 0;
- _connectionState = ConnectionState.Connected;
- return true;
+ 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), channelNumber, deliveryMethod, userData);
}
///
- /// Gets maximum size of packet that will be not fragmented.
+ /// Send data to peer with delivery event called
///
- /// Type of packet that you want send
- /// size in bytes
- public int GetMaxSinglePacketSize(DeliveryMethod options)
+ /// Data
+ /// Start of data
+ /// Length of 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(byte[] data, int start, int length, byte channelNumber, DeliveryMethod deliveryMethod, object userData)
{
- return _mtu - NetPacket.GetHeaderSize(options == DeliveryMethod.Unreliable ? PacketProperty.Unreliable : PacketProperty.Channeled);
+ 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), 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
+ /// 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 Send(byte[] data, DeliveryMethod options)
+ public void SendWithDeliveryEvent(NetDataWriter dataWriter, byte channelNumber, DeliveryMethod deliveryMethod, object userData)
{
- Send(data, 0, data.Length, 0, options);
+ if (deliveryMethod != DeliveryMethod.ReliableOrdered && deliveryMethod != DeliveryMethod.ReliableUnordered)
+ throw new ArgumentException("Delivery event will work only for ReliableOrdered/Unordered packets");
+ SendInternal(dataWriter.AsReadOnlySpan(), channelNumber, deliveryMethod, userData);
}
///
- /// Send data to peer (channel - 0)
+ /// Send data to peer with delivery event called
///
- /// DataWriter with data
- /// Send options (reliable, unreliable, etc.)
- ///
- /// If size exceeds maximum limit:
- /// MTU - headerSize bytes for Unreliable
- /// Fragment count exceeded ushort.MaxValue
+ /// 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 Send(NetDataWriter dataWriter, DeliveryMethod options)
+ public void SendWithDeliveryEvent(ReadOnlySpan data, byte channelNumber, DeliveryMethod deliveryMethod, object userData)
{
- Send(dataWriter.Data, 0, dataWriter.Length, 0, options);
+ 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)
{
- Send(data, start, length, 0, options);
+ //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));
+ }
}
///
@@ -355,32 +143,28 @@ public void Send(byte[] data, int start, int length, DeliveryMethod options)
///
/// Data
/// Number of channel (from 0 to channelsCount - 1)
- /// Send options (reliable, unreliable, etc.)
+ /// 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, byte channelNumber, DeliveryMethod options)
- {
- Send(data, 0, data.Length, channelNumber, options);
- }
+ 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.)
+ /// 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 options)
- {
- Send(dataWriter.Data, 0, dataWriter.Length, channelNumber, options);
- }
+ public void Send(ReadOnlySpan data, byte channelNumber, DeliveryMethod deliveryMethod) =>
+ SendInternal(data, channelNumber, deliveryMethod, null);
///
/// Send data to peer
@@ -389,644 +173,95 @@ public void Send(NetDataWriter dataWriter, byte channelNumber, DeliveryMethod op
/// Start of data
/// Length of data
/// Number of channel (from 0 to channelsCount - 1)
- /// Send options (reliable, unreliable, etc.)
+ /// Delivery method (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, byte channelNumber, DeliveryMethod options)
- {
- if (_connectionState == ConnectionState.ShutdownRequested ||
- _connectionState == ConnectionState.Disconnected)
- return;
- if (channelNumber >= _channelsTotalCount)
- return;
-
- //Select channel
- PacketProperty property;
- BaseChannel channel;
-
- if (options == DeliveryMethod.Unreliable)
- {
- property = PacketProperty.Unreliable;
- channel = _unreliableChannel;
- }
- else
- {
- property = PacketProperty.Channeled;
- channel = CreateChannel(NetConstants.ChannelNumberToId(options, channelNumber, _channelsCount));
- }
-
- //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 (options != DeliveryMethod.ReliableOrdered && options != DeliveryMethod.ReliableUnordered)
- throw new TooBigPacketException("Unreliable packet size exceeded maximum of " + (_mtu - headerSize) + " bytes");
-
- int packetFullSize = mtu - headerSize;
- int packetDataSize = packetFullSize - NetConstants.FragmentHeaderSize;
-
- int fullPacketsCount = length / packetDataSize;
- int lastPacketSize = length % packetDataSize;
- int totalPackets = fullPacketsCount + (lastPacketSize == 0 ? 0 : 1);
-
- NetDebug.Write("FragmentSend:\n" +
- " MTU: {0}\n" +
- " headerSize: {1}\n" +
- " packetFullSize: {2}\n" +
- " packetDataSize: {3}\n" +
- " fullPacketsCount: {4}\n" +
- " lastPacketSize: {5}\n" +
- " totalPackets: {6}",
- mtu, headerSize, packetFullSize, packetDataSize, fullPacketsCount, lastPacketSize, totalPackets);
+ public void Send(byte[] data, int start, int length, byte channelNumber, DeliveryMethod deliveryMethod) =>
+ SendInternal(new ReadOnlySpan(data, start, length), channelNumber, deliveryMethod, null);
- if (totalPackets > ushort.MaxValue)
- {
- throw new TooBigPacketException("Data was split in " + totalPackets + " fragments, which exceeds " + ushort.MaxValue);
- }
-
- int dataOffset = headerSize + NetConstants.FragmentHeaderSize;
-
- lock (_sendLock)
- {
- for (ushort i = 0; i < fullPacketsCount; i++)
- {
- NetPacket p = _packetPool.GetWithProperty(property, packetFullSize);
- p.FragmentId = _fragmentId;
- p.FragmentPart = i;
- p.FragmentsTotal = (ushort)totalPackets;
- p.MarkFragmented();
- Buffer.BlockCopy(data, i * packetDataSize, p.RawData, dataOffset, packetDataSize);
- channel.AddToQueue(p);
- }
- if (lastPacketSize > 0)
- {
- NetPacket p = _packetPool.GetWithProperty(property, lastPacketSize + NetConstants.FragmentHeaderSize);
- p.FragmentId = _fragmentId;
- p.FragmentPart = (ushort)fullPacketsCount; //last
- p.FragmentsTotal = (ushort)totalPackets;
- p.MarkFragmented();
- Buffer.BlockCopy(data, fullPacketsCount * packetDataSize, p.RawData, dataOffset, lastPacketSize);
- channel.AddToQueue(p);
- }
- _fragmentId++;
- }
- return;
- }
-
- //Else just send
- NetPacket packet = _packetPool.GetWithData(property, data, start, length);
- channel.AddToQueue(packet);
- }
-
- public void Disconnect(byte[] data)
- {
- _netManager.DisconnectPeer(this, data);
- }
-
- public void Disconnect(NetDataWriter writer)
- {
- _netManager.DisconnectPeer(this, writer);
- }
-
- public void Disconnect(byte[] data, int start, int count)
- {
- _netManager.DisconnectPeer(this, data, start, count);
- }
-
- public void Disconnect()
- {
- _netManager.DisconnectPeer(this);
- }
-
- internal DisconnectResult ProcessDisconnect(NetPacket packet)
- {
- if ((_connectionState == ConnectionState.Connected || _connectionState == ConnectionState.Outcoming) &&
- 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 Reject(long connectionId, byte connectionNumber, byte[] data, int start, int length)
- {
- _connectTime = connectionId;
- _connectNum = connectionNumber;
- Shutdown(data, start, length, false);
- }
-
- internal bool Shutdown(byte[] data, int start, int length, bool force)
- {
- lock (_shutdownLock)
- {
- //trying to shutdown already disconnected
- if (_connectionState == ConnectionState.Disconnected ||
- _connectionState == ConnectionState.ShutdownRequested)
- {
- return false;
- }
-
- //don't send anything
- if (force)
- {
- _connectionState = ConnectionState.Disconnected;
- return true;
- }
-
- //reset time for reconnect protection
- _timeSinceLastPacket = 0;
-
- //send shutdown packet
- _shutdownPacket = new NetPacket(PacketProperty.Disconnect, length);
- _shutdownPacket.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 true;
- }
- }
-
- private void UpdateRoundTripTime(int roundTripTime)
+ ///
+ /// 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)
{
- _rtt += roundTripTime;
- _rttCount++;
- _avgRtt = _rtt/_rttCount;
- _resendDelay = 25.0 + _avgRtt * 2.1; // 25 ms + double rtt
+ int idx = channelNumber * NetConstants.ChannelTypeCount +
+ (byte) (ordered ? DeliveryMethod.ReliableOrdered : DeliveryMethod.ReliableUnordered);
+ return ((ReliableChannel)_channels[idx])?.PacketsInQueue ?? 0;
}
- internal void AddIncomingPacket(NetPacket p)
- {
- if (p.IsFragmented)
- {
- NetDebug.Write("Fragment. Id: {0}, Part: {1}, Total: {2}", p.FragmentId, p.FragmentPart, p.FragmentsTotal);
- //Get needed array from dictionary
- ushort packetFragId = p.FragmentId;
- IncomingFragments incomingFragments;
- if (!_holdedFragments.TryGetValue(packetFragId, out incomingFragments))
- {
- incomingFragments = new IncomingFragments
- {
- Fragments = new NetPacket[p.FragmentsTotal]
- };
- _holdedFragments.Add(packetFragId, incomingFragments);
- }
-
- //Cache
- var fragments = incomingFragments.Fragments;
-
- //Error check
- if (p.FragmentPart >= fragments.Length || fragments[p.FragmentPart] != null)
- {
- _packetPool.Recycle(p);
- NetDebug.WriteError("Invalid fragment packet");
- return;
- }
- //Fill array
- fragments[p.FragmentPart] = p;
-
- //Increase received fragments count
- incomingFragments.ReceivedCount++;
-
- //Increase total size
- int dataOffset = p.GetHeaderSize() + NetConstants.FragmentHeaderSize;
- incomingFragments.TotalSize += p.Size - dataOffset;
-
- //Check for finish
- if (incomingFragments.ReceivedCount != fragments.Length)
- return;
-
- NetDebug.Write("Received all fragments!");
- NetPacket resultingPacket = _packetPool.GetWithProperty( p.Property, incomingFragments.TotalSize );
-
- int resultingPacketOffset = resultingPacket.GetHeaderSize();
- int firstFragmentSize = fragments[0].Size - dataOffset;
- for (int i = 0; i < incomingFragments.ReceivedCount; i++)
- {
- //Create resulting big packet
- int fragmentSize = fragments[i].Size - dataOffset;
- Buffer.BlockCopy(
- fragments[i].RawData,
- dataOffset,
- resultingPacket.RawData,
- resultingPacketOffset + firstFragmentSize * i,
- fragmentSize);
-
- //Free memory
- _packetPool.Recycle(fragments[i]);
- fragments[i] = null;
- }
-
- //Send to process
- _netManager.ReceiveFromPeer(resultingPacket, _remoteEndPoint);
-
- //Clear memory
- _holdedFragments.Remove(packetFragId);
- }
- else //Just simple packet
- {
- _netManager.ReceiveFromPeer(p, _remoteEndPoint);
- }
- }
+ ///
+ /// Returns packets count in queue for reliable channel 0
+ ///
+ /// type of channel ReliableOrdered or ReliableUnordered
+ /// packets count in channel queue
+ public new int GetPacketsCountInReliableQueue(bool ordered) =>
+ GetPacketsCountInReliableQueue(0, ordered);
- private void ProcessMtuPacket(NetPacket packet)
+ protected override void UpdateChannels()
{
- //header + int
- if (packet.Size < NetConstants.PossibleMtu[0])
- return;
-
- //first stage check (mtu check and mtu ok)
- int receivedMtu = BitConverter.ToInt32(packet.RawData, 1);
- int endMtuCheck = BitConverter.ToInt32(packet.RawData, packet.Size - 4);
- if (receivedMtu != packet.Size || receivedMtu != endMtuCheck || receivedMtu > NetConstants.MaxPacketSize)
- {
- NetDebug.WriteError("[MTU] Broken packet. RMTU {0}, EMTU {1}, PSIZE {2}", receivedMtu, endMtuCheck, packet.Size);
+ //Pending send
+ if (_channelSendQueue.IsEmpty)
return;
- }
- if (packet.Property == PacketProperty.MtuCheck)
+ int count = _channelSendQueue.Count;
+ while (count-- > 0)
{
- _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])
- return;
-
- lock (_mtuMutex)
+ if (!_channelSendQueue.TryDequeue(out var channel))
+ break;
+ if (channel.SendAndCheckQueue())
{
- _mtuIdx++;
- _mtu = receivedMtu;
+ // still has something to send, re-add it to the send queue
+ _channelSendQueue.Enqueue(channel);
}
- //if maxed - finish.
- if (_mtuIdx == NetConstants.PossibleMtu.Length - 1)
- _finishMtu = true;
-
- NetDebug.Write("[MTU] ok. Increase to: " + _mtu);
}
}
- private void UpdateMtuLogic(int deltaTime)
+ internal override void ProcessChanneled(NetPacket packet)
{
- if (_finishMtu)
- return;
-
- _mtuCheckTimer += deltaTime;
- if (_mtuCheckTimer < MtuCheckDelay)
- return;
-
- _mtuCheckTimer = 0;
- _mtuCheckAttempts++;
- if (_mtuCheckAttempts >= MaxMtuCheckAttempts)
+ if (packet.ChannelId >= _channels.Length)
{
- _finishMtu = true;
+ NetManager.PoolRecycle(packet);
return;
}
-
- lock (_mtuMutex)
- {
- if (_mtuIdx >= NetConstants.PossibleMtu.Length - 1)
- return;
-
- //Send increased packet
- int newMtu = NetConstants.PossibleMtu[_mtuIdx + 1];
- var p = _packetPool.GetWithProperty(PacketProperty.MtuCheck, newMtu - NetConstants.HeaderSize);
- 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 or just ID update
- case ConnectionState.Outcoming:
- case ConnectionState.Incoming:
- //change connect id if newer
- if (connRequest.ConnectionTime >= _connectTime)
- {
- //Change connect id
- _connectTime = connRequest.ConnectionTime;
- ConnectionNum = connRequest.ConnectionNumber;
- }
- return _connectionState == ConnectionState.Outcoming
- ? ConnectRequestResult.P2PConnection
- : ConnectRequestResult.None;
-
- 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;
+ internal override void AddToReliableChannelSendQueue(BaseChannel channel) =>
+ _channelSendQueue.Enqueue(channel);
- case ConnectionState.Disconnected:
- case ConnectionState.ShutdownRequested:
- if (connRequest.ConnectionTime >= _connectTime)
- {
- return ConnectRequestResult.NewConnection;
- }
- break;
- }
- return ConnectRequestResult.None;
- }
-
- //Process incoming packet
- internal void ProcessPacket(NetPacket packet)
+ internal override BaseChannel CreateChannel(byte idx)
{
- //not initialized
- if (_connectionState == ConnectionState.Incoming)
- {
- _packetPool.Recycle(packet);
- return;
- }
- if (packet.ConnectionNumber != _connectNum && packet.Property != PacketProperty.ShutdownOk) //without connectionNum
- {
- NetDebug.Write(NetLogLevel.Trace, "[RR]Old packet");
- _packetPool.Recycle(packet);
- return;
- }
- _timeSinceLastPacket = 0;
-
- NetDebug.Write("[RR]PacketProperty: {0}", 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;
- NetPacket mergedPacket = _packetPool.GetPacket(size, false);
- if (!mergedPacket.FromBytes(packet.RawData, pos, size))
- {
- _packetPool.Recycle(packet);
- break;
- }
- pos += size;
- ProcessPacket(mergedPacket);
- }
- break;
- //If we get ping, send pong
- case PacketProperty.Ping:
- if (NetUtils.RelativeSequenceNumber(packet.Sequence, _pongPacket.Sequence) > 0)
- {
- NetDebug.Write("[PP]Ping receive, send pong");
- FastBitConverter.GetBytes(_pongPacket.RawData, 3, DateTime.UtcNow.Ticks);
- _pongPacket.Sequence = packet.Sequence;
- _netManager.SendRaw(_pongPacket, _remoteEndPoint);
- }
- _packetPool.Recycle(packet);
- break;
-
- //If we get pong, calculate ping time and rtt
- case PacketProperty.Pong:
- if (packet.Sequence == _pingPacket.Sequence)
- {
- _pingTimer.Stop();
- int elapsedMs = (int)_pingTimer.ElapsedMilliseconds;
- _remoteDelta = BitConverter.ToInt64(packet.RawData, 3) + (elapsedMs * TimeSpan.TicksPerMillisecond ) / 2 - DateTime.UtcNow.Ticks;
- UpdateRoundTripTime(elapsedMs);
- _netManager.ConnectionLatencyUpdated(this, elapsedMs / 2);
- NetDebug.Write("[PP]Ping: {0} - {1} - {2}", packet.Sequence, elapsedMs, _remoteDelta);
- }
- _packetPool.Recycle(packet);
- break;
-
- case PacketProperty.Ack:
- case PacketProperty.Channeled:
- if (packet.ChannelId > _channelsTotalCount)
- {
- _packetPool.Recycle(packet);
- break;
- }
- var channel = _channels[packet.ChannelId] ?? (packet.Property == PacketProperty.Ack ? null : CreateChannel(packet.ChannelId));
- if (channel != null)
- {
- if (!channel.ProcessPacket(packet))
- _packetPool.Recycle(packet);
- }
+ case DeliveryMethod.ReliableUnordered:
+ newChannel = new ReliableChannel(this, false, idx);
break;
-
- //Simple packet without acks
- case PacketProperty.Unreliable:
- AddIncomingPacket(packet);
- return;
-
- case PacketProperty.MtuCheck:
- case PacketProperty.MtuOk:
- ProcessMtuPacket(packet);
+ case DeliveryMethod.Sequenced:
+ newChannel = new SequencedChannel(this, false, idx);
break;
-
- case PacketProperty.ShutdownOk:
- if(_connectionState == ConnectionState.ShutdownRequested)
- _connectionState = ConnectionState.Disconnected;
- _packetPool.Recycle(packet);
- break;
-
- default:
- NetDebug.WriteError("Error! Unexpected packet type: " + packet.Property);
+ case DeliveryMethod.ReliableOrdered:
+ newChannel = new ReliableChannel(this, true, 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 STATS_ENABLED
- Statistics.PacketsSent++;
- Statistics.BytesSent += (ulong)bytesSent;
-#endif
- _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 STATS_ENABLED
- Statistics.PacketsSent++;
- Statistics.BytesSent += (ulong)bytesSent;
-#endif
- 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);
- }
-
- ///
- /// Flush all queued packets
- ///
- public void Flush()
- {
- if (_connectionState != ConnectionState.Connected)
- return;
- lock (_flushLock)
- {
- BaseChannel currentChannel = _headChannel;
- while (currentChannel != null)
- {
- currentChannel.SendNextPackets();
- currentChannel = currentChannel.Next;
- }
- SendMerged();
- }
- }
-
- internal void Update(int deltaTime)
- {
- _timeSinceLastPacket += deltaTime;
- switch (_connectionState)
- {
- case ConnectionState.Connected:
- if (_timeSinceLastPacket > _netManager.DisconnectTimeout)
- {
- NetDebug.Write(
- "[UPDATE] Disconnect by timeout: {0} > {1}",
- _timeSinceLastPacket,
- _netManager.DisconnectTimeout);
- _netManager.DisconnectPeerForce(this, DisconnectReason.Timeout, 0, null);
- return;
- }
+ case DeliveryMethod.ReliableSequenced:
+ newChannel = new SequencedChannel(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.Outcoming:
- _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:
- case ConnectionState.Incoming:
- return;
- }
-
- //Send ping
- _pingSendTimer += deltaTime;
- if (_pingSendTimer >= _netManager.PingInterval)
- {
- NetDebug.Write("[PP] Send ping...");
- //reset timer
- _pingSendTimer = 0;
- //send ping
- _pingPacket.Sequence++;
- //ping timeout
- if (_pingTimer.IsRunning)
- UpdateRoundTripTime((int)_pingTimer.ElapsedMilliseconds);
- _pingTimer.Reset();
- _pingTimer.Start();
- _netManager.SendRaw(_pingPacket, _remoteEndPoint);
}
+ var prevChannel = Interlocked.CompareExchange(ref _channels[idx], newChannel, null);
+ if (prevChannel != null)
+ return prevChannel;
- //RTT - round trip time
- _rttResetTimer += deltaTime;
- if (_rttResetTimer >= _netManager.PingInterval * 3)
- {
- _rttResetTimer = 0;
- _rtt = _avgRtt;
- _rttCount = 1;
- }
-
- UpdateMtuLogic(deltaTime);
-
- //Pending send
- Flush();
- }
-
- //For channels
- internal void Recycle(NetPacket packet)
- {
- _packetPool.Recycle(packet);
+ return newChannel;
}
}
}
diff --git a/LiteNetLib/NetSocket.cs b/LiteNetLib/NetSocket.cs
deleted file mode 100644
index 0b1e6db6..00000000
--- a/LiteNetLib/NetSocket.cs
+++ /dev/null
@@ -1,324 +0,0 @@
-#if UNITY_4 || UNITY_5 || UNITY_5_3_OR_NEWER
-#define UNITY
-#endif
-#if NETSTANDARD2_0 || NETCOREAPP2_0
-using System.Runtime.InteropServices;
-#endif
-
-using System;
-using System.Net;
-using System.Net.Sockets;
-using System.Threading;
-
-namespace LiteNetLib
-{
- internal interface INetSocketListener
- {
- void OnMessageReceived(byte[] data, int length, SocketError errorCode, IPEndPoint remoteEndPoint);
- }
-
- internal sealed class NetSocket
- {
- public const int ReceivePollingTime = 1000000; //1 second
- private Socket _udpSocketv4;
- private Socket _udpSocketv6;
- private Thread _threadv4;
- private Thread _threadv6;
- private volatile bool _running;
- private readonly INetSocketListener _listener;
- private static readonly IPAddress MulticastAddressV6 = IPAddress.Parse("FF02:0:0:0:0:0:0:1");
- internal static readonly bool IPv6Support;
-
- public int LocalPort { get; private set; }
-
- public short Ttl
- {
- get { return _udpSocketv4.Ttl; }
- set { _udpSocketv4.Ttl = value; }
- }
-
- static NetSocket()
- {
-#if DISABLE_IPV6 || (!UNITY_EDITOR && ENABLE_IL2CPP && !UNITY_2018_3_OR_NEWER)
- IPv6Support = false;
-#elif !UNITY_2019_1_OR_NEWER && !UNITY_2018_4_OR_NEWER && (!UNITY_EDITOR && ENABLE_IL2CPP && UNITY_2018_3_OR_NEWER)
- string version = UnityEngine.Application.unityVersion;
- IPv6Support = Socket.OSSupportsIPv6 && int.Parse(version.Remove(version.IndexOf('f')).Split('.')[2]) >= 6;
-#elif UNITY_2018_2_OR_NEWER
- IPv6Support = Socket.OSSupportsIPv6;
-#elif UNITY
-#pragma warning disable 618
- IPv6Support = Socket.SupportsIPv6;
-#pragma warning restore 618
-#else
- IPv6Support = Socket.OSSupportsIPv6;
-#endif
- }
-
- public NetSocket(INetSocketListener listener)
- {
- _listener = listener;
- }
-
- private void ReceiveLogic(object state)
- {
- Socket socket = (Socket)state;
- EndPoint bufferEndPoint = new IPEndPoint(socket.AddressFamily == AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any, 0);
- byte[] receiveBuffer = new byte[NetConstants.MaxPacketSize];
-
- while (_running)
- {
- int result;
-
- //Reading data
- try
- {
- if (socket.Available == 0 && !socket.Poll(ReceivePollingTime, SelectMode.SelectRead))
- continue;
- result = socket.ReceiveFrom(receiveBuffer, 0, receiveBuffer.Length, SocketFlags.None,
- ref bufferEndPoint);
- }
- catch (SocketException ex)
- {
- switch (ex.SocketErrorCode)
- {
- case SocketError.Interrupted:
- case SocketError.NotSocket:
- return;
- case SocketError.ConnectionReset:
- case SocketError.MessageSize:
- case SocketError.TimedOut:
- NetDebug.Write(NetLogLevel.Trace, "[R]Ignored error: {0} - {1}",
- (int) ex.SocketErrorCode, ex.ToString());
- break;
- default:
- NetDebug.WriteError("[R]Error code: {0} - {1}", (int) ex.SocketErrorCode,
- ex.ToString());
- _listener.OnMessageReceived(null, 0, ex.SocketErrorCode, (IPEndPoint) bufferEndPoint);
- break;
- }
-
- continue;
- }
- catch (ObjectDisposedException)
- {
- return;
- }
-
- //All ok!
- NetDebug.Write(NetLogLevel.Trace, "[R]Received data from {0}, result: {1}", bufferEndPoint.ToString(), result);
- _listener.OnMessageReceived(receiveBuffer, result, 0, (IPEndPoint)bufferEndPoint);
- }
- }
-
- public bool Bind(IPAddress addressIPv4, IPAddress addressIPv6, int port, bool reuseAddress, bool ipv6)
- {
- _udpSocketv4 = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
- if (!BindSocket(_udpSocketv4, new IPEndPoint(addressIPv4, port), reuseAddress))
- return false;
- LocalPort = ((IPEndPoint) _udpSocketv4.LocalEndPoint).Port;
- _running = true;
- _threadv4 = new Thread(ReceiveLogic);
- _threadv4.Name = "SocketThreadv4(" + LocalPort + ")";
- _threadv4.IsBackground = true;
- _threadv4.Start(_udpSocketv4);
-
- //Check IPv6 support
- if (!IPv6Support || !ipv6)
- return true;
-
- _udpSocketv6 = new Socket(AddressFamily.InterNetworkV6, SocketType.Dgram, ProtocolType.Udp);
- //Use one port for two sockets
- if (BindSocket(_udpSocketv6, new IPEndPoint(addressIPv6, LocalPort), reuseAddress))
- {
- try
- {
-#if !UNITY
- _udpSocketv6.SetSocketOption(
- SocketOptionLevel.IPv6,
- SocketOptionName.AddMembership,
- new IPv6MulticastOption(MulticastAddressV6));
-#endif
- }
- catch(Exception)
- {
- // Unity3d throws exception - ignored
- }
-
- _threadv6 = new Thread(ReceiveLogic);
- _threadv6.Name = "SocketThreadv6(" + LocalPort + ")";
- _threadv6.IsBackground = true;
- _threadv6.Start(_udpSocketv6);
- }
-
- return true;
- }
-
- private bool BindSocket(Socket socket, IPEndPoint ep, bool reuseAddress)
- {
- //Setup socket
- socket.ReceiveTimeout = 500;
- socket.SendTimeout = 500;
- socket.ReceiveBufferSize = NetConstants.SocketBufferSize;
- socket.SendBufferSize = NetConstants.SocketBufferSize;
- try
- {
- socket.ExclusiveAddressUse = !reuseAddress;
- socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, reuseAddress);
- }
- catch
- {
- //Unity with IL2CPP throws an exception here, it doesn't matter in most cases so just ignore it
- }
- if (socket.AddressFamily == AddressFamily.InterNetwork)
- {
- socket.Ttl = NetConstants.SocketTTL;
-
-#if NETSTANDARD2_0 || NETCOREAPP2_0
- if(!RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
-#endif
- try { socket.DontFragment = true; }
- catch (SocketException e)
- {
- NetDebug.WriteError("[B]DontFragment error: {0}", e.SocketErrorCode);
- }
-
- try { socket.EnableBroadcast = true; }
- catch (SocketException e)
- {
- NetDebug.WriteError("[B]Broadcast error: {0}", e.SocketErrorCode);
- }
- }
-
- //Bind
- try
- {
- socket.Bind(ep);
- NetDebug.Write(NetLogLevel.Trace, "[B]Successfully binded to port: {0}", ((IPEndPoint)socket.LocalEndPoint).Port);
- }
- catch (SocketException bindException)
- {
- switch (bindException.SocketErrorCode)
- {
- //IPv6 bind fix
- case SocketError.AddressAlreadyInUse:
- if (socket.AddressFamily == AddressFamily.InterNetworkV6)
- {
- try
- {
- socket.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, true);
- socket.Bind(ep);
- }
- catch (SocketException ex)
- {
- NetDebug.WriteError("[B]Bind exception: {0}, errorCode: {1}", ex.ToString(), ex.SocketErrorCode);
- return false;
- }
- return true;
- }
- break;
- //hack for iOS (Unity3D)
- case SocketError.AddressFamilyNotSupported:
- return true;
- }
- NetDebug.WriteError("[B]Bind exception: {0}, errorCode: {1}", bindException.ToString(), bindException.SocketErrorCode);
- return false;
- }
- return true;
- }
-
- public bool SendBroadcast(byte[] data, int offset, int size, int port)
- {
- bool broadcastSuccess = false;
- bool multicastSuccess = false;
- try
- {
- broadcastSuccess = _udpSocketv4.SendTo(
- data,
- offset,
- size,
- SocketFlags.None,
- new IPEndPoint(IPAddress.Broadcast, port)) > 0;
-
- if (_udpSocketv6 != null)
- {
- multicastSuccess = _udpSocketv6.SendTo(
- data,
- offset,
- size,
- SocketFlags.None,
- new IPEndPoint(MulticastAddressV6, port)) > 0;
- }
- }
- catch (Exception ex)
- {
- NetDebug.WriteError("[S][MCAST]" + ex);
- return broadcastSuccess;
- }
- return broadcastSuccess || multicastSuccess;
- }
-
- public int SendTo(byte[] data, int offset, int size, IPEndPoint remoteEndPoint, ref SocketError errorCode)
- {
- try
- {
- var socket = _udpSocketv4;
- if (remoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6 && IPv6Support)
- socket = _udpSocketv6;
- int result = socket.SendTo(data, offset, size, SocketFlags.None, remoteEndPoint);
- NetDebug.Write(NetLogLevel.Trace, "[S]Send packet to {0}, result: {1}", remoteEndPoint, result);
- return result;
- }
- catch (SocketException ex)
- {
- switch (ex.SocketErrorCode)
- {
- case SocketError.NoBufferSpaceAvailable:
- case SocketError.Interrupted:
- return 0;
- case SocketError.MessageSize: //do nothing
- break;
- default:
- NetDebug.WriteError("[S]" + ex);
- break;
- }
- errorCode = ex.SocketErrorCode;
- return -1;
- }
- catch (Exception ex)
- {
- NetDebug.WriteError("[S]" + ex);
- return -1;
- }
- }
-
- public void Close()
- {
- _running = false;
- // first close sockets
- if (_udpSocketv4 != null)
- {
- _udpSocketv4.Close();
- _udpSocketv4 = null;
- }
- if (_udpSocketv6 != null)
- {
- _udpSocketv6.Close();
- _udpSocketv6 = null;
- }
- // then join threads
- if (_threadv4 != null)
- {
- if (_threadv4 != Thread.CurrentThread)
- _threadv4.Join();
- _threadv4 = null;
- }
- if (_threadv6 != null)
- {
- if (_threadv6 != Thread.CurrentThread)
- _threadv6.Join();
- _threadv6 = null;
- }
- }
- }
-}
diff --git a/LiteNetLib/NetStatistics.cs b/LiteNetLib/NetStatistics.cs
index e763e7ad..e2d0304c 100644
--- a/LiteNetLib/NetStatistics.cs
+++ b/LiteNetLib/NetStatistics.cs
@@ -1,28 +1,112 @@
-namespace LiteNetLib
+using System.Threading;
+
+namespace LiteNetLib
{
+ ///
+ /// Thread-safe counter for network statistics including sent/received packets, bytes, and packet loss.
+ ///
public sealed class NetStatistics
{
- public ulong PacketsSent;
- public ulong PacketsReceived;
- public ulong BytesSent;
- public ulong BytesReceived;
- public ulong PacketLoss;
- public ulong PacketLossPercent
+ private long _packetsSent;
+ private long _packetsReceived;
+ private long _bytesSent;
+ 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 { return PacketsSent == 0 ? 0 : PacketLoss * 100 / PacketsSent; }
- }
+ get
+ {
+ long sent = PacketsSent, loss = PacketLoss;
- public ulong SequencedPacketLoss;
+ return sent == 0 ? 0 : loss * 100 / sent;
+ }
+ }
+ ///
+ /// Resets all statistical counters to zero.
+ ///
public void Reset()
{
- PacketsSent = 0;
- PacketsReceived = 0;
- BytesSent = 0;
- BytesReceived = 0;
- PacketLoss = 0;
+ Interlocked.Exchange(ref _packetsSent, 0);
+ Interlocked.Exchange(ref _packetsReceived, 0);
+ Interlocked.Exchange(ref _bytesSent, 0);
+ Interlocked.Exchange(ref _bytesReceived, 0);
+ Interlocked.Exchange(ref _packetLoss, 0);
}
+ ///
+ /// Increments the count of sent packets by one.
+ ///
+ public void IncrementPacketsSent() =>
+ Interlocked.Increment(ref _packetsSent);
+
+ ///
+ /// Increments the count of received packets by one.
+ ///
+ public void IncrementPacketsReceived() =>
+ Interlocked.Increment(ref _packetsReceived);
+
+ ///
+ /// Adds a specific amount to the total bytes sent.
+ ///
+ /// Number of bytes to add.
+ public void AddBytesSent(long bytesSent) =>
+ Interlocked.Add(ref _bytesSent, bytesSent);
+
+ ///
+ /// Adds a specific amount to the total bytes received.
+ ///
+ /// Number of bytes to add.
+ public void AddBytesReceived(long bytesReceived) =>
+ Interlocked.Add(ref _bytesReceived, bytesReceived);
+
+ ///
+ /// Increments the count of lost packets by one.
+ ///
+ public void IncrementPacketLoss() =>
+ Interlocked.Increment(ref _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 510d8fee..593c2f2a 100644
--- a/LiteNetLib/NetUtils.cs
+++ b/LiteNetLib/NetUtils.cs
@@ -1,5 +1,6 @@
-using System;
+using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Sockets;
using System.Net.NetworkInformation;
@@ -22,20 +23,35 @@ public enum LocalAddrType
///
public static class NetUtils
{
- public static IPEndPoint MakeEndPoint(string hostStr, int port)
- {
- return new IPEndPoint(ResolveAddress(hostStr), port);
- }
+ private static readonly NetworkSorter NetworkSorter = new NetworkSorter();
+
+ ///
+ /// 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")
return IPAddress.Loopback;
-
- IPAddress ipAddress;
- if (!IPAddress.TryParse(hostStr, out ipAddress))
+
+ if (!IPAddress.TryParse(hostStr, out var ipAddress))
{
- if (NetSocket.IPv6Support)
+ if (LiteNetManager.IPv6Support)
ipAddress = ResolveAddress(hostStr, AddressFamily.InterNetworkV6);
if (ipAddress == null)
ipAddress = ResolveAddress(hostStr, AddressFamily.InterNetwork);
@@ -46,9 +62,15 @@ public static IPAddress ResolveAddress(string hostStr)
return ipAddress;
}
- private static IPAddress ResolveAddress(string hostStr, AddressFamily addressFamily)
+ ///
+ /// 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 = ResolveAddresses(hostStr);
+ IPAddress[] addresses = Dns.GetHostEntry(hostStr).AddressList;
foreach (IPAddress ip in addresses)
{
if (ip.AddressFamily == addressFamily)
@@ -59,23 +81,11 @@ private static IPAddress ResolveAddress(string hostStr, AddressFamily addressFam
return null;
}
- private static IPAddress[] ResolveAddresses(string hostStr)
- {
-#if NETSTANDARD2_0 || NETCOREAPP2_0
- var hostTask = Dns.GetHostEntryAsync(hostStr);
- hostTask.GetAwaiter().GetResult();
- var host = hostTask.Result;
-#else
- var host = Dns.GetHostEntry(hostStr);
-#endif
- return host.AddressList;
- }
-
///
/// Get all local ip addresses
///
/// type of address (IPv4, IPv6 or both)
- /// List with all local ip adresses
+ /// List with all local ip addresses
public static List GetLocalIpList(LocalAddrType addrType)
{
List targetList = new List();
@@ -88,16 +98,21 @@ public static List GetLocalIpList(LocalAddrType addrType)
///
/// result list
/// type of address (IPv4, IPv6 or both)
- public static void GetLocalIpList(List targetList, LocalAddrType addrType)
+ public static void GetLocalIpList(IList targetList, LocalAddrType addrType)
{
bool ipv4 = (addrType & LocalAddrType.IPv4) == LocalAddrType.IPv4;
bool ipv6 = (addrType & LocalAddrType.IPv6) == LocalAddrType.IPv6;
try
{
- foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces())
+ // Sort networks interfaces so it prefer Wifi over Cellular networks
+ // Most cellulars networks seems to be incompatible with NAT Punch
+ var networks = NetworkInterface.GetAllNetworkInterfaces();
+ Array.Sort(networks, NetworkSorter);
+
+ foreach (NetworkInterface ni in networks)
{
//Skip loopback and disabled network interfaces
- if (ni.NetworkInterfaceType == NetworkInterfaceType.Loopback ||
+ if (ni.NetworkInterfaceType == NetworkInterfaceType.Loopback ||
ni.OperationalStatus != OperationalStatus.Up)
continue;
@@ -115,23 +130,24 @@ public static void GetLocalIpList(List targetList, LocalAddrType addrTyp
targetList.Add(address.ToString());
}
}
+
+ //Fallback mode (unity android)
+ if (targetList.Count == 0)
+ {
+ IPAddress[] addresses = Dns.GetHostEntry(Dns.GetHostName()).AddressList;
+ foreach (IPAddress ip in addresses)
+ {
+ if((ipv4 && ip.AddressFamily == AddressFamily.InterNetwork) ||
+ (ipv6 && ip.AddressFamily == AddressFamily.InterNetworkV6))
+ targetList.Add(ip.ToString());
+ }
+ }
}
catch
{
//ignored
}
- //Fallback mode (unity android)
- if (targetList.Count == 0)
- {
- IPAddress[] addresses = ResolveAddresses(Dns.GetHostName());
- foreach (IPAddress ip in addresses)
- {
- if((ipv4 && ip.AddressFamily == AddressFamily.InterNetwork) ||
- (ipv6 && ip.AddressFamily == AddressFamily.InterNetworkV6))
- targetList.Add(ip.ToString());
- }
- }
if (targetList.Count == 0)
{
if(ipv4)
@@ -162,7 +178,7 @@ public static string GetLocalIp(LocalAddrType addrType)
// ===========================================
internal static void PrintInterfaceInfos()
{
- NetDebug.WriteForce(NetLogLevel.Info, "IPv6Support: {0}", NetSocket.IPv6Support);
+ NetDebug.WriteForce(NetLogLevel.Info, $"IPv6Support: { LiteNetManager.IPv6Support}");
try
{
foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces())
@@ -174,24 +190,67 @@ internal static void PrintInterfaceInfos()
{
NetDebug.WriteForce(
NetLogLevel.Info,
- "Interface: {0}, Type: {1}, Ip: {2}, OpStatus: {3}",
- ni.Name,
- ni.NetworkInterfaceType.ToString(),
- ip.Address.ToString(),
- ni.OperationalStatus.ToString());
+ $"Interface: {ni.Name}, Type: {ni.NetworkInterfaceType}, Ip: {ip.Address}, OpStatus: {ni.OperationalStatus}");
}
}
}
}
catch (Exception e)
{
- NetDebug.WriteForce(NetLogLevel.Info, "Error while getting interface infos: {0}", e.ToString());
+ NetDebug.WriteForce(NetLogLevel.Info, $"Error while getting interface infos: {e}");
}
}
- internal static int RelativeSequenceNumber(int number, int expected)
+ 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
{
- return (number - expected + NetConstants.MaxSequence + NetConstants.HalfMaxSequence) % NetConstants.MaxSequence - NetConstants.HalfMaxSequence;
+#if NET5_0_OR_GREATER || NET5_0
+ return GC.AllocateUninitializedArray(count, true);
+#else
+ return new T[count];
+#endif
+ }
+ }
+
+ // Pick the most obvious choice for the local IP
+ // Ethernet > Wifi > Others > Cellular
+ internal class NetworkSorter : IComparer
+ {
+ [SuppressMessage("ReSharper", "PossibleNullReferenceException")]
+ public int Compare(NetworkInterface a, NetworkInterface b)
+ {
+ var isCellularA = a.NetworkInterfaceType == NetworkInterfaceType.Wman ||
+ a.NetworkInterfaceType == NetworkInterfaceType.Wwanpp ||
+ a.NetworkInterfaceType == NetworkInterfaceType.Wwanpp2;
+
+ var isCellularB = b.NetworkInterfaceType == NetworkInterfaceType.Wman ||
+ b.NetworkInterfaceType == NetworkInterfaceType.Wwanpp ||
+ b.NetworkInterfaceType == NetworkInterfaceType.Wwanpp2;
+
+ var isWifiA = a.NetworkInterfaceType == NetworkInterfaceType.Wireless80211;
+ var isWifiB = b.NetworkInterfaceType == NetworkInterfaceType.Wireless80211;
+
+ var isEthernetA = a.NetworkInterfaceType == NetworkInterfaceType.Ethernet ||
+ a.NetworkInterfaceType == NetworkInterfaceType.Ethernet3Megabit ||
+ a.NetworkInterfaceType == NetworkInterfaceType.GigabitEthernet ||
+ a.NetworkInterfaceType == NetworkInterfaceType.FastEthernetFx ||
+ a.NetworkInterfaceType == NetworkInterfaceType.FastEthernetT;
+
+ var isEthernetB = b.NetworkInterfaceType == NetworkInterfaceType.Ethernet ||
+ b.NetworkInterfaceType == NetworkInterfaceType.Ethernet3Megabit ||
+ b.NetworkInterfaceType == NetworkInterfaceType.GigabitEthernet ||
+ b.NetworkInterfaceType == NetworkInterfaceType.FastEthernetFx ||
+ b.NetworkInterfaceType == NetworkInterfaceType.FastEthernetT;
+
+ var isOtherA = !isCellularA && !isWifiA && !isEthernetA;
+ var isOtherB = !isCellularB && !isWifiB && !isEthernetB;
+
+ var priorityA = isEthernetA ? 3 : isWifiA ? 2 : isOtherA ? 1 : 0;
+ var priorityB = isEthernetB ? 3 : isWifiB ? 2 : isOtherB ? 1 : 0;
+
+ return priorityA > priorityB ? -1 : priorityA < priorityB ? 1 : 0;
}
}
}
diff --git a/LiteNetLib/PausedSocketFix.cs b/LiteNetLib/PausedSocketFix.cs
new file mode 100644
index 00000000..a8947348
--- /dev/null
+++ b/LiteNetLib/PausedSocketFix.cs
@@ -0,0 +1,67 @@
+#if UNITY_2018_3_OR_NEWER
+using System.Net;
+using UnityEngine;
+
+namespace LiteNetLib
+{
+ public class PausedSocketFix
+ {
+ 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(LiteNetManager netManager, IPAddress ipv4, IPAddress ipv6, int port, bool manualMode)
+ {
+ _netManager = netManager;
+ _ipv4 = ipv4;
+ _ipv6 = ipv6;
+ _port = port;
+ _manualMode = manualMode;
+ Application.focusChanged += Application_focusChanged;
+ Application.quitting += Deinitialize;
+ _initialized = true;
+ }
+
+ public void Deinitialize()
+ {
+ if (_initialized)
+ {
+ Application.focusChanged -= Application_focusChanged;
+ Application.quitting -= Deinitialize;
+ }
+
+ if (_netManager.IsRunning)
+ {
+ _netManager.Stop();
+ }
+ _initialized = false;
+ }
+
+ private void Application_focusChanged(bool focused)
+ {
+ //If coming back into focus see if a reconnect is needed.
+ if (focused)
+ {
+ //try reconnect
+ if (!_initialized)
+ return;
+ //Was intentionally disconnected at some point.
+ if (!_netManager.IsRunning)
+ return;
+ //Socket is in working state.
+ if (_netManager.NotConnected == false)
+ return;
+
+ //Socket isn't running but should be. Try to start again.
+ if (!_netManager.Start(_ipv4, _ipv6, _port, _manualMode))
+ {
+ NetDebug.WriteError($"[S] Cannot restore connection. Ipv4 {_ipv4}, Ipv6 {_ipv6}, Port {_port}, ManualMode {_manualMode}");
+ }
+ }
+ }
+ }
+}
+#endif
diff --git a/LiteNetLib/PooledPacket.cs b/LiteNetLib/PooledPacket.cs
new file mode 100644
index 00000000..927b0335
--- /dev/null
+++ b/LiteNetLib/PooledPacket.cs
@@ -0,0 +1,32 @@
+namespace LiteNetLib
+{
+ public readonly ref struct PooledPacket
+ {
+ internal readonly NetPacket _packet;
+ internal readonly byte _channelNumber;
+
+ ///
+ /// Maximum data size that you can put into such packet
+ ///
+ public readonly int MaxUserDataSize;
+
+ ///
+ /// Offset for user data when writing to Data array
+ ///
+ public readonly int UserDataOffset;
+
+ ///
+ /// Raw packet data. Do not modify header! Use UserDataOffset as start point for your data
+ ///
+ public byte[] Data => _packet.RawData;
+
+ internal PooledPacket(NetPacket packet, int maxDataSize, byte channelNumber)
+ {
+ _packet = packet;
+ UserDataOffset = _packet.HeaderSize;
+ _packet.Size = UserDataOffset;
+ MaxUserDataSize = maxDataSize - UserDataOffset;
+ _channelNumber = channelNumber;
+ }
+ }
+}
diff --git a/LiteNetLib/ReliableChannel.cs b/LiteNetLib/ReliableChannel.cs
index 08252dd8..8aa49851 100644
--- a/LiteNetLib/ReliableChannel.cs
+++ b/LiteNetLib/ReliableChannel.cs
@@ -1,19 +1,33 @@
-using System;
+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