Checking out Unity’s NetworkTransport

I wrote up some code to help me understand Unity’s Transport Layer API. I created a “client” script and a “server” script. I then attached these two scripts to a Main Camera and hit Play. If you want, you can attach more than one “client” script.

llapichat

When the client connects, I can choose how big of a message to send. When the server receives a message, it then echoes it back to everyone who’s connected.

When you call AddHost, I think it’s spawning a thread that then listens to and sends on the port.

Here is my “ClientUI.cs”:

using UnityEngine;
using UnityEngine.Networking;

public class ClientUI : MonoBehaviour
{
    public void Start()
    {
        NetworkTransport.Init();

        ConnectionConfig config = new ConnectionConfig();
        reliableChannel = config.AddChannel(QosType.Reliable);
        HostTopology topology = new HostTopology(config, 1); // Only connect once
        // Do not put port since we are a client and want to take any port available to us
#if UNITY_EDITOR
        m_hostId = NetworkTransport.AddHostWithSimulator(topology, 200, 400);
#else
        m_hostId = NetworkTransport.AddHost(topology);
#endif
    }

    Rect windowRect = new Rect(500, 20, 100, 50);
    string ipField = System.Net.IPAddress.Loopback.ToString();
    string portField = "25000";
    byte reliableChannel;
    int m_hostId = -1;
    int m_connectionId;
    Vector2 scrollPos;
    string sizeField = "1000";
    string receiveLabel;
    public void OnGUI()
    {
        windowRect = GUILayout.Window(GetInstanceID(), windowRect, MyWindow, "Client Window");
    }

    void MyWindow(int id)
    {
        GUILayout.BeginHorizontal();
        GUILayout.Label("IP");
        ipField = GUILayout.TextField(ipField);
        GUILayout.Label("Port");
        portField = GUILayout.TextField(portField);
        if (GUILayout.Button("Connect"))
        {
            byte error;
            int connectionId;
            connectionId = NetworkTransport.Connect(m_hostId, ipField, int.Parse(portField), 0, out error);
            if (connectionId != 0) // Could go over total connect count
                m_connectionId = connectionId;
        }
        if (GUILayout.Button("Disconnect"))
        {
            byte error;
            bool ret = NetworkTransport.Disconnect(m_hostId, m_connectionId, out error);
            print("Disconnect " + ret + " error " + error);
        }
        GUILayout.FlexibleSpace();
        GUILayout.EndHorizontal();

        GUILayout.BeginHorizontal();
        GUILayout.Label("Size");
        sizeField = GUILayout.TextField(sizeField);
        if (GUILayout.Button("Send"))
        {
            byte error;
            byte[] buffer = new byte[int.Parse(sizeField)];
            // Just send junk
            bool ret = NetworkTransport.Send(m_hostId, m_connectionId, reliableChannel, buffer, buffer.Length, out error);
            print("Send " + ret + " error " + error);
        }
        GUILayout.FlexibleSpace();
        GUILayout.EndHorizontal();

        scrollPos = GUILayout.BeginScrollView(scrollPos, GUILayout.Height(200.0f), GUILayout.Width(400.0f));
        GUILayout.Label(receiveLabel);
        GUILayout.EndScrollView();

        GUILayout.BeginHorizontal();
        if (GUILayout.Button("Clear"))
        {
            receiveLabel = "";
        }
        GUILayout.FlexibleSpace();
        GUILayout.EndHorizontal();

        GUI.DragWindow();
    }

    public void Update()
    {
        if (m_hostId == -1)
            return;
        int connectionId;
        int channelId;
        byte[] buffer = new byte[1500];
        int receivedSize;
        byte error;
        NetworkEventType networkEvent = NetworkTransport.ReceiveFromHost(m_hostId, out connectionId, out channelId, buffer, 1500, out receivedSize, out error);
        if (networkEvent == NetworkEventType.Nothing)
            return;
        receiveLabel += string.Format("{0} connectionId {1} channelId {2} receivedSize {3}\n", networkEvent.ToString(), connectionId, channelId, receivedSize);
    }
}

My “ServerUI.cs”:

using UnityEngine;
using UnityEngine.Networking;
using System.Collections.Generic;
using System.Net;

public class ServerUI : MonoBehaviour
{
    public void Start()
    {
        NetworkTransport.Init();

        ConnectionConfig config = new ConnectionConfig();
        reliableChannel = config.AddChannel(QosType.Reliable);
        HostTopology topology = new HostTopology(config, 5); // Allow five connections
#if UNITY_EDITOR
        m_hostId = NetworkTransport.AddHostWithSimulator(topology, 200, 400, 25000);
#else
        m_hostId = NetworkTransport.AddHost(topology, 25000);
#endif
    }

