You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;¶

问题根源

  1. TCP流式协议特性误处理:TCP是流式传输,HasPendingData返回的是当前缓冲区所有待读数据的总大小,而非单条消息的长度。原代码用当前待读数据量判断是否达到目标长度,若后续新消息提前到达,会读取到不属于当前消息的额外数据。
  2. 不可靠的等待逻辑:FPlatformProcess::Sleep(0.01f)的等待时长固定,可能出现等待不足(数据未完全到达)或等待过度(后续消息混入缓冲区)。
  3. 字符串读取长度错误:原代码读取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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 17:43:26