简体   繁体   中英

Receiving a complete network stream using int NetworkStream.Read(Span<Bytes>)

As the title says I am trying to use the new (C# 8.0) object (Span) for my networking project. On my previous implementation I learned that it was mandatory to make sure that a NetworkStream have received a complete buffer before trying to use its content, otherwise, depending on the connection, the data received on the other end may not be whole.

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

However, it was introduced a different approach for reading network stream data (Read(Span)). And assuming that stackalloc will help with performance I am attempting to migrate my old implementation to accomodate this method. Here is what it looks like now:

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

But now how can I be sure that the buffer was completely read since it does not provides methods like the one I was using?

Edit:

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

I'm not sure I understand what you're asking. All the same features you relied on in the first code example still exist when using Span<byte> .

The Read(Span<byte>) overload still returns the count of bytes read. And since the Span<byte> is not the buffer itself, but rather just a window into the buffer, you can update the Span<byte> value to indicate the new starting point to read additional data. Having the count of bytes read and being able to specify the offset for the next read are all you need to duplicate the functionality in your old example. Of course, you don't currently have any code that saves the original buffer reference; you'll need to add that too.

I would expect something like this to work fine:

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...
}

Note that this requires unsafe code because of the way stackalloc works. You can only avoid that by using Span<T> and allocating new blocks each time. Of course, that will eventually eat up all your stack.

Since in your implementation you apparently are dedicating a thread to this infinite loop, I don't see how stackalloc is helpful. You might as well just allocate a long-lived buffer array in the heap and use that.

In other words, I don't really see how this is better than just using the original Read(byte[], int, int) overload with a regular managed array. But the above is how you'd get the code to work.


Aside: you should learn how the async APIs work. Since you're already using NetworkStream , the async / await patterns are a natural fit. And regardless of what API you use, a loop checking DataAvailable is just plain crap. Don't do that. The Read() method is already a blocking method; you don't need to wait for data to show up in a separate loop, since the Read() method won't return until there is some.

I am just adding a little bit of additional information.

The function that you are talking about has the following description

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

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

Where the int returned is the amount of byte read from the NetworkStream. Now if we are looking at the Span functions we find Slice with the following description

public Span<T> Slice (int start);

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

Which returns a portion of our Span, which you can use to send a certain portion of your stackalloc to your NetworkStream without using unsafe code.

Reusing your Code you could use something like this

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

In simple words, we "create" a new Span that is a portion of the initial Span pointing to our stackalloc with Slice, the "start" parameter allows us to choose where to start this portion. The portion is then passed to the function read which will start writing in our buffer wherever we "started" our Slice.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM