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.
| Criterion | TCP | UDP | WebSocket |
|---|---|---|---|
| Reliability | Yes, retransmits lost packets | No; build it in the application if needed | Yes (runs over TCP) |
| Ordering | Preserved | Not preserved | Preserved |
| Head-of-line blocking | Yes: a lost packet stalls later ones | Not at the protocol level | Yes, inherited from TCP |
| Data model | Byte stream; the app splits messages | Discrete datagrams | Framed messages (text/binary) |
| Congestion control | Built in | None; the app must handle it | Yes (from TCP) |
| NAT / firewall | Outbound connections usually pass | May need NAT handling; some networks block it | Uses HTTP/HTTPS ports, passes proxies more easily |
| Browser support | No raw sockets | No raw sockets | Yes |
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.
| Library | Transport | Good fit | Notes |
|---|---|---|---|
| ASP.NET Core SignalR | WebSockets (preferred), Server-Sent Events, Long Polling | Realtime web apps, broadcast to many clients, transport fallback; JSON and MessagePack | Microsoft recommends SignalR over raw WebSockets for most apps; runs over TCP |
| gRPC (.NET) | HTTP/2 | Service-to-service calls, .proto contracts, streaming | Browsers cannot call it directly (needs gRPC-Web); no built-in broadcast concept |
| MagicOnion | gRPC (HTTP/2) | C# interfaces as schema, StreamingHub for realtime, Unity client support | Runs over HTTP/2 (TCP); uses MessagePack instead of Protobuf |
| LiteNetLib | Reliable UDP | Games needing UDP with several delivery methods (e.g. ReliableOrdered), Unity support | You still design the game-level protocol |
| Nalix | TCP, UDP, WebSocket | Game and realtime servers on .NET 10, binary packets defined as C# classes, X25519 + ChaCha20-Poly1305 security | Only .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.