SetNet 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package SetNet --version 1.0.0
                    
NuGet\Install-Package SetNet -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SetNet" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SetNet" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="SetNet" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SetNet --version 1.0.0
                    
#r "nuget: SetNet, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SetNet@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SetNet&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=SetNet&version=1.0.0
                    
Install as a Cake Tool

SetNet 🌐

A lightweight, high-throughput .NET networking library for client–server games and real-time apps β€” over TCP, UDP, or both at once.

.NET Standard 2.1 Serialization Transports License

SetNet gives you a persistent, message-oriented connection with automatic handler registration, a pluggable transport (reliable TCP, raw/reliable UDP, or both together), per-message delivery selection, and production-grade hardening β€” so you can focus on your game/app logic instead of sockets.

// per-message channel selection β€” reliable for events, unreliable for movement
await SendAsync(MsgType.Chat,     chat,     DeliveryMethod.Reliable);
await SendAsync(MsgType.Position, position, DeliveryMethod.Unreliable);

Why SetNet

  • 🚦 TCP / UDP / Both β€” one API, choose per Configuration.TransportType; pick the channel per message via DeliveryMethod.
  • πŸ›‘οΈ Reliable UDP, optional β€” sequence / ACK / retransmit / ordered delivery with a bounded receive window and back-pressure; multiple independent channels (UdpReliableChannels) so a loss on one stream never head-of-line-blocks another.
  • 🀝 Emulated UDP connections β€” handshake + heartbeat give UDP the same OnConnected/OnDisconnected/peer lifecycle as TCP. Both mode binds a TCP lifeline and a UDP channel to one logical peer, with graceful TCP-only fallback.
  • πŸ”„ Lifecycle done right β€” intentional vs unexpected disconnects, auto-reconnect hooks, heartbeat liveness; OnDisconnected fires exactly once.
  • ⚑ Fast β€” ~1.6M msgs/sec on one connection with send batching, ~10 KB per endpoint; allocation-light hot paths.
  • πŸ”’ Production-hardened β€” TLS over TCP, connection/UDP-peer caps, per-IP rate limiting, frame-size cap, back-pressure, bounded inbound queues (OOM protection), a resilient accept loop, and live NetworkMetrics.
  • 🧩 Auto handler registration β€” mark a class [MessageHandler(type)]; reflection wires it up. Handlers are strongly typed β€” IServerMessageHandler<T>/IClientMessageHandler<T> receive the deserialized message; the library (de)serializes for you.
  • πŸ”€ Raw relay escape hatch β€” override OnRawFrame(type, data) to intercept frames and SendRawAsync to forward bytes without (de)serializing β€” build an Among Us-style relay/proxy with zero overhead, while normal handlers stay typed.
  • πŸ“¦ Pluggable serialization β€” the core bundles no serializer. Pick a format via ISerializer: drop in the SetNet.MessagePack package (hardened MessagePack), or supply your own JSON/Protobuf/custom adapter, and register it once with SetNetSerializer.Use(...).

Install

Requires .NET Standard 2.1 (consumable from .NET Core 3.0+/.NET 5–8, Unity, Mono, MAUI β€” not .NET Framework).

dotnet add package SetNet
# the core bundles no serializer β€” add one (or supply your own ISerializer):
dotnet add package SetNet.MessagePack

Then register the serializer once at startup, before connecting:

SetNetSerializer.Use(new MessagePackNetSerializer());   // from SetNet.MessagePack

Unity: works on desktop/mobile standalone (Unity 2021+, netstandard2.1). Two things to know: message handlers run on background threads, so marshal to the main thread before touching the Unity API (e.g. queue and drain in Update()); and on IL2CPP/AOT builds, MessagePack needs pre-generated formatters (or swap in an AOT-friendly serializer β€” see Serialization). WebGL is not supported (no threads/sockets).

Quick start

1. Define messages (MessagePack DTOs):

public enum MsgType : ushort { Chat = 1 }

[MessagePackObject]
public class ChatMessage { [Key(0)] public string Text { get; set; } = ""; }

2. Server:

using SetNet.Core;
using SetNet.Config;

public class ChatPeer : BasePeer
{
    public ChatPeer(PeerInfo info) : base(info) { }
    protected override void OnDisconnected() { }
    protected override void OnError(string error) { }
}

