Client Sessions
On this page
This guide walks through creating, configuring, and connecting a client session using Nalix.SDK. It focuses on the actual SDK surface in the current source tree: TransportOptions, TcpSession, UdpSession, and the transport extension methods built on top of them.
If you haven’t yet, start with Your First Server — it covers the simpler session.RequestAsync<T>() path this guide builds on.
1. Shared Packet Catalog
The client and server must reference the same shared packet contracts project. When TransportSession.ConnectAsync() runs, it automatically invokes PacketRegistry.Build() if no PacketRegistry is registered yet, using the compile-time [ModuleInitializer] emitted by PacketRegistryGenerator:
// Ensure your client references the shared contracts assembly so its [ModuleInitializer] runs.
// ConnectAsync() automatically calls PacketRegistry.Build() on first connect,
// or you can build it explicitly at startup:
PacketRegistry.Build();
2. Configure Transport Options
The TransportOptions govern connection backoff patterns, socket buffer sizing, keepalive intervals, and cipher selection. You can fetch these directly from ConfigurationManager or manually construct them.
using Nalix.SDK.Options;
using Nalix.Environment.Configuration;
// OPTION A: Load automatically from the environment's configuration files (Recommended)
// This binds values from your INI definitions and natively supports hot-reloading.
TransportOptions options = ConfigurationManager.Instance.Get<TransportOptions>();
// OPTION B: Manual Initialization
TransportOptions manualOptions = new()
{
// Connectivity
Address = "127.0.0.1",
Port = 57206,
ConnectTimeoutMillis = 30000,
// Auto-reconnect configuration (ReconnectSupervisor)
AutoReconnectEnabled = true,
ReconnectBaseDelayMillis = 500,
ReconnectMaxDelayMillis = 30000,
ReconnectMaxAttempts = 0, // 0 = infinite retries
// Performance tuning
NoDelay = true, // Disable Nagle's algorithm (highly recommended for Games/Real-time)
BufferSize = 65536, // Socket send/receive buffer size (default 64 KB)
MaxUdpDatagramSize = 1400 // UDP limit including 8-byte SessionToken and 4-byte XxHash32 MAC
};
3. Create and Connect a TCP Session
A TcpSession uses the configured TransportOptions to establish TCP streams. In practice, you will usually register events before calling ConnectAsync() so you do not miss early connection or error signals.
using System;
using System.Threading.Tasks;
using Nalix.SDK.Transport;
using Nalix.SDK.Transport.Extensions;
using Nalix.Framework.Memory.Buffers;
async Task ConnectTcpStandardAsync()
{
// The 'using' declaration ensures sockets and background loops are safely tracked
using TcpSession session = new(options);
// 1. Bind Lifecycle Events
session.OnConnected += (_, _) =>
{
Console.WriteLine("TCP Connected successfully.");
};
session.OnDisconnected += (_, ex) =>
{
// ex contains underlying SocketExceptions or NetworkExceptions
Console.WriteLine($"TCP Disconnected. Reason: {ex.Message}");
};
session.OnError += (_, ex) =>
{
Console.WriteLine($"TCP Error observed: {ex.Message}");
};
// 2. Bind Message Handlers (Strongly-Typed)
// The framework will automatically read the byte stream, deserialize the packet,
// and invoke your callback strictly when the matching type is observed.
session.On<MyResponsePacket>(packet =>
{
Console.WriteLine($"Received strongly-typed data: {packet.StatusMessage}");
});
// 3. Connect, try resume, then fall back to handshake when needed
// `src/Nalix.SDK/Transport/Extensions/ResumeExtensions.cs` connects first,
// attempts resume only when ResumeEnabled + SessionToken + Secret are present,
// and reconnects before falling back to HandshakeAsync when configured to do so.
Console.WriteLine("Connecting to server...");
bool resumed = await session.ConnectWithResumeAsync();
Console.WriteLine(resumed ? "Session resumed." : "Fresh handshake completed.");
// At this point, `session.State` contains `EncryptionEnabled = true`,
// `Secret` (32-byte key), and the 8-byte `SessionToken` assigned by the server.
// ---------------------------------------------------------
// 4. Ship Payloads
// session.SendAsync(...) handles packing, compression, encryption, and dispatch over the wire.
// await session.SendAsync(new MyCustomPacket());
// Prevent context from tearing down
await Task.Delay(-1);
}
4. Create and Connect a UDP Session (Auxiliary Channel)
UdpSession follows the same base transport contract, but handles datagrams instead of framed TCP streams. Pass tcpSession.State into the UdpSession constructor so both sessions share the negotiated SessionToken and 32-byte Secret.
using System;
using System.Threading.Tasks;
using Nalix.SDK.Transport;
async Task ConnectUdpStandardAsync(TcpSession tcpSession)
{
// Pass `tcpSession.State` so UdpSession shares the negotiated SessionToken and Secret
using UdpSession udp = new(options, tcpSession.State);
udp.OnConnected += (_, _) => Console.WriteLine("UDP socket bound and active.");
udp.OnDisconnected += (_, ex) => Console.WriteLine($"UDP Disconnected: {ex?.Message}");
udp.OnError += (_, ex) => Console.WriteLine($"UDP Error: {ex.Message}");
// Connect to setup targeted UDP Socket (binding against Server IP and Port)
await udp.ConnectAsync();
// UDP internally wraps outbound datagrams with the 8-byte SessionToken and 4-byte XxHash32 MAC.
// await udp.SendAsync(new PlayerMovementPacket { X = 1, Y = 2, Z = 5 });
await Task.Delay(-1);
}
5. Common SDK Extensions
To help you get started quickly and handle advanced networking tasks gracefully, here are some built-in extensions that can be executed directly on any session instance:
using Nalix.SDK.Options;
using Nalix.SDK.Transport.Extensions;
// 1. Network Latency Check (Ping)
// Measures round-trip time (RTT) safely across the protocol
double rttMs = await session.PingAsync();
Console.WriteLine($"Current Latency: {rttMs}ms");
// 2. Time Synchronization
// Re-aligns the client's internal clock offsets against the core server's clock
var sync = await session.SyncTimeAsync();
Console.WriteLine($"RTT={sync.RttMs}ms Adjusted={sync.AdjustedMs}ms");
// 3. Request-Response Mechanics (RPC via Packets)
// Send a request packet and cleanly await a strongly-typed response
var response = await session.RequestAsync<MyResponsePacket>(
request: new MyRequestPacket { UserId = 123 },
options: RequestOptions.Default.WithTimeout(3000), // timeout within 3s
predicate: p => p.IsSuccess // Optional: Filter exactly which response to accept
);
6. Automatic Keep-Alive (Heartbeat)
TransportSession includes a built-in background keepalive loop whenever options.KeepAliveIntervalMillis > 0 (KeepAliveTimeoutMillis = 5000, KeepAliveMissThreshold = 3):
TransportOptions options = new()
{
Address = "127.0.0.1",
Port = 57206,
KeepAliveIntervalMillis = 15_000, // Probe every 15 seconds automatically
KeepAliveTimeoutMillis = 5_000, // Wait up to 5 seconds for PONG
KeepAliveMissThreshold = 3 // Fault & trigger ReconnectSupervisor after 3 missed PONGs
};
If you prefer to run a custom application-level heartbeat loop manually (with KeepAliveIntervalMillis = 0), you can call session.PingAsync() on a timer or background task.
7. Graceful Cleanup
Always enforce rigorous teardowns if destroying connection lifetimes. This collapses the asynchronous looping structures safely and reclaims memory leases instantly.
// Inform sockets to shut down and unmanaged handlers to cease executing
await session.DisconnectAsync();
// Reclaim buffers and class instances
session.Dispose();