Guides

Networking for game servers and realtime systems in C#: TCP, UDP or WebSocket

A vendor-neutral guide to C# socket programming for game servers and realtime systems: TCP vs UDP vs WebSocket, binary serialization, framing, low-allocation techniques in .NET, tick rate, encryption, load testing and choosing a library.

Should a game server use TCP, UDP or WebSocket?

If data is time-sensitive and only the latest value matters (character positions, inputs), UDP with a reliability layer you build or get from a library is the usual choice for action games. If every message must arrive complete and in order (chat, transactions, admin commands), TCP is simple and good enough. If the client runs in a browser, WebSocket (which runs over TCP) is the practical option.

Glenn Fiedler, author of the Gaffer On Games series, explains why: TCP delivers data reliably and in order, so when a packet is lost, newer packets are held in a queue while the old one is resent. For first-person shooters that produces hitches in multiples of round-trip time, which is why he says virtually all FPS games are networked using UDP.

TCP vs UDP vs WebSocket compared

Fiedler advises against mixing TCP and UDP in one game protocol, since you lose control and must handle NAT twice; instead, implement the TCP features you actually need on top of UDP. He also notes that UDP has no congestion avoidance: sending too much can make routers buffer packets and induce very large latency, so a UDP-based protocol must throttle itself.

TCP, UDP and WebSocket for realtime systems
CriterionTCPUDPWebSocket
ReliabilityYes, retransmits lost packetsNo; build it in the application if neededYes (runs over TCP)
OrderingPreservedNot preservedPreserved
Head-of-line blockingYes: a lost packet stalls later onesNot at the protocol levelYes, inherited from TCP
Data modelByte stream; the app splits messagesDiscrete datagramsFramed messages (text/binary)
Congestion controlBuilt inNone; the app must handle itYes (from TCP)
NAT / firewallOutbound connections usually passMay need NAT handling; some networks block itUses HTTP/HTTPS ports, passes proxies more easily
Browser supportNo raw socketsNo raw socketsYes

What WebSocket does and does not do

Per RFC 6455, WebSocket consists of an opening handshake followed by basic message framing, layered over TCP. It is designed around minimal framing: framing exists only to turn a stream into messages and to distinguish text from binary; other metadata is layered on top by the application. All frames sent from client to server must be masked. Because it runs over TCP, WebSocket still suffers head-of-line blocking under packet loss.

Serialization: binary or JSON?

JSON is readable, easy to debug and available everywhere, but it costs more bandwidth and parsing CPU than binary formats. For systems sending dozens of packets per second per player, packet size and parse cost add up quickly.

Microsoft's documentation says SignalR supports two hub protocols: text-based JSON (the default) and binary MessagePack, with MessagePack generally creating smaller messages than JSON. gRPC uses Protobuf, an efficient binary format; in exchange, Microsoft notes that Protobuf messages are not human-readable and need extra tooling to inspect. MagicOnion uses MessagePack to serialize arguments and return values.

Framing: turning a TCP stream into messages

TCP is a byte stream: a single read can return half a message or several messages stuck together. The application must define boundaries, most commonly with a length prefix (a few bytes giving the payload length) or a delimiter. Microsoft's System.IO.Pipelines article illustrates exactly this with a TCP server receiving newline-delimited messages, and shows how hand-written stream reading quickly becomes complex once partial data has to be handled.

With UDP each datagram is already a unit, but payloads larger than the MTU must be fragmented and reassembled at the application layer; Gaffer On Games has a series on packet fragmentation and reassembly. Whatever the protocol, set a maximum length: a peer can announce a large length without sending the body, and an unbounded parser will hold memory indefinitely.

Reducing allocations in .NET

In realtime systems, constant allocation increases garbage collector pressure and can cause pauses that raise tail latency. .NET ships several tools to cut allocations on the hot path.

System.IO.Pipelines

According to Microsoft Learn, System.IO.Pipelines is a library designed to make high-performance I/O easier, and in modern .NET versions it is included in the shared framework. The .NET Blog explains that Pipelines grew out of the work on Kestrel; in the Pipelines example no buffers are explicitly allocated because buffer management is delegated to PipeReader/PipeWriter. Pipe also has pause thresholds for flow control when the reader lags the writer, and separating parsing from networking code makes it easy to unit test against in-memory buffers.

  • Always call PipeReader.AdvanceTo after each ReadAsync.
  • Always complete both the PipeReader and the PipeWriter.
  • Give each PipeReader/PipeWriter a single owning context; they are not thread-safe.

