问题描述
我知道TIME_WAIT是TCP/IP不可或缺的一部分,但是在SO(以及其他地方)存在很多问题,即每秒创建多个套接字,并且服务器最终会耗尽临时端口.
I know that TIME_WAIT is an integral part of TCP/IP, but there's many questions on SO (and other places) where multiple sockets are being created per second and the server ends up running out of ephemeral ports.
我发现的是,当我使用 TCPClient
(或针对此问题的 Socket
)时,如果我调用 Close()
或 Dispose()
方法,套接字的TCP状态更改为TIME_WAIT,并将在完全关闭之前遵守超时时间.
What I found out is that when using a TCPClient
(or Socket
for that matter), if I call either the Close()
or Dispose()
methods the socket's TCP state changes to TIME_WAIT and will respect the timeout period before fully closing.
但是,如果仅将变量设置为 null
,则套接字将在下一次GC运行时完全关闭,这当然可以强制执行,而不会经历TIME_WAIT状态.
However, if It just set the variable to null
the socket will be fully closed on the next GC run, which can of course be forced, without ever going through a TIME_WAIT state.
这对我来说意义不大,因为这是一个 IDisposable
对象,GC也不应该调用该对象的 Dispose()
方法?
This doesn't make a lot of sense for me, since this is an IDisposable
object shouldn't the GC also invoke the Dispose()
method of the object?
这里有一些PowerShell代码演示了这一点(此计算机上未安装VS).我使用Sysinternals的TCPView实时检查套接字状态:
Here's some PowerShell code that demonstrates that (no VS installed on this machine). I used TCPView from Sysinternals to check the sockets state in real time:
$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()
Using this method, the sockets never go into a TIME_WAIT state. Same if I just close the app before manually invoking Close()
or Dispose()
有人可以解释一下这是否是一个好习惯吗(我想人们会说这不是一个好习惯).
Can someone shed some light and explain whether this would be a good practice (which I imagine people are going to say it's not).
编辑
GC在此问题上的利益已经得到了回答,但是我仍然有兴趣找出为什么这会对套接字状态产生任何影响,因为这应该由操作系统而不是.NET来控制.
GC's stake in the matter has already been answered, but I am still interested in finding out why this would have any impact on the socket state as this should be controlled by the OS, not .NET.
还想了解使用此方法来防止TIME_WAIT状态是否是一种好习惯,以及最终这是否是某个地方的错误(例如,所有套接字都应该经过TIME_WAIT状态吗?)
Also interested in finding out whether it would be good practice to use this method to prevent TIME_WAIT states and ultimately whether this is a bug somewhere (i.e., should all sockets go through a TIME_WAIT state?)
推荐答案
处置模式,称为IDisposable的IDisposable,提供了两种清除非托管对象的方法.Dispose方法提供了一种直接且快速的方式来清理资源.由垃圾回收器调用的finalize方法是一种故障安全的方法,可以确保清理掉非托管资源,以防其他使用该代码的开发人员忘记调用Dispose方法.这有点类似于C ++开发人员忘记在堆分配的内存上调用Delete,这会导致内存泄漏.
The Dispose pattern, also known as IDisposable, provides two ways for an unmanaged object to be cleaned up. The Dispose method provides a direct and fast way to clean up the resource. The finalize method, which is called by the garbage collector, is a fail-safe way to make sure that the unmanaged resource is cleaned up in case another developer using the code forgets to call the Dispose method. This is somewhat similar to C++ developers forgetting to call Delete on heap allocated memory - which results in memory leaks.
根据引用的链接:
"尽管终结器在某些清理方案中有效,但它们有两个明显的缺点:
"Although finalizers are effective in some cleanup scenarios, they have two significant drawbacks:
-
当GC检测到符合收集条件的对象时,将调用终结器.在不再需要资源之后的某个不确定的时间段内会发生这种情况.在获取许多稀缺资源(很容易耗尽的资源)的程序中,或者在存在资源的情况下,开发人员可以或希望释放资源的时间与终结器实际释放资源的时间之间的延迟可能是不可接受的.保持使用成本很高(例如,大型非托管内存缓冲区).
The finalizer is called when the GC detects that an object is eligible for collection. This happens at some undetermined period of time after the resource is not needed anymore. The delay between when the developer could or would like to release the resource and the time when the resource is actually released by the finalizer might be unacceptable in programs that acquire many scarce resources (resources that can be easily exhausted) or in cases in which resources are costly to keep in use (e.g., large unmanaged memory buffers).
当CLR需要调用终结器时,它必须将对象内存的收集推迟到下一轮垃圾回收为止(终结器在两个收集之间运行).这意味着该对象的内存(及其引用的所有对象)将不会释放更长的时间."
When the CLR needs to call a finalizer, it must postpone collection of the object’s memory until the next round of garbage collection (the finalizers run between collections). This means that the object’s memory (and all objects it refers to) will not be released for a longer period of time."
有人可以解释一下这是否是一个好习惯吗(我想人们会说这不是一个好习惯).
Can someone shed some light and explain whether this would be a good practice (which I imagine people are going to say it's not).
之所以需要一段时间才能关闭它,是因为代码默认情况下会徘徊,以使该应用程序有一些时间来处理所有排队的消息.根据MSDN上的TcpClient.Close 方法文档:
The reason why it is taking a while for it shut down is because the code lingers by default to give the app some time to handle any queued messages. According to the TcpClient.Close method doc on MSDN:
" Close方法将实例标记为已处置,并请求关联的Socket关闭TCP连接.基于LingerState属性,当仍有数据要发送时,在调用Close方法之后,TCP连接可能会保持打开状态一段时间.当基础连接完成关闭时,不会提供任何通知.
"The Close method marks the instance as disposed and requests that the associated Socket close the TCP connection. Based on the LingerState property, the TCP connection may stay open for some time after the Close method is called when data remains to be sent. There is no notification provided when the underlying connection has completed closing.
调用此方法最终将导致关联的Socket关闭,并且还将关闭关联的NetworkStream(如果已创建,则该NetworkStream用于发送和接收数据)."
Calling this method will eventually result in the close of the associated Socket and will also close the associated NetworkStream that is used to send and receive data if one was created."
以下代码:
// 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();
关于将对TcpClient对象的引用设置为null,建议的方法是调用Close方法.当引用设置为null时,GC最终将调用finalize方法.finalize方法最终调用Dispose方法,以合并用于清理非托管资源的代码.因此,它将可以关闭套接字-只是不建议这样做.
As for setting the reference to the TcpClient object to null, the recommended approach is to call the Close method. When the reference is set to null, the GC ends up calling the finalize method. The finalize method eventually calls the Dispose method in order to consolidate the code for cleaning up the unmanaged resource. So, it will work to close the socket - its just not recommended.
我认为,是否应留出一些时间让应用程序有时间处理排队的消息,这取决于应用程序.如果我确定我的客户端应用程序已经处理了所有必要的消息,那么我可能会给它指定0秒钟的延迟时间,或者如果我认为将来可能会改变,则为1秒钟.
In my opinion, it depends on the app whether or not some linger time should be allowed to give the app time to handle queued messages. If I was certain my client app had processed all the necessary messages, then I would probably either give it a linger time of 0 seconds or perhaps 1 second if I thought that might change in the future.
对于非常繁忙的客户端和/或较弱的硬件-那么我可以给它更多的时间.对于服务器,我必须在负载下对不同的值进行基准测试.
For a very busy client and / or weak hardware - then I might give it more time. For a server, I would have to benchmark different values under load.
其他有用的参考文献:
是否有任何情况下TcpClient.Close或Socket.Close(0)会阻止我的代码?
这篇关于要关闭套接字,请不要关闭套接字.嗯?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!