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

时间:2010-11-23 21:56:07

标签: c# tcpclient networkstream

因此,似乎阻塞的Read()可以在接收到发送给它的所有数据之前返回。反过来,我们用一个循环来包装Read(),该循环由相关流中的DataAvailable值控制。问题是你可以在这个while循环中接收更多数据,但是没有幕后处理让系统知道这一点。我在网上找到的大部分解决方案都不适用于我。

我最终做的是作为循环的最后一步,在从流中读取每个块之后,我做了一个简单的Thread.Sleep(1)。这似乎给系统提供了更新的时间,而且我没有得到准确的结果,但这对于解决方案而言似乎有些过时且非常“间接”。

以下列出了我正在处理的情况:IIS应用程序和独立应用程序之间的单个TCP连接,都是用C#编写的,用于发送/接收通信。它发送请求然后等待响应。这个请求是由HTTP请求发起的,但是我没有从HTTP请求中读取数据这个问题,它是事后的。

以下是处理传入连接的基本代码

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

所有反馈都欢迎提供更好的解决方案,或者只是赞一下需要让Sleep(1)在我们检查DataAvailable值之前允许正确更新。

猜猜我希望在2年之后this question的答案不是现状仍然如此:)

5 个答案:

答案 0 :(得分:9)

我看到了这个问题 您期望通信速度比while()循环快,这是不太可能的 只要没有更多数据,while()循环就会完成,这可能不是退出后几毫秒的情况。

你期待一定数量的字节吗? OnClientCommunication()多久被解雇一次?是谁触发了它?

while()循环后,您如何处理数据?你是否继续追加以前的数据?

DataAvailable 返回false,因为您的阅读速度比通信速度快,所以只有在您不断回到此代码块处理更多数据时才会这样做。

答案 1 :(得分:8)

您必须知道需要阅读多少数据;你不能简单地循环读取数据,直到没有更多的数据,因为你永远不能确定不会再有了。

这就是HTTP GET结果在HTTP标头中有字节数的原因:因此客户端将知道它何时收到了所有数据。

根据您是否可以控制对方发送的内容,以下是两种解决方案:

  1. 使用“成帧”字符:(SB)数据(EB),其中SB和EB是起始块和结束块字符(根据您的选择),但不能在数据内部出现。当你“看到”EB时,你知道你已经完成了。

  2. 在每条消息前面实现一个长度字段,以指示后面有多少数据:(len)数据。读(len),然后读(len)字节;必要时重复。

  3. 这与读取零长度读取意味着数据结束的文件不同(这意味着另一方已断开连接,但这是另一个故事)。

    第三种(不推荐)解决方案是您可以实现计时器。 一旦开始获取数据,请设置计时器。如果接收循环空闲一段时间(比如几秒钟,如果数据不常出现),您可能会假设没有更多的数据即将到来。最后一种方法是最后的手段......它不是很可靠,很难调整,而且很脆弱。

答案 2 :(得分:2)

我试图在从网络流中读取数据之前检查DataAvailable并且它将返回false,尽管在读取单个字节后它将返回true。所以我检查了MSDN文档,他们也在检查前阅读。我会将while循环重新安排到do while循环以遵循这种模式。

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

答案 3 :(得分:2)

当我有这段代码时:

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

从我可以观察到:

  • 当发件人发送1000个字节并且读者想要阅读它们时。然后我怀疑NetworkStream不知何故"知道"它应该收到1000个字节。
  • 当我从NetworkStream到达任何数据之前调用.Read然后.Read应该阻塞,直到它获得超过0个字节(或更多,如果.NoDelay在networkStream上为false)
  • 然后,当我读第一批数据时,我怀疑.Read以某种方式从它的结果更新了NetworkStream上那1000个字节的计数器,在此之前我怀疑,在这个时候.DataAvailable被设置为false并且之后更新计数器,然后如果计数器数据小于1000字节,则将.DataAvailable设置为正确的值。当你想到它时,它是有道理的。因为否则它会在检查1000个字节到达之前进入下一个周期并且.Read方法将无限期地阻塞,因为读者可能已经读取了1000个字节而没有更多数据到达。
  • 我认为这是失败的原因,詹姆斯已经说过:
  

是的,这就是这些图书馆工作的方式。他们需要有时间运行以完全验证传入的数据。 - James Apr 20' 16 at 5:24

  • 我怀疑.Read结尾和访问.DataAvailable之前内部计数器的更新不是原子操作(事务),因此TcpClient需要更多时间来正确设置DataAvailable。

当我有这段代码时:

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

然后NetworkStream有足够的时间来正确设置.DataAvailable,这个方法应该能正常运行。

有趣的事实......这似乎是某种程度上依赖于操作系统版本。因为没有睡眠的第一个功能在Win XP和Win 10上为我工作,但是在Win 7上没有收到整整1000个字节。不要问我为什么,但我对它进行了彻底的测试,并且它很容易重现。< / p>

答案 4 :(得分:0)

使用TcpClient.Available将允许此代码精确地读取每次可用的内容。当要读取的剩余数据量大于或等于TcpClient.ReceiveBufferSize时,TcpClient.Available将自动设置为TcpClient.ReceiveBufferSize。否则,将其设置为剩余数据的大小。 因此,您可以通过设置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();
        }