SocketAsyncEventArgs

Microsoft Learn describes SocketAsyncEventArgs as an alternative asynchronous pattern for specialized high-performance socket applications. Its main feature is avoiding repeated allocation and synchronization of objects during high-volume async socket I/O: operations are described by reusable SocketAsyncEventArgs objects allocated and maintained by the application, typically combined with a buffer pool. The trade-off is a more complex API than ordinary async/await; use it only when measurements justify it.

Span<T>, Memory<T> and ArrayPool<T>

Span<T> and Memory<T> let you work over existing memory without copying. Microsoft has a dedicated usage-guidelines page for these types, especially around buffer ownership and lifetime. ArrayPool<T> lets you rent and return arrays instead of allocating new ones; the .NET Blog post on Pipelines also mentions ArrayPool<T> and MemoryPool<T> as foundational types. Common bugs are returning a buffer to the pool while an async operation still uses it, or sending the whole rented array (which may be larger than requested) instead of only the valid data.

Tick rate, snapshots and state synchronization

According to Valve's Source Multiplayer Networking documentation, the server simulates the game in discrete time steps called ticks; by default the timestep is 15 ms, about 66.67 ticks per second, and during each tick the server processes user commands, runs a physics step, checks game rules and updates object states. After each tick it decides whether clients need a world update and takes a snapshot when necessary. A higher tickrate increases simulation precision but requires more CPU and bandwidth on both server and client.

Clients use prediction (simulating the result of their own input) and interpolation (smoothing between snapshots). Valve states the default interpolation delay is 0.1 seconds, and the snapshot rate is limited by the server tickrate. The server also uses lag compensation, and Valve stresses that a game server cannot trust clients on important decisions such as hits, because packets can be modified in transit.

Encryption: TLS or application-level AEAD?

