Skip to content

NTDLS.DatagramMessagingv1.3.3

Just a friendly UDP packet framing, sending and receiving wrapper. It handles corruption checks, concatenation, fragmentation, serialization and adds compression.

Stable Released Dec 31, 202432,192 downloadsMIT License

NTDLS.DatagramMessaging is a C# library that wraps UdpClient so you can send and receive UDP datagrams with ease. It handles the unglamorous parts of reliable-feeling UDP for you: corruption checks, frame concatenation, fragmentation of payloads larger than a single packet, serialization (JSON by default), compression, encryption, and NAT-friendly keep-alives — all behind one simple DmMessenger class.

📦 Be sure to check out the NuGet package: https://www.nuget.org/packages/NTDLS.DatagramMessaging

When to use this package

Reach for NTDLS.DatagramMessaging when you want UDP's speed and simplicity without hand-rolling the transport layer yourself:

  • You need to exchange structured messages over UDP. Define a plain C# class, mark it as a datagram type, and the library serializes, fragments, sends, reassembles and deserializes it for you.
  • Your payloads are bigger than a single UDP packet. The framing layer transparently fragments large payloads and reassembles them on the other side, and verifies integrity along the way.
  • You want optional compression and/or encryption. Plug in the built-in Brotli or Deflate compression providers and an AES cryptography provider in a couple of lines, or supply your own via the pluggable provider interfaces (IDmCompressionProvider, IDmCryptographyProvider, IDmSerializationProvider).
  • You're working across NATs. Listening messengers automatically run keep-alive traffic to hold open NAT reply-port mappings, so clients behind routers can reach you.
  • You want low-overhead raw byte transfers too. In addition to serialized objects you can dispatch raw byte[] payloads, which skip serialization entirely.

It's well suited to game networking, telemetry, IoT-style messaging, LAN tools, and any scenario where TCP would be overkill but plain UDP is too fiddly to get right.

Usage examples

Defining a datagram

Any JSON-serializable class can be sent as a datagram by implementing the marker interface IDmDatagram:

using NTDLS.DatagramMessaging;

// A plain, JSON-serializable class marked as a datagram type.
public class MyFirstUDPPacket : IDmDatagram
{
    public DateTime TimeStamp { get; set; } = DateTime.UtcNow;
    public Guid UID { get; set; } = Guid.NewGuid();
    public string Message { get; set; } = string.Empty;

    public MyFirstUDPPacket()
    {
    }

    public MyFirstUDPPacket(string message)
    {
        Message = message;
    }
}

UDP server (event based)

Passing a listen port puts the messenger into listen mode. Every received datagram is delivered through the OnDatagramReceived event:

using NTDLS.DatagramMessaging;

static void Main()
{
    // Create a messenger bound to listen port 1234.
    using var messenger = new DmMessenger(1234);

    // Subscribe to inbound datagrams.
    messenger.OnDatagramReceived += Messenger_OnDatagramReceived;

    Console.WriteLine("Press enter to quit.");
    Console.ReadLine();
}

private static void Messenger_OnDatagramReceived(DmContext context, IDmDatagram datagram)
{
    if (datagram is MyFirstUDPPacket myFirstUDPPacket)
    {
        // Echo the datagram back to the sender.
        context.Dispatch(myFirstUDPPacket);
        Console.WriteLine($"{myFirstUDPPacket.Message}->{myFirstUDPPacket.UID}->{myFirstUDPPacket.TimeStamp}");
    }
}

UDP server (convention based)

Alternatively, register a handler class with AddHandler(). Any method whose signature matches (DmContext, YourDatagramType) is invoked for datagrams of that type — no central switch statement needed:

using NTDLS.DatagramMessaging;

static void Main()
{
    // Create a messenger bound to listen port 1234.
    var messenger = new DmMessenger(1234);

    // Register a class containing convention-based handler methods.
    messenger.AddHandler(new HandlePackets());

    Console.WriteLine("Press enter to quit.");
    Console.ReadLine();
}

// Each DatagramHandler overload is routed to by the datagram type of its second parameter.
private class HandlePackets : IDmDatagramHandler
{
    public static void DatagramHandler(DmContext context, MyFirstUDPPacket datagram)
    {
        // Echo the datagram back to the sender.
        context.Dispatch(datagram);
        Console.WriteLine($"{datagram.Message}->{datagram.UID}->{datagram.TimeStamp}");
    }
}

UDP client

A messenger created without a listen port operates in write-only mode. Resolve a DmContext for the remote endpoint once, then dispatch datagrams to it:

using NTDLS.DatagramMessaging;

static void Main()
{
    // No listen port: this messenger only sends.
    using var messenger = new DmMessenger();

    int packetNumber = 0;

    // Get (or create) the context for the remote endpoint.
    var endpointCtx = messenger.GetEndpointContext("127.0.0.1", 1234);

    while (true)
    {
        // Send a serialized datagram to the remote endpoint.
        messenger.Dispatch(new MyFirstUDPPacket($"Packet#{packetNumber++}"), endpointCtx);
        Thread.Sleep(10);
    }
}

Sending raw bytes

Raw byte[] payloads skip serialization entirely and arrive at the receiver wrapped in a DmBytesDatagram:

// Dispatch raw bytes to the remote endpoint.
var randomBytes = new byte[100];
Random.Shared.NextBytes(randomBytes);
messenger.Dispatch(randomBytes, endpointCtx);
// On the receiving end, raw bytes arrive as a DmBytesDatagram.
if (datagram is DmBytesDatagram bytes)
{
    Console.WriteLine($"Received {bytes.Bytes.Length} bytes.");
}

Compression and encryption

Both sides of the connection must agree on the same providers. The built-in options are Brotli or Deflate compression and AES encryption (keyed from a password or a raw key):

using NTDLS.DatagramMessaging;

var messenger = new DmMessenger(1234);

// Compress payloads with Brotli before they go on the wire.
messenger.SetCompressionProvider(new DmBrotliCompressionProvider());

// Encrypt payloads with AES, deriving the key from a password (AES-128 by default).
messenger.SetCryptographyProvider(new DmAesCryptographyProvider("This is my password"));

Providers can also be swapped per peer via context.SetCompressionProvider(...), context.SetCryptographyProvider(...) and context.SetSerializationProvider(...).

License

MIT

An unhandled error has occurred. Reload 🗙