Unreal Engine C++ TCP客户端接收C#服务端消息末尾乱码求助
问题:UE C++ TCP客户端接收C#服务端消息时偶现乱码/额外字符
Unreal Engine开发的C++ TCP客户端接收C# TCP服务端消息时,约20%概率会在消息末尾出现空字符或随机乱码,极少数情况出现多个额外字符,其余80%接收正常。期望接收固定长度的正常文本,无额外字符。
C#服务端发送代码
// send a message string str = "Hello World;"; byte[] strBytes = Encoding.UTF8.GetBytes(str); int strLength = strBytes.Length; // Allocate a buffer to hold the entire message byte[] buffer = new byte[4 + strLength]; // Convert the string length to a 4-byte array in network byte order (big-endian) byte[] strLengthBytes = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(strLength)); Array.Copy(strLengthBytes, 0, buffer, 0, 4); // Copy the string data to the buffer Array.Copy(strBytes, 0, buffer, 4, strLength); // Send the buffer in a single packet stream.Write(buffer, 0, buffer.Length); Console.WriteLine("Sent " + str);
Unreal Engine C++客户端接收代码
void ATcpManager::Tick(float DeltaTime) { Super::Tick(DeltaTime); uint32_t DataSize = 0; TArray<uint8> DataBuffer; if (socket->HasPendingData(DataSize)) { // Wait for the int32 prefix to arrive if (DataSize > 4) { FPlatformProcess::Sleep(0.01f); } UE_LOG(LogTemp, Warning, TEXT("Received DataSize: %i"), DataSize); // Read the int32 prefix int32_t BytesRead = 0; bool bReadSuccess = socket->Recv(reinterpret_cast<uint8*>(&DataSize), sizeof(DataSize), BytesRead); if (bReadSuccess) { DataSize = ntohl(DataSize); UE_LOG(LogTemp, Warning, TEXT("New DataSize: %i"), DataSize); uint32_t StrDataSize = 0; // Wait for the string to arrive while (socket->HasPendingData(StrDataSize) && StrDataSize < DataSize) { FPlatformProcess::Sleep(0.01f); } UE_LOG(LogTemp, Warning, TEXT("StrDataSize Arrived")); // Read the string DataBuffer.SetNumUninitialized(StrDataSize); BytesRead = 0; bReadSuccess = socket->Recv(DataBuffer.GetData(), StrDataSize, BytesRead); if (bReadSuccess && BytesRead == StrDataSize) { FString Message = FString::Printf(TEXT("%s"), UTF8_TO_TCHAR(DataBuffer.GetData())); UE_LOG(LogTemp, Warning, TEXT("Received message: %s"), *Message); OnDataReceived(Message); } else { UE_LOG(LogTemp, Warning, TEXT("Failed to read string data")); } } else { UE_LOG(LogTemp, Warning, TEXT("Failed to read int32 prefix")); } } }
异常输出示例
LogTemp: Warning: Received DataSize: 16 LogTemp: Warning: New DataSize: 12 LogTemp: Warning: StrDataSize Arrived LogTemp: Warning: Received message: Hello World;¶
问题根源
- TCP流式协议特性误处理:TCP是流式传输,
HasPendingData返回的是当前缓冲区所有待读数据的总大小,而非单条消息的长度。原代码用当前待读数据量判断是否达到目标长度,若后续新消息提前到达,会读取到不属于当前消息的额外数据。 - 不可靠的等待逻辑:
FPlatformProcess::Sleep(0.01f)的等待时长固定,可能出现等待不足(数据未完全到达)或等待过度(后续消息混入缓冲区)。 - 字符串读取长度错误:原代码读取
StrDataSize(当前缓冲区所有数据)而非服务端约定的DataSize,导致读取到额外数据;且未添加字符串终止符,UTF8_TO_TCHAR可能越界读取内存产生乱码。
修复方案
修改客户端接收逻辑,严格按照服务端约定的长度读取数据,确保单条消息的完整性:
void ATcpManager::Tick(float DeltaTime) { Super::Tick(DeltaTime); uint32_t PendingSize = 0; if (socket->HasPendingData(PendingSize)) { // 确保先读取完整的4字节长度前缀 while (PendingSize < sizeof(int32_t)) { if (!socket->HasPendingData(PendingSize)) { return; } FPlatformProcess::Sleep(0.001f); } // 读取长度前缀 int32_t RawSize = 0; int32_t BytesRead = 0; bool bReadSuccess = socket->Recv(reinterpret_cast<uint8*>(&RawSize), sizeof(RawSize), BytesRead); if (!bReadSuccess || BytesRead != sizeof(RawSize)) { UE_LOG(LogTemp, Warning, TEXT("Failed to read length prefix")); return; } int32_t TargetStrLength = ntohl(RawSize); UE_LOG(LogTemp, Warning, TEXT("Expected string length: %i"), TargetStrLength); // 循环读取指定长度的字符串数据,直到读满目标字节数 TArray<uint8> DataBuffer; DataBuffer.SetNumUninitialized(TargetStrLength); int32_t TotalRead = 0; while (TotalRead < TargetStrLength) { int32_t CurrentRead = 0; bReadSuccess = socket->Recv(DataBuffer.GetData() + TotalRead, TargetStrLength - TotalRead, CurrentRead); if (!bReadSuccess || CurrentRead <= 0) { UE_LOG(LogTemp, Warning, TEXT("Failed to read string data, total read: %i"), TotalRead); return; } TotalRead += CurrentRead; } // 添加NULL终止符,确保UTF-8转FString时不会越界 DataBuffer.Add(0); FString Message = FString(UTF8_TO_TCHAR(DataBuffer.GetData())); UE_LOG(LogTemp, Warning, TEXT("Received message: %s"), *Message); OnDataReceived(Message); } }
关键修改点
- 确保先读取完整的4字节长度前缀,避免部分读取导致的长度解析错误。
- 严格按照服务端发送的
TargetStrLength循环读取数据,保证只读取当前消息的内容,避免粘包问题。 - 添加NULL终止符,确保
UTF8_TO_TCHAR能正确解析字符串,防止内存越界产生乱码。 - 替换固定时长的等待逻辑,改为循环检查数据是否到达,提升可靠性。
内容的提问来源于stack exchange,提问作者Renaldo Technico
相关产品推荐
相关产品推荐

