Search Unity

  1. Unity 2019.2 is now released.
    Dismiss Notice

Unity Multiplayer Client/Server performace to high need ideas for better performance

Discussion in 'Connected Games' started by Krause-Biagosch, Jul 24, 2017.

  1. Krause-Biagosch

    Krause-Biagosch

    Joined:
    Mar 8, 2017
    Posts:
    19
    HI
    i have create a client/server solution for me with this tutorials

    its works good, but the performance are very bad at my extended solution.
    The server do "nothing" an take over 50% CPU and with one client i have 100% CPU used.

    I am new on Unity/Multiplayer and i speak with one other unity developer an he say i must putout so much things from Update(). I look my code but no ideas what i can do.

    Here the code i shorted the code where i think it not needed for better view.

    Have you ideas for better performance??

    That is my cleint:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using System.Text;
    4. using UnityEngine;
    5. using UnityEngine.Networking;
    6. using UnityEngine.UI;
    7.  
    8. public class User
    9. {
    10.     public GameObject avatar;
    11.     public string visibleClientName;
    12.     public int connectionId;
    13.     public bool isBusy;
    14.      
    15. }
    16.  
    17. public class NET_Client : MonoBehaviour {
    18.  
    19.     public static NET_Client instance;
    20.  
    21.     private int hostId;
    22.     private int webHostId;
    23.  
    24.     private int reliableChannel;
    25.     private int unreliableChannel;
    26.  
    27.     private int ourClientId;
    28.     private int connectionId;
    29.  
    30.     private float connectionTime;
    31.     private bool isConnected = false;
    32.     private bool isStarted = false;
    33.  
    34.     public bool isCalling = false;
    35.  
    36.     private byte error;
    37.  
    38.     private string playerName;
    39.  
    40.     [Header("Loging Prefab")]
    41.     public GameObject playerPrefab;
    42.     public GameObject emailImput;
    43.     public InputField loginStatus;
    44.     //public GameObject login;
    45.     public Dictionary<int, User> allUsersDic = new Dictionary<int,User>();
    46.  
    47.     [Header("Connection Settings")]
    48.     public string uHostIp;
    49.     //private string hostIp = "127.0.0.1";
    50.     public int uPort;
    51.     //private int port = 5701;
    52.  
    53.     private const int MAX_CONNECTION = 100;
    54.    
    55.     [Header("Debuing Console")]
    56.     /// <summary>
    57.     /// Debug console to be able to see the unity log on every platform
    58.     /// </summary>
    59.     public bool uDebugConsole = false;
    60.  
    61.     [Header("Call Prefab")]
    62.     public GameObject IncomingCall;
    63.     public InputField CalledByName;
    64.  
    65.  
    66.     private void Start()
    67.     {
    68.         if (uDebugConsole)
    69.             DebugHelper.ActivateConsole();
    70.     }
    71.  
    72.     private void Update()
    73.     {
    74.         if (!isConnected)
    75.             return;
    76.  
    77.         int recHostId;
    78.         int connectionId;
    79.         int channelId;
    80.         byte[] recBuffer = new byte[1024];
    81.         int bufferSize = 1024;
    82.         int dataSize;
    83.         byte error;
    84.         NetworkEventType recData = NetworkTransport.Receive(out recHostId, out connectionId, out channelId, recBuffer, bufferSize, out dataSize, out error);
    85.         switch (recData)
    86.         {
    87.             case NetworkEventType.DataEvent:
    88.                 string msg = Encoding.Unicode.GetString(recBuffer, 0, dataSize);
    89.                 Debug.Log("Receving : " + msg);
    90.  
    91.                 string[] splitData = msg.Split('|');
    92.  
    93.                 switch (splitData[0])
    94.                 {
    95.                     case "ASKNAME":
    96.                         OnAskName(splitData);
    97.                         break;
    98.                     case "CNN":
    99.                         SpawnUser(splitData[1], int.Parse(splitData[2]));
    100.                         break;
    101.                     case "WAITCALL":
    102.                         OnWaitkCall(splitData);
    103.                         break;
    104.                     case "BUSY":
    105.                         OnBusyStatus(splitData);
    106.                         break;
    107.                     case "DC":
    108.                         PlayerDisconnected(int.Parse(splitData[1]));
    109.                         break;
    110.                     default:
    111.                         Debug.Log("Invalid message : " + msg);
    112.                         break;
    113.                 }
    114.  
    115.                 break;
    116.         }
    117.     }
    118.  
    119.     public void Connect()
    120.     {
    121.         //Does the player have a name?
    122.         //string pName = GameObject.Find("EmailImput").GetComponent<InputField>().text;
    123.         string pName = emailImput.GetComponent<InputField>().text;
    124.         if (pName == "")
    125.         {
    126.             Debug.Log("You must enter your email!");
    127.             return;
    128.         }
    129.  
    130.         playerName = pName;
    131.  
    132.         NetworkTransport.Init();
    133.         ConnectionConfig cc = new ConnectionConfig();
    134.  
    135.         reliableChannel = cc.AddChannel(QosType.Reliable);
    136.         unreliableChannel = cc.AddChannel(QosType.Unreliable);
    137.  
    138.         HostTopology topo = new HostTopology(cc, MAX_CONNECTION);
    139.  
    140.         hostId = NetworkTransport.AddHost(topo, 0);
    141.         connectionId = NetworkTransport.Connect(hostId, uHostIp, uPort, 0, out error);
    142.  
    143.         connectionTime = Time.time;
    144.         isConnected = true;
    145.  
    146.         // Set the login name on the top InputField
    147.         SetLoginStatus();
    148.     }
    149.    
    150.     // Show the dictionary from the client. Show the busy status of all users
    151.     public void ShowUsersCalling() {...}
    152.  
    153.     // Send request for ask the player there are busy?
    154.     public bool AskIsUserBusy(string toAskUser)
    155.     {...}
    156.  
    157.     // Look at all user and return true if find the searchUser, then the searchUser is online/connect
    158.     public bool AskOnlineUser(string searchUser)
    159.     {...}  
    160.  
    161.     // Show the login status in the right top corner
    162.     private void SetLoginStatus()
    163.     {...}      
    164.  
    165.     private void OnAskName(string[] data)
    166.     {...}
    167.    
    168.     private void SpawnUser(string userName, int cnnId)
    169.     {...}
    170.  
    171.     // Open call request to the other player
    172.     public void CallByPlayer(InputField calledNameInput)
    173.     {...}
    174.        
    175.     // Set the client in isCalling status and seng this status to all users
    176.     public void BlockClientForCall()
    177.     {...}
    178.  
    179.     // Set the client in not calling status and send this status to all users
    180.     public void UnblockedClientFromCall()
    181.     {...}
    182.        
    183.     // Open the signal window by the call player
    184.     private void OnWaitkCall(string [] splitData)
    185.     {...}  
    186.    
    187.     private void OnBusyStatus(string[] splitData)
    188.     {...}
    189.  
    190.     private void PlayerDisconnected(int cnnId)
    191.     {...}
    192.  
    193.     private void Send(string message, int channelId)
    194.     {...}  
    195. }
    That is the server:
    Code (CSharp):
    1. using System;
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using System.Text;
    5. using UnityEngine;
    6. using UnityEngine.Networking;
    7.  
    8. public class ServerClient
    9. {
    10.     public static ServerClient instance;
    11.     public int connectionId;
    12.     public string playerName;
    13.     public Vector3 position;
    14.     public bool clientIsCalling;
    15. }
    16.  
    17. public class NET_Server : MonoBehaviour
    18. {
    19.     public static NET_Server instance;
    20.     private int hostId;
    21.     private int webHostId;
    22.  
    23.     private int reliableChannel;
    24.     private int unreliableChannel;
    25.  
    26.     private bool isStarted = false;
    27.     private byte error;
    28.  
    29.     public List<ServerClient> clients = new List<ServerClient>();
    30.  
    31.     private float lastMovementUpdate;
    32.     private float movementUpdateRate = 0.5f;
    33.  
    34.     private const int MAX_CONNECTION = 100;
    35.  
    36.     public int uPort;
    37.     //private int port = 5701;
    38.  
    39.     /// <summary>
    40.     /// Debug console to be able to see the unity log on every platform
    41.     /// </summary>
    42.     public bool uDebugConsole = false;
    43.  
    44.  
    45.     private void Start()
    46.     {
    47.         if (uDebugConsole)
    48.             DebugHelper.ActivateConsole();
    49.  
    50.         NetworkTransport.Init();
    51.         ConnectionConfig cc = new ConnectionConfig();
    52.  
    53.         reliableChannel = cc.AddChannel(QosType.Reliable);
    54.         unreliableChannel = cc.AddChannel(QosType.Unreliable);
    55.  
    56.         HostTopology topo = new HostTopology(cc, MAX_CONNECTION);
    57.  
    58.         hostId = NetworkTransport.AddHost(topo, uPort, null);
    59.         webHostId = NetworkTransport.AddWebsocketHost(topo, uPort, null);
    60.  
    61.         isStarted = true;
    62.         Debug.Log("Server is started.");
    63.     }
    64.  
    65.     private void Update()
    66.     {
    67.         if (!isStarted)
    68.             return;
    69.  
    70.         int recHostId;
    71.         int connectionId;
    72.         int channelId;
    73.         byte[] recBuffer = new byte[1024];
    74.         int bufferSize = 1024;
    75.         int dataSize;
    76.         byte error;
    77.         NetworkEventType recData = NetworkTransport.Receive(out recHostId, out connectionId, out channelId, recBuffer, bufferSize, out dataSize, out error);
    78.         switch (recData)
    79.         {
    80.             case NetworkEventType.ConnectEvent:    //2
    81.                 Debug.Log("Player " + connectionId + " has connected.");
    82.                 OnConnection(connectionId);
    83.                 break;
    84.             case NetworkEventType.DataEvent:       //3
    85.                 string msg = Encoding.Unicode.GetString(recBuffer, 0, dataSize);
    86.                 Debug.Log("Receiving from " + connectionId + " : " + msg);
    87.  
    88.                 string[] splitData = msg.Split('|');
    89.  
    90.                 switch (splitData[0])
    91.                 {
    92.                     case "NAMEIS":
    93.                         OnNameIs(connectionId, splitData[1]);
    94.                         break;
    95.                     case "MYPOSITION":
    96.                         OnMyPosition(connectionId, float.Parse(splitData[1]), float.Parse(splitData[2]));
    97.                         break;
    98.                     case "CALL":
    99.                         CallByPlayer(connectionId, splitData[1], splitData[2]);
    100.                         break;
    101.                     case "SENDBUSY":
    102.                         SendBusy(connectionId, splitData);
    103.                         break;
    104.                     default:
    105.                         Debug.Log("Invalid message : " + msg);
    106.                         break;
    107.                 }
    108.  
    109.                 break;
    110.  
    111.             case NetworkEventType.DisconnectEvent: //4
    112.                 Debug.Log("Player " + connectionId + " has disconnected.");
    113.                 OnDisconnection(connectionId);
    114.                 break;
    115.         }
    116.  
    117.         /***
    118.         // It was only for the testing the server at the moment don't needed
    119.  
    120.         // Ask player for ther position
    121.         if (Time.time - lastMovementUpdate > movementUpdateRate)
    122.         {
    123.             lastMovementUpdate = Time.time;
    124.             string msg = "ASKPOSITION|";
    125.             foreach(ServerClient sc in clients)
    126.                 msg += sc.connectionId.ToString() + '%' + sc.position.x.ToString() + '%' + sc.position.y.ToString() + '|';
    127.             msg = msg.Trim('|');
    128.             Send(msg, unreliableChannel, clients);
    129.         }
    130.         */
    131.     }
    132.    
    133.     private void OnConnection(int cnnId)
    134.     {
    135.         // Add him to a list
    136.         ServerClient c = new ServerClient();
    137.         c.connectionId = cnnId;
    138.         c.playerName = "Temp";
    139.         clients.Add(c);
    140.  
    141.         //Debug.Log("[OnConnection] Client: ID-" + c.connectionId + "; " + c.playerName);
    142.              
    143.         // When the player joins the server, tell him his ID
    144.         // Request his name and send the name of the other players
    145.         string msg = "ASKNAME|" + cnnId + "|";
    146.         foreach (ServerClient sc in clients)
    147.             msg += sc.playerName + '%' + sc.connectionId + '|';
    148.  
    149.         msg = msg.Trim('|');
    150.  
    151.         // ASKNAME|3|Dave%1|Michael%2|Temp%3
    152.         Send(msg, reliableChannel, cnnId);
    153.     }
    154.    
    155.     private void CallByPlayer(int cnnId, string playerName, string callByName)
    156.     {...}
    157.  
    158.     // Send all users the user busystatus with the playerName
    159.     private void SendBusy(int cnnId, string[] splitData)
    160.     {...}
    161.    
    162.     private void OnDisconnection(int cnnId)
    163.     {...}
    164.    
    165.     private void OnNameIs(int cnnId, string userName)
    166.     {...}
    167.    
    168.     private void OnMyPosition(int cnnId, float x, float y)
    169.     {...}
    170.  
    171.     private void Send(string message, int channelId, int cnnId)
    172.     {...}
    173.  
    174.     private void Send(string message, int channelId, List<ServerClient> c)
    175.     {...}  
    176. }
     
  2. robochase

    robochase

    Joined:
    Mar 1, 2014
    Posts:
    213
    this immediately jumps out me -

    byte[] recBuffer = new byte[1024];

    you have that line in both update functions for the server and client.

    move that outside of the update functions and just reuse the variable. you're basically creating an array of 1024 bytes every frame when you really don't need to. that's a lot of unnecessary memory allocation.
     
    xVergilx and wobes like this.
  3. robochase

    robochase

    Joined:
    Mar 1, 2014
    Posts:
    213
    also, i strongly recommend not sending strings over the network. this kind of stuff is really bad-

    1. string[] splitData = msg.Split('|');

    2. switch (splitData[0])
    just pass an int and let 1 == "NAMEIS", 2 == "MYPOSITION" etc. you're sending wayyyy more data than you need to. NEVER send strings over a socket connection unless you're sending like a player name or something like that.

    if you're having trouble with this stuff, I suggest you use the NetworkReader/NetworkWriter classes to help with sending more compact data over the network

    try looking at your code in a profiler too. it should help you figure out what is eating up your CPU
     
    Last edited: Jul 24, 2017
    xVergilx and wobes like this.
  4. Oskar_Kasprzak

    Oskar_Kasprzak

    Joined:
    Mar 26, 2016
    Posts:
    42
    Hi @robochase

    I am lurking through these forums to learn and help whenever I can. You just made me curious... why you shouldn't send strings via socket connection and if - why only player names? Any doc about it or something?
     
  5. robochase

    robochase

    Joined:
    Mar 1, 2014
    Posts:
    213
    hey @Oskar_Kasprzak it's a waste of bandwidth to send strings when ints would suffice. also string parsing consumes memory and isn't particularly fast.

    consider this example - you want to send an x/y/z position message to a player. you could pack the message up like this as a unicode string:

    "MYPOSITION|321|64|18"

    that's 20 characters long. let's be generous and assume that each character is only 1 byte (8 bits). though in reality, unicode can be anywhere from 1-4 bytes (8-32 bits). 20 characters X 1 byte = 20 bytes (160 bits). worst case scenario: 20 characters X 4 bytes = 80 bytes (640 bits)

    now let's try sending this as actual integers. we'll just assume in our code that if the first number in the sequence is "3", it means that it's a position message. so we basically send 4 ints here -

    3,321,64,18

    here, you're sending 4 ints, which are 4 bytes a piece. 4*4=16 bytes (128 bits).

    now let's consider compacting this data even further:

    let's say you've only got 32 message types, that means your first number you send, you only actually need to send 5 bits to convey this information. and you know for your game, you can accurately convey position using only 17 bits per coordinate. now your message looks like this:

    [5 bits],[17 bits],[17 bits],[17 bits]

    that's 56 bits, or just 7 bytes.

    7 bytes compared to 160....

    i suggested to only send player names as strings, but really what i meant was that the only time you should send a string is when you ACTUALLY need to send a string. like you can't represent a player name as an Int, really.

    also consider that with the string method, each time your numbers get bigger, you're sending more bytes. for example, if you send over "321", you're sending 3 bytes, but if you send over "321467", you're now sending 6 bytes. whereas if this was an int, you don't pay that penalty because a 4 byte int can cover a huge expanse of values.

    PS: here is an excellent article about this topic: http://joostdevblog.blogspot.com/2014/01/bitcrunching-numbers-for-bandwidth.html
     
    Last edited: Jul 24, 2017
    xVergilx likes this.
  6. Oskar_Kasprzak

    Oskar_Kasprzak

    Joined:
    Mar 26, 2016
    Posts:
    42
    Wow thanks for such a reply. It makes sense, definitely going to remember about this
     
  7. xVergilx

    xVergilx

    Joined:
    Dec 22, 2014
    Posts:
    1,875
    Also, make sure you've set Application.targetFrameRate for the server. Otherwise it'll chew up CPU like insane in batchmode. I'd suggest setting to 60 for better response, but that depends on the game you're making and server response you need.
     
  8. TwoTen

    TwoTen

    Joined:
    May 25, 2016
    Posts:
    1,133
    If you don't recomend sending strings and splitting them. What's the recomendation? Except for the enum part of the NAMEIS.

    And also, if per say you are sending large data. Say you are informing the client about every client in the scene. What is best to do. I mean 5|posxSPLITposySPLITposzSPLITrotxSPLITrotySPLITrotzSPLIT isn't very effective with many objects either?: That's gonna be some NASTY string concatenation.
    And also, is it recomended to use a char like | to split the pecies?
    Or should we just protobuf it? That's what we are currently doing and we find it very easy to do and it's very effective.
     
    Last edited: Jul 25, 2017
  9. robochase

    robochase

    Joined:
    Mar 1, 2014
    Posts:
    213
    sending binary data. write your stuff into a byte array and send that.

    a base assumption when using Unet is that you'd be using the relay servers where bandwidth consumption is a top priority. if that's not the case, and you favor readability of data as it comes in, then by all means :D

    i mean if i ever have a big list of similar objects i need to sync, i usually start by writing the number of objects that are in the update, so that the reader knows how much of the buffer to read. something like this i guess?

    Code (CSharp):
    1. // very very psuedo code. packing up an update to be sent.
    2. writer.WriteInt(numPlayersToUpdate);
    3. for (int i = 0; i < numPlayersToUpdate; i++) {
    4.     Player p = GetPlayer(i);
    5.     writer.WriteInt(p.netId.Value);
    6.     writer.WriteVector3(p.transform.position);
    7.     writer.WriteVector3(p.Transform.eulerAngles);
    8. }
    9.  
    10. // & later, on the receiving end...
    11. int numPlayersToUpdate = reader.ReadInt();
    12. for (int i = 0; i < numPlayersToUpdate; i++) {
    13.     int netId = reader.ReadInt();
    14.  
    15.     Player p = GetPlayerFromNetId(netId);
    16.     p.transform.position = reader.ReadVector3();
    17.     p.Transform.eulerAngles = reader.ReadVector3();
    18. }
    note that this is very similar to how the NetworkWriter & NetworkReader classes work. there's no need to build a string of data and separate values with pipe characters or anything like that as long as your code reads things in the order that they were written.

    yeah, that's fine if you're building data in a string like that. just make sure that if there's any sort of player-entered text like chat messages, player names etc, that it doesn't break your string parsing when they use your special characters :D

    i think we've used protobuf on a project years ago, but i don't remember much about it. that's more for like auto-serializing a bunch of properties/variables/whatevers on an object right?
     
  10. nxrighthere

    nxrighthere

    Joined:
    Mar 2, 2014
    Posts:
    522
  11. Krause-Biagosch

    Krause-Biagosch

    Joined:
    Mar 8, 2017
    Posts:
    19
    thank you all for your ideas

    now i must try to build this ideas in my project