【发布时间】:2021-02-17 09:24:29
【问题描述】:
我有一个使用 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);
Edit #2 这是输出的图像。 } 之后的值应该是 Message 之前的大数组的一部分。
【问题讨论】:
-
我要提防的一件事——你正确地循环接收消息的正文——但你假设你总是会一次性获得描述长度的 4 个字节。这种假设也可能不合理。
-
我之前考虑过这个问题,但我认为第一个数据包不太可能小于 4 个字节(或者是吗?)。为了安全起见,我想我可以一次读取 1 个字节,谢谢指出。
-
TCP 不会乱序传送数据。甚至在 Mac 上也没有。您在某处的代码中存在错误,并且 任何 假设您对哪些字节一起交付所做的假设是完全无效的假设。 TCP 只保证字节将按顺序传送,没有其他保证。每当您未能检查读取操作的字节数时,您的代码都是错误的,需要修复。如果您已经以文本形式发送数据,那么您可能会发现最好使用
StreamReader和StreamWriter并让换行符作为数据分隔符。如果要读取二进制数据,请... -
...查看
BinaryReader,因为它封装了用于读取给定类型数据的所有必要字节的逻辑(但要小心其字符串处理...它使用了一种不寻常的方案编码字符串)。 -
说了这么多,您的问题太模糊,无法提供实际答案。您需要提供minimal reproducible example,当涉及网络时,它意味着连接的两个端点,您需要更清楚问题是什么。 “乱序”可能意味着任何事情。您需要准确地解释代码的作用,以及它与您的预期有何不同。
标签: c# macos unity3d tcpclient