简体   繁体   English

TcpClient.GetStream().DataAvailable 返回 false,但流有更多数据

[英]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.因此,阻塞 Read() 似乎可以在完成接收发送给它的所有数据之前返回。 In turn we wrap the Read() with a loop that is controlled by the DataAvailable value from the stream in question.反过来,我们用一个循环包装 Read(),该循环由相关流中的 DataAvailable 值控制。 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.问题是你可以在这个 while 循环中接收更多数据,但没有幕后处理让系统知道这一点。 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.我最终做的是作为我循环的最后一步,在从流中读取每个块后,我做了一个简单的 Thread.Sleep(1) 。 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.这是我正在处理的情况列表: IIS 应用程序和独立应用程序之间的单个 TCP 连接,两者都是用 C# 编写的,用于发送/接收通信。 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.此请求是由 HTTP 请求发起的,但我在从 HTTP 请求读取数据时没有遇到此问题,这是事后发生的。

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.欢迎所有反馈以获得更好的解决方案,或者只是对需要给 Sleep(1) 一个赞许以在我们检查 DataAvailable 值之前允许事情正确更新。

Guess I am hoping after 2 years that the answer to this question isn't how things still are :)猜猜我希望 2 年后这个问题的答案不是现在的情况:)

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.这就是为什么 HTTP GET 结果在 HTTP 标头中有一个字节计数:这样客户端就会知道它何时收到了所有数据。

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.使用“框架”字符:(SB)data(EB),其中 SB 和 EB 是开始块和结束块字符(您选择的),但不能出现在数据内部。 When you "see" EB, you know you are done.当您“看到”EB 时,您就知道您已经完成了。

  2. Implement a length field in front of each message to indicate how much data follows: (len)data.在每条消息前面实现一个长度字段来指示后面有多少数据:(len)data。 Read (len), then read (len) bytes;读取(len),然后读取(len)字节; 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.最后一种方法是最后的手段……它不是很可靠,难以调整,而且很脆弱。

I'm seeing a problem with this.我发现这有问题。
You're expecting that the communication will be faster than the while() loop, which is very unlikely.您期望通信将比while()循环更快,这不太可能。
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. while()循环将在没有更多数据时立即结束,这可能不是它退出后几毫秒的情况。

Are you expecting a certain amount of bytes?你期待一定数量的字节吗?
How often is OnClientCommunication() fired? OnClientCommunication()多久触发一次? Who triggers it?谁触发它?

What do you do with the data after the while() loop? while()循环后的数据怎么办? 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. DataAvailable返回 false,因为您的读取速度比通信速度快,因此只有当您继续返回此代码块以处理DataAvailable更多数据时才可以。

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.我试图在从网络流读取数据之前检查 DataAvailable 并且它会返回 false,尽管在读取单个字节后它会返回 true。 So I checked the MSDN documentation and they also read before checking.所以我检查了 MSDN 文档,他们在检查之前也阅读了。 I would re-arrange the while loop to a do while loop to follow this pattern.我会将 while 循环重新安排为 do while 循环以遵循此模式。

http://msdn.microsoft.com/en-us/library/system.net.sockets.networkstream.dataavailable.aspx 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.");
        }

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.当发送方发送 1000 个字节并且读者想要读取它们时。 Then I suspect that NetworkStream somehow "knows" that it should receive 1000 bytes.然后我怀疑 NetworkStream 以某种方式“知道”它应该接收 1000 个字节。
  • 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)当我在任何数据从 NetworkStream 到达之前调用 .Read 时, .Read 应该阻塞,直到它得到超过 0 个字节(或者更多,如果 .NoDelay 在 networkStream 上为 false)
  • 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.然后,当我读取第一批数据时,我怀疑 .Read 以某种方式从其结果更新了 NetworkStream 上那 1000 个字节的计数器,在此之前我怀疑,此时 .DataAvailable 设置为 false 并且在计数器之后如果计数器数据小于 1000 字节,则更新 .DataAvailable 然后设置为正确的值。 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.因为否则它会在检查 1000 个字节到达之前进入下一个循环,并且 .Read 方法将无限期阻塞,因为读取器可能已经读取了 1000 个字节并且不会有更多数据到达。
  • 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 – 詹姆斯 2016 年 4 月 20 日,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.我怀疑 .Read 结束和访问 .DataAvailable 之前内部计数器的更新不是原子操作(事务),因此 TcpClient 需要更多时间来正确设置 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.然后 NetworkStream 有足够的时间正确设置 .DataAvailable 并且此方法应该正常运行。

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.因为第一个没有睡眠的功能在 Win XP 和 Win 10 上对我有用,但在 Win 7 上却无法接收整个 1000 字节。不要问我为什么,但我对其进行了非常彻底的测试,并且很容易重现。

