繁体   English   中英

奇怪的NetworkStream阅读问题

[英]Weird NetworkStream reading issue

目标:仅在不会阻塞时才执行NetworkStream / SslStream Read()方法。

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

    /// <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)解决方法。 没有这个,我的通信代码就在建立连接并交换了一些初始消息后就死掉了。 我什至不知道为什么。 我的代码应该只是从一个端点接收消息,对其进行检查并将其传递给另一端点。 没有1毫秒的等待,客户端只会冻结。 然后,在我杀死我的应用程序之后,客户端可以正常工作,因为它将接收所有数据,没有发现任何错误。

经过彻底的调查,我排除了代码中所有线程同步的问题。 顺便说一句,该代码使用阻塞操作,仅同步方法和尽可能少的线程。 BTW产生的基准测试结果比任何异步版本都要好得多,因为与直接交换通常小于1KB的消息相比,任务创建的成本非常高。

因此,我的流量检查工具运行稳定,经过了测试和基准测试,但是让我发疯,我不明白为什么会这样。 没有Thread.Sleep(1) ,就没有。 所有数据已正确交换,但是客户端应用程序冻结,直到我的应用程序被杀死为止。

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

您可能会想,为什么我不只是检查我的代码是否进入了阻塞读取操作并冻结。 海森堡。 Debug.WriteLineConsole.WriteLine类的任何调试都可以使它工作。 好吧,有时甚至在使用Visual Studio诊断工具运行时它也可以正常工作,但是在运行时不进行调试就会冻结。

此问题很难重现,因为它是随机发生的,并且仅在不涉及调试的情况下发生。 仅使用真实世界的客户端应用程序,不可能使用任何测试代码进行复制。 当我尝试连接基于.NET的客户端进行测试时,我出于测试目的而编写的程序从未冻结。

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

顺便说一句,我传输的协议使用TLV编码,所以我不阅读贪婪,而是阅读消息,首先是标头说明消息将持续多久,然后是数据,如果我读的内容少于标头中指定的内容,则数据可能是不完整的,我等待更多数据完成消息。 我注意到,我描述的冻结仅在收到不完整的消息时才会发生-需要在多个读取操作中读取的消息。 但是再说一次-如果我的消息处理中有错误,为什么在引入1毫秒的延迟后它可以完美工作?

我了解到这个问题非常深奥,可能描述得很模糊,有些问题会遗漏整个源代码。 但这就是事实。 这不是一个简单的案例,不能简化为一个案例。 我已经尝试测试系统的各个部分,并发现此问题仅在以下特定情况下存在:TLV消息编码,读取非贪婪,实时LDAP客户端实时读取目录树。 如果我进行了任何更改,例如将读取更改为贪婪(总是最大可用字节数),则不会发生此问题。 如果我尝试使用自己的.NET应用程序读取目录,则不会发生此问题。 如果我在读取流之前插入了任何调试指令,则不会发生此问题。

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

我讨厌这样说,但是在套接字上等待 (阻塞)可用是一个可怕的可怕主意。 您是否知道可以进行零长度异步读取,并且在数据可用时它将为您调用回调,而无需事先为其提供缓冲区? (或者至少您可以共享并重复使用static readonly byte[] ZeroLengthBuffer = new byte[0]; )然后, 数据可用时,您将获得一个有意义的缓冲区并进行一些实际读取(同步或异步,具体取决于您) )-也许要留意。套接字上.Available 零长度读取技巧适用于IIRC的任一Socket异步读取方法-因此Socket.BeginReceiveSocket.ReceiveAsync (尽管名称如此,但在async / await意义上不是async的,并且可能是同步完成的)。

暂无
暂无

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

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