Networking
Basics
Cloud
A Cloud
represents a pool of connections. It may contains:
- A Server (1 max).
_cloud.Server
- Many Clients. Connection to other servers.
- Unknown Peers. Clients on other machine connected to the cloud server.
- Networks. Just a way. It is used to sort all packets. So, a packet is identified by a Code and a Network ID.
Unlike UNet, Wonderland.Networking
Client/Server is TCP only. A Client/Server connection is a data stream with responses, dialogs, latency checks... However, there is an UDP implementation, more simple but using packet system (see next page).
Server
using EODE.Wonderland.Networking;
public int Port = 5882;
Cloud _cloud;
async void Start() {
_cloud = await Cloud.Create();
_cloud.CreatePeer(Port); // creates the server Peer
//_cloud.CreatePeer(-1, Port+1); // creates the web server Peer
//_cloud.CreatePeer(Port, Port+1); // native and web app (on port+1)
_cloud.OnUnknownPeerConnected += OnUnknownPeerConnected;
}
async void OnDestroy() {
await _cloud.Close();
// cloud is automatically closed after the garbage collection but object is not destroyed immediately in editor
}
void Update() {
if (_cloud == null) return;
_cloud.Update(); // do not forget this ! (current error)
}
void OnUnknownPeerConnected(Peer peer, Packet packet) {
var password = packet.Read<string>(); // using a password to be sure it is not a bot or other random ping
if (password != "123456") {
peer.Reject(); // peer unauthorized
return;
}
// Init handlers
peer.AddHandler(1, _cloud.DefaultNetwork, p => {
Debug.Log("Message from Peer#"+peer.ID+": "+p.Read<string>());
});
peer.Accept(); // peer is connected and can send packets
}
// send to all clients
public void Send(string msg) {
_cloud.Server.Send(Packet.Create(1, msg), _cloud.DefaultNetwork).WrapErrors();
}
Client
using EODE.Wonderland.Networking;
public string Address = "localhost";
public int Port = 5882;
// example with web build
/* #if UNITY_WEBGL && !UNITY_EDITOR
public int Port = 5883;
#else
public int Port = 5882;
#endif */
Cloud _cloud;
Peer _peer;
async void Start() {
_cloud = await Cloud.Create();
_peer = await _cloud.CreatePeer(Address, Port, "123456");
if (_peer.IsConnected) {
// init handlers
_peer.AddHandler(1, _cloud.DefaultNetwork, p => {
Debug.Log("Message from Server: "+p.Read<string>());
});
}
else {
Debug.LogError("[Client] HasError !");
}
}
public void Stop() {
if (_peer != null) _cloud.RemovePeer(_peer).WrapErrors();
}
async void OnDestroy() {
await _cloud.Close();
// cloud is automatically closed after the garbage collection but object is not destroyed immediately in editor
}
void Update() {
if (_cloud != null) _cloud.Update();
}
public void Send(string msg) {
_peer.Send(Packet.Create(1, msg), _cloud.DefaultNetwork).WrapErrors();
}
Structure
Networks
A Network
is just a way to send packets. Useful to organize your code.
For example, in SimpleNet, there is 1 network per scene instance. The default network is automatically created at cloud start.
_cloud.DefaultNetwork; // Network ID = 0
Networks management
// create
_cloud.CreateNetwork(5); // create a network with ID = 5
// remove
_cloud.RemoveNetwork(5);
_cloud.RemoveNetwork(myNetwork);
_cloud.RemoveAllNetworks(); // remove all except default !
// get
var myNetwork = _cloud.GetNetwork(5);
A
Network
is attached to itsCloud
. _cloud1.DefaultNetwork != _cloud2.DefaultNetwork
Capabilities
- Handle packets
- Send to a Peer (via this network)
- Send to all Peers (via this network)
- Retrieve stats
Packet Batching
Clent/Server connection is a stream, all packet are batched before send.
peer.Send(packet, network, float priority);
Here, priority is the max delay to send this packet.
You can use PacketsBatcher.Priority
peer.Send(packet, network, PacketsBatcher.Priority.fast);
All generic priorities:
- immediate: next frame
- fast: 0.05s
- medium: 0.2s (default value)
- slow: 0.5s
- batched: wait forever
Finaly, that is the structure of a batch. Sort by Network -> Sort by packet length (1 square = 1 byte)
Features
Connection
Above we see a simple connection with a password. The connection packet is a Packet.. So you can send what you want to check the peer trying to connect.
public class ConnectionInfo {
public string UserName;
public string Password;
public string ClientVersion;
}
_peer = await _cloud.CreatePeer(Address, Port, new ConnectionInfo() {
UserName = "Billy",
Password = "qwerty",
ClientVersion = Application.version
});
Responses
Wait response
var response = await peer.Send(packet, network);
if (response != null) {
response.Read<MyClass>();
}
Answer
_peer.AddHandler(code, network, p => {
return p.CreateResponse(value, value2, ...); // like Packet.Create without code
});
CreateResponse
create a response packet.
_peer.AddHandler(code, network, p => {
var res = p.CreateResponse(value);
res.Write(value2);
return res;
});
CreateResponse
is an alias of
var packet = Packet.Create(handledPacket.Code, value);
packet.Network = handledPacket.Network;
In a handler you can reply by returning a packet with the same code and the same network
Only 1 response per code in the same batch (for example if you call a send with response and normal priority each frame, response flow may be compromized)
Dialogs
You can create a chain of message between a client and a server like this example:
async void CreateServerDialog(Peer peer) {
using(var dialog = new Dialog(_cloud.DefaultNetwork, peer, packetCode)) {
dialog.OnError = err => Debug.LogError("[Server] Error : " + err);
var packet = await dialog.Listen();
do {
var msg = packet?.Read<string>();
Debug.Log("[Server] Client : " + msg);
if (msg == "Pong") dialog.Break();
else packet = await dialog.Send("Pong");
}
while (!dialog.Broken);
}
Debug.Log("[Server] Dialog stopped");
}
async void CreateClientDialog(Peer peer) {
using(var dialog = new Dialog(_cloud.DefaultNetwork, peer, packetCode)) {
dialog.OnError = err => Debug.LogError("[Client] Error : " + err);
int count = 10;
do {
var packet = await dialog.Send("Ping");
var msg = packet?.Read<string>();
Debug.Log("[Client] Server : " + msg);
}
while (--count > 0 && !dialog.Broken);
dialog.Send("Pong").WrapErrors();
}
Debug.Log("[Client] Dialog stopped");
}
Connection Errors
If you need more informations on a connection failure, use serverPeer.ConnectionException
server = await _cloud.CreatePeer(address, port, "password");
if (server.IsConnected) {
Debug.LogError("Connected");
}
else {
if (server.ConnectionException != null) {
Debug.LogError("Server is offline");
Debug.LogError(server.ConnectionException);
}
else {
Debug.LogError("Cannot connect to server"); // Peer Rejected
}
}
Delayed value
To up the accuracy of a value, you can pick the value at the batch's send.
peer.Send(() => Packet.Create(code, value), network);
The function will be called during the batch data creation.
The getter cannot be async
async Handlers
You can easily create it by adding the async
keywork.
_peer.AddHandler(code, network, async p => {
await new WaitForSecondsRealtime(1f);
return p.CreateResponse(value);
});
Stats
Stats on peers and networks are enabled in the build with one of these #define directives
:
- WONDERLAND_SOCKETSTATS
- UNITY_EDITOR
peer.SocketStats
_cloud.GetNetwork(1).SocketStats
Latency simulation
With one of these #define directives
:
- WONDERLAND_LATENCY_SIM
- UNITY_EDITOR
You can simulate a latency on a client peer
peer.SetLatencySimulation(0.04f, 0.06f); // latency is randomized between 40~60 ms
Implementation example
SimpleNet
is open source, the folder path is Wonderland/Net/SimpleNet
.