A game network developer knows about TCP head-of-line-blocking(HOL) and uses a solution such a NetCode IO (C++, with Go, Rust, C# bindings), RakNet (C++), Steam Network Socket (C++), LiteNetLib (C#), ENet (`C`), KCP (‘C’) to get reliability via UDP. Is QUIC a new alternative that can be added to this list, and perhaps replace current reliable UDP implementations?
This article is divided into several sections:
- Requirements Game Network Protocol
- QUIC introduction (especially the aspects useful for game networking)
- QUIC application to game networking
1. Requirements Game Network Protocol
A good game network protocol consists of several components and properties. Depending on the type of game and data being transmitted, different requirements are required. A chess game could perfectly use TCP/HTTP for all network operations while an FPS shooter has to deal directly with HOL when operating over TCP. If the FPS shooter is completely deterministic all client input can be sent unreliably with some sequencing whilst it also happens that during the game, some data might be sent reliably. This shows that a game developer needs different reliability options. It is possible to open a TCP stream and use UDP datagrams, however, most people prefer a single generalized protocol that does everything without worrying too much about implementation details.
Those are the main parts of a semi-reliable UDP-based gaming protocol:
- Acknowledgment System: Tracks which packets have arrived at the other endpoint.
- Connection Management System: Introduces some kind of connection on top of UDP, it contains connection trust, connection handshaking, connection maintenance.
- Encryption System: Encrypts all outgoing, and decrypts all incoming packets.
- Packet Serialisation System: Serializes a given packet and injects a header with all required information to transmit the packet according to the desired delivery guarantees.
- Packet Compression System: Compresses packets if this makes transmission faster.
- Packet Batch System: Baches packets together up to a certain threshold that is based upon congestion control.
- Congestion control System: Manages the rate of outgoing packets based upon the current network conditions.
- Packet Fragmentation System: Splits packets up to stay below the max MTU, and recombines them at the other endpoint.
- Optional reliability and unreliability: This is an essential feature, as some packets are quired to be very reliable, and others very unreliable.
- Optional ordering and sequencing: This is an essential feature, as some packets are required to arrive in order, and others in sequence.
Most libraries provide these capabilities in different ways and use a sliding confirmation window with a bit field of the 32 last acknowledged packets.
QUIC is a general-purpose network protocol built on top of UDP. The protocol is still under development and standardized by the IETF. It replaces TCP in the HTTP/3 IE-draft, which all major browsers and 22% of the top 10 million websites, support. While QUIC’s intentions are originally web-oriented, it is being standardized as a general-purpose transport protocol.
QUIC is a stream-based protocol and has an unreliable extension for sending datagrams. QUIC’s stream multiplexing feature allows applications to run multiple streams over a single connection without the HOL problem. This is why QUIC will replace TCP in HTTP/3.
A single stream is reliable and ordered. Although there is no guarantee of transmission, reception, or delivery order across streams. To prevent any HOL, data that can be sent independently should be transmitted over separate streams. The mapping of application data to streams is application-specific which gives great control over sending data. This allows for optimization for various contexts such as web data, game data, and video data.
Initial limits are advertised using the `initial_max_streams_bidi` and
`initial_max_streams_uni` transport parameters. Once stream limits are reached, no more streams can be opened, which prevents applications using QUIC from making further progress. The hard limit is 2⁶⁴ streams which is very unlikely to reach in the context of one connection.
IETF specifies a QUIC extension to enable sending and receiving unreliable datagrams over QUIC. The implementation does not provide sending barebones UDP packets but instead includes congestion control, and security by sharing the cryptographic and authentication context used for reliable streams. The datagrams will not be flow-controlled, and as such data chunks, they may be dropped if the receiver is overloaded. Also, DATAGRAM frames are not fragmented by the QUIC implementation; therefore, application protocols need to handle cases where the maximum datagram size is limited by other factors. Lastly, the datagrams don't belong to specific steam but belong to a connection.
3. QUIC application to game networking.
QUIC unreliable extention can be useful for optimizing audio/video streaming
applications, gaming applications, and other real-time network
applications. [draft 06]
3.1 Known Resources about Game Networking
IT-HARE (author of multiplayer networking) briefly wrote that QUIC is a “possible” application and that readers should experiment with it. However, he has no further details. Furthermore, there are a number of toxic Reddit posts where protocol warfare is being waged and not really going into detail about the elements. When I was an active developer for Laminar, there was a conversation with the author Ralith about using QUINN — Rust QUIC implementation — for the implementation of Lamiar. Some interesting remarks were made and I will use some of his insights to further develop my article. He calls QUIC “a natural fit for game network programming”. Next to that, there have been some discussions over at the Github of the steam network protocol related to the question of QUIC its role in-game development. They reasoned that QUIC wasn’t invented yet when they initially build that protocol and that if it was there they might have used it.
3.2 Reliability and Ordering Control
The most important aspect of a semi-reliable UPD protocol is having configurable reliability and ordering of packets. Ralith indicated that this was possible through the following ways:
- Reliable + Ordered: Data that is reliable and order is sent on a single stream and is not multiplexed.
- Reliable + Unordered: Data that is reliable and unorder is sent on a single stream per packet. By creating a new stream the ordering aspect is broken but the reliability is maintained. “Streams are free and instantaneous to construct so long as you do not exceed the peer’s flow control limits and have compact framing, so even large numbers of streams sending small messages will work well.”
- Reliable + Sequenced: Same as Reliable + Unordered, and in addition, send an incremental sequence id with each datagram, and only process packets, at the application layer, if they have a higher sequence number than the highest received sequence number.
- Unreliable + Sequenced: Use the unreliable extension, send an incremental sequence id with each datagram, and only process packets, at the application layer, if they have a higher sequence number than the highest received sequence number.
- Unreliable + Unordered:
- Create a new stream for every message, and reset them when they are no longer relevant. For example, a game server could reset outgoing streams containing world state updates for an old timestep after it has transmitted data for a newer one, either immediately or after it has been acknowledged by the receiver. Alternatively, you could reset outgoing streams on a timer. A QUIC implementation could also support implicitly resetting streams after transmitting their data a single time without requiring any changes to the protocol, if necessary. Resetting a stream means telling the other side that retransmission will never be done for any packet. Therefore it is unreliable and unordered.
- Probably the better method is to use the unreliable QUIC extension and send datagrams.
All data sent over QUIC is secure. TLS provides two basic handshake modes of interest to QUIC. Namely, 0 and 1-RTT handshakes. As discussed earlier, datagrams are secured by sharing the cryptographic and authentication context that was initiated by a reliable stream. In the context of game networking DTLS is often used to secure UDP traffic. Security-wise QUIC is suitable.
3.4 Congestion Control
Congestion control is sharing a single context between both datagrams and streams. Therefore allowing a user to implement the way congestion control is handled.
QUIC can be used for any data that is unreliable and unordered in nature with the “unreliable extension”. On top of this extension, the programmer is able to build packet sequencing for both reliable and unreliable transport at the application layer. Reliable ordered data transfer is the default for QUIC, however reliable unordered should be simply implemented by using the stream only once. In addition to these transport capabilities, all data is secured by TLS 1.3, there is custom controllable congestion control, connection migration, and 0-RTT handshakes, etc... However, the user does need to implement fragmentation, packet batching, and flow control (when using the datagrams extension).
In theory, QUIC is suitable for role-play games, FPS games, web games, deterministic games, etc… One last thing, this doesn't mean any current RUDP protocols are obsolete, in fact, they served many triple-A games very well. Now QUIC is nearing completion and is already seeing a great adoption it is safe to say it is a great alternative to creating your own protocol. Are you going to use QUIC for game networking?