Search Unity

Resolved Unity TCP Client Socket often does not receive data (packets) from the server - they just disappear

Discussion in 'Scripting' started by Volchok, Dec 4, 2022.

  1. Volchok

    Volchok

    Joined:
    Jul 26, 2017
    Posts:
    127
    Hi!

    Why does Unity Async BeginReceive not accept stably sent packets from the server? They just disappear somewhere on the way from the server to the client. The acceptance of packets on Unity over TCP is very unstable and unpredictable. On the Internet, I did not find a normal example and a normal explanation of why this is so. As far as I understand, it is told that Unity OnFrame FPS (Update) and Unity main thread prevent stable accepting TCP packets in Unity. I also heard that Unity has a time limit and the volume of packets received per unit of time.

    https://stackoverflow.com/questions/53671112/c-sharp-socket-stops-receiving
    https://forum.unity.com/threads/c-tcp-ip-socket-how-to-receive-from-server.227259/

    But the fact is that I tried to send delayed packets from the server both at once and in stacks and one by one and in a row - in different ways - it didn't help. Also, Receive() in Update() does not help.

    Here is a log showing how packets are sent from the server and how they are received in the Unity client in the same socket connection (and where does packet loss occur):



    !!! GOOD !!!

    SERVER (SENT)

    [01:39.01.9157] ClientPacket_TryBuySellMarketObject.ClientPacket_TryBuySellMarketObject()!
    [01:39.01.9207] ServerPacket_UpdatePlayerMainInfo.ServerPacket_UpdatePlayerMainInfo()...
    [01:39.01.9207] ServerPacket_TryBuySellMarketObjectServerAnswer.ServerPacket_TryBuySellMarketObjectServerAnswer(). Code = 1

    CLIENT (RECEIVED)

    ClientPacket_TryBuySellMarketObject.ClientPacket_TryBuySellMarketObject()...
    UnityEngine.StackTraceUtility:ExtractStackTrace ()
    UnityEngine.DebugLogHandler:LogFormat (UnityEngine.LogType,UnityEngine.Object,string,object[])
    UnityEngine.Logger:Log (UnityEngine.LogType,object)
    UnityEngine.Debug:Log (object)
    ClientPacket_TryBuySellMarketObject:.ctor (int,System.Collections.Generic.List`1<Item>,int) (at Assets/Scripts/Communication/Packets/Client/ClientPacket_TryBuySellMarketObject.cs:29)
    TryBuySellItemController:OnAddInModalPanel (System.Collections.Hashtable) (at Assets/Scripts/Market/TryBuySellItemController.cs:307)
    UnityEngine.Events.InvokableCall`1<System.Collections.Hashtable>:Invoke (System.Collections.Hashtable)
    UnityEngine.Events.UnityEvent`1<System.Collections.Hashtable>:Invoke (System.Collections.Hashtable)
    SDKBoyEventManager:Update () (at Assets/Scripts/Events/SDKBoyEventManager.cs:193)

    (Filename: Assets/Scripts/Communication/Packets/Server/ServerPacket_TryBuySellMarketObjectServerAnswer.cs Line: 33)

    ServerPacket_UpdatePlayerMainInfo.ServerPacket_UpdatePlayerMainInfo()...
    UnityEngine.StackTraceUtility:ExtractStackTrace ()
    UnityEngine.DebugLogHandler:LogFormat (UnityEngine.LogType,UnityEngine.Object,string,object[])
    UnityEngine.Logger:Log (UnityEngine.LogType,object)
    UnityEngine.Debug:Log (object)
    ServerPacket_UpdatePlayerMainInfo:.ctor (Formater&) (at Assets/Scripts/Communication/Packets/Server/ServerPacket_UpdatePlayerMainInfo.cs:29)
    ClientSocket:ReceiveCallback (System.IAsyncResult) (at Assets/Scripts/Sockets/ClientSocket.cs:603)
    System.Net.Sockets.SocketAsyncResult/<>c:<Complete>b__27_0 (object)
    System.Threading.QueueUserWorkItemCallback:System.Threading.IThreadPoolWorkItem.ExecuteWorkItem ()
    System.Threading.ThreadPoolWorkQueue:Dispatch ()
    System.Threading._ThreadPoolWaitCallback:performWaitCallback ()

    (Filename: Assets/Scripts/Communication/Packets/Client/ClientPacket_TryBuySellMarketObject.cs Line: 29)

    ServerPacket_TryBuySellMarketObjectServerAnswer.ServerPacket_TryBuySellMarketObjectServerAnswer()... Code = 1
    UnityEngine.StackTraceUtility:ExtractStackTrace ()
    UnityEngine.DebugLogHandler:LogFormat (UnityEngine.LogType,UnityEngine.Object,string,object[])
    UnityEngine.Logger:Log (UnityEngine.LogType,object)
    UnityEngine.Debug:Log (object)
    ServerPacket_TryBuySellMarketObjectServerAnswer:.ctor (Formater&) (at Assets/Scripts/Communication/Packets/Server/ServerPacket_TryBuySellMarketObjectServerAnswer.cs:33)
    ClientSocket:ReceiveCallback (System.IAsyncResult) (at Assets/Scripts/Sockets/ClientSocket.cs:625)
    System.Net.Sockets.SocketAsyncResult/<>c:<Complete>b__27_0 (object)
    System.Threading.QueueUserWorkItemCallback:System.Threading.IThreadPoolWorkItem.ExecuteWorkItem ()
    System.Threading.ThreadPoolWorkQueue:Dispatch ()
    System.Threading._ThreadPoolWaitCallback:performWaitCallback ()




    !!! BAD !!!

    SERVER (SENT)

    [01:07.40.3569] ClientPacket_TryBuySellMarketObject.ClientPacket_TryBuySellMarketObject()!
    [01:07.40.3799] ServerPacket_UpdatePlayerMainInfo.ServerPacket_UpdatePlayerMainInfo()... <------------------------------------!!! SENT
    [01:07.40.3809] ServerPacket_TryBuySellMarketObjectServerAnswer.ServerPacket_TryBuySellMarketObjectServerAnswer(). Code = 1

    CLIENT (RECEIVED)

    ClientPacket_TryBuySellMarketObject.ClientPacket_TryBuySellMarketObject()...
    UnityEngine.StackTraceUtility:ExtractStackTrace ()
    UnityEngine.DebugLogHandler:LogFormat (UnityEngine.LogType,UnityEngine.Object,string,object[])
    UnityEngine.Logger:Log (UnityEngine.LogType,object)
    UnityEngine.Debug:Log (object)
    ClientPacket_TryBuySellMarketObject:.ctor (int,System.Collections.Generic.List`1<Item>,int) (at Assets/Scripts/Communication/Packets/Client/ClientPacket_TryBuySellMarketObject.cs:29)
    TryBuySellItemController:OnAddInModalPanel (System.Collections.Hashtable) (at Assets/Scripts/Market/TryBuySellItemController.cs:307)
    UnityEngine.Events.InvokableCall`1<System.Collections.Hashtable>:Invoke (System.Collections.Hashtable)
    UnityEngine.Events.UnityEvent`1<System.Collections.Hashtable>:Invoke (System.Collections.Hashtable)
    SDKBoyEventManager:Update () (at Assets/Scripts/Events/SDKBoyEventManager.cs:193)

    (Filename: Assets/Scripts/Communication/Packets/Client/ClientPacket_TryBuySellMarketObject.cs Line: 29)

    ServerPacket_UpdatePlayerMainInfo <-------------------- ??? Not accepted by Unity! LOST

    ServerPacket_TryBuySellMarketObjectServerAnswer.ServerPacket_TryBuySellMarketObjectServerAnswer()... Code = 1
    UnityEngine.StackTraceUtility:ExtractStackTrace ()
    UnityEngine.DebugLogHandler:LogFormat (UnityEngine.LogType,UnityEngine.Object,string,object[])
    UnityEngine.Logger:Log (UnityEngine.LogType,object)
    UnityEngine.Debug:Log (object)
    ServerPacket_TryBuySellMarketObjectServerAnswer:.ctor (Formater&) (at Assets/Scripts/Communication/Packets/Server/ServerPacket_TryBuySellMarketObjectServerAnswer.cs:33)
    ClientSocket:ReceiveCallback (System.IAsyncResult) (at Assets/Scripts/Sockets/ClientSocket.cs:625)
    System.Net.Sockets.SocketAsyncResult/<>c:<Complete>b__27_0 (object)
    System.Threading.QueueUserWorkItemCallback:System.Threading.IThreadPoolWorkItem.ExecuteWorkItem ()
    System.Threading.ThreadPoolWorkQueue:Dispatch ()
    System.Threading._ThreadPoolWaitCallback:performWaitCallback ()

    (Filename: Assets/Scripts/Communication/Packets/Server/ServerPacket_TryBuySellMarketObjectServerAnswer.cs Line: 33)




    !!! BAD !!!

    SERVER (SENT)

    [02:06.34.9346] ClientPacket_TryBuySellMarketObject.ClientPacket_TryBuySellMarketObject()!
    [02:06.34.9546] ServerPacket_UpdatePlayerMainInfo.ServerPacket_UpdatePlayerMainInfo()...
    [02:06.34.9556] ServerPacket_TryBuySellMarketObjectServerAnswer.ServerPacket_TryBuySellMarketObjectServerAnswer(). Code = 1 <------------------------------------!!! SENT

    CLIENT (RECEIVED)

    ClientPacket_TryBuySellMarketObject.ClientPacket_TryBuySellMarketObject()...
    UnityEngine.StackTraceUtility:ExtractStackTrace ()
    UnityEngine.DebugLogHandler:LogFormat (UnityEngine.LogType,UnityEngine.Object,string,object[])
    UnityEngine.Logger:Log (UnityEngine.LogType,object)
    UnityEngine.Debug:Log (object)
    ClientPacket_TryBuySellMarketObject:.ctor (int,System.Collections.Generic.List`1<Item>,int) (at Assets/Scripts/Communication/Packets/Client/ClientPacket_TryBuySellMarketObject.cs:29)
    TryBuySellItemController:OnAddInModalPanel (System.Collections.Hashtable) (at Assets/Scripts/Market/TryBuySellItemController.cs:307)
    UnityEngine.Events.InvokableCall`1<System.Collections.Hashtable>:Invoke (System.Collections.Hashtable)
    UnityEngine.Events.UnityEvent`1<System.Collections.Hashtable>:Invoke (System.Collections.Hashtable)
    SDKBoyEventManager:Update () (at Assets/Scripts/Events/SDKBoyEventManager.cs:193)

    (Filename: Assets/Scripts/Communication/Packets/Client/ClientPacket_TryBuySellMarketObject.cs Line: 29)

    ServerPacket_UpdatePlayerMainInfo.ServerPacket_UpdatePlayerMainInfo()...
    UnityEngine.StackTraceUtility:ExtractStackTrace ()
    UnityEngine.DebugLogHandler:LogFormat (UnityEngine.LogType,UnityEngine.Object,string,object[])
    UnityEngine.Logger:Log (UnityEngine.LogType,object)
    UnityEngine.Debug:Log (object)
    ServerPacket_UpdatePlayerMainInfo:.ctor (Formater&) (at Assets/Scripts/Communication/Packets/Server/ServerPacket_UpdatePlayerMainInfo.cs:29)
    ClientSocket:ReceiveCallback (System.IAsyncResult) (at Assets/Scripts/Sockets/ClientSocket.cs:603)
    System.Net.Sockets.SocketAsyncResult/<>c:<Complete>b__27_0 (object)
    System.Threading.QueueUserWorkItemCallback:System.Threading.IThreadPoolWorkItem.ExecuteWorkItem ()
    System.Threading.ThreadPoolWorkQueue:Dispatch ()
    System.Threading._ThreadPoolWaitCallback:performWaitCallback ()

    (Filename: Assets/Scripts/Communication/Packets/Server/ServerPacket_UpdatePlayerMainInfo.cs Line: 29)

    ServerPacket_TryBuySellMarketObjectServerAnswer <-------------------- ??? Not accepted by Unity! LOST




    Here are the code of receiving packages on the client side. How do I create receiving of packages so that they are not lost? Maybe I don't understand something? Thanks!



    Code (CSharp):
    1. public class ClientSocket : MonoBehaviour {
    Code (CSharp):
    1. void Awake() {
    2.             DontDestroyOnLoad(gameObject);    
    3.     }
    Code (CSharp):
    1. private Socket _clientSocket = new Socket(
    2.         AddressFamily.InterNetwork,
    3.         SocketType.Stream,
    4.         ProtocolType.Tcp
    5.     );
    6.  
    7. private bool _ready = false;
    8. private byte[] _recieveBuffer = new byte[32768];
    9.  
    10.  
    11. void Update()
    12.     {
    13.         if (_ready)
    14.         {
    15.             Receive();
    16.         }
    17.     }
    18.  
    19.  
    20.  
    21. public void SetupServer() {
    22.  
    23.         try {
    24.  
    25.             IPAddress ip = IPAddress.Parse(CCommunication.ServerIp());
    26.      
    27.             _clientSocket.Connect(
    28.                 new IPEndPoint(
    29.                     ip,
    30.                     CCommunication.ServerPort()
    31.                 )
    32.             );
    33.  
    34.         } catch (SocketException socketException) {
    35.             Debug.LogError(string.Format("ClientSocket.SetupServer(). SocketException: {0}", socketException));
    36.             return;
    37.         }
    38.  
    39.         _ready = true;
    40.  
    41.         Receive();
    42.     }
    43.  
    44.  
    45. private void Receive() {
    46.  
    47.         try {
    48.  
    49.             _clientSocket.BeginReceive(
    50.                 _recieveBuffer,
    51.                 0,
    52.                 _recieveBuffer.Length,
    53.                 SocketFlags.None,
    54.                 new AsyncCallback(ReceiveCallback),
    55.                 _clientSocket
    56.             );
    57.  
    58.         } catch (Exception e) {
    59.  
    60.             Debug.LogError("ClientSocket.Receive(). Message = " + e.Message);
    61.  
    62.         }
    63.     }
    64.  
    65.  
    Code (CSharp):
    1. private void ReceiveCallback(IAsyncResult ar) {
    2.  
    3.             try {
    4.  
    5.                 int recieved = _clientSocket.EndReceive(ar);
    6.  
    7.                 if (recieved == 0) {
    8.                     Receive();
    9.                     return;
    10.                 }
    11.  
    12.                 byte[] recData = new byte[recieved];
    13.  
    14.                 Buffer.BlockCopy(
    15.                     _recieveBuffer,
    16.                     0,
    17.                     recData,
    18.                     0,
    19.                     recieved
    20.                 );
    21.  
    22.                 Receive();
    23.  
    24.                 Packet packet = null;
    25.  
    26.                 Formater formater = new Formater(
    27.                     new MemoryStream(
    28.                         recData
    29.                     )
    30.                 );
    31.  
    32.                 int readBytes = 0;
    33.  
    34.                 short length = formater.ReadInt16(ref readBytes);
    35.  
    36.                 byte packetType = formater.ReadByte(ref readBytes);
    37.  
    38.                 switch (packetType) {
    39.  
    40.                     cases...
    41.  
    42.                     default:
    43.                         packet = null;
    44.                         Debug.LogError(string.Format("ClientSocket.ReceiveCallback(). Unknown packet!"));
    45.                         break;
    46.                 }
    47.  
    48.                 if (packet == null) {
    49.                     Debug.LogError(string.Format("ClientSocket.ReceiveCallback(). Packet read error! Unknown packet! Packet = null! {0} {1}", packetType, length));
    50.                     Disconnect();
    51.                     return;
    52.  
    53.                 } else {
    54.                     Debug.Log("ClientSocket.ReceiveCallback(). Packet! packetType = " + packetType.ToString("X2") + ", length = " + length);
    55.                 }
    56.          
    57.                 packet.Execute();
    58.  
    59.             } catch (Exception e) {
    60.                 Debug.LogError(string.Format("ClientSocket.ReceiveCallback(). Exception: " + e.Message + e.StackTrace));
    61.                 Disconnect();
    62.             }
    63. }
    Code (CSharp):
    1. }
     
    Last edited: Dec 5, 2022
  2. Volchok

    Volchok

    Joined:
    Jul 26, 2017
    Posts:
    127
    By the way, the Receive() call in Update() is not needed, because sometimes a critical error occurs in the ReceiveCallback(IAsyncResult ar) method at the time of reading the package.

    Need to be deleted:

    Code (CSharp):
    1. void Update()
    2.     {
    3.         if (_ready)
    4.         {
    5.             Receive();
    6.         }
    7.     }
    But it still doesn't help in solving packet loss.
     
    Last edited: Dec 5, 2022
  3. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,990
    Right at the very top the way you worded your question I always assumed what's probably the issue. Your main issue is that you think that a TCP connection works with packets which is just a false assumption. Yes, TCP / IP is transported over a packet based protocol (IP) but TCP itself is a streaming protocol. It does not have the concept of packets. You can receive the stream in chunks, however it's not guaranteed how the data may arrive. The data could be fragmented even more so only a fraction has arrived yet or a single receive call could read 2 or more "write" calls. Again, there are no packets in TCP. It's an endless stream. So if you want to send packets over a TCP connection, you have to implement your own application protocol on top. Some text based protocols like HTTP uses new line characters to indicate the end of a line of the header and an empty line (two new line sequences in a row) as the end of the header. The header field Content-Length then indicates how much raw data would follow the header. Of course HTTP originally was a single request, single response protocol, but nowadays the connection is usually kept open.

    Over here on UA I made a small example where a Unity client connects to a standalone server that sends images over a TCP connection. I did not use async methods but did handle the receiving in a blocking thread. The nice thing here is that the NetworkStream actually blocks until enough data has arrived. So when I used GetBytes of the binary reader with x number of bytes, the method would block until the networkstream has gathered at least that amount of bytes.

    Though the main takeaway should be:
    • TCP does not send packets.
    • If you need packets you have to implement your own packet header that can be detected or ensure a certain structure.
    In my image example the application layer protocol I implemented is super simple: send a 4byte (32 bit) integer number indicating the amount of data we're going to send and then just send that amount of data. So the receiving side reads 4 bytes, knows how much data will follow and then just reads that amount. This continues in a loop. So right after the last byte of data, we get the next 4 byte length for the next image.

    When using Async methods you have to take care of the buffering yourself. Also keep in mind that at the end of the last data chunk you read, you may already received the beginning of the next one.

    It's not clear what your "Formater" does. However you clearly do not handle and additional bytes you may have received that may be relevant for the next "packet".
     
    Volchok likes this.
  4. Volchok

    Volchok

    Joined:
    Jul 26, 2017
    Posts:
    127
    Thanks for the answer!

    Yes, I know about what you are talking about. And everything is already working for me. It remains to figure out how to take the packet correctly. My packet is divided into length (short), packet type (byte) and the packet itself. I pick up the packet via a string along the length of the packet, at the end of which there should be \0.

    But I have a question. For example, a packet arrived in receive buffer, I took it properly, but can a part of the next packet come in the this buffer below got packet. If so, do I have to somehow save part of the next packet and attach it to the beginning of the next received data so that the packet does not disappear? Can a part of the next packet(s) get into the buffer? Am I right?

     
    Last edited: Dec 5, 2022
  5. Volchok

    Volchok

    Joined:
    Jul 26, 2017
    Posts:
    127
    Thank you! I fixed everything thanks to your advice! :):):)
    Yes, packets sometimes came partially or in several pieces. I had to run through the buffer in the while loop and pull out all the packets, and save the partially received packet in order to attach it later to the beginning of the next received data. This is the only way I stopped losing packages both on the server side and on the client side.

    In addition, I no longer needed to limit the sending of packages to a time frame. Everything works very fast and without packet loss.

    Thank you! The question is closed!
     
    Bunny83 likes this.