Advanced Networking in .NET Gaming
This guide provides deep‑dive coverage of networking techniques required for modern multiplayer games built on the .NET platform. Topics include latency handling, bandwidth optimisation, reliable UDP, NAT traversal, and security considerations.
Overview
When building a fast‑paced multiplayer experience, the network layer must be both efficient and robust. .NET provides low‑level socket APIs, high‑level abstractions like System.Net.Sockets.UdpClient
, and third‑party libraries that simplify common patterns.
Latency & Tick Rate
Latency is the round‑trip time for a message. A typical approach is to decouple rendering tick rate from network tick rate.
// Example: Fixed network tick loop
using System;
using System.Diagnostics;
using System.Threading.Tasks;
class NetworkLoop {
const int TicksPerSecond = 30;
static readonly TimeSpan TickInterval = TimeSpan.FromSeconds(1.0 / TicksPerSecond);
static async Task RunAsync() {
var stopwatch = Stopwatch.StartNew();
while (true) {
var start = stopwatch.Elapsed;
// Process incoming packets
await ProcessNetworkAsync();
// Send state updates
await SendUpdatesAsync();
var elapsed = stopwatch.Elapsed - start;
var delay = TickInterval - elapsed;
if (delay > TimeSpan.Zero) await Task.Delay(delay);
}
}
static Task ProcessNetworkAsync() => Task.CompletedTask;
static Task SendUpdatesAsync() => Task.CompletedTask;
}
Bandwidth Management
Compress game state, send only deltas, and use bit‑packing to reduce packet size.
// Simple delta compression example
struct PlayerState {
public float X, Y, Z;
public byte Health;
}
class StateCompressor {
private PlayerState _lastSent;
public byte[] SerializeDelta(PlayerState current) {
var buffer = new System.IO.MemoryStream();
var writer = new System.IO.BinaryWriter(buffer);
if (current.X != _lastSent.X) { writer.Write((byte)0x01); writer.Write(current.X); }
if (current.Y != _lastSent.Y) { writer.Write((byte)0x02); writer.Write(current.Y); }
if (current.Z != _lastSent.Z) { writer.Write((byte)0x03); writer.Write(current.Z); }
if (current.Health != _lastSent.Health) { writer.Write((byte)0x04); writer.Write(current.Health); }
_lastSent = current;
return buffer.ToArray();
}
}
Reliable UDP
UDP is connectionless and fast but unreliable. Implement an ACK/NAK system to guarantee delivery of critical messages.
// Minimal Reliable UDP wrapper
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using System.Threading;
class ReliableUdp {
private readonly UdpClient _client;
private readonly IPEndPoint _remote;
private readonly ConcurrentDictionary _pending = new();
private ushort _seq = 0;
public ReliableUdp(string host, int port) {
_client = new UdpClient();
_remote = new IPEndPoint(IPAddress.Parse(host), port);
new Thread(ReceiveLoop).Start();
}
public void SendReliable(byte[] data) {
var packet = new byte[data.Length + 3];
packet[0] = 0xAA; // marker
packet[1] = (byte)(_seq >> 8);
packet[2] = (byte)(_seq & 0xFF);
Buffer.BlockCopy(data, 0, packet, 3, data.Length);
_pending[_seq] = packet;
_client.Send(packet, packet.Length, _remote);
_seq++;
}
private void ReceiveLoop() {
var ep = new IPEndPoint(IPAddress.Any, 0);
while (true) {
var bytes = _client.Receive(ref ep);
if (bytes.Length < 3) continue;
if (bytes[0] == 0xFF) { // ACK
ushort ackSeq = (ushort)(bytes[1] << 8 | bytes[2]);
_pending.TryRemove(ackSeq, out _);
}
}
}
}
NAT Traversal
Use STUN/TURN servers or UDP hole punching to enable peer‑to‑peer connections behind NATs.
Security
Encrypt payloads with TLS (for TCP) or DTLS (for UDP). Validate all incoming data to prevent abuse.
Best Practices
- Keep packets under the MTU (usually 1200‑1400 bytes for UDP).
- Prioritise critical gameplay data over ancillary information.
- Gracefully handle packet loss and re‑ordering.
- Profile network usage on target hardware.
© 2025 Microsoft. All rights reserved.