    Rect windowRect = new Rect(20, 20, 100, 50);
    Dictionary<int, IPEndPoint> connectionDictionary = new Dictionary<int, IPEndPoint>();

    byte reliableChannel;
    int m_hostId = -1;
    Vector2 scrollPos;
    string receiveLabel;
    public void OnGUI()
    {
        windowRect = GUILayout.Window(GetInstanceID(), windowRect, MyWindow, "Server Window");
    }

    void MyWindow(int id)
    {
        scrollPos = GUILayout.BeginScrollView(scrollPos, GUILayout.Height(200.0f), GUILayout.Width(400.0f));
        GUILayout.Label(receiveLabel);
        GUILayout.EndScrollView();

        GUILayout.BeginHorizontal();
        if (GUILayout.Button("Clear"))
        {
            receiveLabel = "";
        }
        GUILayout.FlexibleSpace();
        GUILayout.EndHorizontal();

        GUI.DragWindow();
    }

    public void Update()
    {
        if (m_hostId == -1)
            return;
        int connectionId;
        int channelId;
        byte[] buffer = new byte[1500];
        int receivedSize;
        byte error;
        NetworkEventType networkEvent = NetworkTransport.ReceiveFromHost(m_hostId, out connectionId, out channelId, buffer, 1500, out receivedSize, out error);
        if (networkEvent == NetworkEventType.Nothing)
            return;
        receiveLabel += string.Format("{0} connectionId {1} channelId {2} receivedSize {3}\n", networkEvent.ToString(), connectionId, channelId, receivedSize);
        // If someone connected then save this info
        if (networkEvent == NetworkEventType.ConnectEvent)
        {
            string address;
            int port;
            UnityEngine.Networking.Types.NetworkID network;
            UnityEngine.Networking.Types.NodeID dstNode;
            NetworkTransport.GetConnectionInfo(m_hostId, connectionId, out address, out port, out network, out dstNode, out error);
            receiveLabel += string.Format("address {0} port {1}\n", address, port);
            connectionDictionary.Add(connectionId, new IPEndPoint(IPAddress.Parse(address), port));
        }
        else if (networkEvent == NetworkEventType.DisconnectEvent) // Remove from connection list
        {
            connectionDictionary.Remove(connectionId);
        }
        else if (networkEvent == NetworkEventType.DataEvent)
        {
            // Echo to everyone what we just received
            foreach (var pair in connectionDictionary)
            {
                NetworkTransport.Send(m_hostId, pair.Key, reliableChannel, buffer, receivedSize, out error);
            }
        }
    }
}

If this helped you out or you can think of any improvements or things I did wrong, please let me know. Thanks.

Basic SocketAsyncEventArgs Server and Client

A while ago I tried to write a simple server and client that worked with the SocketAsyncEventArgs class. I also used BlockingCollection which was introduced in .NET 4.

Here’s the client executable:

using System;
using System.Text;
using System.Net;
using System.Threading;
using UdpLibrary;

namespace Client
{
class Client
{
static UdpSocket socket;
static void Main(string[] args)
{
socket = new UdpSocket();
socket.Bind(IPAddress.Any);

// Start a thread that prints anything sent to us
new Thread(() => printit()).Start();
string s;
while (true)
{
s = Console.ReadLine();
// Send input to server 100 times
for (int i = 0; i < 100; i++)
socket.Send(IPAddress.Loopback, 10100, Encoding.UTF8.GetBytes(s));
}
}

static void printit()
{
EndPoint ip;
byte[] buffer;
string s;
while (true)
{
socket.Receive(out ip, out buffer);
// Print out anything we receive
s = Encoding.UTF8.GetString(buffer);
Console.WriteLine(ip.ToString() + " " + s);
}
}
}
}

The server executable:

using System;
using System.Text;
using System.Net;
using UdpLibrary;

namespace Server
{
class Server
{
static void Main(string[] args)
{
UdpSocket socket = new UdpSocket();
socket.Bind(IPAddress.Loopback, 10100);

EndPoint ip;
byte[] buffer;
string s;
while (true)
{
socket.Receive(out ip, out buffer);
s = Encoding.UTF8.GetString(buffer);
// Print out what you recv then send back echo
Console.WriteLine(ip.ToString() + " " + s);
socket.Send(((IPEndPoint)ip).Address, ((IPEndPoint)ip).Port, Encoding.UTF8.GetBytes(s));
}
}
}
}

The “UdpLibrary” both executables reference:

using System;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.Collections.Concurrent;

namespace UdpLibrary
{
public class UdpSocket
{
bool running;
int _transferDelay;
double _loss;
Random rnd;
Socket socket;
struct PacketStruct
{
public EndPoint ip;
public byte[] buffer;
}
BlockingCollection<PacketStruct> sendQueue;
BlockingCollection<PacketStruct> receiveQueue;

public UdpSocket()
{
running = false;
sendQueue = new BlockingCollection<PacketStruct>();
receiveQueue = new BlockingCollection<PacketStruct>();
rnd = new Random();
}

~UdpSocket()
{
running = false;
}

public bool Send(IPAddress address, int port, byte[] buffer, int timeout = -1)
{
// Don't really send, but add to the queue to be sent out
PacketStruct tmp;
tmp.ip = new IPEndPoint(address, port);
tmp.buffer = new byte[buffer.Length];
Buffer.BlockCopy(buffer, 0, tmp.buffer, 0, buffer.Length);
return sendQueue.TryAdd(tmp, timeout);
}

public bool Receive(out EndPoint ip, out byte[] buffer, int timeout = -1)
{
// See if there's anything in the queue for us to receive
PacketStruct tmp;
bool ret = receiveQueue.TryTake(out tmp, timeout);
if (ret)
{
ip = tmp.ip;
buffer = new byte[tmp.buffer.Length];
Buffer.BlockCopy(tmp.buffer, 0, buffer, 0, tmp.buffer.Length);
}
else
{
ip = null;
buffer = null;
}
return ret;
}

public void Bind(IPAddress address, int port = 0, int transferDelay = 0, double loss = 0)
{
if (running)
return;
socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
try
{
socket.Bind(new IPEndPoint(address, port));
}
catch
{
return;
}
_transferDelay = transferDelay;
_loss = loss;

// Bind to port and start a thread
Thread thread = new Thread(() => ThreadProc());
thread.Start();
}

void ThreadProc()
{
running = true;

byte[] buffer = new byte[1300];
SocketAsyncEventArgs receiveEvent = new SocketAsyncEventArgs();
receiveEvent.Completed += receiveEvent_Completed;
receiveEvent.RemoteEndPoint = new IPEndPoint(IPAddress.Any, 0);
receiveEvent.SetBuffer(buffer, 0, buffer.Length);

if (!socket.ReceiveMessageFromAsync(receiveEvent))
throw new NotImplementedException();

PacketStruct tmp;
while (running)
{
sendQueue.TryTake(out tmp, -1);

SocketAsyncEventArgs sendEvent = new SocketAsyncEventArgs();
sendEvent.Completed += sendEvent_Completed;
sendEvent.RemoteEndPoint = tmp.ip;
sendEvent.SetBuffer(tmp.buffer, 0, tmp.buffer.Length);

if (!socket.SendToAsync(sendEvent))
throw new NotImplementedException();
}
}

void sendEvent_Completed(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success && e.LastOperation == SocketAsyncOperation.SendTo)
{
PacketStruct tmp;
if (sendQueue.TryTake(out tmp))
{
e.RemoteEndPoint = tmp.ip;
e.SetBuffer(tmp.buffer, 0, tmp.buffer.Length);

if (!((Socket)sender).SendToAsync(e))
throw new NotImplementedException();
}
else
{
e.Completed -= sendEvent_Completed;
}
return;
}
throw new NotImplementedException();
}

void receiveEvent_Completed(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.MessageSize)
{
if (!((Socket)sender).ReceiveMessageFromAsync(e))
throw new NotImplementedException();
return;
}
if (e.SocketError == SocketError.Success && e.LastOperation == SocketAsyncOperation.ReceiveMessageFrom)
{
if (_loss != 0)
{
lock (rnd)
{
if (rnd.NextDouble() < _loss)
{
if (!((Socket)sender).ReceiveMessageFromAsync(e))
throw new NotImplementedException();
return;
}
}
}

if (_transferDelay != 0)
Thread.Sleep(_transferDelay);

PacketStruct tmp;
tmp.ip = e.RemoteEndPoint;
tmp.buffer = new byte[e.BytesTransferred];
Buffer.BlockCopy(e.Buffer, 0, tmp.buffer, 0, e.BytesTransferred);
receiveQueue.TryAdd(tmp, -1);

if (!((Socket)sender).ReceiveMessageFromAsync(e))
throw new NotImplementedException();
return;
}
if (e.SocketError == SocketError.ConnectionReset)
{
}
throw new NotImplementedException();
}
}
}

Maybe this can help someone?