public class ChatServer : BaseServer
{
    public ChatServer(Configuration config) : base(config) { }
    protected override BasePeer OnNewClient(PeerInfo info) => new ChatPeer(info);
}

await new ChatServer(new Configuration { Host = "0.0.0.0", Port = 5000 }).StartAsync();

3. Client:

public class ChatClient : BaseClient
{
    public ChatClient(Configuration config) : base(config) { }
    protected override void OnConnected()    => Console.WriteLine("connected");
    protected override void OnDisconnected() { }
    protected override void OnError(string error) { }
    public Task SayAsync(string text) => SendAsync((ushort)MsgType.Chat, new ChatMessage { Text = text });
}

var client = new ChatClient(new Configuration { Host = "127.0.0.1", Port = 5000 });
await client.ConnectAsync();
await client.SayAsync("hello");

4. Handle messages (auto-discovered, strongly typed β€” the library deserializes for you):

[MessageHandler((ushort)MsgType.Chat)]
public class ChatHandler : IServerMessageHandler<ChatMessage>
{
    public Task HandleAsync(BasePeer peer, ChatMessage msg)
    {
        Console.WriteLine(msg.Text);
        return Task.CompletedTask;
    }
}

A full runnable chat (separate server + client processes) is in examples/.

Serialization

The core library bundles no serializer β€” you choose the format behind the ISerializer seam and register it once at startup.

MessagePack (recommended) via the SetNet.MessagePack package β€” MessagePackNetSerializer is hardened with the UntrustedData security profile (deserialization-DoS protection):

using SetNet.MessagePack;
SetNetSerializer.Use(new MessagePackNetSerializer());   // once, at startup

Or your own format (JSON, Protobuf, MemoryPack, …) β€” implement ISerializer:

public sealed class JsonSerializer : ISerializer
{
    public byte[] Serialize<T>(T value) => System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(value);
    public T Deserialize<T>(byte[] data) => System.Text.Json.JsonSerializer.Deserialize<T>(data)!;
}

SetNetSerializer.Use(new JsonSerializer());             // once, at startup

Handlers are strongly typed β€” they receive the deserialized message directly (IServerMessageHandler<ChatMessage> β†’ HandleAsync(peer, ChatMessage msg)); the library serializes on send and deserializes on receive through this one registered serializer. Both ends of a connection must use the same serializer. (Until one is registered, send/receive throws a clear "configure a serializer" error.)

Transport selection

Set Configuration.TransportType (default Tcp, so existing TCP code is unchanged):

TransportType DeliveryMethod Carried over
Tcp any TCP
Udp Reliable UDP reliability layer (needs UdpReliabilityEnabled)
Udp Unreliable raw UDP datagram
Both Reliable TCP
Both Unreliable UDP (falls back to TCP until the UDP channel attaches)
var config = new Configuration
{
    Host = "127.0.0.1", Port = 5000,
    TransportType = TransportType.Both,
    UdpReliabilityEnabled = true,
    UdpReliableChannels = 2,          // independent ordered streams
    DefaultDelivery = DeliveryMethod.Reliable,
};

Lifecycle at a glance

BaseClient distinguishes intentional from unexpected disconnects; OnDisconnected fires exactly once.

Event OnError OnUnexpectedDisconnect OnDisconnected Auto-Reconnect
Disconnect() (intentional) ❌ ❌ βœ… ❌
Network error / server crash βœ… βœ… βœ… (if reconnect fails) βœ… (if enabled)
Graceful server close ❌ ❌ βœ… ❌

Enable: AutoReconnect = true, HeartbeatEnabled = true (both off by default).

Production hardening

var config = new Configuration
{
    Host = "0.0.0.0", Port = 5000,
    UseSsl = true, ServerCertificate = cert,        // TLS over TCP (UDP is not encrypted)
    MaxConnectionsLimit = 5000,
    MaxConnectionsPerIpPerSecond = 20,              // per-IP rate limit
    MaxInFlightMessages = 256,                      // handler back-pressure
    MaxInboundQueue = 16384,                        // per-connection inbound cap (OOM protection)
    HeartbeatEnabled = true,
};

Authentication is intentionally left to your application β€” validate inside OnNewClient/handlers. UDP has no per-packet encryption; route sensitive data over TLS-over-TCP (or Both with reliable delivery).

Performance

