仅 Mac 上的 Unity TCP 客户端连接中的 TCP 数据包乱序

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

我有一个使用 TCP 连接到服务器的 Unity 脚本。服务器发送任意长度的消息。消息的长度在消息的前 4 个字节中描述,我用它来确定完整消息何时到达。由于某种原因,消息没有以正确的顺序“缝合”在一起。但是,此问题仅发生在 Mac 上。相同的脚本在 Windows 上运行良好(也是相同版本的 Unity)。有什么想法吗?

Unity版本:2018.4.19f

连接代码如下(为简化问题我删掉了一些东西):

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.
                }
            }
        }
    }
}

编辑: 服务器是使用 Qt 在 C++ 中实现的,运行 在 Windows 机器上。服务器发送可能包含大量数据的响应。整个消息由 4 个字节组成,指示整个消息的长度,然后是响应本身。响应是 Json 字符串。我得到的错误是,从上面的代码中获得的最终字符串的排序方式与发送时的排序方式不同。消息以 } 结束(关闭外部 Json 对象)。但是,在收到的响应中,在 } 之后,还有大量其他值本应属于 Json 对象中的数组。举个例子,服务器发送的响应如下:

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

上面的代码是这样的:

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

只有当响应开始达到千字节范围(或更高)时才会发生这种情况,我强调,完全相同的代码在 Windows.

上运行得非常好

服务器通过以下方式发送消息:

// 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);

编辑 #2 这是输出图像。 } 之后的值应该是 Message.

之前的大数组的一部分

任何时候你看到 Read/ReadAsync 而没有捕获和使用结果:代码完全错误。

幸运的是,这不是一个 巨大 修复:

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();

附带说明:BitConverter.ToInt32 是危险的 - 它假定 CPU 字节顺序, 不固定 。最好使用 BinaryPrimitives.ReadInt32BigEndianBinaryPrimitives.ReadInt32LittleEndian.