如何停止从 NetworkStream 读取?

Posted

技术标签:

【中文标题】如何停止从 NetworkStream 读取?【英文标题】:How to stop reading from NetworkStream? 【发布时间】:2017-08-23 03:26:57 【问题描述】:

我正在尝试从 NetworkStream 读取数据。我写下一段代码:

Imports System.Net
Imports System.Net.Sockets

Public Class Form1
    Dim tcpclnt1 As New TcpClient
    Dim streamTcp1 As NetworkStream
    Dim DataBuffer(1024) As Byte    'Buffer for reading
    Dim numberOfBytes As Integer
    ' event for reading data from stream
    Dim evtDataArrival As New AsyncCallback(AddressOf DataProcessing) 

    Private Sub Btn_Connect5000_Click(sender As Object, e As EventArgs)_
            Handles Btn_Connect5000.Click
        ' Connecting to server
        tcpclnt1 = New TcpClient   
        tcpclnt1.Connect("192.168.1.177", 5000)
        streamTcp1 = tcpclnt1.GetStream()   'Create stream for current tcpClient
        ' HERE WE START TO READ
        streamTcp1.BeginRead(DataBuffer, 0, DataBuffer.Length, evtDataArrival, Nothing) 

    End Sub

    Public Sub DataProcessing(ByVal dr As IAsyncResult)
        numberOfBytes = streamTcp1.EndRead(dr) 'END READ
        ' ...HERE SOME ROUTINE FOR PRINT DATA TO TEXTBOXES...
        'START NEW READING 
        streamTcp1.BeginRead(DataBuffer, 0, DataBuffer.Length, evtDataArrival, Nothing)
    End Sub

    Private Sub Btn_Disconnect5000_Click(sender As Object, e As EventArgs)_
            Handles Btn_Disconnect5000.Click
        ' Disconnect from port 5000. Close TcpClient
        streamTcp1.Dispose()
        streamTcp1.EndRead(Nothing) 'And here mistake appears !!!
        streamTcp1.Close()
        tcpclnt1.Close()
    End Sub
End Class

问题:我创建了新客户端和新流。据我了解,通过使用BeginRead,它开始在新线程中读取数据。所以为了实时数据,我在DataProcessing函数的末尾开始新的BeginRead。但是我在尝试断开连接时遇到了问题(查看Btn_Disconnect5000_Click 函数):我尝试关闭流和客户端,但它仍然尝试在DataProcessing 方法中读取并告诉我:

无法访问已处置的对象

(感谢 djv 的正确翻译!)。

所以我想我需要先停止线程但不知道如何做到这一点:我尝试了Dispose() 方法,先尝试关闭流但仍然不能。我也尝试手动调用EndRead 方法,但无法理解我必须分配给它作为参数(参数)的内容。

【问题讨论】:

也许这会有所帮助:***.com/questions/11785735/… 您的异常消息的英文翻译是Cannot access a disposed object 正如莲花链接中的答案所建议的那样,尝试关闭NetworkStream 而不是调用EndRead(我的回答在某种程度上解释了为什么你也不需要在那里调用EndRead)。 很高兴您提出与 TCP 相关的问题!这是我的专长之一,但在 VB.NET 中很少有任何 TCP 问题,而 C# 中的问题要么是我无法回答的问题,要么是 OP 复制粘贴整个应用程序的问题。 ;) @VisualVincent 我也很高兴!因为通常这样的问题是一个高度专业化的问题,找到答案真的很棘手:) 我真的很感谢你的帮助!我很高兴你发现它们很有趣! 【参考方案1】:

只需关闭套接字而不调用EndRead

正如微软文档所说,在您调用EndRead 时,您正在阻止套接字读取操作:

此方法阻塞,直到 I/O 操作完成。

另外,您在 EndRead 之前调用方法 Dispose,这释放了 Stream 使用的所有资源,这就是您出现该异常的原因。

考虑关闭套接字,这将释放套接字正在使用的所有资源,就像这样:

Private Sub Btn_Disconnect5000_Click(sender As Object, e As EventArgs)_
    tcpclnt1.Close()
End Sub

升级:考虑捕获方法DataProcessing 中的EndRead 异常。在您关闭套接字的那一刻,System.ObjectDisposedException 将被引发并且异常消息是无法访问已处置的对象。。发生这种情况是因为您在 EndRead 中阻塞了套接字,并且在调用 tcpclnt1.Close() 的那一刻,它被解除阻塞并释放套接字的所有资源(已释放)并引发异常。

