Giraffe.Net
1.0.0
dotnet add package Giraffe.Net --version 1.0.0
NuGet\Install-Package Giraffe.Net -Version 1.0.0
<PackageReference Include="Giraffe.Net" Version="1.0.0" />
<PackageVersion Include="Giraffe.Net" Version="1.0.0" />
<PackageReference Include="Giraffe.Net" />
paket add Giraffe.Net --version 1.0.0
#r "nuget: Giraffe.Net, 1.0.0"
#:package Giraffe.Net@1.0.0
#addin nuget:?package=Giraffe.Net&version=1.0.0
#tool nuget:?package=Giraffe.Net&version=1.0.0
Giraffe.Net
A low-level .NET networking library that gives you fine-grained control over the bytes sent over the network. Built for applications where data usage must be minimal — staying under a data cap, running over metered connections, or operating on bandwidth-constrained hardware.
Features
- Compact 8-byte wire header — magic byte, version, 16-bit message type, 32-bit payload length. Minimal overhead on every message.
- Per-message protocol selection — choose TCP or UDP at the point of each
SendAsynccall. - Generic message types — define your own
enum : ushortfor message identifiers; the library enforces type safety without owning your enum. - Structured logging — accepts any
ILogger<T>compatible with Serilog, NLog, Microsoft Console, or any MEL provider. - Low-allocation receive path — uses
System.IO.Pipelinesfor TCP framing and reuses buffers across reads. - Zero serialization opinion — payloads are
ReadOnlyMemory<byte>; use MessagePack, Protobuf, System.Text.Json, or raw structs.
Installation
dotnet add package Giraffe.Net
Quick Start
1. Define your message types
// Must use ushort as the underlying type — the wire header is 16 bits.
public enum AppMessage : ushort
{
Login = 0,
Chat = 1,
Position = 2,
Ping = 3,
}
2. Start a server
using Giraffe;
using Giraffe.Messaging;
using Microsoft.Extensions.Logging;
var loggerFactory = LoggerFactory.Create(b => b.AddConsole());
var server = new GiraffeServer<AppMessage>(
new GiraffeOptions { TcpPort = 7777, UdpPort = 7778 },
loggerFactory.CreateLogger<GiraffeServer<AppMessage>>());
server.ClientConnected += id => Console.WriteLine($"Client connected: {id}");
server.ClientDisconnected += id => Console.WriteLine($"Client disconnected: {id}");
server.MessageReceived += async (connectionId, message, ct) =>
{
Console.WriteLine($"[{message.MessageType}] from {connectionId}: {message.Payload.Length} bytes");
// Echo back over TCP
await server.SendToAsync(connectionId, new GiraffeMessage<AppMessage>
{
MessageType = AppMessage.Chat,
Payload = message.Payload,
}, TransportMode.Tcp, ct);
};
await server.StartAsync();
Console.WriteLine("Server running. Press Enter to stop.");
Console.ReadLine();
await server.StopAsync();
3. Connect a client
var client = new GiraffeClient<AppMessage>(
new GiraffeOptions(),
loggerFactory.CreateLogger<GiraffeClient<AppMessage>>());
client.MessageReceived += async (message, ct) =>
{
Console.WriteLine($"Received [{message.MessageType}]: {message.Payload.Length} bytes");
return; // suppress CS1998
};
await client.ConnectAsync("localhost", tcpPort: 7777, udpPort: 7778);
// Send a reliable message over TCP
await client.SendAsync(new GiraffeMessage<AppMessage>
{
MessageType = AppMessage.Chat,
Payload = System.Text.Encoding.UTF8.GetBytes("Hello, Giraffe!"),
}, TransportMode.Tcp);
// Send a low-overhead position update over UDP
await client.SendAsync(new GiraffeMessage<AppMessage>
{
MessageType = AppMessage.Position,
Payload = new byte[] { 0x01, 0x02, 0x03, 0x04 }, // your encoded position
}, TransportMode.Udp);
await client.DisconnectAsync();
Configuration
All options are set through GiraffeOptions at construction time.
| Property | Default | Description |
|---|---|---|
TcpPort |
9000 |
Port the server listens on for TCP connections |
UdpPort |
9001 |
Port the server listens on for UDP datagrams |
MaxUdpPayloadBytes |
508 |
Maximum UDP payload in bytes. 508 avoids IP fragmentation over most networks. Raise to 1472 for controlled LAN environments. |
MaxTcpPayloadBytes |
10485760 (10 MB) |
Maximum TCP payload the server will accept from a single frame. Protects against malicious peers claiming huge allocations. |
TcpBacklog |
100 |
TCP listener backlog (pending connection queue depth) |
ConnectTimeout |
10s |
How long ConnectAsync waits before timing out |
ReuseAddress |
true |
Sets SO_REUSEADDR on both sockets |
TCP vs UDP — Choosing the Right Transport
| TCP | UDP | |
|---|---|---|
| Delivery guarantee | Yes — retransmits lost packets | No — packets may be silently dropped |
| Ordering guarantee | Yes — arrives in send order | No — packets may arrive out of order |
| Overhead | ~20 bytes IP + ~20 bytes TCP header | ~20 bytes IP + ~8 bytes UDP header |
| Use for | Login, chat, game state, commands | Position updates, sensor readings, real-time telemetry |
Rule of thumb: use TCP for anything where missing a message would corrupt application state. Use UDP for high-frequency data where a dropped update is acceptable because a newer one is coming.
Wire Protocol
Every message — TCP or UDP — is prefixed with an 8-byte header:
Offset Size Field Description
------ ---- ------------- -----------------------------------------
0 1 Magic 0x47 ('G') — identifies a Giraffe frame
1 1 Version 0x01 — protocol version
2 2 MessageType ushort, big-endian — your enum value
4 4 PayloadLength uint, big-endian — payload byte count
--- payload follows ---
TCP frames are length-prefixed streams. UDP frames are one datagram per message.
Logging
Giraffe uses Microsoft.Extensions.Logging abstractions. Pass any compatible ILogger<T> at construction:
// Serilog
Log.Logger = new LoggerConfiguration().WriteTo.Console().CreateLogger();
var loggerFactory = new SerilogLoggerFactory();
// NLog, Microsoft Console, etc. — same pattern
Known Limitations (v1)
- Server cannot send UDP to a specific client. UDP is connectionless; the server has no reliable way to map a connection ID to a client UDP endpoint without a handshake (planned for v2). Calling
server.SendToAsync(..., TransportMode.Udp)throwsNotSupportedException. - No TLS/encryption. All traffic is plaintext. For production use over the public internet, run Giraffe inside a VPN or WireGuard tunnel. TLS support is planned for v2.
- No UDP retry or sequencing. The library does not add acknowledgement or ordering on top of UDP. Implement these at the application layer if needed.
- UDP behind NAT. Packets from two clients behind the same NAT may appear to come from the same endpoint. This affects server-side UDP source identification.
Requirements
- .NET 10.0 or later
- The
TMessageTypeenum must useushortas its underlying type:enum MyMessages : ushort { ... }
License
MIT — see LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.0
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.8)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 40 | 6/3/2026 |