我知道TIME_WAIT是TCP / IP的一个组成部分,但在SO(和其他地方)有很多问题,每秒创建多个套接字,服务器最终耗尽短暂的端口。
我发现当使用TCPClient
(或Socket
时),如果我使用Close()
或Dispose()
方法调用套接字&#39 ; 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()
有人能说清楚这是否是一种好的做法(我想人们会说它不是)。
修改
GC已经回答了这个问题,但是我仍然有兴趣找出为什么这会对套接字状态产生任何影响,因为这应该由操作系统控制,而不是.NET。还有兴趣了解使用此方法防止TIME_WAIT状态并最终是否是某个地方的错误(即,所有套接字是否应通过TIME_WAIT状态?)
答案 0 :(得分:7)
这对我来说没有多大意义,因为这是一个IDisposable对象,GC不应该调用对象的Dispose()方法吗?
Dispose pattern,也称为IDisposable,提供了两种清除非托管对象的方法。 Dispose方法提供了一种直接快速的清理资源的方法。由垃圾收集器调用的finalize方法是一种故障安全方法,用于确保清除非托管资源,以防其他开发人员使用代码忘记调用Dispose方法。这有点类似于C ++开发人员忘记在堆分配的内存上调用Delete - 这会导致内存泄漏。
根据引用的链接:
“尽管终结器在某些清理方案中很有效,但它们有两个明显的缺点:
当GC检测到某个对象符合收集条件时,将调用终结器。这在不再需要资源之后的某个未确定的时间段发生。开发人员可能或者想要释放资源的时间与终结器实际释放资源的时间之间的延迟在获取许多稀缺资源(可能容易耗尽的资源)的程序中或者在资源的情况下可能是不可接受的。保持使用成本很高(例如,大型非托管内存缓冲区)。
当CLR需要调用终结器时,它必须推迟对象内存的收集,直到下一轮垃圾收集(终结器在集合之间运行)。这意味着对象的内存(及其引用的所有对象)将不会在较长时间内释放。“
使用此方法,套接字永远不会进入TIME_WAIT状态。如果我在手动调用Close()或Dispose()
之前关闭应用程序,则相同有人可以解释一下是否这是一个好习惯(我想人们会说这不是)。
关闭它需要一段时间的原因是因为代码lingers by default为应用程序提供了一些时间来处理任何排队的消息。根据MSDN上的TcpClient.Close方法文档:
“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秒的逗留时间。
对于非常繁忙的客户端和/或弱硬件 - 那么我可能会给它更多时间。对于服务器,我必须在加载时对不同的值进行基准测试。
其他有用的参考资料:
What is the proper way of closing and cleaning up a Socket connection?
Are there any cases when TcpClient.Close or Socket.Close(0) could block my code?
答案 1 :(得分:2)
我想提及一个关于SO_LINGER
的问题https://stackoverflow.com/a/13088864/2138959的好答案TCP option SO_LINGER (zero) - when it's required,这可能会向您澄清更多内容,以便您可以在每个特定情况下做出决定关闭套接字的方法。
总而言之,您应该按照客户端关闭连接的方式设计客户端 - 服务器通信协议,以避免服务器上出现TIME_WAIT。
答案 2 :(得分:1)
Socket class有一个相当冗长的方法protected virtual void Dispose(bool disposing)
,使用true
作为.Dispose()
和false
的参数作为析构函数中的参数调用被垃圾收集器调用。
有可能,您可以在此方法中找到处理套接字处理方面的任何差异的答案。事实上,它不会从析构函数中false
上执行任何,所以你有解释。
答案 3 :(得分:1)
我最终查找了一堆这些链接,最后将我的问题排除在外。这真的很有帮助。
在服务器端,我基本上什么都不做。接收,发送响应,退出处理程序。我确实添加了1的LingerState,但我认为它没有做任何事情。
在客户端,我使用相同的LingerState,但在收到之后(我知道数据全部存在,因为我在数据包开头基于UInt32长度接收),我关闭()客户端套接字,然后将Socket对象设置为NULL。
在同一台机器上非常积极地运行客户端和服务器,它立即清理所有套接字;我之前在TIME_WAIT离开了数千人。
答案 4 :(得分:0)
使用
tcpClient.Close(0);
指定0秒超时就足够了。