转载自http://www.serverframework.com/asynchronousevents/2011/01/time-wait-and-its-design-implications-for-protocols-and-scalable-servers.html
在构建TCP客户端服务器系统时,很容易犯一些严重限制可伸缩性的简单错误。其中一个错误就是没有考虑到TIME_WAIT
国家。在这篇博客文章中,我将解释为什么TIME_WAIT
存在,它可能导致的问题,如何解决它,以及什么时候不应该。
TIME_WAIT
是TCP状态转换图中经常被误解的状态。这是一个状态,如果你有足够的套接字,TIME_WAIT
那么一些套接字可以进入并保持相当长的时间,那么你的创建新套接字连接的能力可能会受到影响,并且这可能会影响客户机服务器系统的可伸缩性。对于套接字如何以及为什么TIME_WAIT
首先出现,往往存在一些误解,不应该存在,这不是神奇的。从下面的TCP状态转换图可以看出TIME_WAIT
,TCP客户端通常是最终状态。
虽然状态转换图显示TIME_WAIT
为客户端的最终状态,但它不一定是客户端TIME_WAIT
。事实上,最终状态是启动“主动关闭”的对等端最终进入,这可以是客户端或服务器。那么,发布“主动关闭”是什么意思?
如果TCP对等方是第一个呼叫Close()
连接的对等方,则TCP对等方会启动“主动关闭” 。在许多协议和客户端/服务器设计中,这是客户端。在HTTP和FTP服务器中,这通常是服务器。导致同伴结束的事件的实际顺序TIME_WAIT
如下。
现在我们知道套接字是如何结束TIME_WAIT
的,理解为什么这个状态存在以及它为什么会成为一个潜在的问题是有用的。
TIME_WAIT
通常也被称为2MSL等待状态。这是因为转换到的套接字在此期间的持续时间TIME_WAIT
为2 x最大段寿命。MSL是任何段的最大时间量,对于构成TCP协议一部分的数据报的所有意图和目的而言,在丢弃之前,网络可以在网络上保持有效。这个时间限制最终以用于传输TCP段的IP数据报中的TTL字段为界。不同的实现为MSL选择不同的值,常用值为30秒,1分钟或2分钟。RFC 793指定MSL为2分钟,Windows系统默认为此值,但可以使用TcpTimedWaitDelay注册表设置进行调整。
TIME_WAIT
可能影响系统可伸缩性 的原因是,TCP连接中一个完全关闭的套接字将保持TIME_WAIT
约4分钟的状态。如果许多连接正在快速打开和关闭,那么套接字TIME_WAIT
可能会开始累积在系统上; 您可以TIME_WAIT
使用netstat查看套接字。一次可以建立有限数量的套接字连接,限制此数量的其中一个因素是可用本地端口的数量。如果TIME_WAIT
插入太多的套接字,您会发现很难建立新的出站连接,因为缺少可用于新连接的本地端口。但为什么TIME_WAIT
存在呢?
国家有两个原因TIME_WAIT
。首先是防止一个连接的延迟段被误解为后续连接的一部分。丢弃在连接处于2MSL等待状态时到达的任何段。
在上图中,我们有两个从终点1到终点2的连接。每个终点的地址和端口在每个连接中都是相同的。第一个连接终止于由端点2启动的主动关闭。如果端点2没有保持TIME_WAIT
足够长的时间以确保来自先前连接的所有分段已经失效,则延迟的分段(具有适当的序列号)可以是误认为是第二个连接的一部分...
请注意,延迟片段很可能会导致这样的问题。首先,每个端点的地址和端口需要相同; 这通常不太可能,因为操作系统通常从短暂端口范围为您选择客户端端口,从而在连接之间进行更改。其次,延迟段的序列号需要在新连接中有效,这也是不太可能的。但是,如果出现这两种情况,TIME_WAIT
则会阻止新连接的数据被破坏。
该TIME_WAIT
州的第二个原因是可靠地实施TCP的全双工连接终端。如果ACK
从终点2开始的终点被丢弃,则终点1将重新发送终点FIN
。如果连接已经过渡到CLOSED
终点2,那么唯一可能的应答是发送一个,RST
因为重发FIN
是意外的。即使所有数据传输正确,这也会导致终点1接收到错误。
不幸的是,一些操作系统实现的方式TIME_WAIT
似乎有点幼稚。只有TIME_WAIT
通过阻塞才能提供保护的连接与需要的插座完全匹配TIME_WAIT
。这意味着由客户端地址,客户端端口,服务器地址和服务器端口标识的连接。但是,某些操作系统会施加更严格的限制,并且阻止本地端口号被重新使用,而该端口号包含在连接中TIME_WAIT
。如果有足够的套接字结束,TIME_WAIT
则不能建立新的出站连接,因为没有剩余本地端口分配给新连接。
Windows不会执行此操作,只会阻止与其中的连接完全匹配的出站连接TIME_WAIT
。
入站连接受影响较小TIME_WAIT
。虽然由服务器主动关闭的TIME_WAIT
连接与客户端连接完全一样,但是服务器正在侦听的本地端口不会阻止其成为新的入站连接的一部分。在Windows上,服务器正在监听的众所周知的端口可以构成随后接受的连接的一部分,并且如果从远程地址和端口建立了新的连接,该连接当前构成TIME_WAIT
该本地地址和端口的连接的一部分,则只要新序列号大于当前连接的最终序列号,就允许连接TIME_WAIT
。然而,TIME_WAIT
在服务器上累积可能会影响性能和资源使用,因为TIME_WAIT
最终需要超时的连接需要进行一些工作,直到TIME_WAIT
状态结束,连接仍占用(少量)服务器资源。
鉴于TIME_WAIT
由于本地端口号耗尽而影响出站连接的建立,并且这些连接通常使用由临时端口范围由操作系统自动分配的本地端口,因此您可以做的第一件事情是确保你正在使用一个体面的短暂端口范围。在Windows上,您可以通过调整MaxUserPort
注册表设置来完成此操作 详情请看这里。请注意,默认情况下,许多Windows系统的临时端口范围大约为4000,这对于许多客户端服务器系统来说可能太低。
虽然有可能缩短套接字在TIME_WAIT
这方面的花费时间通常不会有帮助。鉴于TIME_WAIT
当许多连接建立并且主动关闭时,这只是一个问题,调整2MSL等待周期通常会导致在给定时间内建立和关闭更多连接的情况,因此必须不断调整2MSL直到由于延迟片段似乎是后来连接的一部分,因此它可能会开始出现问题; 如果您连接到相同的远程地址和端口,并且非常快速地使用所有本地端口范围,或者如果连接到相同的远程地址和端口并将本地端口绑定到固定值,这种情况才会变得可能。
更改2MSL延迟通常是机器范围内的配置更改。您可以尝试TIME_WAIT
使用SO_REUSEADDR
套接字选项解决套接字级别问题。这允许在具有相同地址和端口的现有套接字已经存在的同时创建套接字。新的套接字基本上劫持了旧的套接字。您可以使用它SO_REUSEADDR
来允许创建套接字,同时具有相同端口的套接字已经在其中,TIME_WAIT
但这也会导致诸如拒绝服务攻击或数据窃取等问题。在Windows平台上另一套接字选项,SO_EXCLUSIVEADDRUSE
可以帮助防止某些缺点的SO_REUSEADDR
,看到这里,但在我看来,最好避免这些尝试在各地工作TIME_WAIT
,而是设计系统,使TIME_WAIT
不是问题。
上面的TCP状态转换图都显示有序的连接终止。还有另一种方法来终止TCP连接,这是通过中止连接并发送一个RST
而不是一个FIN
。这通常通过将SO_LINGER
套接字选项设置为0 来实现。这会导致挂起的数据被丢弃,并且连接将被中止,RST
而不是等待传输的数据,并且使用a清除干净的连接FIN
。重要的是要认识到,当连接被中止时,可能在对等体之间流动的任何数据将被丢弃,RST
直接送达; 通常作为表示“连接已被同级重置”的事实的错误。远程节点知道连接被中止,并且两个节点都没有进入TIME_WAIT
。
当然,已经中止使用的连接的新化身RST
可能成为TIME_WAIT
阻碍的延迟段问题的受害者,但是成为问题所需的条件是不太可能的,无论如何,参见上面的更多细节。为了防止已经中止的连接导致延迟段问题,两个对等端都必须转换到TIME_WAIT
连接关闭可能由诸如路由器之类的中介引起。但是,这不会发生,连接的两端都会关闭。
有几件事你可以做,以避免TIME_WAIT
成为你的问题。其中一些假定您有能力更改您的客户端和服务器之间所说的协议,但是对于自定义服务器设计,您通常会这样做。
对于永远不会建立自己的出站连接的服务器,除了维护连接的资源和性能意义之外TIME_WAIT
,您不必过分担心。
对于确实建立出站连接并接受入站连接的服务器,黄金法则是始终确保如果TIME_WAIT
需要发生这种情况,则它会在另一个对等而不是服务器上结束。做到这一点的最佳方式是永远不要从服务器发起主动关闭,无论原因是什么。如果您的对等方超时,则以中止连接RST
而不是关闭连接。如果你的对方发送无效数据,中止连接等等。这个想法是,如果你的服务器永远不会启动一个主动关闭,它永远不会累积TIME_WAIT
套接字,因此永远不会受到它们导致的可伸缩性问题的困扰。虽然在出现错误情况时很容易看到如何中止连接,但正常连接终止的情况如何?理想情况下,您应该为协议设计一种方式,让服务器告诉客户端它应该断开连接,而不是简单地让服务器发起主动关闭。因此,如果服务器需要终止连接,服务器会发送一个应用程序级别“我们已经完成”的消息,客户将此消息作为关闭连接的原因。如果客户端未能在合理的时间内关闭连接,则服务器会中止连接。
在客户端上,事情稍微复杂一些,毕竟,有人必须发起一个主动关闭来干净地终止TCP连接,并且如果它是客户端,那么这就是TIME_WAIT
最终会结束的地方。然而,TIME_WAIT
最终在客户端有几个优点。首先,如果由于某种原因,客户端由于套接字在TIME_WAIT
一个客户端中的积累而最终导致连接问题。其他客户不会受到影响。其次,快速打开和关闭TCP连接到同一台服务器是没有效率的,因此超出了问题的意义TIME_WAIT
尝试维持更长时间的连接而不是更短的时间。不要设计一个协议,客户端每分钟连接一次,并打开一个新的连接。而是使用持久连接设计,并且只有在连接失败时才重新连接,如果中间路由器拒绝在没有数据流的情况下保持连接处于打开状态,则可以实现应用程序级别ping,使用TCP保持活动状态或仅接受路由器重置连接; 好处是你没有积累TIME_WAIT
插座。如果你在连接上做的工作自然是短暂的,那么考虑某种形式的“连接池”设计,从而保持连接的开放性和重用性。最后,如果您绝对必须快速地从客户端打开和关闭连接到同一台服务器,那么也许您可以设计一个可以使用的应用程序级别关闭顺序,然后按照此顺序关闭。您的客户可能会发送“我完成了”消息,您的服务器可能会发送“再见”消息,然后客户端可能会中止连接。
TIME_WAIT
存在原因并通过缩短2MSL周期或允许地址重用来解决这个问题SO_REUSEADDR
并不总是一个好主意。如果你能够设计你的协议TIME_WAIT
避免在脑海中,那么你通常可以完全避免这个问题。