TCP/IP TIME_WAIT状态原理(转)

TIME_WAIT状态原理java

----------------------------nginx

通讯双方创建TCP链接后,主动关闭链接的一方就会进入TIME_WAIT状态。后端

客户端主动关闭链接时,会发送最后一个ack后,而后会进入TIME_WAIT状态,再停留2个MSL时间(后有MSL的解释),进入CLOSED状态。安全

下图是以客户端主动关闭链接为例,说明这一过程的。服务器

 

 

 

TIME_WAIT状态存在的理由网络

----------------------------app

TCP/IP协议就是这样设计的,是不可避免的。主要有两个缘由:socket

1)可靠地实现TCP全双工链接的终止tcp

TCP协议在关闭链接的四次握手过程当中,最终的ACK是由主动关闭链接的一端(后面统称A端)发出的,若是这个ACK丢失,对方(后面统称B端)将重发出最终的FIN,所以A端必须维护状态信息(TIME_WAIT)容许它重发最终的ACK。若是A端不维持TIME_WAIT状态,而是处于CLOSED 状态,那么A端将响应RST分节,B端收到后将此分节解释成一个错误(在java中会抛出connection reset的SocketException)。post

于是,要实现TCP全双工链接的正常终止,必须处理终止过程当中四个分节任何一个分节的丢失状况,主动关闭链接的A端必须维持TIME_WAIT状态 。

 

2)容许老的重复分节在网络中消逝 

TCP分节可能因为路由器异常而“迷途”,在迷途期间,TCP发送端可能因确认超时而重发这个分节,迷途的分节在路由器修复后也会被送到最终目的地,这个迟到的迷途分节到达时可能会引发问题。在关闭“前一个链接”以后,立刻又从新创建起一个相同的IP和端口之间的“新链接”,“前一个链接”的迷途重复分组在“前一个链接”终止后到达,而被“新链接”收到了。为了不这个状况,TCP协议不容许处于TIME_WAIT状态的链接启动一个新的可用链接,由于TIME_WAIT状态持续2MSL,就能够保证当成功创建一个新TCP链接的时候,来自旧链接重复分组已经在网络中消逝。

 

 

 

 

MSL时间

----------------------------

MSL就是maximum segment lifetime(最大分节生命期),这是一个IP数据包能在互联网上生存的最长时间,超过这个时间IP数据包将在网络中消失 。MSL在RFC 1122上建议是2分钟,而源自berkeley的TCP实现传统上使用30秒。

 

TIME_WAIT状态维持时间

----------------------------

TIME_WAIT状态维持时间是两个MSL时间长度,也就是在1-4分钟。Windows操做系统就是4分钟。

 

 

 

 

用于统计当前各类状态的链接的数量的命令

---------------------------

#netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

 

返回结果以下:

LAST_ACK 14

SYN_RECV 348

ESTABLISHED 70

FIN_WAIT1 229

FIN_WAIT2 30

CLOSING 33

TIME_WAIT 18122

 

对上述结果的解释:

CLOSED:无链接是活动的或正在进行

LISTEN:服务器在等待进入呼叫

SYN_RECV:一个链接请求已经到达,等待确认

SYN_SENT:应用已经开始,打开一个链接

ESTABLISHED:正常数据传输状态

FIN_WAIT1:应用说它已经完成

FIN_WAIT2:另外一边已赞成释放

ITMED_WAIT:等待全部分组死掉

CLOSING:两边同时尝试关闭

TIME_WAIT:另外一边已初始化一个释放

LAST_ACK:等待全部分组死掉

 

 

进一步论述这个问题:

===============================

 

 

--------------客户端主动关闭链接-----------------------

注意一个问题,进入TIME_WAIT状态的通常状况下是客户端。

大多数服务器端通常执行被动关闭,服务器不会进入TIME_WAIT状态。

当在服务器端关闭某个服务再从新启动时,服务器是会进入TIME_WAIT状态的。

