This guide walks you through using the Unity Transport package to create a simple client and server that use a remote function over a UDP connection to add two numbers with the following flow:
It shows how to use the Unity Transport API to:
The client-server workflow in this guide shows the subtle differences between using the NetworkDriver
for clients and servers.
A server is an endpoint that listens for incoming connection requests and sends and receives messages. This section shows creating a simple server with UTP 2.0.
Start by creating a C# script in the Unity Editor. Name the script ServerBehaviour.cs
.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ServerBehaviour : MonoBehaviour {
void Start () {
}
void Update () {
}
}
The com.unity.transport
package is a low-level API, and, as a result, there is a bit of boilerplate code you should set up. The necessity of the boilerplate code is due to an architecture design to ensure you always have full control.
note
As development on the com.unity.transport package evolves, Unity might create more abstractions to reduce your workload on a day-to-day basis.
In the ServerBehaviour.cs
script, clean up the dependencies and add the boilerplate code:
Filename: ServerBehaviour.cs
using UnityEngine;
using UnityEngine.Assertions;
using Unity.Collections;
using Unity.Networking.Transport;
...
The following code shows the necessary boilerplate code and creates empty bodies for the Start
, OnDestroy
, and Update
methods. It also declares a NetworkDriver
and creates a NativeList
to hold all connections between the client and server.
Filename: ServerBehaviour.cs
using ...
public class ServerBehaviour : MonoBehaviour {
public NetworkDriver m_Driver;
private NativeList<NetworkConnection> m_Connections;
void Start () {
}
void OnDestroy() {
}
void Update () {
}
}
Next, expand the Start
, OnDestroy
, and Update
methods.
Start
methodâ
First, define the logic in the MonoBehaviour.Start
method.
Filename: ServerBehaviour.cs
void Start ()
{
m_Driver = NetworkDriver.Create();
var endpoint = NetworkEndPoint.AnyIpv4;
endpoint.Port = 9000;
if (m_Driver.Bind(endpoint) != 0)
Debug.Log("Failed to bind to port 9000");
else
m_Driver.Listen();
m_Connections = new NativeList<NetworkConnection>(16, Allocator.Persistent);
}
The first line of code, m_Driver = NetworkDriver.Create()
, creates a NetworkDriver
instance without any parameters.
Next, m_Driver.Bind
binds the NetworkDriver
instance to a specific network address and port, and if that doesn't fail, it calls the Listen
method.
if (m_Driver.Bind(endpoint) != 0)
Debug.Log("Failed to bind to port 9000");
else
m_Driver.Listen();
info
The call to the Listen
method sets the NetworkDriver
to the Listen
state, which means the NetworkDriver
actively listens for incoming connections.
m_Connections
creates a NativeList
to hold all the connections.
m_Connections = new NativeList<NetworkConnection>(16, Allocator.Persistent);
OnDestroy
methodâ
You must dispose of both NetworkDriver
and NativeList
because they allocate unmanaged memory. To ensure proper disposal, call the Dispose
method when you no longer need them.
Add the following code to the OnDestroy method on MonoBehaviour
:
Filename: ServerBehaviour.cs
public void OnDestroy()
{
if (m_Driver.IsCreated)
{
m_Driver.Dispose();
m_Connections.Dispose();
}
}
The check for m_Driver.IsCreated
ensures you don't dispose of unallocated memory. For example, UTP doesnât allocate memory for disabled components.
Update
loopâ
The com.unity.transport
package uses the Unity C# Job System internally. As a result, the m_Driver
has a ScheduleUpdate
method call. Call the Complete
method on the returned JobHandle inside the Update
loop to ensure you know when to process updates.
void Update () {
m_Driver.ScheduleUpdate().Complete();
note
This example forces synchronization on the main thread to update and handle the data later in the MonoBehaviour::Update
call. The workflow Create a jobified client and server shows how to use the Transport package with the C# Job System.
After updating m_Driver
, the first thing you must do is handle the connections. Start by cleaning up any stale connections from the list before processing new ones. Cleaning up stale connections ensures you don't have any old connections lying around when you iterate through the list to check for new events.
The following code iterates through the connection list and removes any stale connections.
for (int i = m_Connections.Length - 1; i >= 0; i--)
{
if (!m_Connections[i].IsCreated)
{
m_Connections.RemoveAtSwapBack(i);
}
}
The following code adds a connection while there are new connections to accept.
NetworkConnection c;
while ((c = m_Driver.Accept()) != default(NetworkConnection))
{
m_Connections.Add(c);
Debug.Log("Accepted a connection");
}
You now have an up-to-date connection list and can start querying the driver for events that might have happened since the last update.
DataStreamReader stream;
for (int i = 0; i < m_Connections.Length; i++)
{
if (!m_Connections[i].IsCreated)
continue;
Begin by defining a DataStreamReader
, which youâll use to process received Data events. Next, loop through the connections.
Call PopEventForConnection
for each connection while unprocessed events exist.
NetworkEvent.Type cmd;
while ((cmd = m_Driver.PopEventForConnection(m_Connections[i], out stream)) != NetworkEvent.Type.Empty)
{
note
You can also use the NetworkEvent.Type PopEvent(out NetworkConnection con, out DataStreamReader slice)
method call, which returns the first available event, the NetworkConnection that it's for, and possibly a DataStreamReader
.
Now itâs time to process events. Start with the Data
event.
if (cmd == NetworkEvent.Type.Data)
{
Next, try to read a uint
from the stream and output the received data:
uint number = stream.ReadUInt();
Debug.Log("Got " + number + " from the Client adding + 2 to it.");
After outputting the received data, add the received numbers and send the sum back to the client. To send anything with the NetworkDriver
, you need an instance of a DataStreamWriter
. A DataStreamWriter
is a new type that comes with the com.unity.transport
package. You get a DataStreamWriter
when you start sending a message by calling BeginSend
.
After youâve written the sum of the two numbers to the stream, call the EndSend
method on the driver. Off it goes!
number +=2;
m_Driver.BeginSend(NetworkPipeline.Null, m_Connections[i], out var writer);
writer.WriteUInt(number);
m_Driver.EndSend(writer);
}
note
This example passes NetworkPipeline.Null
to the BeginSend
function to tell the driver to use the unreliable pipeline to send the data. However, itâs also possible to choose not to specify a pipeline.
You must handle the disconnect case. Handling the disconnect case is pretty straightforward: if you receive a disconnect message, reset that connection to a default(NetworkConnection)
. The next time the Update
loop runs, you should clean up the stale connections..
else if (cmd == NetworkEvent.Type.Disconnect)
{
Debug.Log("Client disconnected from server");
m_Connections[i] = default(NetworkConnection);
}
}
}
}
Youâve now created the server, and youâre ready to create a client. Hereâs a summary of the server logic:
Start
method logic, which includes declaring (and binding to) a NetworkDriver
and creating
a NativeList to hold the connections.OnDestroy
method logic, which includes disposing of the NetworkDriver
and the NativeList that holds the connections.Update
loop logic, which includes listening for connections, adding connections, processing data, handling disconnections, and cleaning up stale connections.See ServerBehaviour.cs
for the full source code.
This section shows creating a simple client with UTP 2.0. The client code looks pretty similar to the server code at first glance, but there are subtle differences. This part of the workflow covers the differences between them and not so much the similarities.
Start by creating a C# script in the Unity Editor. Name the script ClientBehaviour.cs
.
Similar to the server behavior, you still need to define a NetworkDriver
. However, the client only has one instead of a list of connections. Thereâs also a Done flag to indicate when the client is finished with the connection. You can also use the Done flag to exit if you have connection issues.
Filename: Assets\Scripts\ClientBehaviour.cs
using ...
public class ClientBehaviour : MonoBehaviour {
public NetworkDriver m_Driver;
public NetworkConnection m_Connection;
public bool Done;
void Start () { ... }
public void OnDestroy() { ... }
void Update() { ... }
}
Create and connect a clientâ
Start by creating a NetworkDriver
for the client and an address for the server.
void Start () {
m_Driver = NetworkDriver.Create();
m_Connection = default(NetworkConnection);
var endpoint = NetworkEndPoint.LoopbackIpv4;
endpoint.Port = 9000;
m_Connection = m_Driver.Connect(endpoint);
}
Then call the Connect
method on the NetworkDriver
you created.
Cleaning up the client is easier than cleaning up the server because you donât need a NativeList
to hold the connections. You only need to dispose of the NetworkDriver
.
public void OnDestroy()
{
m_Driver.Dispose();
}
Client Update
loopâ
You start the client Update loop the same way as the server: by calling m_Driver.ScheduleUpdate().Complete()
, then ensuring the connection succeeded.
void Update()
{
m_Driver.ScheduleUpdate().Complete();
if (!m_Connection.IsCreated)
{
if (!Done)
Debug.Log("Something went wrong during connect");
return;
}
The following code is similar to the server Update loop code. However, if you look closely, you can see the call to m_Driver.PopEventForConnection
is replaced with a call to m_Connection.PopEvent
. This is technically the same method; it just makes it clearer that you are handling a single connection.
DataStreamReader stream;
NetworkEvent.Type cmd;
while ((cmd = m_Connection.PopEvent(m_Driver, out stream)) != NetworkEvent.Type.Empty)
{
Now you encounter a new event you haven't seen yet: a NetworkEvent.Type.Connect
event. The NetworkEvent.Type.Connect
event tells you that you received a ConnectionAccept
message and are now connected to the remote peer.
note
In this case, the server listens on port 9000 on NetworkEndPoint.LoopbackIpv4
(more commonly known as 127.0.0.1).
if (cmd == NetworkEvent.Type.Connect)
{
Debug.Log("We are now connected to the server");
uint value = 1;
m_Driver.BeginSend(m_Connection, out var writer);
writer.WriteUInt(value);
m_Driver.EndSend(writer);
}
Upon establishing a connection between the client and the server, the client sends a number to the server (that the server increments by two). Using the BeginSend
/ EndSend
pattern with the DataStreamWriter
, set value to one, write it into the stream, and send it out on the network.
When the NetworkEvent
type is Data, read the value you received back from the server, then call the Disconnect
method.
note
The recommended best practice is to set NetworkConnection
to default(NetworkConnection)
to avoid stale references.
else if (cmd == NetworkEvent.Type.Data)
{
uint value = stream.ReadUInt();
Debug.Log("Got the value = " + value + " back from the server");
Done = true;
m_Connection.Disconnect(m_Driver);
m_Connection = default(NetworkConnection);
}
You must handle potential server disconnects:
else if (cmd == NetworkEvent.Type.Disconnect)
{
Debug.Log("Client got disconnected from server");
m_Connection = default(NetworkConnection);
}
}
}
Youâve now created the server and client, and youâre ready to put it all together. Hereâs a summary of the client logic:
Start
method logic, which includes declaring (and binding to) a NetworkDriver
.OnDestroy
method logic, which includes disposing of the NetworkDriver
.Update
loop logic, which includes connecting to the server, handling the Connect event, sending data, receiving data, and handling disconnections.See ClientBehaviour.cs
for the full source code.
If youâve been following along with the tutorial and using the sample code, you now have a functional server and client. This section shows how to put it together and test it in the Unity Editor.
Add a new empty GameObject to your Scene.
Add both the ServerBehaviour
and the ClientBehaviour
scripts to the GameObject.
Select Play to enter Play mode. You should see five log messages in the Console window:
WebSocketâSee Client and server over WebSocket.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4