所以你的DataProcessing 应该是这样的:

Public Sub DataProcessing(ByVal dr As IAsyncResult)
    Try
        numberOfBytes = streamTcp1.EndRead(dr) 'END READ
        ' ...HERE SOME ROUTINE FOR PRINT DATA TO TEXTBOXES...
        'START NEW READING 
        streamTcp1.BeginRead(DataBuffer, 0, DataBuffer.Length, evtDataArrival, Nothing)
    Catch ex As Exception
        System.Console.WriteLine(ex.Message)
    End Try

End Sub

【讨论】:

我删除了Dispose()EndRead,所以现在我只使用tcpclnt1.Close,但仍然出现同样的错误:TcpClient 已经关闭,但它仍在尝试在我的内部调用numberOfBytes = streamTcp1.EndRead(dr) DataProcessing 函数。 @Mikhail_Sam 我运行了你的代码来重复你的问题,我升级了我的答案。另外我只调用tcpclnt1.Close() 来关闭套接字并释放所有资源。 是的,我通过添加异常解决了这个问题,你是对的!谢谢!【参考方案2】:

EndRead 实际上并没有停止异步读取。它获取读取的字节数,结束当前读取操作并抛出操作期间可能发生的任何异常。因此调用它不会阻止进一步的异步操作发生。

我建议你在回调中插入一个空检查,而不是退出方法并阻止BeginRead被更多地调用。

Public Sub DataProcessing(ByVal dr As IAsyncResult)
    If streamTcp1 Is Nothing Then _
        Return 'Stream is disposed, stop any further reading.

    numberOfBytes = streamTcp1.EndRead(dr)

正如 the_lotus 所说,您可能还需要添加异常处理,因为如果您不够幸运,streamTcp1 可能会在通过 null 检查后被丢弃。

您应该在回调中检查流的原因是,正如我们所讨论的,在“关闭”TCP 连接后,它会进入CLOSE_WAITTIME_WAITFIN_* 状态之一,以便操作系统可以仍然将延迟/重新传输的数据包映射到它。

NetworkStream.BeginRead() 最终调用WSARecv 在本机端注册一个异步操作,因此它不知道您的套接字是否被释放,因为它只关心连接是否处于活动状态(CLOSE_WAIT 和@ 987654332@ 连接仍被视为对操作系统处于活动状态)。即使套接字/流被释放,这也可能导致回调被调用。

【讨论】:

在极少数情况下,流是否有可能在 If 和 EndRead 之间结束?似乎唯一的选择是捕获异常。 @the_lotus :我不这么认为,或者至少不太可能......不是在当前线程中引发了回调吗?不过,无论哪种方式,异常处理都很好。 :) @the_lotus :经过测试,它似乎不是在同一个线程中引发的回调,所以可能像你说的那样存在并发问题,是的。 Vincent,@the_lotus - 我试过这段代码:问题是streamTcp1.Close()tcpclnt1.Close() 不要将Nothing 分配给我的streamTcp1 变量!它仍然处理 NetworkStream,但是这个对象的一些字段现在改变了。所以它永远不会从此函数返回。但是:我尝试在Btn_Disconnect5000_Click 函数中使用streamTcp1 = Nothing,然后它显然可以工作:) 我可以这样吗?还是使用异常是更好的方法? @Mikhail_Sam : TcpClient.Close() 也会处理流。 -- 将流设置为Nothing 实际上是一个好主意,因为它有助于表明不应再使用它。 -- 异常处理应该以任何一种方式进行,在已发布的应用程序中获取未处理的异常并不是很流行。 :)

以上是关于如何停止从 NetworkStream 读取?的主要内容,如果未能解决你的问题,请参考以下文章

调整缓冲区长度以从 NetworkStream 读取小数据

为啥不读取 NetworkStream 中的数据? .net TcpClient

C# 使用 NetworkStream 进行异步读写

NetworkStream 睡眠()问题

C# 通过tcp协议向硬件发送命令,networkstream read 读取返回信息时卡死。

读取ftp文件最后一行以后报错,无法访问已释放的对象。 对象名:System.Net.Sockets.NetworkStream