【发布时间】:2017-04-08 17:02:38
【问题描述】:
我知道 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() 之前关闭应用程序,也是一样的
有人可以解释一下这是否是一个好的做法(我想人们会说它不是)。
编辑
GC 在这件事上的利益已经得到解答,但我仍然想知道为什么这会对套接字状态产生任何影响,因为这应该由操作系统而不是 .NET 控制。
也有兴趣了解使用此方法来防止 TIME_WAIT 状态是否是一种好习惯,并最终确定这是否是某个地方的错误(即,是否所有套接字都应该经历 TIME_WAIT 状态?)
【问题讨论】:
-
"GC不应该也调用对象的
Dispose()方法吗?" GC 从不 调用Dispose。它只调用类的终结器(如果有的话)。通常,Dispose()是从终结器调用的,但这需要重复:Dispose仅用于using。 GC 根本不关心它。 -
这是对 GC 参与所有这一切的一个很好的解释,谢谢!
-
请注意,您的代码没有给 GC 时间来实际收集套接字,因为它们具有终结器。您需要执行
GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();来强制执行此操作。您的GC.Collect();调用只是将套接字放入终结器队列(如果是的话) - 套接字终结器可能需要很长时间(实际上通常会杀死您的进程)。套接字最有可能不在 TIME_WAIT 中,因为它们甚至还没有关闭。当您终止进程时也是如此 - 关闭 TCP 套接字实际上并非易事,即使对于操作系统也是如此。列出所有端口,而不仅仅是 TIME_WAIT。 -
我没有过滤 TIME_WAIT 套接字,我正在查看 all 套接字,无论其状态如何。在我告诉 GC 运行之前,我可以看到它们都已建立。在我告诉 GC 运行大约 2 秒后,它们都完全消失而没有先进入 TIME_WAIT。如果我首先
Close()或Dispose(),那么它们会从ESTABLISHED 转到TIME_WAIT。所以,虽然我的代码缺少某些东西你可能是对的,但绝对不是因为它们还没有关闭而没有进入 TIME_WAIT。
标签: c# .net sockets powershell