Socket高并发出现TIME_WAIT的原因以及解决办法

小技术_C# 专栏收录该内容
22 篇文章 0 订阅

最近公司项目遇到个问题,在Socket并发量较大的情况下,每次服务端发送完数据后,Sevice端采取主动Close的方断开连接。客户端用短连接的方式发送请求,会出现大量请求返回TIME_WAIT的现象。

在网上找了一些可用的解决方法,下次运维再来找,直接扔给他们。

参考链接如下:

TCP/IP详解--TIME_WAIT状态详解:https://blog.csdn.net/yusiguyuan/article/details/21445773

 

socket的TIME_WAIT状态的原因及解决办法和避免的办法:https://blog.csdn.net/windyf2013/article/details/78756791

1.TCP/IP详解--TIME_WAIT状态详解

Socket中的TIME_WAIT状态
在高并发短连接的server端,当server处理完client的请求后立刻closesocket此时会出现time_wait状态然后如果client再并发2000个连接,此时部分连接就连接不上了,用linger强制关闭可以解决此问题,但是linger会导致数据丢失,linger值为0时是强制关闭,无论并发多少多能正常连接上,如果非0会发生部分连接不上的情况!(可调用setsockopt设置套接字的linger延时标志,同时将延时时间设置为0。)
TCP/IP的RFC文档。TIME_WAIT是TCP连接断开时必定会出现的状态。
是无法避免掉的,这是TCP协议实现的一部分。
在WINDOWS下,可以修改注册表让这个时间变短一些

time_wait的时间为2msl,默认为4min.
你可以通过改变这个变量:
TcpTimedWaitDelay 
把它缩短到30s
TCP要保证在所有可能的情况下使得所有的数据都能够被投递。当你关闭一个socket时,主动关闭一端的socket将进入TIME_WAIT状态,而被动关闭一方则转入CLOSED状态,这的确能够保证所有的数据都被传输。当一个socket关闭的时候,是通过两端互发信息的四次握手过程完成的,当一端调用close()时,就说明本端没有数据再要发送了。这好似看来在握手完成以后,socket就都应该处于关闭CLOSED状态了。但这有两个问题,首先,我们没有任何机制保证最后的一个ACK能够正常传输,第二,网络上仍然有可能有残余的数据包(wandering duplicates),我们也必须能够正常处理。
通过正确的状态机,我们知道双方的关闭过程如下



假设最后一个ACK丢失了,服务器会重发它发送的最后一个FIN,所以客户端必须维持一个状态信息,以便能够重发ACK;如果不维持这种状态,客户端在接收到FIN后将会响应一个RST,服务器端接收到RST后会认为这是一个错误。如果TCP协议能够正常完成必要的操作而终止双方的数据流传输,就必须完全正确的传输四次握手的四个节,不能有任何的丢失。这就是为什么socket在关闭后,仍然处于 TIME_WAIT状态,因为他要等待以便重发ACK。

如果目前连接的通信双方都已经调用了close(),假定双方都到达CLOSED状态,而没有TIME_WAIT状态时,就会出现如下的情况。现在有一个新的连接被建立起来,使用的IP地址与端口与先前的完全相同,后建立的连接又称作是原先连接的一个化身。还假定原先的连接中有数据报残存于网络之中,这样新的连接收到的数据报中有可能是先前连接的数据报。为了防止这一点,TCP不允许从处于TIME_WAIT状态的socket建立一个连接。处于TIME_WAIT状态的socket在等待两倍的MSL时间以后(之所以是两倍的MSL,是由于MSL是一个数据报在网络中单向发出到认定丢失的时间,一个数据报有可能在发送图中或是其响应过程中成为残余数据报,确认一个数据报及其响应的丢弃的需要两倍的MSL),将会转变为CLOSED状态。这就意味着,一个成功建立的连接,必然使得先前网络中残余的数据报都丢失了。

由于TIME_WAIT状态所带来的相关问题,我们可以通过设置SO_LINGER标志来避免socket进入TIME_WAIT状态,这可以通过发送RST而取代正常的TCP四次握手的终止方式。但这并不是一个很好的主意,TIME_WAIT对于我们来说往往是有利的。

 

 

客户端与服务器端建立TCP/IP连接后关闭SOCKET后,服务器端连接的端口
状态为TIME_WAIT
是不是所有执行主动关闭的socket都会进入TIME_WAIT状态呢?
有没有什么情况使主动关闭的socket直接进入CLOSED状态呢?
主动关闭的一方在发送最后一个 ack 后
就会进入 TIME_WAIT 状态 停留2MSL(max segment lifetime)时间
这个是TCP/IP必不可少的,也就是“解决”不了的。

也就是TCP/IP设计者本来是这么设计的
主要有两个原因
1。防止上一次连接中的包,迷路后重新出现,影响新连接
   (经过2MSL,上一次连接中所有的重复包都会消失)
2。可靠的关闭TCP连接
   在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发
   fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以
   主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。

TIME_WAIT 并不会占用很大资源的,除非受到攻击。

还有,如果一方 send 或 recv 超时,就会直接进入 CLOSED 状态
TCP状态转移要点
    TCP协议规定,对于已经建立的连接,网络双方要进行四次握手才能成功断开连接,如果缺少了其中某个步骤,将会使连接处于假死状态,连接本身占用的资源不会被释放。网络服务器程序要同时管理大量连接,所以很有必要保证无用连接完全断开,否则大量僵死的连接会浪费许多服务器资源。在众多TCP状态中,最值得注意的状态有两个:CLOSE_WAIT和TIME_WAIT。  
 
1、LISTENING状态
  FTP服务启动后首先处于侦听(LISTENING)状态。
2、ESTABLISHED状态
  ESTABLISHED的意思是建立连接。表示两台机器正在通信。

3、CLOSE_WAIT

    对方主动关闭连接或者网络异常导致连接中断,这时我方的状态会变成CLOSE_WAIT 此时我方要调用close()来使得连接正确关闭

4、TIME_WAIT

    我方主动调用close()断开连接,收到对方确认后状态变为TIME_WAIT。TCP协议规定TIME_WAIT状态会一直持续2MSL(即两倍的分段最大生存期),以此来确保旧的连接状态不会对新连接产生影响。处于TIME_WAIT状态的连接占用的资源不会被内核释放,所以作为服务器,在可能的情况下,尽量不要主动断开连接,以减少TIME_WAIT状态造成的资源浪费。

///

    目前有一种避免TIME_WAIT资源浪费的方法,就是关闭socket的LINGER选项。但这种做法是TCP协议不推荐使用的,在某些情况下这个操作可能会带来错误。


不久前,我的Socket Client程序遇到了一个非常尴尬的错误。它本来应该在一个socket长连接上持续不断地向服务器发送数据,如果socket连接断开,那么程序会自动不断地重试建立连接。
有一天发现程序在不断尝试建立连接,但是总是失败。用netstat查看,这个程序竟然有上千个socket连接处于CLOSE_WAIT状态,以至于达到了上限,所以无法建立新的socket连接了。
为什么会这样呢?
它们为什么会都处在CLOSE_WAIT状态呢?
CLOSE_WAIT状态的生成原因
首先我们知道,如果我们的Client程序处于CLOSE_WAIT状态的话,说明套接字是被动关闭的!
因为如果是Server端主动断掉当前连接的话,那么双方关闭这个TCP连接共需要四个packet:
       Server ---> FIN ---> Client
       Server <--- ACK <--- Client
    这时候Server端处于FIN_WAIT_2状态;而我们的程序处于CLOSE_WAIT状态。
       Server <--- FIN <--- Client
这时Client发送FIN给Server,Client就置为LAST_ACK状态。
        Server ---> ACK ---> Client
Server回应了ACK,那么Client的套接字才会真正置为CLOSED状态。

 
我们的程序处于CLOSE_WAIT状态,而不是LAST_ACK状态,说明还没有发FIN给Server,那么可能是在关闭连接之前还有许多数据要发送或者其他事要做,导致没有发这个FIN packet。
 
原因知道了,那么为什么不发FIN包呢,难道会在关闭己方连接前有那么多事情要做吗?
还有一个问题,为什么有数千个连接都处于这个状态呢?难道那段时间内,服务器端总是主动拆除我们的连接吗?
 
不管怎么样,我们必须防止类似情况再度发生!
首先,我们要防止不断开辟新的端口,这可以通过设置SO_REUSEADDR套接字选项做到:
重用本地地址和端口
以前我总是一个端口不行,就换一个新的使用,所以导致让数千个端口进入CLOSE_WAIT状态。如果下次还发生这种尴尬状况,我希望加一个限定,只是当前这个端口处于CLOSE_WAIT状态!
在调用
sockConnected = socket(AF_INET, SOCK_STREAM, 0);
之后,我们要设置该套接字的选项来重用:
/// 允许重用本地地址和端口:
/// 这样的好处是,即使socket断了,调用前面的socket函数也不会占用另一个,而是始终就是一个端口
/// 这样防止socket始终连接不上,那么按照原来的做法,会不断地换端口。
int nREUSEADDR = 1;
setsockopt(sockConnected,
              SOL_SOCKET,
              SO_REUSEADDR,
              (const char*)&nREUSEADDR,
              sizeof(int));
教科书上是这么说的:这样,假如服务器关闭或者退出,造成本地地址和端口都处于TIME_WAIT状态,那么SO_REUSEADDR就显得非常有用。
也许我们无法避免被冻结在CLOSE_WAIT状态永远不出现,但起码可以保证不会占用新的端口。
其次,我们要设置SO_LINGER套接字选项:
从容关闭还是强行关闭?
LINGER是“拖延”的意思。
默认情况下(Win2k),SO_DONTLINGER套接字选项的是1;SO_LINGER选项是,linger为{l_onoff:0,l_linger:0}。
如果在发送数据的过程中(send()没有完成,还有数据没发送)而调用了closesocket(),以前我们一般采取的措施是“从容关闭”:
因为在退出服务或者每次重新建立socket之前,我都会先调用
/// 先将双向的通讯关闭
     shutdown(sockConnected, SD_BOTH);
     /// 安全起见,每次建立Socket连接前,先把这个旧连接关闭
closesocket(sockConnected);
 
我们这次要这么做:
设置SO_LINGER为零(亦即linger结构中的l_onoff域设为非零,但l_linger为0),便不用担心closesocket调用进入“锁定”状态(等待完成),不论是否有排队数据未发送或未被确认。这种关闭方式称为“强行关闭”,因为套接字的虚电路立即被复位,尚未发出的所有数据都会丢失。在远端的recv()调用都会失败,并返回WSAECONNRESET错误。
在connect成功建立连接之后设置该选项:
linger m_sLinger;
m_sLinger.l_onoff = 1;  // (在closesocket()调用,但是还有数据没发送完毕的时候容许逗留)
m_sLinger.l_linger = 0; // (容许逗留的时间为0秒)
setsockopt(sockConnected,
         SOL_SOCKET,
         SO_LINGER,
         (const char*)&m_sLinger,
         sizeof(linger));
 
总结
也许我们避免不了CLOSE_WAIT状态冻结的再次出现,但我们会使影响降到最小,希望那个重用套接字选项能够使得下一次重新建立连接时可以把CLOSE_WAIT状态踢掉。
Feedback
# 回复:[Socket]尴尬的CLOSE_WAIT状态以及应对策略 2005-01-30 3:41 PM yun.zheng 
回复人: elssann(臭屁虫和他的开心果) ( ) 信誉:51 2005-01-30 14:00:00 得分: 0


我的意思是:当一方关闭连接后,另外一方没有检测到,就导致了CLOSE_WAIT的出现,上次我的一个朋友也是这样,他写了一个客户端和 APACHE连接,当APACHE把连接断掉后,他没检测到,出现了CLOSE_WAIT,后来我叫他检测了这个地方,他添加了调用 closesocket的代码后,这个问题就消除了。 
如果你在关闭连接前还是出现CLOSE_WAIT,建议你取消shutdown的调用,直接两边closesocket试试。


另外一个问题:

比如这样的一个例子: 
当客户端登录上服务器后,发送身份验证的请求,服务器收到了数据,对客户端身份进行验证,发现密码错误,这时候服务器的一般做法应该是先发送一个密码错误的信息给客户端,然后把连接断掉。

如果把 
m_sLinger.l_onoff = 1; 
m_sLinger.l_linger = 0; 
这样设置后,很多情况下,客户端根本就收不到密码错误的消息,连接就被断了。

 

# 回复:[Socket]尴尬的CLOSE_WAIT状态以及应对策略 2005-01-30 3:41 PM yun.zheng 
elssann(臭屁虫和他的开心果) ( ) 信誉:51 2005-01-30 13:24:00 得分: 0


出现CLOSE_WAIT的原因很简单,就是某一方在网络连接断开后,没有检测到这个错误,没有执行closesocket,导致了这个状态的实现,这在TCP/IP协议的状态变迁图上可以清楚看到。同时和这个相对应的还有一种叫TIME_WAIT的。

另外,把SOCKET的SO_LINGER设置为0秒拖延(也就是立即关闭)在很多时候是有害处的。 
还有,把端口设置为可复用是一种不安全的网络编程方法。

 


# 回复:[Socket]尴尬的CLOSE_WAIT状态以及应对策略 2005-01-30 3:42 PM yun.zheng 
elssann(臭屁虫和他的开心果) ( ) 信誉:51 2005-01-30 14:48:00 得分: 0

 

断开连接的时候, 
当发起主动关闭的左边这方发送一个FIN过去后,右边被动关闭的这方要回应一个ACK,这个ACK是TCP回应的,而不 是应用程序发送的,此时,被动关闭的一方就处于CLOSE_WAIT状态了。如果此时被动关闭的这一方不再继续调用closesocket,那么他就不会 发送接下来的FIN,导致自己老是处于CLOSE_WAIT。只有被动关闭的这一方调用了closesocket,才会发送一个FIN给主动关闭的这一 方,同时也使得自己的状态变迁为LAST_ACK。

 


# 回复:[Socket]尴尬的CLOSE_WAIT状态以及应对策略 2005-01-30 3:54 PM yun.zheng 
elssann(臭屁虫和他的开心果) ( ) 信誉:51 2005-01-30 15:39:00 得分: 0


比如被动关闭的是客户端。。。

当对方调用closesocket的时候,你的程序正在

int nRet = recv(s,....); 
if (nRet == SOCKET_ERROR) 

// closesocket(s); 
return FALSE; 
}

很多人就是忘记了那句closesocket,这种代码太常见了。

我的理解,当主动关闭的一方发送FIN到被动关闭这边后,被动关闭这边的TCP马上回应一个ACK过去,同时向上面应用程序提交一个ERROR,导 致上面的SOCKET的send或者recv返回SOCKET_ERROR,正常情况下,如果上面在返回SOCKET_ERROR后调用了 closesocket,那么被动关闭的者一方的TCP就会发送一个FIN过去,自己的状态就变迁到LAST_ACK.

 


# 回复:[Socket]尴尬的CLOSE_WAIT状态以及应对策略 2005-01-30 4:17 PM yun.zheng 
int nRecvBufLength = 
recv(sockConnected, 
szRecvBuffer, 
sizeof(szRecvBuffer), 
0); 
/// zhengyun 20050130: 
/// elssann举例说,当对方调用closesocket的时候,我的程序正在 
/// recv,这时候有可能对方发送的FIN包我没有收到,而是由TCP代回了 
/// 一个ACK包,所以我这边程序进入CLOSE_WAIT状态。 
/// 所以他建议在这里判断是否已出错,是就主动closesocket。 
/// 因为前面我们已经设置了recv超时时间为30秒,那么如果真的是超时了, 
/// 这里收到的错误应该是WSAETIMEDOUT,这种情况下也可以关闭连接的 
if (nRecvBufLength == SOCKET_ERROR) 

TRACE_INFO(_T("=用recv接收发生Socket错误=")); 
closesocket(sockConnected); 
continue; 
}

这样可以吗? 
网络连接无法释放—— CLOSE_WAIT
关键字:TCP ,CLOSE_WAIT, Java, SocketChannel

 

问题描述:最 近性能测试碰到的一个问题。客户端使用NIO,服务器还是一般的Socket连接。当测试进行一段时间以后,发现服务器端的系统出现大量未释放的网络连 接。用netstat -na查看,连接状态为CLOSE_WAIT。这就奇怪了,为什么Socket已经关闭而连接依然未释放。

 

解决:Google了半天,发现关于CLOSE_WAIT的问题一般是C的,Java似乎碰到这个问题的不多(这有一篇不错的,也是解决CLOSE_WAIT的,但是好像没有根本解决,而是选择了一个折中的办法)。接着找,由于使用了NIO,所以怀疑可能是这方面的问题,结果找到了这篇。顺着帖子翻下去,其中有几个人说到了一个问题—— 一端的Socket调用close后,另一端的Socket没有调用close.于是查了一下代码,果然发现Server端在某些异常情况时,没有关闭Socket。改正后问题解决。

时间基本上花在Google上了,不过也学到不少东西。下面为一张TCP连接的状态转换图:

 


 

说明:虚线和实线分别对应服务器端(被连接端)和客户端端(主动连接端)。

结合上图使用netstat -na命令即可知道到当前的TCP连接状态。一般LISTEN、ESTABLISHED、TIME_WAIT是比较常见。

 

分析:

上面我碰到的这个问题主要因为TCP的结束流程未走完,造成连接未释放。现设客户端主动断开连接,流程如下

 

       Client                            消息                                    Server

         close()
                                      ------ FIN ------->
        FIN_WAIT1                                                         CLOSE_WAIT
                                      <----- ACK -------
        FIN_WAIT2 
                                                                                  close()
                                       <------ FIN ------                     
        TIME_WAIT                                                       LAST_ACK      

                                      ------ ACK ------->  
                                                                                   CLOSED
           CLOSED

 

如上图所示,由于Server的Socket在客户端已经关闭时而没有调用关闭,造成服务器端的连接处在“挂起”状态,而客户端则处在等待应答的状态上。此问题的典型特征是:一端处于FIN_WAIT2 ,而另一端处于CLOSE_WAIT. 不过,根本问题还是程序写的不好,有待提高。

TIME_WAIT状态
根据TCP协议,主动发起关闭的一方,会进入TIME_WAIT状态,持续2*MSL(Max Segment Lifetime),缺省为240秒,在这个post中简洁的介绍了为什么需要这个状态。

值得一说的是,对于基于TCP的HTTP协议,关闭TCP连接的是Server端,这样,Server端会进入TIME_WAIT状态,可 想而知,对于访问量大的Web Server,会存在大量的TIME_WAIT状态,假如server一秒钟接收1000个请求,那么就会积压240*1000=240,000个 TIME_WAIT的记录,维护这些状态给Server带来负担。当然现代操作系统都会用快速的查找算法来管理这些TIME_WAIT,所以对于新的 TCP连接请求,判断是否hit中一个TIME_WAIT不会太费时间,但是有这么多状态要维护总是不好。

HTTP协议1.1版规定default行为是Keep-Alive,也就是会重用TCP连接传输多个 request/response,一个主要原因就是发现了这个问题。还有一个方法减缓TIME_WAIT压力就是把系统的2*MSL时间减少,因为 240秒的时间实在是忒长了点,对于Windows,修改注册表,在HKEY_LOCAL_MACHINE/ SYSTEM/CurrentControlSet/Services/ Tcpip/Parameters上添加一个DWORD类型的值TcpTimedWaitDelay,一般认为不要少于60,不然可能会有麻烦。

对于大型的服务,一台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要上涨不要下降。
 

socket的TIME_WAIT状态的原因及解决办法和避免的办法

一查看现在time_wait的数量及浅析

         netstat -an | grep TIME_WAIT | wc -l 

发现系统存在大量TIME_WAIT状态的连接,通过调整内核参数解决,在 /etc/sysctl.conf中加入

         net.ipv4.tcp_tw_recycle = 1    (表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭)

 

建立TCP需要3次握手,而终止TCP需要4次交互;

 
主动关闭socket的一方最终为time_wait,被动关闭的则为close_wait;


为什么time_wait需要2*MSL等待时间?
MSL就是maximum segment lifetime(最大分节生命期),这是一个IP数据包能在互联网上生存的最长时间,超过这个时间将在网络中消失。
假设最终的 ACK 丢失 , server 将重发 FIN , client 必须维护 TCP 状态信息以便可以重发最终的 ACK ,否则会发送RST ,结果 server 认为发生错误。 
若要TCP可靠地终止连接的两个方向 ( 全双工关闭 ) , client 必须进 TIME_WAIT状态。
现在我们考虑终止连接时的被动方发送了一个FIN,然后主动方回复了一个ACK,然而这个ACK可能会丢失,这会造成被动方重发FIN,这个FIN可能会在互联网上存活MSL。
如果没有TIME_WAIT的话,假设连接1已经断开,然而其被动方最后重发的那个FIN(或者FIN之前发送的任何TCP分段)还在网络上,然而连接2重用了连接1的所有的5元素(源IP,目的IP,TCP,源端口,目的端口),刚刚将建立好连接,连接1迟到的FIN到达了,这个FIN将以比较低但是确实可能的概率终止掉连接2.


如何消除大量TCP短连接引发的TIME_WAIT?
1)可以改为长连接,但代价较大,长连接太多会导致服务器性能问题,而且PHP等脚本语言,需要通过proxy之类的软件才能实现长连接;
2)修改ipv4.ip_local_port_range,增大可用端口范围,但只能缓解问题,不能根本解决问题;
3)客户端程序中设置socket的SO_LINGER选项;
4)客户端机器打开tcp_tw_recycle和tcp_timestamps选项;
5)客户端机器打开tcp_tw_reuse和tcp_timestamps选项;
6)客户端机器设置tcp_max_tw_buckets为一个很小的值

So_linger的作用
struct linger {
     int l_onoff; /* 0 = off, nozero = on */
     int l_linger; /* linger time */
};
其取值和处理如下:
1、设置 l_onoff为0,则该选项关闭,l_linger的值被忽略,等于内核缺省情况,close调用会立即返回给调用者,如果可能将会传输任何未发送的数据;
2、设置 l_onoff !=0 && l_linger = 0,则套接口关闭时TCP夭折连接,TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个RST给对方,而不是通常的四分组终止序列,这避免了TIME_WAIT状态;
3、设置 l_onoff != 0 && l_linger != 0,当套接口关闭时内核将拖延一段时间(由l_linger决定)。

1、 若设置了SO_LINGER(亦即linger结构中的l_onoff域设为非零),并设置了零超时间隔,则closesocket()不被阻塞立即执行,不论是否有排队数据未发送或未被确认。这种关闭方式称为 “强制”或“失效”关闭 ,因为套接口的虚电路立即被复位,且丢失了未发送的数据。在远端的recv()调用将以WSAECONNRESET出错。 
2、 若设置了SO_LINGER并确定了非零的超时间隔,则closesocket()调用阻塞进程,直到所剩数据发送完毕或超时。这种关闭称为 “优雅”或“从容”关闭 。请注意如果套接口置为非阻塞且SO_LINGER设为非零超时,则closesocket()调用将以WSAEWOULDBLOCK错误返回。 
3、 若在一个流类套接口上设置了SO_DONTLINGER(也就是说将linger结构的l_onoff域设为零),则closesocket()调用立即返回。但是,如果可能,排队的数据将在套接口关闭前发送。请注意,在这种情况下WINDOWS套接口实现将在一段不确定的时间内保留套接口以及其他资源,这对于想用所以套接口的应用程序来说有一定影响。 

 

 

如果套接口缓冲区中仍残留数据,进程将处于睡眠状态,直到
(a)所有数据发送完且被对方确认,之后进行正常的终止序列(描述字访问计数为0)
 或(b)延迟时间到。
此种情况下,应用程序检查close的返回值是非常重要的,如果在数据发送完并被确认前时间到,close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失。

close的成功返回仅告诉我们发送的数据(和FIN)已由对方TCP确认,它并不能告诉我们对方应用进程是否已读了数据。如果套接口设为非阻塞的,它将不等待close完成。


tcp_tw_recycle
tcp_tw_recycle选项作用为:Enable fast recycling TIME-WAIT sockets. Default value is 0.
tcp_timestamps选项作用为:TCP timestamps are used to provide protection against wrapped sequence numbers. 缺省值为1。
1)快速回收到底有多快?
2)有的资料说只要打开tcp_tw_recycle即可,有的又说要tcp_timestamps同时打开,具体是哪个正确?
3)为什么从虚拟机NAT出去发起客户端连接时选项无效,非虚拟机连接就有效?
计算快速回收的时间,等于 RTO * 3.5,回答第一个问题的关键是RTO(Retransmission Timeout)大概是多少
RFC中有关于RTO计算的详细规定,一共有三个:RFC-793、RFC-2988、RFC-6298,Linux的实现是参考RFC-2988。
=====linux-2.6.37 net/ipv4/tcp.c 126================
#define TCP_RTO_MAX ((unsigned)(120*HZ))
#define TCP_RTO_MIN ((unsigned)(HZ/5))
==========================================
这里的HZ是1s,因此可以得出RTO最大是120s,最小是200ms,对于局域网的机器来说,正常情况下RTO基本上就是200ms,因此3.5 RTO就是700ms
1)快速回收到底有多快?
局域网环境下,700ms就回收;
2)有的资料说只要打开tcp_tw_recycle即可,有的又说要tcp_timestamps同时打开,具体是哪个正确?
需要同时打开,但默认情况下tcp_timestamps就是打开的,所以会有人说只要打开tcp_tw_recycle即可;
3)为什么从虚拟机发起客户端连接时选项无效,非虚拟机连接就有效?
和网络组网有关系,无法获取对端信息时就不进行快速回收;


tcp_tw_reuse
tcp_tw_reuse选项的含义如下
tcp_tw_reuse - BOOLEAN
Allow to reuse TIME-WAIT sockets for new connections when it is safe from protocol viewpoint. Default value is 0.
这里的关键在于“协议什么情况下认为是安全的”,由于环境限制,没有办法进行验证,通过看源码简单分析了一下。
=====linux-2.6.37 net/ipv4/tcp_ipv4.c 114=====
int tcp_twsk_unique(struct sock *sk, struct sock *sktw, void *twp)
总结一下:
1)tcp_tw_reuse选项和tcp_timestamps选项也必须同时打开;
2)重用TIME_WAIT的条件是收到最后一个包后超过1s。
官方手册有一段警告:
It should not be changed without advice/request of technical experts.
对于大部分局域网或者公司内网应用来说,满足条件2)都是没有问题的,因此官方手册里面的警告其实也没那么可怕:)


tcp_max_tw_buckets
参考官方文档(http://www.kernel.org/doc/Documentation/networking/ip-sysctl.txt),解释如下:
tcp_max_tw_buckets - INTEGER
Maximal number of timewait sockets held by system simultaneously. If this number is exceeded time-wait socket is immediately destroyed and warning is printed. 
官方文档没有说明默认值,通过几个系统的简单验证,初步确定默认值是180000。
官方手册中有一段警告:
    This limit exists only to prevent simple DoS attacks, you _must_ not lower the limit artificially, but rather increase it (probably, after increasing installed memory),if network conditions require more than default value.
基本意思是这个用于防止Dos攻击,我们不应该人工减少,如果网络条件需要的话,反而应该增加。

 

二.出现原因分析

TCP连接的终止 
TCP建立一个连接至少需要交换三个分组,也因此称之为TCP的三路握手(three-way handshake),然而在TCP终止连接时,由于双方都需要发送一个FIN分节给对端确认,因此TCP终止连接一般是需要交换四个分节。具体来看: 

1、 应用进程(active close)首先调用close,于是导致TCP发送一个FIN分节,表示数据已分送完毕,请求关闭套接字。 
2、 另一端应用进程(passive close)接受收到FIN,并由该端的TCP确认(确认的过程是TCP发送ACK分节给对端套接字)。FIN的接受也作为文件结束符传递给上层应用进程。这里的文件结束符并非应用进程的EOF,在TCP字节流中,EOF的读或写通过收发一个特殊的FIN分节来实现。 
3、 另端(passive close)应用进程在接受到文件束符后,会调用close关闭它的套接字,这导致该端的TCP也发送了一个FIN分节。 
4、 主动关闭端(active close)接受到这个FIN后,TCP对它进行确认。(TCP发送ACK分节,值得注意的是主动关闭端在未接受到FIN之前,它的状态就是TIME_WAIT)。 

 

综上所述:TIME_WAIT状态出现场景是主动关闭端在未接受到FIN之前,它的状态就是TIME_WAIT。

二.TCP为什么如此设计

1。防止上一次连接中的包(特别是最后一个ACK包),迷路后重新出现,影响新连接  (经过2MSL(max segment lifetime),上一次连接中所有的重复包都会消失)。
2。可靠的关闭TCP连接  在主动关闭方发送的最后一个 ack(fin) ,有可能丢失,这时被动方会重新发
  fin, 如果这时主动方处于 CLOSED 状态 ,就会响应 rst 而不是 ack。所以  主动方要处于 TIME_WAIT 状态,而不能是 CLOSED 。TIME_WAIT 并不会占用很大资源的,除非受到攻击。还有,如果一方 send 或 recv 超时,就会直接进入 CLOSED 状态。

 

三.规避大量出现TIME_WAIT的方法

 

net.ipv4.tcp_tw_reuse和net.ipv4.tcp_tw_recycle的开启都是为了回收处于TIME_WAIT状态的资源。

net.ipv4.tcp_fin_timeout这个时间可以减少在异常情况下服务器从FIN-WAIT-2转到TIME_WAIT的时间。

net.ipv4.tcp_keepalive_*一系列参数,是用来设置服务器检测连接存活的相关配置。

 

 

 

在服务器的日常维护过程中,会经常用到下面的命令:

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

它会显示例如下面的信息:

TIME_WAIT 814
CLOSE_WAIT 1
FIN_WAIT1 1
ESTABLISHED 634
SYN_RECV 2
LAST_ACK 1

常用的三个状态是:ESTABLISHED 表示正在通信,TIME_WAIT 表示主动关闭,CLOSE_WAIT 表示被动关闭。

ps:以上为自己总结网上各个blog的内容,方便自己掌握知识点用!

 

四.三次握手的详细描述

 

  1. 第一次握手:建立连接。客户端发送连接请求报文段,将SYN位置为1,Sequence Number为x;然后,客户端进入SYN_SEND状态,等待服务器的确认;
  2. 第二次握手:服务器收到SYN报文段。服务器收到客户端的SYN报文段,需要对这个SYN报文段进行确认,设置Acknowledgment Number为x+1(Sequence Number+1);同时,自己自己还要发送SYN请求信息,将SYN位置为1,Sequence Number为y;服务器端将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN_RECV状态;
  3. 第三次握手:客户端收到服务器的SYN+ACK报文段。然后将Acknowledgment Number设置为y+1,向服务器发送ACK报文段,这个报文段发送完毕以后,客户端和服务器端都进入ESTABLISHED状态,完成TCP三次握手。

 

完成了三次握手,客户端和服务器端就可以开始传送数据。以上就是TCP三次握手的总体介绍。

 

为什么要三次握手

 

既然总结了TCP的三次握手,那为什么非要三次呢?怎么觉得两次就可以完成了。那TCP为什么非要进行三次连接呢?在谢希仁的《计算机网络》中是这样说的:

为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。

在书中同时举了一个例子,如下:

“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”

这就很明白了,防止了服务器端的一直等待而浪费资源。

 

 

 

五。四次挥手的详细描述

当客户端和服务器通过三次握手建立了TCP连接以后,当数据传送完毕,肯定是要断开TCP连接的啊。那对于TCP的断开连接,这里就有了神秘的“四次分手”。

  1. 第一次分手:主机1(可以使客户端,也可以是服务器端),设置Sequence NumberAcknowledgment Number,向主机2发送一个FIN报文段;此时,主机1进入FIN_WAIT_1状态;这表示主机1没有数据要发送给主机2了;
  2. 第二次分手:主机2收到了主机1发送的FIN报文段,向主机1回一个ACK报文段,Acknowledgment NumberSequence Number加1;主机1进入FIN_WAIT_2状态;主机2告诉主机1,我也没有数据要发送了,可以进行关闭连接了;
  3. 第三次分手:主机2向主机1发送FIN报文段,请求关闭连接,同时主机2进入CLOSE_WAIT状态;
  4. 第四次分手:主机1收到主机2发送的FIN报文段,向主机2发送ACK报文段,然后主机1进入TIME_WAIT状态;主机2收到主机1的ACK报文段以后,就关闭连接;此时,主机1等待2MSL后依然没有收到回复,则证明Server端已正常关闭,那好,主机1也可以关闭连接了。

至此,TCP的四次分手就这么愉快的完成了。当你看到这里,你的脑子里会有很多的疑问,很多的不懂,感觉很凌乱;没事,我们继续总结。

为什么要四次分手

那四次分手又是为何呢?TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。TCP是全双工模式,这就意味着,当主机1发出FIN报文段时,只是表示主机1已经没有数据要发送了,主机1告诉主机2,它的数据已经全部发送完毕了;但是,这个时候主机1还是可以接受来自主机2的数据;当主机2返回ACK报文段时,表示它已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1的;当主机2也发送了FIN报文段时,这个时候就表示主机2也没有数据要发送了,就会告诉主机1,我也没有数据要发送了,之后彼此就会愉快的中断这次TCP连接。如果要正确的理解四次分手的原理,就需要了解四次分手过程中的状态变化。

  • FIN_WAIT_1: 这个状态要好好解释一下,其实FIN_WAIT_1FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。(主动方)
  • FIN_WAIT_2:上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你(ACK信息),稍后再关闭连接。(主动方)
  • CLOSE_WAIT:这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以 close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。(被动方)
  • LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。(被动方)
  • TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FINWAIT1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。(主动方)
  • CLOSED: 表示连接中断。
  • 1
    点赞
  • 0
    评论
  • 3
    收藏
  • 扫一扫,分享海报

参与评论 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:黑客帝国 设计师:我叫白小胖 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值