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

因此,似乎阻塞的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年后这个问题的答案不是如何仍然是:)

我看到了这个问题。
您期望通信将比while()循环更快,这是非常不可能的。
一旦没有更多数据, while()循环将立即完成,这可能不是它退出后几毫秒的情况。

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

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

DataAvailable 返回false,因为您的读取速度比通信速度快,所以只有在您继续回到此代码块以处理更多数据DataAvailable

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

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

以下是两种解决方案,具体取决于您是否可以控制另一方发送的内容:

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

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

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

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

我试图在从网络流中读取数据之前检查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."); } 

当我有这个代码时:

  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个字节而没有更多数据到达。
  • 詹姆斯说,我认为这是失败的关键所在:

是的,这就是这些图书馆工作的方式。 他们需要有时间运行以完全validation传入的数据。 – 詹姆斯2016年4月20日5:24

  • 我怀疑.Read结束之前和访问.DataAvailable之前的内部计数器更新不是primefaces操作(事务),所以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,这个方法应该正常运行。

有趣的事实……这似乎是某种程度上依赖于OS版本。 因为没有睡眠的第一个function在Win XP和Win 10上为我工作,但是在Win 7上没有收到整个1000字节。不要问我为什么,但我测试它非常彻底并且它很容易重现。