【问题标题】:c# NetworkStream BeginRead seemingly overwriting bytec# NetworkStream BeginRead 看似覆盖字节
【发布时间】:2018-12-05 21:58:01
【问题描述】:

对于某些程序,我想将数据从 python 程序发送到 c# 程序。我将所有数据放入 python 中的列表中,并在转换为字节后发送(将它们打包为双精度,所以我发送的每个数字有 8 个字节)。了解了套接字和 TCP 流的工作原理后,列表中的第一个数字是列表其余部分占用的字节数。因此,流的前 8 个字节告诉我需要读取多少字节才能获取所有其他数据。

但是,当我调用 BeginRead 并调用回调时,它读取的内容比我要求它读取的少 8 个字节。例如,前 8 个字节告诉我有 116432 个字节要读取,但是当我调用 EndRead 时它返回 116424。

好吧,那又怎样?缺少八个字节,这相当于丢失了一个双倍。这本身就是一个问题,但我什至弄清楚了这个替身去了哪里。

在 python 中,在我的数据中的特定点(虽然它仍然是两倍),我看到我正在发送:"...,1961.0, 0.0128, 2033.0, 0.0442, 2034.0,..." 当我在 c# 中检查同一点时(将我的字节转换回双精度后),我看到:"..,1961.0, 2033.0002, 0.0442,2034.0,..."

对我来说,很明显这 8 个字节不知何故被混为一个,以某种方式将两个数字(可能是按位?)融合在一起。我还注意到,字节数据中出现这种情况的索引大致在 64k-65k 标记处。所以我怀疑 64kbytes 是 TCP 数据包的最大数据包大小,流有某种打嗝并覆盖我的缓冲区的一部分而不清除它,导致一些文字混淆?有谁知道我该如何解决这个问题,或者我犯了什么错误导致这种情况发生?

我将两个相关的函数贴在这里。

private void Listen(int port)
{
    try
    {
        // Perform a blocking call to accept requests.
        // You could also user server.AcceptSocket() here.
        var client = _server.AcceptTcpClient();

        // Get a stream object for reading and writing
        var stream = client.GetStream();

        var pLength = new byte[8];          

        // Loop to receive all the data sent by the client.
        while(_running)
        {
            if(!stream.DataAvailable && stream.Read(pLength, 0, 8) <= 0)
                continue;            
            var nOfBytes = (int) BitConverter.ToDouble(pLength, 0);
            pLength = new byte[8];

            if (nOfBytes <= 0) 
            {
                continue;
            }

            var localBytes = new byte[nOfBytes];

            var scriptData = new ScriptData(stream, localBytes);
            stream.BeginRead(localBytes, 0, nOfBytes, new AsyncCallback(GotAllBytes), scriptData);
        }

        // Shutdown and end connection
        client.Close();
    }
    catch (SocketException e)
    {
        _errorBool = true;
        _errorString = "Port: " + port + "\n" + e.Message + e.StackTrace;
    }
    finally
    {
        // Stop listening for new clients.
        _server.Stop();
    }
}

private void GotAllBytes(IAsyncResult result)
{
    var scriptData = (ScriptData)result.AsyncState;
    if (OnlinePaused)
    {
        scriptData.Stream.EndRead(result);
        return;
    }
    var bytesRead = scriptData.Stream.EndRead(result);
    _rawDataQueue.Enqueue(scriptData.Buffer.ToList());
}

感谢阅读,希望对你有所帮助。

【问题讨论】:

  • Read 保证给您的所有内容(如果成功)是 1 个字节。如果您想要/需要读取一定数量的字节,则必须循环(并调整您要求数据去往的缓冲区中的位置以及要读取的数量)。或者换句话说,GotAllBytes 被乐观但错误地命名。即使您最初读取 8 个字节,也不能保证一次获得所有字节。
  • 这听起来像是 BinaryReader 的工作。
  • @Damien_The_Unbeliever 是的,我知道是这样。但是,我很难相信我总是错过数据的最后 8 个字节,而这两个特定数字似乎混合在一起。据我了解,BeginRead 只会在确实读取 nOfBytes 个字节后才调用“GotAllBytes”。如果我错了,请纠正我。
  • @JeroenMostert 确实如此!我已经在我的代码的另一部分中使用了 BinaryReader。事实上,在 _rawDataQueue 中将数据排入队列后,我在其他地方出列并使用 BinaryReader 将其转换回双精度数。这就是我注意到这两点似乎混合在一起的原因。
  • 看看BinaryReader.FillBuffer是如何实现的;你会看到它使用了 Damien 提到的那种循环来确保它读取必要的字节。 Stream.Read保证给你特定数量的字节;只有建立在流之上的各种阅读器类才能这样做。您发现“难以置信”的情况可能正是数据包在本地接口上被截断时发生的情况;你只是(不)幸运的数据并没有更加碎片化。像这样的代码甚至可以运行得很好(偶然),然后在其他情况下会失败。

标签: c# asynchronous


【解决方案1】:

感谢原始问题的 cmets 中的 Jeroen Mostert,这个问题已经通过不工作异步解决,而是实现了 BinaryReader。

private void Listen(int port)
{
    try
    {
        // Perform a blocking call to accept requests.
        // You could also user server.AcceptSocket() here.
        var client = _server.AcceptTcpClient();

        // Get a stream object for reading and writing
        var stream = client.GetStream();

        var pLength = new byte[8];          

        // Loop to receive all the data sent by the client.
        while(_running)
        {
            if(!stream.DataAvailable && stream.Read(pLength, 0, 8) <= 0)
                continue;            
            var nOfBytes = (int) BitConverter.ToDouble(pLength, 0);
            pLength = new byte[8];

            if (nOfBytes <= 0) 
            {
                continue;
            }

            var localBytes = new byte[nOfBytes];

            var reader = new BinaryReader(stream);
            ProcessData(reader);
        }

        // Shutdown and end connection
        client.Close();
    }
    catch (SocketException e)
    {
        _errorBool = true;
        _errorString = "Port: " + port + "\n" + e.Message + e.StackTrace;
    }
    finally
    {
        // Stop listening for new clients.
        _server.Stop();
    }
}

感谢所有试图提供帮助的人。

【讨论】:

  • localBytesnOfBytes 都未使用 - 当前代码将吞噬所有双精度,直到流完成而不注意初始长度,这可能是也可能不是你的想。如果您确实想尊重长度,只需使用BinaryReader 来读取该值(并循环value / 8 次)——当前对Read 的初始调用仍然是错误的,因为它可能返回少于8 个字节,这将使流的其余部分返回垃圾。 (对于一个数据包在前 8 个字节中分片,您确实需要非常不走运,但这并非不可能。)
  • 你是对的。当我看到一切正常时,我把它扔在这里。我有一些其他的代码让我可以看到我的流中有多少双打,所以我现在使用这些而不是使用一定数量的字节。事后看来,那里有冗余是愚蠢的。正如您所说,我完全依赖 BinaryReader 并删除了所有未使用的代码。假设我不包括更多上下文是不好的。再次感谢伙计!
猜你喜欢
  • 2011-05-22
  • 1970-01-01
  • 2011-10-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-09
  • 2013-07-29
  • 2014-06-21
相关资源
最近更新 更多