网络-TIME_WAIT和CLOSE_WAIT

网络-TIME_WAIT和CLOSE_WAIT

转载声明

本文大量内容系转载自以下文章,有删改,并参考其他文档资料加入了一些内容:

转载仅为方便学习查看,一切权利属于原作者,本人只是做了整理和排版,如果带来不便请联系我删除。

1 背景

昨天解决了一个HttpClient调用错误导致的服务器异常,具体过程如下:

http://blog.csdn.net/shootyou/article/details/6615051

里头的分析过程有提到,通过查看服务器网络状态检测到服务器有大量的CLOSE_WAIT的状态。

使用netstat查看本机连接信息

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

2 TCP报文格式

TCP报文格式如图:
在这里插入图片描述
上图中有几个字段需要重点介绍下:

  • 序号:Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。

  • 确认序号:Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。

  • 标志位:共6个,即URG、ACK、PSH、RST、SYN、FIN等,具体含义如下:

    • URG:紧急指针(urgent pointer)有效。
    • ACK:确认序号有效。
      只有ACK标志位为1时,确认序号字段才有效
    • PSH:接收方应该尽快将这个报文交给应用层。
    • RST:重置连接。
    • SYN:发起一个新连接。
    • FIN:释放一个连接。

需要注意的是:

  • 不要将确认序号Ack与标志位中的ACK搞混了。
  • 确认方Ack=发起方Req+1,两端配对。

3 常用连接状态

3.1 概述

  • ESTABLISHED 表示正在通信
  • TIME_WAIT 表示主动关闭
  • CLOSE_WAIT 表示被动关闭

3.2 三次握手

3.2.1 概述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
注意区分上图ACK和ack,大写的ACK为标志位,小写ack为确认序号总是为接收到的来源的seq+1。

3.2.2 三次握手步骤

所谓三次握手(Three-Way Handshake)是指建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立。步骤如下:

  1. 第一次握手
    Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。

  2. 第二次握手
    Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack (number )=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。

  3. 第三次握手
    Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server。

    随后Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。

3.2.3 三次握手实例

在这里插入图片描述

3.2.4 SYN攻击

三次握手过程中,Server发送SYN-ACK之后,收到Client的ACK之前的TCP连接称为半连接(half-open connect),此时Server处于SYN_RCVD状态,当收到ACK后,Server才转入ESTABLISHED状态。

SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server回复确认包,并等待Client的确认,由于源地址是不存在的,因此,Server需要不断重发直至超时,这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络堵塞甚至系统瘫痪。

SYN攻击时一种典型的DDOS攻击,检测SYN攻击的方式非常简单,即当Server上有大量半连接状态且源IP地址是随机的,则可以断定遭到SYN攻击了,使用如下命令可以让之现行:

netstat -nap | grep SYN_RECV 

3.3 四次挥手

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.3.1 概述

所谓四次挥手(Four-Way Wavehand)即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发。

3.3.2 四次挥手步骤

  1. 第一次挥手
    Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。

  2. 第二次挥手
    Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。此时Sever协议层在等待上层的应用程序,主动调用close操作后才主动关闭这条连接,发送FIN包。

  3. 第三次挥手
    在等待数据处理完毕后不再发送数据后,Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态,表示同意关闭连接。

  4. 第四次挥手
    Client收到FIN后,立刻发送一个ACK给Server,确认序号为收到序号+1,还会等待个2MSL时间此时Client进入TIME_WAIT状态。等待2MSL是为了确保ACK包到达Server。

    Server收到ACK包后进入CLOSED状态,完成四次挥手。

3.3.3 从客户端来看

  1. 客户端主动断开连接时,会先发送FIN包,客户端此时进入FIN_WAIT_1状态;
  2. 客户端收到服务器的ACK包(对步骤1中FIN包的应答)后,客户端进入FIN_WAIT_2状态;
  3. 客户端接收到服务器的FIN包并回复ACK包给服务端,然后客户端进入TIME_WAIT状态,此时会等待2个MSL的时间,确保发送的ACK包是否达到了对端。
  4. 客户端在等待了2个MSL的时间没有收到服务器重传的FIN包,就默认ACK数据包已经抵达了对端。

3.3.4 从服务端来看

  1. 服务器收到客户端发送的FIN数据包后,回复ACK包给客户端,此时服务器进入CLOSE_WAIT状态,开始做一些收尾工作。
  2. 等待服务器将剩余的数据全部发送给客户端时,然后执行断开操作,(老夫把该做的事都做了,然后再给这小子发送FIN包来结束,哈哈,姜还是老的辣!)服务器向客户端发送出FIN包后,服务器端进入LAST_ACK状态,等待最后一个ACK确认包。
  3. 服务端收到客户端发送的ACK包后,从LAST_ACK状态转为CLOSED状态,服务器正式关闭了