In-process benchmark (dotnet run -c Release --project SetNet.Tests -- bench, ServerGC):

Mode Throughput (1 connection) Optimized for
Batched (SendBatching = true) ~1.6M msgs/sec throughput
Default (TcpNoDelay = true) ~230k msgs/sec latency

~10 KB per endpoint; 2,000 connections established in ~110 ms. The default favors latency (every small message sent immediately); enable SendBatching for high message rates. These numbers include serialization cost β€” the library deserializes each inbound message into the handler's typed T. Full model, scaling limits and roadmap: docs/PERFORMANCE.en.md.

Documentation

Build & test

dotnet build                                              # build (library targets netstandard2.1)
dotnet test SetNet.UnitTests/SetNet.UnitTests.csproj      # 78 unit + integration tests
dotnet run --project SetNet.Tests -- <frag|tcp|udp|loss|both|idle|deadlock>   # in-process transport scenarios
dotnet run --project SetNet.Tests -- bench                # throughput / connection benchmark

# chat example (two terminals)
dotnet run --project examples/Chat.Server -- 127.0.0.1 5000
dotnet run --project examples/Chat.Client -- 127.0.0.1 5000 alice

Project structure

SetNet/             core library (transport abstraction, reliability, hardening) β€” no serializer dependency
SetNet.MessagePack/ MessagePack ISerializer adapter (companion package)
SetNet.Tests/       in-process scenario harness + benchmark
SetNet.UnitTests/   xUnit unit + integration tests
examples/           runnable chat (Chat.Shared / Chat.Server / Chat.Client)
docs/               GUIDE.{en,ua}.md, PERFORMANCE.{en,ua}.md

Status

SetNet has been through extensive adversarial auditing (multi-round correctness convergence + a performance pass) with a full unit/integration suite and in-process scenarios. It is well-suited as the network layer for .NET ↔ .NET real-time systems (multiplayer games, chat, collaborative apps).

It is not a general-purpose RPC/HTTP framework: there is no request/response correlation, no DI/hosting integration, and no WebSocket/browser transport. Before production, implement authentication, set the hardening config, and run a soak/load test under realistic traffic.

License

MIT β€” see LICENSE.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.1

    • No dependencies.

NuGet packages (76)

Showing the top 5 NuGet packages that depend on SetNet:

Package Downloads
SetNet.StateSync

Server-authoritative entity replication for SetNet: fixed-rate, delta-compressed world snapshots over the unreliable channel with reliable spawns/despawns, client-side interpolation, interest management, and an input channel for client prediction. Engine-agnostic core (headless dedicated server + any .NET client); the Unity binding (SetNet.StateSync.Unity) adds NetworkObject/NetworkTransform/NetworkAnimator/NetworkRigidbody. Composition, no base class. Depends only on SetNet.

SetNet.MessagePack

MessagePack serializer for SetNet. Provides MessagePackNetSerializer (an ISerializer) hardened with the MessagePack UntrustedData security profile. Register it once at startup: SetNetSerializer.Use(new MessagePackNetSerializer());

SetNet.Rooms

Rooms / lobbies for SetNet, by composition (no base class). Create and join rooms by code, broadcast within a room, and get player-joined/left events β€” on a dedicated server (server is the hub; no relay needed). Pluggable room store (default in-memory), auto-leave on disconnect. Serializer-agnostic; depends only on SetNet.

SetNet.Inventory

Server-authoritative player inventory for SetNet: game logic grants/revokes stackable items by player key (server.UseInventory()), connected clients read + subscribe to changes (client.UseInventory()). Atomic TryRevoke primitive backs trades and mail claims; pluggable IInventoryStore (memory default, swap for Redis/DB). Depends only on SetNet.

SetNet.Auth

Authentication and sessions for SetNet, by composition (no base class). Enforced gate: until a peer authenticates, its application frames (regular messages and RPC) are dropped; you validate a token via IAuthenticator. Includes session store with TTL, multi-session policy, and automatic reconnect-resume. Serializer-agnostic; depends only on SetNet. Use over TLS.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.6.3 51 8/23/2026
1.6.2 53 8/23/2026
1.6.1 61 8/20/2026
1.3.0 161 8/14/2026
1.2.0 1,856 8/5/2026
1.1.0 1,822 7/2/2026
1.0.1 112 6/30/2026
1.0.0 109 6/30/2026