我读了this answer on a previous question,其中说:
因此,启动终止的对等体 - 即首先调用close() - 将最终处于TIME_WAIT状态。 [...]
但是,在服务器上处于TIME_WAIT状态的许多套接字可能会出现问题,因为它最终可能会阻止接受新连接。 [...]
相反,请设计您的应用程序协议,以便始终从客户端启动连接终止。如果客户端总是知道它何时读取了所有剩余数据,则它可以启动终止序列。例如,浏览器在读取所有数据时可以从Content-Length HTTP标头中获知,并且可以启动关闭。 (我知道在HTTP 1.1中它会保持打开一段时间以便重用,然后关闭它。)
我想使用TcpClient / TcpListener来实现它,但它不清楚如何使其正常工作。
这是大多数MSDN示例的典型方式 - 双方都呼叫Close()
,而不仅仅是客户端:
private static void AcceptLoop()
{
listener.BeginAcceptTcpClient(ar =>
{
var tcpClient = listener.EndAcceptTcpClient(ar);
ThreadPool.QueueUserWorkItem(delegate
{
var stream = tcpClient.GetStream();
ReadSomeData(stream);
WriteSomeData(stream);
tcpClient.Close(); <---- note
});
AcceptLoop();
}, null);
}
private static void ExecuteClient()
{
using (var client = new TcpClient())
{
client.Connect("localhost", 8012);
using (var stream = client.GetStream())
{
WriteSomeData(stream);
ReadSomeData(stream);
}
}
}
运行20个客户端之后,TCPView显示了很多来自客户端和服务器的套接字卡在TIME_WAIT
中,这需要相当长的时间才能消失。
根据上面的引用,我删除了我的监听器上的Close()
次呼叫,现在我只依靠客户端关闭:
var tcpClient = listener.EndAcceptTcpClient(ar);
ThreadPool.QueueUserWorkItem(delegate
{
var stream = tcpClient.GetStream();
ReadSomeData(stream);
WriteSomeData(stream);
// tcpClient.Close(); <-- Let the client close
});
AcceptLoop();
现在我不再拥有任何TIME_WAIT
,但我确实在CLOSE_WAIT
,FIN_WAIT
等不同阶段留下了插座,这也需要很长时间才能消失。
这次我在关闭服务器连接之前添加了延迟:
var tcpClient = listener.EndAcceptTcpClient(ar);
ThreadPool.QueueUserWorkItem(delegate
{
var stream = tcpClient.GetStream();
ReadSomeData(stream);
WriteSomeData(stream);
Thread.Sleep(100); // <-- Give the client the opportunity to close first
tcpClient.Close(); // <-- Now server closes
});
AcceptLoop();
这似乎更好 - 现在只有客户端套接字在TIME_WAIT
;服务器套接字已正确关闭:
这似乎与先前链接的文章所说的一致:
因此,启动终止的对等体 - 即首先调用close() - 将最终处于TIME_WAIT状态。
TIME_WAIT
中卡住连接,以释放客户端上的资源。在这种情况下,我应该让服务器执行主动关闭,并将睡眠/关闭放在我的客户端上吗? 自己试用的完整代码就在这里:
https://gist.github.com/PaulStovell/a58cd48a5c6b14885cf3
修改:另一个有用的资源:
对于确实建立出站连接以及接受入站连接的服务器,黄金法则是始终确保如果需要发生TIME_WAIT,它最终会在另一个对等体而不是服务器上。无论出于什么原因,最好的方法是永远不要从服务器发起主动关闭。如果您的对等体超时,则使用RST中止连接而不是关闭它。如果您的对等方发送无效数据,则中止连接等。这个想法是,如果您的服务器永远不会启动活动关闭,它永远不会累积TIME_WAIT套接字,因此永远不会遇到它们导致的可伸缩性问题。虽然很容易看到在发生错误情况时如何中止连接正常连接终止?理想情况下,您应该在协议中设计一种方法,让服务器告诉客户端它应该断开连接,而不是简单地让服务器发起主动关闭。因此,如果服务器需要终止连接,则服务器发送应用程序级别&#34;我们已完成&#34;客户端将关闭连接的消息作为理由。如果客户端无法在合理的时间内关闭连接,则服务器将中止连接。
在客户端上,事情稍微复杂一点,毕竟,有人必须发起主动关闭才能干净地终止TCP连接,如果是客户端,那么TIME_WAIT将会结束。但是,将TIME_WAIT结束在客户端上有几个优点。首先,如果出于某种原因,由于TIME_WAIT中套接字的累积,客户端最终会遇到连接问题,那么它只是一个客户端。其他客户不会受到影响。其次,快速打开和关闭到同一服务器的TCP连接效率很低,因此在TIME_WAIT问题之外尝试维持连接的时间较长而不是较短的时间段是有意义的。不要设计一种协议,客户端每分钟都会连接到服务器,并通过打开新连接来实现。相反,使用持久连接设计并仅在连接失败时重新连接,如果中间路由器拒绝在没有数据流的情况下保持连接打开,那么您可以实现应用程序级别ping,使用TCP保持活动状态或仅接受路由器正在重置连接;好的是你没有累积TIME_WAIT套接字。如果您在连接上所做的工作自然是短暂的,那么请考虑某种形式的连接池&#34;连接保持开放和重复使用的设计。最后,如果您必须快速打开和关闭从客户端到同一服务器的连接,那么您可以设计一个可以使用的应用程序级别关闭序列,然后以一个中断关闭的方式执行此操作。您的客户可以发送&#34;我已完成&#34;消息,您的服务器然后可以发送&#34;再见&#34;消息然后客户端可以中止连接。
答案 0 :(得分:3)
这就是TCP的工作方式,你无法避免。您可以为服务器上的TIME_WAIT或FIN_WAIT设置不同的超时,但这是关于它的。
原因是在TCP上,数据包可以到达您很久以前关闭的套接字。如果你已经在同一个IP和端口上打开了另一个套接字,那么它将接收上一个会话的数据,这会混淆它的地狱。特别是考虑到大多数人认为TCP是可靠的:)
如果客户端和服务器都正确实现TCP(例如,正确处理干净关闭),客户端或服务器是否关闭连接并不重要。因为听起来你管理双方,所以不应该成为一个问题。
您的问题似乎与服务器上的正确关闭有关。当插座的一侧关闭时,另一侧将Read
的长度为0
- 这表明通信已结束。您很可能在服务器代码中忽略了这一点 - 它是一个特殊情况,表示&#34;您现在可以安全地处理此套接字,立即执行此操作&#34;。
在您的情况下,服务器端关闭似乎是最合适的。
但实际上,TCP相当复杂。互联网上的大多数样本都存在严重缺陷(特别是C#的样本 - 例如,为C ++找到一个好的样本并不难,并且忽略了许多重要的内容)并没有帮助协议的一部分。我已经得到了一些可能对您有用的简单示例 - https://github.com/Luaancz/Networking/tree/master/Networking%20Part%201它仍然不是完美的TCP,但它比MSDN样本要好得多,例如。
答案 1 :(得分:3)
http://vincent.bernat.im/en/blog/2014-tcp-time-wait-state-linux.html
它有一些特定于Linux的问题,但所有TCP级别的内容都是通用的。
最终双方都应该关闭(即完成FIN / ACK握手),因为你不希望FIN_WAIT或CLOSE_WAIT状态延迟,这只是&#34;坏&#34; TCP。我避免使用RST强制关闭连接,因为这可能会导致其他地方出现问题而感觉就像是一个贫穷的网友。
确实,TIME_WAIT状态将发生在首先终止连接的末尾(即发送第一个FIN数据包),并且您应该优化以在连接流失最少的一端关闭连接。
在Windows上,默认情况下,每个IP只有超过15,000个TCP端口可供使用,因此您需要正常的连接流失来实现这一目标。 TCB跟踪TIME_WAIT状态的内存应该是完全可以接受的。
https://support.microsoft.com/kb/929851
注意TCP连接可以半关闭也很重要。也就是说,一端可以选择关闭连接以进行发送,但保持开放状态以便接收。在.NET中,这样做是这样的:
tcpClient.Client.Shutdown(SocketShutdown.Send);
http://msdn.microsoft.com/en-us/library/system.net.sockets.socket.shutdown.aspx
在将部分netcat工具从Linux移植到PowerShell时,我发现这是必要的:
http://www.powershellmagazine.com/2014/10/03/building-netcat-with-powershell/
我必须重新考虑这样的建议:如果你可以保持连接打开并且空闲,直到你再次需要它,这通常会对减少TIME_WAIT产生巨大的影响。
除此之外,尝试在TIME_WAIT成为问题时进行测量...实际上需要批次连接流失来耗尽TCP资源。
我希望其中一些有用。
答案 2 :(得分:1)
这些方法中哪一种是正确的方法,为什么? (假设我希望客户成为&#39;活跃的关闭方)
在理想情况下,您可以让服务器向客户端发送带有某个操作码的RequestDisconnect
数据包,然后客户端通过在接收到该数据包时关闭连接来处理该数据包。这样,你就不会在服务器端出现过时的套接字(因为套接字是资源,资源是有限的,所以有了stales是件坏事)。
如果客户端然后通过设置套接字来执行其断开连接序列(或者如果您使用Close()
则调用TcpClient
,它将使套接字处于CLOSE_WAIT
状态在服务器上,这意味着连接正在关闭。
有没有更好的方法来实施方法3?我们希望关闭由客户端启动(以便客户端留下TIME_WAIT),但是当客户端关闭时,我们还想关闭服务器上的连接。
是。如上所述,让服务器发送数据包以请求客户端关闭其与服务器的连接。
我的场景实际上与Web服务器相反;我有一个客户端连接和断开许多不同的远程计算机。我宁愿服务器的连接卡在TIME_WAIT中,以释放客户端上的资源。在这种情况下,我应该让服务器执行主动关闭,并将睡眠/关闭放在我的客户端上吗?
是的,如果您有什么意思,请随时致电服务器上的Dispose
以取消客户。
另外,您可能希望使用原始Socket对象而不是TcpClient
,因为它非常有限。如果直接操作套接字,则可以使用SendAsync
和所有其他异步方法进行套接字操作。使用手动Thread.Sleep
来电是我应避免的 - 这些操作本质上是异步的 - 在您向流中写入内容之后断开连接应该在SendAsync
的回调中完成而不是在Sleep
之后。