3.3.5 为什么要4次挥手?

确保被动关闭方的数据能够完整传输。

当被动方收到主动方的FIN报文通知时,它仅仅表示主动方没有数据再发送给被动方了。

但未必被动方所有的数据都完整的发送给了主动方,所以被动方不会马上关闭SOCKET,它可能还需要发送一些数据给主动方后,最后再发送FIN报文给主动方,告诉主动方同意关闭连接,所以这里的ACK报文和FIN报文多数情况下都是分开发送的。

由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。

首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。

3.3.6 四次挥手实例

在这里插入图片描述

3.3.7 同时发起主动关闭的情况

上面是一方主动关闭,另一方被动关闭的情况,实际中还会出现同时发起主动关闭的情况,具体流程如下图:
在这里插入图片描述

3.3.8 为什么TIME_WAIT和CLOSE_WAIT会占用资源?

因为在一个连接没有进入CLOSED状态之前,这个连接是不能被重用的。

3.3.9 TimeWait的作用

TimeWait为了解决网络的丢包和网络不稳定所带来的其他问题:

首先,防止前一个连接【五元组,我们继续以 180.172.35.150:45678, tcp, 180.97.33.108:80为例】上延迟的数据包或者丢失重传的数据包,被后面复用的连接【前一个连接关闭后,此时你再次访问百度,新的连接可能还是由180.172.35.150:45678, tcp, 180.97.33.108:80 这个五元组来表示,也就是源端口凑巧还是45678】错误的接收(异常:数据丢了,或者传输太慢了),参见下图:
在这里插入图片描述
如上图,SEQ=3的数据包丢失,重传第一次,没有得到ACK确认.

如果没有TIME_WAIT,或者TIME_WAIT时间非常短,那么关闭的连接【180.172.35.150:45678, tcp, 180.97.33.108:80 的状态变为了CLOSED,源端口可被再次利用】,马上被重用【对180.97.33.108:80新建的连接,复用了之前的随机端口45678】,并连续发送SEQ=1,2 的数据包。

但此时,前面的连接上的SEQ=3的数据包再次被重传,同时,seq的序号刚好也是3(这个很重要,不然,SEQ的序号对不上,就会RST掉),此时,前面一个连接上的数据被后面的一个连接错误的接收。

第二,确保连接方能在时间范围内,关闭自己的连接。其实,也是因为丢包造成的,参见下图:
在这里插入图片描述
另外一个作用是,当最后一个ACK丢失时,远程连接进入LAST-ACK状态,它可以确保远程已经关闭当前TCP连接。如果没有TIME-WAIT状态,当远程仍认为这个连接是有效的,则会继续与其通讯,导致这个连接会被重新打开。当远程收到一个SYN 时,会回复一个RST包,因为这SEQ不对,那么新的连接将无法建立成功,报错终止。具体如下:

  1. 主动关闭方关闭了连接,发送了FIN;
  2. 被动关闭方回复ACK同时也执行关闭动作,发送FIN包;此时,被动关闭的一方进入LAST_ACK状态
  3. 主动关闭的一方回复了ACK,主动关闭一方进入TIME_WAIT状态;
  4. 但是最后的ACK丢失,被动关闭的一方还继续停留在LAST_ACK状态
  5. 此时,如果没有TIME_WAIT的存在,或者说,停留在TIME_WAIT上的时间很短,则主动关闭的一方很快就进入了CLOSED状态,也即是说,如果此时新建一个连接,源随机端口如果被复用,在connect发送SYN包后,由于被动方仍认为这条连接【五元组】还在等待ACK,但是却收到了SYN,则被动方会回复RST
  6. 最终造成主动创建连接的一方,由于收到了RST,则连接无法成功

所以,你看到了,TIME_WAIT的存在是很重要的,如果强制忽略TIME_WAIT,还是有很高的机率,造成数据错乱,或者短暂性的连接失败。

3.3.10 为什么TIME_WAIT状态要持续2MSL

MSL为 Maximum Segment Lifetime,即最大报文生存时间,具体来说是报文在网络传输时超时时间,超过的报文会被丢弃。那么传输一个来回就是2MSL。

2MSL即2倍的max segment lifetime

这个2MSL,是RFC 793里定义的。

在这里插入图片描述
如果第四次挥手时,服务端没有收到客户端的最终ACK,则会重发FIN;如果收到了就不会再发。

