简体   繁体   中英

TCP packets out of order in Unity TCP client connection on Mac only

I have a Unity script which connects to a server using TCP. The server sends messages of arbitrary length. The length of the message is described in the first 4 bytes of the message, which I use to determine when the full message has arrived. For some reason, the message isn't being "stitched" together in the correct order. This problem, however, only occurs on Mac. The same script works perfectly fine on Windows (same version of Unity too). Any idea?

Unity version: 2018.4.19f

The connection code is as follows (I edited out things for simplicity of the question):

using System;
using System.Collections.Concurrent;
using System.Net.Sockets;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Events;

public class ServerConnection : MonoBehaviour {
    // Example connection parameters
    public string IP = "192.168.0.10"; 
    public int Port = 8085;

    /// The task object where TCP connection runs.
    Task ServerTask { get; set; }

    TcpClient Client { get; set; }
    Socket Socket { get; set; }
    string Message { get; set; }

    /// Gets called on the push of a button
    public void Connect() {
        ServerTask = Task.Factory.StartNew(listenForDataAsync, TaskCreationOptions.LongRunning);
    }

    async private void listenForDataAsync() {
        Debug.Log(string.Format("Connecting to server: IP={0}, Port={1}", IP, Port));
        try {
            Client = new TcpClient(IP, Port);
            Debug.Log("Connection established");

            byte[] message = new byte[0];
            byte[] msgSize = new byte[4];
            int messageSize = 0;
            using (NetworkStream stream = Client.GetStream()) {
                while (true) {
                    // Wait for 4 bytes to get message size
                    await stream.ReadAsync(msgSize, 0, 4);

                    // Convert message size byte array to an int
                    int totalMessageSize = BitConverter.ToInt32(msgSize, 0);
                
                    // subtract 4 from total message size (4 bytes previously read)
                    messageSize = totalMessageSize - 4;

                    // Wait for the rest of the message
                    message = new byte[messageSize];
                    int readBytes = 0;
                    while (messageSize != 0) {
                        readBytes = await stream.ReadAsync(message, readBytes, messageSize);
                        messageSize -= readBytes;
                    }

                    // Decode byte array to string
                    string response = System.Text.ASCIIEncoding.ASCII.GetString(message);

                    // On Mac, response has the message out of order. 
                    // On Windows, it is always perfectly fine.
                }
            }
        }
    }
}

Edit: The server is implemented in C++ using Qt and is running on a Windows machine. The server sends responses that may contain large chunks of data. The entire message is composed of 4 bytes indicating the length of the whole message followed by the response itself. The response is a Json string. The error I'm getting is that the final string obtained from the code above is not ordered the same way as it was when it was sent. The message ends in } (closing the outer Json object). However, in the received response, after the } , there are tons of other values that were supposed to be part of an an array inside the Json object. To give you an example, take the following response sent by the server:

{data: [0, 1, 2, 3, 4, 5, 6, 7]}

The code above gets something like:

{data: [0, 1, 2]}, 3, 4, 5, 7

This only happens when the responses are starting to hit the kilobytes range (and higher), and I emphasize, the exact same code works perfectly fine on Windows.

The server sends the message in the following way:

// The response in Json
QJsonObject responseJson;

// Convert to byte array
QJsonDocument doc(responseJson);
QByteArray message = doc.toJson(QJsonDocument::JsonFormat::Compact);

// Insert 4 bytes indicating length of message
qint32 messageSize = sizeof(qint32) + message.size();
message.insert(0, (const char*)&messageSize, sizeof(qint32));

// Send message
// In here, socket is a QTcpSocket
socket->write(message);

Edit #2 Here is an image of the output. The values after the } should have been part of the big array right before Message .

在此处输入图片说明

any time you see Read / ReadAsync without capturing and using the result: the code is simply wrong.

Fortunately, it isn't a huge fix:

int toRead = 4, offset = 0, read;
while (toRead != 0 && (read = stream.ReadAsync(msgSize, offset, toRead)) > 0)
{
    offset += read;
    toRead -= read;
}
if (toRead != 0) throw new EndOfStreamException();

As a side note: BitConverter.ToInt32 is dangerous - it assumes CPU endianness, which is not fixed . It would be better to use BinaryPrimitives.ReadInt32BigEndian or BinaryPrimitives.ReadInt32LittleEndian .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM