Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Join us on Thursday, June 8, for a Q&A with Unity's Content Pipeline group here on the forum, and on the Unity Discord, and discuss topics around Content Build, Import Workflows, Asset Database, and Addressables!
    Dismiss Notice

Unity Multiplayer [NetworkTransport] Reliable channels and client disconnections

Discussion in 'Multiplayer' started by K1kk0z90_Unity, Apr 18, 2018.

  1. K1kk0z90_Unity

    K1kk0z90_Unity

    Joined:
    Jun 2, 2013
    Posts:
    90
    Hello,
    I'm developing a mobile turn-based strategy game for 2 players, using the Transport Layer API of UNet (the NetworkTransport class). One player is the Host (server + client) and the other one is the Client (simply a client). Game logic is entirely managed on the Host, while the Client simply sends data to and receives data from the Host.
    I send data over a channel set as QosType.ReliableSequenced. The problem is that, if the Client is temporarily disconnected (e.g. it looses connection for few seconds before reconnecting to the Host), data sent by the Host during this period is lost. Since I use a Reliable channel, I would expect that, when the client reconnects, it would receive the data from the Host sent while the Client was temporarily offline. Why doesn't it work this way? Should I continuously try to resend data (e.g. by firing a Coroutine) whenever a NetworkTransport.Send() fails?
    Thank you in advance for your help!

    Here's the complete code (about 200 lines) of my custom NetworkManager class. If StartHost() is called, then it starts to broadcast in LAN to advertise an available match. If StartClient() is called, it connects to a previously started Host. Then, it is possible for both the Host and the Client to send data to the other one using the Send() method. The DataReceived event is raised whenever data has been received.

    Code (CSharp):
    1. using UnityEngine;
    2. using System;
    3. using System.Collections;
    4. using UnityEngine.Networking;
    5. using UnityEngine.SceneManagement;
    6.  
    7. public class NetworkManager : Singleton<NetworkManager> // Singleton taken from the Unify wiki
    8. {
    9.     [Header("Local discovery")]
    10.     [SerializeField]
    11.     private int broadcastPort = 47777;
    12.     [SerializeField] private int broadcastKey = 2222;
    13.     [SerializeField] private int broadcastVersion = 1;
    14.     [SerializeField] private int broadcastSubversion = 0;
    15.     [SerializeField] private int broadcastInterval = 1000;
    16.     private byte[] broadcastMessage = new byte[] { 255, 127, 0 }; // arbitrary data
    17.     private byte[] broadcastReceiveBuffer = new byte[128];
    18.     public string gameSceneName;
    19.     public string lobbySceneName;
    20.  
    21.     [Header("Debug log")]
    22.     public GUIStyle style;
    23.  
    24.     private ConnectionConfig config;
    25.     private int importantChannelId;
    26.     private int unreliableChannelId;
    27.     private HostTopology topology;
    28.     private int hostId = -1;
    29.     private string hostAddress;
    30.     private int hostPort;
    31.     private int myConnectionId; // used by client only
    32.     private int clientConnectionId; // used by host only
    33.  
    34.     private bool isHost = false;
    35.     private bool isClient = false;
    36.     private bool isStarted = false;
    37.     private bool clientConnected = false;
    38.  
    39.     private string log = "";
    40.  
    41.     public event EventHandler<DataReceivedEventArgs> DataReceived;
    42.  
    43.     private void Start()
    44.     {
    45.         NetworkTransport.Init();
    46.         config = new ConnectionConfig();
    47.         importantChannelId = config.AddChannel(QosType.ReliableSequenced);
    48.         unreliableChannelId = config.AddChannel(QosType.Unreliable);
    49.         topology = new HostTopology(config, 2);
    50.     }
    51.  
    52.     public void StartHost()
    53.     {
    54.         hostId = NetworkTransport.AddHost(topology, 8888, null);
    55.  
    56.         // I am the host: now I start to broadcast to let other machines in the same LAN
    57.         // know that a game exists.
    58.         byte error;
    59.         if (!NetworkTransport.StartBroadcastDiscovery(hostId, broadcastPort, broadcastKey, broadcastVersion, broadcastSubversion,
    60.             broadcastMessage, broadcastMessage.Length, broadcastInterval, out error))
    61.         {
    62.             Debug.LogError("NetworkTransport.StartBroadcastDiscovery() failed with error: " + error);
    63.         }
    64.  
    65.         isHost = true;
    66.         isStarted = true;
    67.     }
    68.  
    69.     public void StartClient()
    70.     {
    71.         hostId = NetworkTransport.AddHost(topology, broadcastPort);
    72.  
    73.         byte error;
    74.         NetworkTransport.SetBroadcastCredentials(hostId, broadcastKey, broadcastVersion, broadcastSubversion, out error);
    75.  
    76.         isClient = true;
    77.         isStarted = true;
    78.     }
    79.  
    80.     private void Update()
    81.     {
    82.         if (isStarted)
    83.         {
    84.             NetworkEventType networkEvent;
    85.             do
    86.             {
    87.                 int connectionId;
    88.                 int channelId;
    89.                 int receivedSize;
    90.                 byte error;
    91.                 networkEvent = NetworkTransport.ReceiveFromHost(hostId, out connectionId, out channelId,
    92.                     broadcastReceiveBuffer, broadcastReceiveBuffer.Length, out receivedSize, out error);
    93.  
    94.                 if (networkEvent == NetworkEventType.BroadcastEvent && isClient && !clientConnected)
    95.                 {
    96.                     NetworkTransport.GetBroadcastConnectionInfo(hostId, out hostAddress, out hostPort, out error);
    97.  
    98.                     myConnectionId = NetworkTransport.Connect(hostId, hostAddress, hostPort, 0, out error);
    99.                     if (myConnectionId != 0)
    100.                     {
    101.                         clientConnected = true;
    102.                         Log("Connected to host at IP: " + hostAddress + " and Port: " + hostPort);
    103.                     }
    104.                     else
    105.                     {
    106.                         Log("Failed connecting to host. Error: " + (NetworkError)error);
    107.                     }
    108.                 }
    109.                 else if (networkEvent == NetworkEventType.ConnectEvent)
    110.                 {
    111.                     if (connectionId != myConnectionId)
    112.                     {
    113.                         // I am host, a client has connected to me
    114.                         Log("I am host, a client has connected to me.");
    115.                         clientConnectionId = connectionId;
    116.                         NetworkTransport.StopBroadcastDiscovery();
    117.                         if (SceneManager.GetActiveScene() != SceneManager.GetSceneByName(gameSceneName))
    118.                             SceneManager.LoadScene(gameSceneName);
    119.                     }
    120.                     else
    121.                     {
    122.                         Log("My active connect request approved.");
    123.                         if (SceneManager.GetActiveScene() != SceneManager.GetSceneByName(gameSceneName))
    124.                             SceneManager.LoadScene(gameSceneName);
    125.                     }
    126.  
    127.                 }
    128.                 else if (networkEvent == NetworkEventType.DisconnectEvent)
    129.                 {
    130.                     if (connectionId != myConnectionId)
    131.                     {
    132.                         // I am host, a client has disconnected from me
    133.                         Log("I am host, a client has disconnected from me! Error: " + (NetworkError)error);
    134.                     }
    135.                     else
    136.                     {
    137.                         // I am client, my connection request has failed
    138.                         Log("Connection request failed with error: " + (NetworkError)error);
    139.                         StartCoroutine(ClientTryReconnect());
    140.                     }
    141.                 }
    142.                 else if (networkEvent == NetworkEventType.DataEvent)
    143.                 {
    144.                     OnDataReceived(new DataReceivedEventArgs(broadcastReceiveBuffer));
    145.                 }
    146.             }
    147.             while (networkEvent != NetworkEventType.Nothing);
    148.         }
    149.     }
    150.  
    151.     private IEnumerator ClientTryReconnect()
    152.     {
    153.         byte error;
    154.         do
    155.         {
    156.             Log("Attempt to reconnect...");
    157.             yield return new WaitForSeconds(1f);
    158.             myConnectionId = NetworkTransport.Connect(hostId, hostAddress, hostPort, 0, out error);
    159.         }
    160.         while (error != 0);
    161.     }
    162.  
    163.     public void SendData(byte[] data)
    164.     {
    165.         int connectionId = isClient ? myConnectionId : clientConnectionId;
    166.         byte error;
    167.         if (NetworkTransport.Send(hostId, connectionId, importantChannelId, data, data.Length, out error))
    168.         {
    169.             Log("Data sent.");
    170.         }
    171.         else
    172.         {
    173.             Debug.LogError("NetworkTransport.Send() failed with error: " + (NetworkError)error);
    174.             Log("NetworkTransport.Send() failed with error: " + (NetworkError)error);
    175.         }
    176.     }
    177.  
    178.     public bool IsHost
    179.     {
    180.         get { return isHost; }
    181.     }
    182.  
    183.     protected virtual void OnDataReceived(DataReceivedEventArgs e)
    184.     {
    185.         EventHandler<DataReceivedEventArgs> handler = DataReceived;
    186.         if (handler != null)
    187.             handler(this, e);
    188.     }
    189.  
    190.     public void Log(string s)
    191.     {
    192.         log = "[" + DateTime.Now.ToString("hh:mm:ss") + "] " + s + Environment.NewLine + log;
    193.         Debug.Log(s);
    194.     }
    195.  
    196.     private void OnGUI()
    197.     {
    198.         GUI.Label(new Rect(Vector2.zero, new Vector2(500, 1000)), log, style);
    199.     }
    200. }
    201.  
    202. public class DataReceivedEventArgs : EventArgs
    203. {
    204.     public byte[] Data { get; private set; }
    205.  
    206.     public DataReceivedEventArgs(byte[] data)
    207.     {
    208.         Data = data;
    209.     }
    210. }
     
  2. aabramychev

    aabramychev

    Unity Technologies

    Joined:
    Jul 17, 2012
    Posts:
    574
    Client disconnect means that connections has been closed. All data related to this connection has been deleted and connection itself switch to fresh state. The behavior of tcp socket is exactly the same. If you close connected tcp socket (close connection) all data which you have sent to them will gone. Unet assures that reliable data will delivered to your peer or you will receive Disconnect event.
     
    K1kk0z90_Unity likes this.
  3. K1kk0z90_Unity

    K1kk0z90_Unity

    Joined:
    Jun 2, 2013
    Posts:
    90
    Thank you very much for your answer!
    So, to manage client's disconnections and reconnections, I should keep undelivered messages in a queue and try to resend them (in the correct order) when the client reconnects? Do you think it can be a good approach or do you suggest a better one @aabramychev ?
    Thanks again.
     
    Last edited: Apr 18, 2018
  4. aabramychev

    aabramychev

    Unity Technologies

    Joined:
    Jul 17, 2012
    Posts:
    574
    hmm, it is one of possible. The deal is not all reliable messages are important due reconnect. For example chat message?
    Another point, you shouldn't (probably) resend all messages. Imagine that you have some sort of object states, so after reconnect you will notify server that you new state is, and server will make soft transition your game object from the last state to new.
    Another options (for addition which you write), for messages which must to be delivered and you want to know when, server can send you direct acknowledgement of the last received message, and in this case you will remove all messages from the waiting queue.

    It is quite difficult to find one universal approach. So, if you can describe what are you doing, probably we can consider different options and find something better :)
     
    K1kk0z90_Unity likes this.