微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用int NetworkStream.ReadSpan <Bytes>接收完整的网络流

如何解决使用int NetworkStream.ReadSpan <Bytes>接收完整的网络流

正如标题所述,我正在尝试将新(C#8.0)对象(Span)用于我的网络项目。在我以前的实现中,我了解到必须强制确保NetworkStream在尝试使用其内容之前已收到完整的缓冲区,否则,取决于连接,另一端接收的数据可能并不完整。

while (true)
{
  while (!stream.DataAvailable)
  Thread.Sleep(10);

  int received = 0;
  byte[] response = new byte[consumerBufferSize];

  //Loop that forces the stream to read all incoming data before using it
  while (received < consumerBufferSize) 
    received += stream.Read(response,received,consumerBufferSize - received);

  string[] message = ObjectWrapper.ConvertByteArrayToObject<string>(response);
  consumerAction(this,message);
}

但是,引入了另一种读取网络流数据的方法(Read(Span))。并且假设stackalloc将有助于提高性能,我正在尝试迁移旧的实现以适应该方法。这是现在的样子:

while (true)
{
  while (!stream.DataAvailable)
    Thread.Sleep(10);

  Span<byte> response = stackalloc byte[consumerBufferSize];

  stream.Read(response);

  string[] message = ObjectWrapper.ConvertByteArrayToObject<string>(response).Split('|');
  consumerAction(this,message);
}

但是现在我如何确定缓冲区已被完全读取,因为它没有提供我正在使用的方法

编辑:

//Former methodd
int Read (byte[] buffer,int offset,int size);
//The one I am looking for
int Read (Span<byte> buffer,int size);

解决方法

我不确定我是否理解您的要求。使用Span<byte>时,您在第一个代码示例中依赖的所有相同功能仍然存在。

Read(Span<byte>)重载仍然返回读取的字节数。而且由于Span<byte>本身并不是缓冲区,而是缓冲区的一个窗口,因此您可以更新Span<byte>值以指示新的起点来读取其他数据。读取旧字节数并能够为下一次读取指定偏移量是您在旧示例中​​重复该功能所需要的。当然,您目前没有保存原始缓冲区引用的任何代码;您也需要添加它。

我希望这样的工作正常:

while (true)
{
  while (!stream.DataAvailable)
    Thread.Sleep(10);

  byte* response = stackalloc byte[consumerBufferSize];

  while (received < consumerBufferSize) 
  {
    Span<byte> span = new Span<byte>(response,received,consumerBufferSize - received);

    received += stream.Read(span);
  }

  // process response here...
}

请注意,由于unsafe的工作方式,因此这需要stackalloc代码。您只能通过使用Span<T>并每次分配新的块来避免这种情况。当然,这最终将耗尽您的所有内存。

由于在您的实现中您显然是将一个线程专用于此无限循环,所以我看不出stackalloc有何帮助。您不妨在堆中分配一个长寿命的缓冲区数组并使用它。

换句话说,我真的没有看到这比仅使用常规托管数组的原始Read(byte[],int,int)重载有什么好处。但是上面就是您使代码工作的方式。


此外:您应该了解异步API的工作方式。由于您已经在使用NetworkStream,因此async / await模式是很自然的选择。而且,无论使用哪种API,循环检查DataAvailable都是胡扯。不要那样做Read()方法已经是阻塞方法;您无需等待数据显示在单独的循环中,因为Read()方法只有在有一些方法时才会返回。

,

我只是添加了一点额外的信息。

你所说的功能有以下说明

public override int Read (Span<byte> buffer);

(来源:https://docs.microsoft.com/en-us/dotnet/api/system.net.sockets.networkstream.read?view=net-5.0

返回的 int 是从 NetworkStream 读取的字节量。现在,如果我们查看 Span 函数,我们会发现 Slice 具有以下描述

public Span<T> Slice (int start);

(来源:https://docs.microsoft.com/en-us/dotnet/api/system.span-1.slice?view=net-5.0#system-span-1-slice(system-int32)

它返回我们 Span 的一部分,您可以使用它在不使用不安全代码的情况下将 stackalloc 的特定部分发送到您的 NetworkStream。

重用你的代码,你可以使用这样的东西

while (true)
{
    while (!stream.DataAvailable)
        Thread.Sleep(10);

        int received = 0;
        Span<byte> response = stackalloc byte[consumerBufferSize];

        //Loop that forces the stream to read all incoming data before using it
        while (received < consumerBufferSize)
            received += stream.Read(response.Slice(received));

        string[] message = ObjectWrapper.ConvertByteArrayToObject<string>(response).Split('|');
        consumerAction(this,message);
}

简单来说,我们“创建”了一个新的 Span,它是初始 Span 的一部分,指向我们的 stackalloc 与 Slice,“start”参数允许我们选择从哪里开始这部分。然后将该部分传递给 read 函数,该函数将在我们“开始”切片的任何地方开始写入我们的缓冲区。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。