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

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-&gt;Hello-&gt;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)

谢谢

推荐答案

根据 comments ,你需要改进:

  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. Efficient message parsing: The strategy of reading into a buffer and then parsing for message terminators needs refinement to make sure it handles edge cases, like partial messages or messages that span multiple buffers.
    If messages are consistently larger than the current buffer, consider adjusting the buffer size dynamically or making sure that your logic can handle messages that span multiple reads.

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

这与this answer一致,这表明一个DIY的解决方案.


My TerminateCharacter is \0 If sent buffer ends with an extra character 'a' for instance, last bytes will be \0\0061.
What will happen to the extra 'a' character in this solution? Won't that character removed from the rest of the buffer?

I am also confused about return (b & 0xC0) == 0x80; this part.
Will that work in my terminate character?

Regarding handling the extra character 'a' after the termination character:
When the buffer ends with a termination character followed by an extra character a (e.g., the bytes are \0 followed by the ASCII representation of a), the a character is preserved for the next message processing cycle. The ProcessMessages function processes up to the termination character, removes the processed message including the termination character from partialMessage, and any subsequent characters (like a) remain in partialMessage for processing during the next cycle.
That means the extra a character will not be removed or lost; instead, it will be the starting point of the next message to be processed.

表达式return (b & 0xC0) == 0x80用于标识UTF-8编码字符中的连续字节.UTF-8 characters的范围从1到4个字节,其中第一个字节表示字符中的字节数,后续字节(连续字节)跟在模式10xxxxxx之后.该方法通过用0xC0对字节进行掩码(这将隔离两个最高有效位)并将结果与0x80进行比较来判断该字节是否为连续字节.如果结果为真,则该字节是一个连续字节,这意味着它是从前面字节开始的多字节字符的一部分.

该判断对于确保不会在从缓冲区的读取之间拆分多字节字符非常重要.然而,它不会直接影响你的终止角色\0的处理.终止字符\0(空字符)是UTF-8中的单字节字符,而不是继续字节,因此该特定判断((b & 0xC0) == 0x80)不用于标识或处理终止字符本身.相反,它有助于防止意外拆分多字节字符,从而 destruct 您的UTF-8编码消息的解析.

Csharp相关问答推荐

EF Core Fluent API中定义的多对多关系

更改对象的旋转方向

始终保留数组中的最后N个值,丢弃最老的

通过条件列表删除/更新EF Core 7中的实体的有效方法

在一个模拟上设置一个方法,该模拟具有一个参数,该参数是一个numc函数表达式

当通过Google的Gmail Api发送邮件时,签名会产生dkim = neutral(正文散列未验证)'

如何定义EFCore中的多个穿透

只有第一个LINQ.Count()语句有效

使用带有WithAppOnly()请求选项的pageIterator

使用C#HttpClient以多部分形式数据发送带有非ASCII文件名的文件的问题

如何使用NumberFormatInfo

HttpClient 415不支持的媒体类型错误

C#普罗米修斯指标

Lambda表达式如何与隐式强制转换一起工作?

链接到字典字符串.拆分为(.Key,.Value)

如何使用类似于[SELECT*FROM&Q;&Q;WHERE&Q;]SQL查询的System.Data.Entity创建查询?

在ObservableCollection上使用[NotifyPropertyChangedFor()]源代码生成器不会更新UI

在Unity C#中按键点击错误的参数

反序列化我以前使用System.Text.Json序列化的文件时出现异常

无法将.Net Framework 4.8.1升级到.Net 7