For TCP and WebSocket the standard approach is TLS (wss:// for WebSocket). TLS is widely supported, available in .NET through SslStream and Kestrel, and relies on familiar certificate infrastructure. For UDP, TLS does not apply directly; options are DTLS or an application-level security layer.

Application-level encryption typically means a key exchange (for example X25519) followed by authenticated encryption of each packet with an AEAD cipher (for example ChaCha20-Poly1305 or AES-GCM). This gives fine-grained control but moves all responsibility for cryptographic correctness (unique nonces, replay protection, server authentication) onto your team. The general rule: use TLS where you can, and build your own only with a clear reason and someone qualified to review it.

Load testing pitfalls

  • Loopback (127.0.0.1) benchmarks do not reflect real networks: there is no packet loss, jitter, bandwidth limit or round-trip latency. Treat loopback numbers as a bound on software cost, not as user-perceived latency.
  • Simulate bad networks: add latency, jitter and loss (tools such as tc/netem on Linux, or the packet-loss simulation built into some libraries such as LiteNetLib).
  • Measure percentiles (p50, p99, p99.9), not just averages; players feel the hitches in the tail.
  • Measure allocations and GC counts alongside latency.
  • Run load generators on separate machines, or the simulated clients will compete for the server's CPU.
  • Test concurrent connection counts, not only messages per second.
  • Compare libraries with the same payload, hardware and network conditions; numbers published by a library's own authors should be re-verified in your environment.

Choosing a library: SignalR, gRPC, MagicOnion, LiteNetLib, Nalix

The table below summarizes each project's own documentation. It is not a ranking; each library solves a different problem.

A neutral comparison of some .NET networking libraries
LibraryTransportGood fitNotes
ASP.NET Core SignalRWebSockets (preferred), Server-Sent Events, Long PollingRealtime web apps, broadcast to many clients, transport fallback; JSON and MessagePackMicrosoft recommends SignalR over raw WebSockets for most apps; runs over TCP
gRPC (.NET)HTTP/2Service-to-service calls, .proto contracts, streamingBrowsers cannot call it directly (needs gRPC-Web); no built-in broadcast concept
MagicOniongRPC (HTTP/2)C# interfaces as schema, StreamingHub for realtime, Unity client supportRuns over HTTP/2 (TCP); uses MessagePack instead of Protobuf
LiteNetLibReliable UDPGames needing UDP with several delivery methods (e.g. ReliableOrdered), Unity supportYou still design the game-level protocol
NalixTCP, UDP, WebSocketGame and realtime servers on .NET 10, binary packets defined as C# classes, X25519 + ChaCha20-Poly1305 securityOnly .NET 10; no Unity; the author's own open-source project

Disclosure about Nalix

Nalix is an open-source project (Apache-2.0) developed by Phan Phúc Nguyên, the author of this article. Per its README, Nalix is a low-latency binary networking framework for .NET game servers and realtime systems, Native AOT compatible, with a .NET client SDK and Blazor WebAssembly support via WebSocket. The README publishes a self-measured loopback benchmark against SignalR, gRPC and MagicOnion; as noted in the testing section, those are the project's own loopback numbers and should be verified in real conditions. The README also includes a “When NOT to use Nalix” section. For details see Nalix: low-latency networking for .NET.

Checklist for starting a realtime project in C#

If your backend also involves AI, see C# and .NET for AI backends. EYC builds software and systems on .NET; for our services see software and SaaS and applied AI R&D.

  • Classify data: which needs reliable, ordered delivery and which only needs the latest value.
  • Identify client platforms: browser, Unity, .NET apps, IoT devices.
  • Choose transport and library based on those two factors, not benchmarks.
  • Design a versioned message format with size limits and clear framing.
  • Default to an authoritative server and validate every client input.
  • Encrypt connections from day one (TLS/wss or a reviewed AEAD layer).
  • Load test with simulated packet loss, measuring p99 and allocations.
  • Plan horizontal scaling: WebSocket/SignalR need a fan-out mechanism across nodes.

Frequently asked questions

What is the difference between TCP and UDP for a game server?

TCP provides reliable, ordered delivery, but a lost packet stalls later ones while it is resent. UDP does not resend lost packets or keep order, but lets you use the newest data immediately, so action games usually use UDP with a custom reliability layer.

Can browser games use UDP?

Browsers do not allow raw UDP or TCP sockets; the common choice is WebSocket, which runs over TCP. SignalR can use WebSockets and fall back to other transports when needed.

Should I use SignalR or raw WebSockets in ASP.NET Core?

Microsoft recommends SignalR for most apps because it provides transport fallback, an RPC model and, in most scenarios, no significant performance disadvantage versus raw WebSockets. Raw WebSockets suit cases where you need full protocol control.

What is System.IO.Pipelines for?

It is a .NET library that makes high-performance streaming parsers easier to write, manages buffers for you, supports flow control and is included in the shared framework of modern .NET. Kestrel is built on it.

What is tick rate in a game server?

The number of simulation steps the server runs per second. Valve's documentation describes a default 15 ms tick (about 66.67 ticks per second); higher rates improve precision but cost more CPU and bandwidth.

Share article:
Key point

No single protocol or library fits every realtime system. Understanding how TCP, UDP and WebSocket handle packet loss, designing a protocol with framing and versioning, cutting allocations with the tools .NET already provides, and testing under realistic network conditions will guide you better than any benchmark table.

Sources

  1. UDP vs. TCP — Gaffer On Games (Glenn Fiedler)
  2. Reliability and Congestion Avoidance over UDP — Gaffer On Games (Glenn Fiedler)
  3. Client Server Connection — Gaffer On Games (Glenn Fiedler)
  4. Source Multiplayer Networking — Valve Developer Community
  5. RFC 6455 – The WebSocket Protocol — IETF
  6. System.IO.Pipelines in .NET — Microsoft Learn
  7. System.IO.Pipelines: High performance IO in .NET — .NET Blog (Microsoft)
  8. SocketAsyncEventArgs Class — Microsoft Learn
  9. Memory<T> and Span<T> usage guidelines — Microsoft Learn
  10. Overview of ASP.NET Core SignalR — Microsoft Learn
  11. WebSockets support in ASP.NET Core — Microsoft Learn
  12. Use gRPC in browser apps — Microsoft Learn
  13. Compare gRPC services with HTTP APIs — Microsoft Learn
  14. MagicOnion – Unified Realtime/API framework for .NET platform and Unity — Cysharp (GitHub)
  15. LiteNetLib – Lite reliable UDP library for .NET — RevenantX (GitHub)
  16. Nalix – low-latency binary networking framework for .NET — ppn-systems (GitHub)