举例:

1.客户端链接服务器的80服务,这时客户端会启用一个本地的端口访问服务器的80,访问完成后关闭此链接,马上再次访问服务器的

80,这时客户端会启用另外一个本地的端口,而不是刚才使用的那个本地端口。缘由就是刚才的那个链接还处于TIME_WAIT状态。

2.客户端链接服务器的80服务,这时服务器关闭80端口,当即再次重启80端口的服务,这时可能不会成功启动,缘由也是服务器的连

接还处于TIME_WAIT状态。

 

服务端提供服务时,通常监听一个端口就够了。例如Apach监听80端口。

客户端则是使用一个本地的空闲端口(大于1024),与服务端的Apache的80端口创建链接。

当通讯时使用短链接,并由客户端主动关闭链接时,主动关闭链接的客户端会产生TIME_WAIT状态的链接,一个TIME_WAIT状态的链接就占用了一个本地端口。这样在TIME_WAIT状态结束以前,本地最多就能承受6万个TIME_WAIT状态的链接,就无故口可用了。

客户端与服务端进行短链接的TCP通讯,若是在同一台机器上进行压力测试模拟上万的客户请求,而且循环与服务端进行短链接通讯,那么这台机器将产生4000个左右的TIME_WAIT socket,后续的短链接就会产生address already in use : connect的异常。

 

关闭的时候使用RST的方式,不进入 TIME_WAIT状态,是否可行?

 

--------------服务端主动关闭链接------------------------------

服务端提供在服务时,通常监听一个端口就够了。例如Apach监听80端口。

客户端则是使用一个本地的空闲端口(大于1024),与服务端的Apache的80端口创建链接。

当通讯时使用短链接,并由服务端主动关闭链接时,主动关闭链接的服务端会产生TIME_WAIT状态的链接。

因为都链接到服务端80端口,服务端的TIME_WAIT状态的链接会有不少个。

假如server一秒钟处理1000个请求,那么就会积压240秒*1000=24万个TIME_WAIT的记录,服务有能力维护这24万个记录。

 

大多数服务器端通常执行被动关闭,服务器不会进入TIME_WAIT状态。

服务端为了解决这个TIME_WAIT问题,可选择的方式有三种:

    Ø  保证由客户端主动发起关闭(即作为B端)

    Ø  关闭的时候使用RST的方式

    Ø  对处于TIME_WAIT状态的TCP容许重用

 

通常Apache的配置是:

Timeout 30  

KeepAlive On   #表示服务器端不会主动关闭连接  

MaxKeepAliveRequests 100  

KeepAliveTimeout 180  

表示:Apache不会主动关闭连接,

两种状况下Apache会主动关闭链接:

一、Apache收到了http协议头中有客户端要求Apache关闭链接信息,如setRequestHeader("Connection", "close");  

二、链接保持时间达到了180秒的超时时间,将关闭。

 

若是配置以下:

KeepAlive Off   #表示服务器端会响应完数据后主动关闭连接  

 

 

--------------有代理时------------------------------

nginx代理使用了短连接的方式和后端交互,若是使用了nginx代理,那么系统TIME_WAIT的数量会变得比较多,这是因为nginx代理使用了短连接的方式和后端交互的缘由,使得nginx和后端的ESTABLISHED变得不多而TIME_WAIT不少。这不但发生在安装nginx的代理服务器上,并且也会使后端的app服务器上有大量的TIME_WAIT。查阅TIME_WAIT资料,发现这个状态不少也没什么大问题,但可能由于它占用了系统过多的端口,致使后续的请求没法获取端口而形成障碍。

 

