要关闭套接字,请不要关闭()套接字。 Uhmm?

我知道TIME_WAIT是TCP / IP的一个组成部分,但是在SO(和其他地方)有很多问题,每秒创建多个套接字,服务器最终耗尽短暂的端口。

我发现当使用TCPClient (或Socket )时,如果我调用Close()Dispose()方法,套接字的TCP状态将更改为TIME_WAIT,并将在完全关闭之前遵循超时时间。

但是,如果它只是将变量设置为null则套接字将在下一次GC运行时完全关闭,这当然可以强制执行,而不会经历TIME_WAIT状态。

这对我来说没有多大意义,因为这是一个IDisposable对象,不应该GC也调用对象的Dispose()方法?

这是一些PowerShell代码,演示了(在这台机器上没有安装VS)。 我使用来自Sysinternals的TCPView实时检查套接字状态:

 $sockets = @() 0..100 | % { $sockets += New-Object System.Net.Sockets.TcpClient $sockets[$_].Connect('localhost', 80) } Start-Sleep -Seconds 10 $sockets = $null [GC]::Collect() 

使用此方法,套接字永远不会进入TIME_WAIT状态。 如果我在手动调用Close()Dispose()之前Close()应用程序,则相同

有人可以解释一下这是否是一个好的做法(我想人们会说这不是)。

编辑

GC已经回答了这个问题,但我仍然有兴趣找出为什么这会对套接字状态产生任何影响,因为这应该由操作系统控制,而不是.NET。

还有兴趣了解使用此方法来防止TIME_WAIT状态并最终是否是某个地方的错误(即,所有套接字是否应该通过TIME_WAIT状态?)是否是一个好习惯?

这对我来说没有多大意义,因为这是一个IDisposable对象,不应该GC也调用对象的Dispose()方法?

Dispose模式 (也称为IDisposable)提供了两种清除非托管对象的方法。 Dispose方法提供了一种直接快速的清理资源的方法。 由垃圾收集器调用的finalize方法是一种故障安全方法,用于确保清除非托管资源,以防另一个使用该代码的开发人员忘记调用Dispose方法。 这有点类似于C ++开发人员忘记在堆分配的内存上调用Delete – 这会导致内存泄漏。

根据引用的链接:

“尽管终结器在某些清理方案中有效,但它们有两个明显的缺点:

  1. 当GC检测到对象符合收集条件时,将调用终结器。 这在不再需要资源之后的某个未确定的时间段发生。 开发人员可能或者想要释放资源的时间与终结器实际释放资源的时间之间的延迟在获取许多稀缺资源(可以轻易耗尽的资源)的程序中或者在资源的情况下可能是不可接受的。保持使用是昂贵的(例如,大的非托管内存缓冲区)。

  2. 当CLR需要调用终结器时,它必须推迟对象内存的收集,直到下一轮垃圾收集(终结器在集合之间运行)。 这意味着对象的内存(以及它引用的所有对象)将不会在更长的时间内释放。“

使用此方法,套接字永远不会进入TIME_WAIT状态。 如果我在手动调用Close()或Dispose()之前关闭应用程序,则相同

有人可以解释一下这是否是一个好的做法(我想人们会说这不是)。

关闭它需要一段时间的原因是因为默认情况下代码会延迟给应用程序一些时间来处理任何排队的消息。 根据MSDN上的TcpClient.Close方法doc:

“Close方法将实例标记为已配置,并请求关联的Socket关闭TCP连接。基于LingerState属性,在调用Close方法后,当数据仍有待发送时,TCP连接可能会保持打开一段时间。底层连接完成关闭时未提供通知。

调用此方法最终将导致关联的Socket关闭,并且还将关闭用于发送和接收数据的相关NetworkStream(如果已创建)。

可以通过以下代码减少或完全消除此超时值:

 // Allow 1 second to process queued msgs before closing the socket. LingerOption lingerOption = new LingerOption (true, 1); tcpClient.LingerState = lingerOption; tcpClient.Close(); // Close the socket right away without lingering. LingerOption lingerOption = new LingerOption (true, 0); tcpClient.LingerState = lingerOption; tcpClient.Close(); 

还有兴趣了解使用此方法来防止TIME_WAIT状态并最终是否是某个地方的错误(即,所有套接字是否应该通过TIME_WAIT状态?)是否是一个好习惯?

至于将对TcpClient对象的引用设置为null,建议的方法是调用Close方法。 当引用设置为null时,GC最终调用finalize方法。 finalize方法最终调用Dispose方法以合并代码以清理非托管资源。 因此,它将关闭套接字 – 它只是不推荐。

在我看来,这取决于应用程序是否应该允许一些延迟时间给予应用程序时间来处理排队的消息。 如果我确定我的客户端应用程序处理了所有必要的消息,那么如果我认为将来可能会改变,我可能会给它一个0秒或者1秒的延迟时间。

对于非常繁忙的客户端和/或弱硬件 – 那么我可能会给它更多时间。 对于服务器,我必须在加载下对不同的值进行基准测试。

其他有用的参考:

关闭和清理Socket连接的正确方法是什么?

有没有TcpClient.Close或Socket.Close(0)可以阻止我的代码的情况?

@Bob Bryan在我准备我的时候发布了相当不错的答案。 它说明了为什么要避免终结器以及如何中断关闭连接以避免服务器上出现TIME_WAIT问题。

我想参考一个很好的答案https://stackoverflow.com/a/13088864/2138959关于SO_LINGER来询问TCP选项SO_LINGER(零) – 当它需要时 ,这可能会更清楚地向你澄清事情,以便你可以制作您在每个特定情况下决定关闭套接字使用哪种方法。

总而言之,您应该按照客户端关闭连接的方式设计客户端 – 服务器通信协议,以避免服务器上出现TIME_WAIT。

Socket类有一个相当冗长的方法protected virtual void Dispose(bool disposing) .Dispose() protected virtual void Dispose(bool disposing) ,调用true作为.Dispose()的参数, false作为垃圾收集器调用的析构函数的参数。

有可能,您可以在此方法中找到处理套接字处理方面的任何差异的答案。 事实上,它对析构函数的false没有任何作用,所以你有解释。

我最终查找了一堆这些链接,最后将我的问题排除在外。 这真的很有帮助。

在服务器端,我基本上什么都不做。 接收,发送响应,退出处理程序。 我确实添加了1的LingerState,但我认为它没有做任何事情。

在客户端,我使用相同的LingerState,但在收到之后(我知道数据全部存在,因为我在数据包开头基于UInt32长度接收),我关闭()客户端套接字,然后将Socket对象设置为NULL。

在同一台机器上真正运行客户端和服务器,它立即清理所有套接字; 我之前在TIME_WAIT离开了数千人。

使用

 tcpClient.Close(0); 

足以指定0秒超时。