This blog is the third in a series about our upcoming networking technology known internally as UNET. We want UNET to be a system that all game developers can use to build multiplayer games for any type of game with any number of players as easily as possible. We’ll be launching UNET in the 5.x cycle.
Get a general overview of the UNET system here, or read on to learn about the UNET transport layer foundation!
When we started to design the new network library for Unity, we want to understand what an ideal library would look like. We realized that we have (roughly) two different types of users:
1. Users who want networking tools that will give them an out of the box result with minimal effort (ideally without any effort at all).
2. Users who develop network-centric games and want very flexible and powerful tools.
Based on these two user types, we divided our network library into two different parts: a HLAPI (high-level API) and a LLAPI (low-level API).
If you’re interested in learning more about the Syncvars we use in the high level API you can read more here. The following discussion relates to the low level API and our library design which was based on the following principles:
Performance, performance, performance...
The LLAPI is a thin layer on top of the UDP socket, most of the work is performed in a separate thread (hence LLAPI can be configured to use the main thread only). There’s no dynamic memory allocation and no heavy synchronization (actually most of the library uses memory barrier synchronization with some atomic increment/decrement operation).
If something can be done using C# it should be
We decided to only expose what we felt our users would need to use. Like BSD sockets, the LLAPI supports just one abstraction - exchanging raw binary messages. There are no tpc-like streams, serializers or RPC calls in the LLAPI; only low level messages.
Flexibility and configurability? Yes please...
If you take a look at TCP socket implementation you can find tons of parameters (timeouts, buffer length etc.) which you can change. We chose to to take a similar approach and to allow users to change almost all of our library parameters so they can tune it to their specific needs. Where we faced a choice between simplicity and flexibility we sacrificed simplicity to flexibility.
Nice and easy
We tried to design the LLAPI to resemble the BSD socket API wherever possible.
Network and transport layers
Logically, the UNET low level library is a network protocol stack built on top of the UDP, containing a “network” layer and a “transport” layer. The network layer is used for creating connections between peers, delivering packets and controlling possible flow and congestion. The transport layer works with “messages” belonging to different communication channels:
Channels serve two different purposes, they can separate messages logically and they provide different delivery grants or quality of service.
Channels configuration is a part of configuration procedure, something we’ll discuss in more detail in an upcoming post. For now, lets just consider the configuration part as “My system will contain up to 10 connections, each connection will have 5 channels, where channel 0 will have this type, and channel 1 will have other type and so on”. The last part of this sentence is defined by:
The second parameter is channel number and last is channel type, or channel qos (delivery grant).
UNET (so far) supports the following QOS:
If you have a use case you feel doesn’t fit conveniently in these categories, we’d love it if you could bring it up in the comments!
Let’s review typical LLAPI function calls:
1. Initialize library
2. Configure network: topology, channels amount, their types, different timeouts and buffer sizes (we discuss this in a later post)
3. Create socket:
This function will open the socket on port 5000 on all network interfaces, and will return an integer value as a descriptor of this socket
4. Make connection to other peer:
This function will send a connection request to “other peer” at address 127.0.0.1/6000. It will return an integer value as a descriptor of this connection for this host. You will receive the connection event when the connection is established or the disconnect event if the connection cannot be established
5. Send message:
This last function will send binary data contained in the buffer via socket described by hostId for peer-described connectionId using channel #1 (in our case this is “reliable channel”, so for this message delivery will be granted.
6. Receiving network events:
For receiving network events, we chose a poll model. User should poll UTransport.Receive() function to be acknowledged about network events. Note that this model is very similar to ordinary select() call with zero timeout. This function receives 4 different events:
7. Send disconnect request:
This function call will send a disconnect request to a connection defined by connectionId on host defined by hostId. The connection will close immediately and can be re-used in the future.
That’s it for now! Thanks for reading, and don’t forget to check back (or subscribe to our blog) to catch the next post in the UNET series in which we’ll be discussing network topology configuration.