奇怪的NetworkStream阅读问题

时间:2017-03-30 08:14:25

标签: c# sockets

目标:只有当Read() / NetworkStream不会阻止时,才会执行SslStream / /// <summary> /// Blocks the current thread until this stream has data available for reading or the token is canceled. /// </summary> /// <param name="token">The object that allows this operation to be canceled.</param> /// <returns>True if data is available to be read, false if canceled.</returns> public bool WaitDataAvailable(CancellationToken token) { lock (Socket) { while (!Socket.Poll(PollingInterval, SelectMode.SelectRead) && !token.IsCancellationRequested) ; Thread.Sleep(1); // THIS IS A ONE CRAZY HACK HERE!!! Why is it necessary? return !token.IsCancellationRequested && Socket.Connected; } } 方法。

这是我拥有的,而且是一种解决方法

Thread.Sleep(1)

我花了两个星期的时间将头撞在墙上以找出Thread.Sleep(1)解决方法。没有这个,我的通信代码就在建立连接并交换几条初始消息后立即死亡。我甚至无法弄明白为什么。我的代码应该只从一个端点接收消息,检查它并将其传递给另一个端点。没有1ms等待客户端就冻结了。然后,在我杀死我的应用程序后,客户端正常工作,因为它会收到所有数据,没有检测到任何错误。

经过彻底调查后,我排除了代码中的所有线程同步问题。顺便说一句,代码使用阻塞操作,只使用同步方法和尽可能少的线程。 BTW这比任何异步版本产生了更好的基准测试结果,因为与直接交换大多数短于1KB的消息相比,任务创建相当昂贵。

所以我的流量检查工具运行稳定,经过测试和基准测试,但它让我发疯,我不明白为什么它有效。没有Debug.WriteLine它没有。所有数据都正确交换,但客户端应用程序冻结,直到我的应用程序被杀死。

我还发现大约5微秒的等待时间足以让它在大多数时间内正常工作。

您可能会想到为什么我不检查我的代码是否进入阻塞读取操作并冻结。海森堡。任何类似Console.WriteLine或{{1}}的调试都可以使其正常运行。好吧,有时它甚至可以在使用Visual Studio诊断工具运行时正常工作,但在没有调试的情况下运行时会冻结。

这里的问题很难重现,因为它是随机发生的,只有在不涉及调试时才会发生。使用任何测试代码都无法重现,只能使用真实的客户端应用程序。当我试图测试连接基于.NET的客户端时,我为了测试目的而编写它从未冻结。

那么,为什么我要在阅读流之前等待,我还在等什么呢?

BTW,我传输的协议使用TLV编码,所以我不读贪婪,我读消息,首先是告诉消息将会有多长的标题,然后是数据,如果我读的少于在标题中指定,我等待更多数据来完成消息。我注意到我描述的冻结只有在我得到不完整的消息时才会发生 - 这些消息需要在多个读操作中读取。但话又说回来 - 如果我的消息处理中存在错误,为什么在引入1ms延迟时它会完美运行?

我理解这个问题非常深奥,可能含糊不清,有些人会错过整个源代码。但这就是它。这不是一个简单的案例,不能简化为一个案例。我已经尝试测试系统的各个部分,发现只有在这种特殊情况下才存在这个问题:TLV消息编码,读取非贪婪,实时LDAP客户端读取目录树。如果我改变任何东西,例如读取贪婪(总是最大可用字节数) - 问题就不会发生。如果我尝试使用自己的.NET应用程序读取目录 - 则不会发生此问题。如果我在读取流之前插入任何调试指令 - 则不会发生此问题。

我测试过的最简单的案例是使用我的代码作为LDAP代理并使用LdapAdmin读取目录树。我知道这不是特定的客户端错误,因为LDP也冻结了,但并非总是如此。

1 个答案:

答案 0 :(得分:3)

我不想这么说,但是等待(阻塞)套接字变得可用是一个可怕的坏主意。您是否知道可以执行零长度异步读取,并且在数据可用时它将为您调用回调,而无需事先为其提供缓冲区? (或者至少你可以共享和重复使用static readonly byte[] ZeroLengthBuffer = new byte[0];)然后数据可用时,你会得到一个合理的缓冲区并做一些实际的阅读(同步或异步,由你决定) - 也许要关注套接字上的.Available。这个零长度读取技巧适用于任何Socket异步读取方法,IIRC - 所以Socket.BeginReceiveSocket.ReceiveAsync(尽管名称不是async async {1}} / await感觉,可以同步完成。)