TCPClient 读取流

问题描述 投票:0回答:1

我有一个 Netty 游戏服务器,它发送带有空分隔符的 JSON 消息。我有一个 AS3 客户端与该服务器正确通信,但我们的新 Unity 客户端无法正确通信,可能是由于消息发送顺序所致。有时我在解析 JSON 字符串时会遇到异常,尤其是长消息。我尝试了不同的缓冲区大小,但没有任何改变。

try {
    _tcpClient = new TcpClient();
    await _tcpClient.ConnectAsync(_host, _port);
    _isListening = true;

    Debug.Log("Connected...");
    UnityMainThreadDispatcher.Instance().Enqueue(DispatchConnected());

    Byte[] bytes = new Byte[BufferSize];
    StringBuilder partialMessage = new();
    while (_isListening && !_stopRequested) {
        if (_tcpClient != null && _tcpClient.Connected) {
            using (NetworkStream stream = _tcpClient.GetStream()) {
                if (stream.CanRead) {
                    try {
                        int bytesRead;
                        while ((bytesRead = stream.Read(bytes, 0, BufferSize)) > 0) {
                            string bufferMessage = Encoding.UTF8.GetString(bytes, 0, bytesRead);

                            // Append the buffer to the existing partial message
                            partialMessage.Append(bufferMessage);

                            // Check if the partial message contains the termination character
                            int terminateIndex;
                            while ((terminateIndex = partialMessage.ToString().IndexOf(TerminateCharacter)) != -1) {
                                string completeMessage = partialMessage.ToString(0, terminateIndex);
                                Debug.Log("R: " + completeMessage);
                             UnityMainThreadDispatcher.Instance().Enqueue(DispatchServerMessage(completeMessage, true)); // <-- This is where I convert to JSON

                                // Remove the processed portion from the partial message
                                partialMessage.Remove(0, terminateIndex + 1);
                            }  
                        }
                    }
                    catch (IOException ioException) {
                        Debug.LogError($"IOException: {ioException.Message}");
                    }
                    catch (Exception exception) {
                        Debug.LogError(exception);
                    }
                }

                
            }
        }
        else {
            Debug.Log("TCP Client is not connected!");
            ClientDisconnected();
            break; // Break out of the loop when the client is not connected
        }
    }

    // Process any remaining partial message after the loop
    if (partialMessage.Length > 0) {
        UnityMainThreadDispatcher.Instance().Enqueue(DispatchServerMessage(partialMessage.ToString(), true));
        partialMessage.Clear();
    }
}
catch (SocketException socketException) {
    if (socketException.ErrorCode == 10061) {
        // Debug.LogError("Connection refused!!!");
        UnityMainThreadDispatcher.Instance().Enqueue(DispatchConnectionRefused());
    }
    else {
        UnityMainThreadDispatcher.Instance().Enqueue(DispatchConnectionInterrupted());
    }
}
catch (IOException ioException) {
    UnityMainThreadDispatcher.Instance().Enqueue(DispatchConnectionInterrupted());
    // UnityMainThreadDispatcher.Instance().Enqueue(DispatchConnectionRefused());
}
finally
{
    _stopRequested = true; // Ensure the thread stops even if an exception occurs
    _tcpClient?.Close();
    _clientReceiveThread = null;
}

当我查看错误时,我可以看到某些消息以无序的方式出现。例如,假设服务器发送了三个带有空分隔符

Hello\0Socket\0World
的消息,我的客户端收到 Socket -> Hello -> World。

有没有更好的方法来处理 JSON 消息?这里可能出了什么问题?如果是服务器问题,AS3 客户端也会出现错误。

下面是 netty 初始化代码。

ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast("timeout", new IdleStateHandler(ServerSettings.MAX_IDLE_TIME_IN_SECONDS, 0, ServerSettings.MAX_IDLE_TIME_IN_SECONDS));
pipeline.addLast(new DelimiterBasedFrameDecoder(1024 * 1024, Delimiters.nulDelimiter()));
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));// (2)
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8)); // (1)
pipeline.addLast(new SimpleTCPHandler()); // (3)

谢谢

c# unity-game-engine sockets netty tcpclient
1个回答
0
投票

根据评论,您需要改进:

  1. 正确的多字节字符处理

    解码问题可能来自跨缓冲区边界分割的多字节字符。确保缓冲区不会无意中分割字符,这可能会损坏消息并使查找终止符变得复杂。这可能涉及检查缓冲区的最后几个字节,以确保它们不会在未完成的情况下启动多字节字符。

    // Assuming UTF8 encoding
    bool IsPotentialMultiByteSequenceStart(byte b) {
        // Checks if the byte is the start of a multi-byte sequence in UTF-8
        return (b & 0xC0) == 0x80;
    }
    
    // Use this function to determine if the last byte of your buffer might be the start of a multi-byte character
    bool MightSplitMultiByteCharacter(byte[] bytes, int bytesRead) {
        if (bytesRead == 0) return false;
        return IsPotentialMultiByteSequenceStart(bytes[bytesRead - 1]);
    }
    
    // You might need to adjust your reading logic to account for this possibility
    
  2. 高效的消息解析: 读入缓冲区然后解析消息终止符的策略需要改进,以确保它能够处理边缘情况,例如部分消息或跨越多个缓冲区的消息。
    如果消息始终大于当前缓冲区,请考虑动态调整缓冲区大小或确保您的逻辑可以处理跨越多次读取的消息。

    // Assuming BufferSize is appropriately sized and TerminateCharacter is defined
    while (_isListening && !_stopRequested && stream.CanRead) {
        int bytesRead = stream.Read(bytes, 0, BufferSize);
        if (bytesRead > 0) {
            // Handle potential multi-byte character split
            int endIndex = bytesRead;
            while (endIndex > 0 && MightSplitMultiByteCharacter(bytes, endIndex)) {
                endIndex--; // Adjust endIndex to make sure multi-byte characters are not split
            }
    
            string bufferMessage = Encoding.UTF8.GetString(bytes, 0, endIndex);
            partialMessage.Append(bufferMessage);
    
            ProcessMessages(partialMessage); // Process complete messages within partialMessage
        }
    }
    
    // That function processes and clears processed messages from the StringBuilder
    void ProcessMessages(StringBuilder partialMessage) {
        int terminateIndex;
        while ((terminateIndex = partialMessage.ToString().IndexOf(TerminateCharacter)) != -1) {
            string completeMessage = partialMessage.ToString(0, terminateIndex);
            Debug.Log("Received: " + completeMessage);
            // Dispatch the message for further processing
            partialMessage.Remove(0, terminateIndex + 1);
        }
    }
    

这与这个答案一致,建议采用 DIY 解决方案。

© www.soinside.com 2019 - 2024. All rights reserved.