Using TcpClient.Available will allow this code to read exactly what is available each time.使用 TcpClient.Available 将允许此代码每次准确读取可用内容。 TcpClient.Available is automatically set to TcpClient.ReceiveBufferSize when the amount of data remaining to be read is greater than or equal to TcpClient.ReceiveBufferSize.当要读取的剩余数据量大于或等于 TcpClient.ReceiveBufferSize 时,TcpClient.Available 会自动设置为 TcpClient.ReceiveBufferSize。 Otherwise it is set to the size of the remaining data.否则,它被设置为剩余数据的大小。 Hence, you can indicate the maximum amount of data that is available for each read by setting TcpClient.ReceiveBufferSize (eg, oClient.ReceiveBufferSize = 4096;).因此,您可以通过设置 TcpClient.ReceiveBufferSize(例如,oClient.ReceiveBufferSize = 4096;)来指示每次读取可用的最大数据量。

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

            byte[] aMessage;
            oClient.ReceiveBufferSize = 4096;
            int iBytesRead = 0;

            while (stream.DataAvailable)
            {
                int myBufferSize = (oClient.Available < 1) ? 1 : oClient.Available;
                aMessage = new byte[oClient.Available];

                int iRead = stream.Read(aMessage, 0, aMessage.Length);
                iBytesRead += iRead;
                msIn.Write(aMessage, 0, iRead);
            }
            MemoryStream msOut = new MemoryStream();

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

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

            oClient.Close();
        }
public class NetworkStream
{
    private readonly Socket m_Socket;

    public NetworkStream(Socket socket)
    {
        m_Socket = socket ?? throw new ArgumentNullException(nameof(socket));
    }

    public void Send(string message)
    {
        if (message is null)
        {
            throw new ArgumentNullException(nameof(message));
        }

        byte[] data = Encoding.UTF8.GetBytes(message);
        SendInternal(data);
    }

    public string Receive()
    {
        byte[] buffer = ReceiveInternal();
        string message = Encoding.UTF8.GetString(buffer);
        return message;
    }

    private void SendInternal(byte[] message)
    {
        int size = message.Length;

        if (size == 0)
        {
            m_Socket.Send(BitConverter.GetBytes(size), 0, sizeof(int), SocketFlags.None);
        }
        else
        {
            m_Socket.Send(BitConverter.GetBytes(size), 0, sizeof(int), SocketFlags.None);
            m_Socket.Send(message, 0, size, SocketFlags.None);
        }
    }

    private byte[] ReceiveInternal()
    {
        byte[] sizeData = CommonReceiveMessage(sizeof(int));
        int size = BitConverter.ToInt32(sizeData);

        if (size == 0)
        {
            return Array.Empty<byte>();
        }

        return CommonReceiveMessage(size);
    }

    private byte[] CommonReceiveMessage(int messageLength)
    {
        if (messageLength < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(messageLength), messageLength, "Размер сообщения не может быть меньше нуля.");
        }

        if (messageLength == 0)
        {
            return Array.Empty<byte>();
        }

        byte[] buffer = new byte[m_Socket.ReceiveBufferSize];
        int currentLength = 0;
        int receivedDataLength;

        using (MemoryStream memoryStream = new())
        {
            do
            {
                receivedDataLength = m_Socket.Receive(buffer, 0, m_Socket.ReceiveBufferSize, SocketFlags.None);
                currentLength += receivedDataLength;
                memoryStream.Write(buffer, 0, receivedDataLength);
            }
            while (currentLength < messageLength);

            return memoryStream.ToArray();
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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