Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TcpClient.GetStream().DataAvailable returns false, but stream has more data

So, it would seem that a blocking Read() can return before it is done receiving all of the data being sent to it. In turn we wrap the Read() with a loop that is controlled by the DataAvailable value from the stream in question. The problem is that you can receive more data while in this while loop, but there is no behind the scenes processing going on to let the system know this. Most of the solutions I have found to this on the net have not been applicable in one way or another to me.

What I have ended up doing is as the last step in my loop, I do a simple Thread.Sleep(1) after reading each block from the stream. This appears to give the system time to update and I am not getting accurate results but this seems a bit hacky and quite a bit 'circumstantial' for a solution.

Here is a list of the circumstances I am dealing with: Single TCP Connection between an IIS Application and a standalone application, both written in C# for send/receive communication. It sends a request and then waits for a response. This request is initiated by an HTTP request, but I am not having this issue reading data from the HTTP Request, it is after the fact.

Here is the basic code for handling an incoming connection

protected void OnClientCommunication(TcpClient oClient)
{
    NetworkStream stream = oClient.GetStream();
    MemoryStream msIn = new MemoryStream();

    byte[] aMessage = new byte[4096];
    int iBytesRead = 0;

    while ( stream.DataAvailable )
    {
        int iRead = stream.Read(aMessage, 0, aMessage.Length);
        iBytesRead += iRead;
        msIn.Write(aMessage, 0, iRead);
        Thread.Sleep(1);
    }
    MemoryStream msOut = new MemoryStream();

    // .. Do some processing adding data to the msOut stream

    msOut.WriteTo(stream);
    stream.Flush();

    oClient.Close();
}

All feedback welcome for a better solution or just a thumbs up on needing to give that Sleep(1) a go to allow things to update properly before we check the DataAvailable value.

Guess I am hoping after 2 years that the answer to this question isn't how things still are :)

like image 514
James Avatar asked Nov 23 '10 21:11

James


4 Answers

I'm seeing a problem with this.
You're expecting that the communication will be faster than the while() loop, which is very unlikely.
The while() loop will finish as soon as there is no more data, which may not be the case a few milliseconds just after it exits.

Are you expecting a certain amount of bytes?
How often is OnClientCommunication() fired? Who triggers it?

What do you do with the data after the while() loop? Do you keep appending to previous data?

DataAvailable WILL return false because you're reading faster than the communication, so that's fine only if you keep coming back to this code block to process more data coming in.

like image 148
BeemerGuy Avatar answered Oct 23 '22 09:10

BeemerGuy


You have to know how much data you need to read; you cannot simply loop reading data until there is no more data, because you can never be sure that no more is going to come.

This is why HTTP GET results have a byte count in the HTTP headers: so the client side will know when it has received all the data.

Here are two solutions for you depending on whether you have control over what the other side is sending:

  1. Use "framing" characters: (SB)data(EB), where SB and EB are start-block and end-block characters (of your choosing) but which CANNOT occur inside the data. When you "see" EB, you know you are done.

  2. Implement a length field in front of each message to indicate how much data follows: (len)data. Read (len), then read (len) bytes; repeat as necessary.

This isn't like reading from a file where a zero-length read means end-of-data (that DOES mean the other side has disconnected, but that's another story).

A third (not recommended) solution is that you can implement a timer. Once you start getting data, set the timer. If the receive loop is idle for some period of time (say a few seconds, if data doesn't come often), you can probably assume no more data is coming. This last method is a last resort... it's not very reliable, hard to tune, and it's fragile.

like image 30
Larry Avatar answered Oct 23 '22 10:10

Larry


I was trying to check DataAvailable before reading data from a network stream and it would return false, although after reading a single byte it would return true. So I checked the MSDN documentation and they also read before checking. I would re-arrange the while loop to a do while loop to follow this pattern.

http://msdn.microsoft.com/en-us/library/system.net.sockets.networkstream.dataavailable.aspx

        // Check to see if this NetworkStream is readable. 
        if(myNetworkStream.CanRead){
            byte[] myReadBuffer = new byte[1024];
            StringBuilder myCompleteMessage = new StringBuilder();
            int numberOfBytesRead = 0;

            // Incoming message may be larger than the buffer size. 
            do{
                 numberOfBytesRead = myNetworkStream.Read(myReadBuffer, 0, myReadBuffer.Length);

                 myCompleteMessage.AppendFormat("{0}", Encoding.ASCII.GetString(myReadBuffer, 0, numberOfBytesRead));

            }
            while(myNetworkStream.DataAvailable);

            // Print out the received message to the console.
            Console.WriteLine("You received the following message : " +
                                         myCompleteMessage);
        }
        else{
             Console.WriteLine("Sorry.  You cannot read from this NetworkStream.");
        }
like image 2
Despertar Avatar answered Oct 23 '22 09:10

Despertar


When I have this code:

    var readBuffer = new byte[1024];
    using (var memoryStream = new MemoryStream())
    {
        do
        {
            int numberOfBytesRead = networkStream.Read(readBuffer, 0, readBuffer.Length);
            memoryStream.Write(readBuffer, 0, numberOfBytesRead);
        }
        while (networkStream.DataAvailable);
    }

From what I can observe:

  • When sender sends 1000 bytes and reader wants to read them. Then I suspect that NetworkStream somehow "knows" that it should receive 1000 bytes.
  • When I call .Read before any data arrives from NetworkStream then .Read should be blocking until it gets more than 0 bytes (or more if .NoDelay is false on networkStream)
  • Then when I read first batch of data I suspect that .Read is somehow updating from its result the counter of those 1000 bytes at NetworkStream and before this happens I suspect, that in this time the .DataAvailable is set to false and after the counter is updated then the .DataAvailable is then set to correct value if the counter data is less than 1000 bytes. It makes sense when you think about it. Because otherwise it would go to the next cycle before checking that 1000 bytes arrived and the .Read method would be blocking indefinitely, because reader could have already read 1000 bytes and no more data would arrive.
  • This I think is the point of failure here as already James said:

Yes, this is just the way these libraries work. They need to be given time to run to fully validate the data incoming. – James Apr 20 '16 at 5:24

  • I suspect that the update of internal counter between end of .Read and before accessing .DataAvailable is not as atomic operation (transaction) so the TcpClient needs more time to properly set the DataAvailable.

When I have this code:

    var readBuffer = new byte[1024];
    using (var memoryStream = new MemoryStream())
    {
        do
        {
            int numberOfBytesRead = networkStream.Read(readBuffer, 0, readBuffer.Length);
            memoryStream.Write(readBuffer, 0, numberOfBytesRead);

            if (!networkStream.DataAvailable)
                System.Threading.Thread.Sleep(1); //Or 50 for non-believers ;)
        }
        while (networkStream.DataAvailable);
    }

Then the NetworkStream have enough time to properly set .DataAvailable and this method should function correctly.

Fun fact... This seems to be somehow OS Version dependent. Because the first function without sleep worked for me on Win XP and Win 10, but was failing to receive whole 1000 bytes on Win 7. Don't ask me why, but I tested it quite thoroughly and it was easily reproducible.

like image 2
Jacob Avatar answered Oct 23 '22 10:10

Jacob