在这里插入图片描述
所以需要2MSL:

  • 第一个MSL用来确保ACK发送到了服务端
  • 第二个MSL用来确保如果丢失了ACK,被动关闭的一方再次重发FIN并等待主动关闭方回复的ACK,一来一去两个来回。

这个定义,更多的是一种保障(IP数据包里的TTL,即数据最多存活的跳数,真正反应的才是数据在网络上的存活时间),确保最后如果丢失了ACK,被动关闭的一方再次重发FIN并等待主动关闭方回复的ACK,一来一去两个来回。内核里,写死了这个MSL的时间为:30秒(有读者提醒,RFC里建议的MSL其实是2分钟,但是很多实现都是30秒),所以TIME_WAIT的即为1分钟。

另一个原因是为了防止过期连接包被当做新连接包处理:
在这里插入图片描述

4 问题

4.1 概述

如果服务器出了异常,百分之八九十都是下面两种情况:

  • 服务器保持了大量TIME_WAIT状态
  • 服务器保持了大量CLOSE_WAIT状态

因为linux分配给一个用户的文件句柄是有限的(可以参考:http://blog.csdn.net/shootyou/article/details/6579139),而TIME_WAITCLOSE_WAIT两种状态如果一直被保持,意味着对应数目的通道就一直被占着不用。这样持续积累,直到达到句柄数上限造成新的请求无法被处理,接着就是大量Too Many Open Files异常,tomcat崩溃等严重问题

4.2 服务器保持大量TIME_WAIT

4.2.1 原因分析

这种情况比较常见,一些爬虫服务器或者WEB服务器(如果网管在安装的时候没有做内核参数优化的话)上经常会遇到这个问题,这个问题是怎么产生的呢?

从上面的示意图可以看得出来,TIME_WAIT是主动关闭连接的一方保持的状态,对于爬虫服务器来说他本身就是“客户端”,在完成一个爬取任务之后,他就会发起主动关闭连接,从而进入TIME_WAIT的状态,然后在保持这个状态2MSL(max segment lifetime)时间之后,彻底关闭回收资源。为什么要这么做?明明就已经主动关闭连接了为啥还要保持资源一段时间呢?这个是TCP/IP的设计者规定的,主要出于以下两个方面的考虑:

  1. 防止上一次连接中的包,迷路后重新出现,影响新连接(经过2MSL,上一次连接中所有的重复包都会消失)

  2. 可靠的关闭TCP连接。在主动关闭方发送的最后一个 ack(fin),有可能丢失,这时被动方会重新发fin

    如果这时主动方处于 CLOSED状态 ,就会响应rst而不是 ack。所以主动方要处于TIME_WAIT状态,而不能是CLOSED

    另外这么设计TIME_WAIT 会定时的回收资源,并不会占用很大资源的,除非短时间内接受大量请求或者受到攻击。

  • 关于MSL引用下面一段话:
    MSL 为一个TCP Segment (某一块TCP 网路封包) 从来源送到目的之间可续存的时间(也就是一个网路封包在网路上传输时能存活的时间),由于RFC 793 TCP 传输协定是在1981 年定义的,当时的网路速度不像现在的网际网路那样发达,你可以想像你从浏览器输入网址等到第一个byte 出现要等4 分钟吗?在现在的网路环境下几乎不可能有这种事情发生,因此我们大可将 TIME_WAIT 状态的续存时间大幅调低,好让端口 (Ports) 能更快空出来给其他连接使用。

  • 再引用网络资源的一段话:
    值得一说的是,对于基于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,一个主要原因就是发现了这个问题。

    也就是说HTTP的交互跟上面画的那个图是不一样的,关闭连接的不是客户端,而是服务器,所以web服务器也是会出现大量的TIME_WAIT的情况的。

4.2.2 带来的问题

  • 占用连接
    处于TIME-WAIT状态的TCP连接,在链接表槽中存活1分钟,意味着另一个相同四元组(源地址,源端口,目标地址,目标端口)的连接不能出现,也就是说新的TCP(相同四元组)连接无法建立。

    再次回想一下前面的问题,如果一条连接,即使在四次握手关闭了,由于TIME_WAIT的存在,这个连接,在1分钟之内,也无法再次被复用,那么,如果你用一台机器做压测的客户端,你一分钟能发送多少并发连接请求?如果这台是一个负载均衡服务器,一台负载均衡服务器,一分钟可以有多少个连接同时访问后端的服务器呢?所以大量TIME_WAIT会影响服务器网络性能。

  • 占用内存
    一条Socket处于TIME_WAIT状态,它也是一条“存在”的socket,内核里也需要有保持它的数据:

    • 内核里有保存所有连接的一个hash table,这个hash table里面既包含TIME_WAIT状态的连接,也包含其他状态的连接。主要用于有新的数据到来的时候,从这个hash table里快速找到这条连接。
      在这里插入图片描述
    • 还有一个hash table用来保存所有的bound ports,主要用于可以快速的找到一个可用的端口或者随机端口:
      在这里插入图片描述
  • 占用内存CPU
    当然!每次找到一个随机端口,还是需要遍历一遍bound ports的吧,这必然需要一些CPU时间。

4.2.3 问题解决

TIME_WAIT很多,既占内存又消耗CPU,这也是为什么很多人,看到TIME_WAIT很多,就蠢蠢欲动的想去干掉他们。其实,如果你再进一步去研究,1万条TIME_WAIT的连接,也就多消耗1M左右的内存,对现代的很多服务器,已经不算什么了。至于CPU,能减少它当然更好,但是不至于因为1万多个hash item就担忧。

解决思路很简单,就是让服务器能够快速回收和重用那些TIME_WAIT的资源。

下面来看一下我们网管对/etc/sysctl.conf文件的修改:

#对于一个新建连接,内核要发送多少个 SYN 连接请求才决定放弃,不应该大于255,默认值是5,对应于180秒左右时间 
net.ipv4.tcp_syn_retries=2
#net.ipv4.tcp_synack_retries=2
#表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为300秒
net.ipv4.tcp_keepalive_time=1200
net.ipv4.tcp_orphan_retries=3
#表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间
net.ipv4.tcp_fin_timeout=30  
#表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。
net.ipv4.tcp_max_syn_backlog = 4096
#表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭
net.ipv4.tcp_syncookies = 1

#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭
net.ipv4.tcp_tw_reuse = 1
#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭
net.ipv4.tcp_tw_recycle = 1

##减少超时前的探测次数 
net.ipv4.tcp_keepalive_probes=5 
##优化网络设备接收队列 
net.core.netdev_max_backlog=3000 

修改完之后执行/sbin/sysctl -p让参数生效。

这里主要注意到这几个参数:

  • 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_*
    一系列参数,是用来设置服务器检测连接存活的相关配置。
  • 关于keepalive的用途可以参考:http://hi.baidu.com/tantea/blog/item/580b9d0218f981793812bb7b.html

[2015.01.13更新]
注意tcp_tw_recycle开启的风险:http://blog.csdn.net/wireless_tech/article/details/6405755

4.3 服务器保持大量CLOSE_WAIT

在这里插入图片描述
TIME_WAIT状态可以通过优化服务器参数得到解决,因为发生TIME_WAIT的情况是服务器自己可控的,要么就是对方连接的异常,要么就是自己没有迅速回收资源,总之不是由于自己程序错误导致的。

但是CLOSE_WAIT就不一样了,从上面的图可以看出来,如果一直保持在CLOSE_WAIT状态,那么只有一种情况,就是在对方关闭连接之后收到FIN M的程序自己没有进一步发出ack信号。换句话说,就是在对方连接关闭之后,程序里没有检测到,或者程序压根就忘记了这个时候需要关闭连接,于是这个资源就一直被程序占着。

个人觉得这种情况,通过服务器内核参数也没办法解决,服务器对于程序抢占的资源没有主动回收的权利,除非终止程序运行。

如果你使用的是HttpClient并且你遇到了大量CLOSE_WAIT的情况,那么这篇日志也许对你有用:http://blog.csdn.net/shootyou/article/details/6615051

在那边日志里头我举了个场景,来说明CLOSE_WAIT和TIME_WAIT的区别,这里重新描述一下:

服务器A是一台爬虫服务器,它使用简单的HttpClient去请求资源服务器B上面的apache获取文件资源,正常情况下,如果请求成功,那么在抓取完资源后,服务器A会主动发出关闭连接的请求,这个时候就是主动关闭连接,服务器A的连接状态我们可以看到是TIME_WAIT。

如果一旦发生异常呢?假设请求的资源服务器B上并不存在,那么这个时候就会由服务器B发出关闭连接的请求,服务器A就是被动的关闭了连接,如果服务器A被动关闭连接之后程序员忘了让HttpClient释放连接,那就不会发出FIN包,就会造成原来的主动方服务器A一直维持在CLOSE_WAIT的状态了。

所以如果将大量CLOSE_WAIT的解决办法总结为一句话那就是:查代码。因为问题出在服务器程序里。

参考文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值