对于大型的服务,一台server搞不定,须要一个LB(Load Balancer)把流量分配到若干后端服务器上,若是这个LB是以NAT方式工做的话,可能会带来问题。假如全部从LB到后端Server的IP包的source address都是同样的(LB的对内地址),那么LB到后端Server的TCP链接会受限制,由于频繁的TCP链接创建和关闭,会在server上留下TIME_WAIT状态,并且这些状态对应的remote address都是LB的,LB的source port撑死也就60000多个(2^16=65536,1~1023是保留端口,还有一些其余端口缺省也不会用),每一个LB上的端口一旦进入Server的TIME_WAIT黑名单,就有240秒不能再用来创建和Server的链接,这样LB和Server最多也就能支持300个左右的链接。若是没有LB,不会有这个问题,由于这样server看到的remote address是internet上广阔无垠的集合,对每一个address,60000多个port实在是够用了。

一开始我以为用上LB会很大程度上限制TCP的链接数,可是实验代表没这回事,LB后面的一台Windows Server 2003每秒处理请求数照样达到了600个,难道TIME_WAIT状态没起做用?用Net Monitor和netstat观察后发现,Server和LB的XXXX端口之间的链接进入TIME_WAIT状态后,再来一个LB的XXXX端口的SYN包,Server照样接收处理了,而是想像的那样被drop掉了。翻书,从书堆里面找出覆满尘土的大学时代买的《UNIX Network Programming, Volume 1, Second Edition: Networking APIs: Sockets and XTI》,中间提到一句,对于BSD-derived实现,只要SYN的sequence number比上一次关闭时的最大sequence number还要大,那么TIME_WAIT状态同样接受这个SYN,难不成Windows也算BSD-derived?有了这点线索和关键字(BSD),找到这个post,在NT4.0的时候,仍是和BSD-derived不同的,不过Windows Server 2003已是NT5.2了,也许有点差异了。

作个试验,用Socket API编一个Client端,每次都Bind到本地一个端口好比2345,重复的创建TCP链接往一个Server发送Keep-Alive=false的HTTP请求,Windows的实现让sequence number不断的增加,因此虽然Server对于Client的2345端口链接保持TIME_WAIT状态,可是老是可以接受新的请求,不会拒绝。那若是SYN的Sequence Number变小会怎么样呢?一样用Socket API,不过此次用Raw IP,发送一个小sequence number的SYN包过去,Net Monitor里面看到,这个SYN被Server接收后如泥牛如海,一点反应没有,被drop掉了。

按照书上的说法,BSD-derived和Windows Server 2003的作法有安全隐患,不过至少这样至少不会出现TIME_WAIT阻止TCP请求的问题,固然,客户端要配合,保证不一样TCP链接的sequence number要上涨不要降低。

 

-------------------------------------------

Q: 我正在写一个unix server程序,不是daemon,常常须要在命令行上重启它,绝大 

多数时候工做正常,可是某些时候会报告"bind: address in use",因而重启失 

败。 

 

A: Andrew Gierth 

server程序老是应该在调用bind()以前设置SO_REUSEADDR套接字选项。至于 

TIME_WAIT状态,你没法避免,那是TCP协议的一部分。

 

 

Q: 编写 TCP/SOCK_STREAM 服务程序时,SO_REUSEADDR到底什么意思? 

 

A: 这个套接字选项通知内核,若是端口忙,但TCP状态位于 TIME_WAIT ,能够重用 

端口。若是端口忙,而TCP状态位于其余状态,重用端口时依旧获得一个错误信息, 

指明"地址已经使用中"。若是你的服务程序中止后想当即重启,而新套接字依旧 

使用同一端口,此时 SO_REUSEADDR 选项很是有用。必须意识到,此时任何非期 

望数据到达,均可能致使服务程序反应混乱,不过这只是一种可能,事实上很不 

可能。 

 

一个套接字由相关五元组构成,协议、本地地址、本地端口、远程地址、远程端 

口。SO_REUSEADDR 仅仅表示能够重用本地本地地址、本地端口,整个相关五元组 

仍是惟一肯定的。因此,重启后的服务程序有可能收到非指望数据。必须慎重使 

用 SO_REUSEADDR 选项。 

相关文章
相关标签/搜索