ITPub博客

首页 > > > 深入浅出连接关闭(1)

深入浅出连接关闭(1)

作者:得一技术 时间:2019-09-09 10:25:09 0 删除 编辑

连接建立后,会占用一定的系统资源,而服务器的资源是有限的。因此,当连接服务完成后,需要及时去关闭连接,以释放资源。

 

连接关闭是相对比较复杂的一个过程,大致原因如下:

[if !supportLists]1.     [endif]TCP连接是全双工的

[if !supportLists]2.     [endif]老的重复数据包可能会干扰连接关闭过程

[if !supportLists]3.     [endif]需要考虑如何抵御网络攻击

[if !supportLists]4.     [endif]需要考虑高吞吐量的需求

[if !supportLists]5.     [endif]需要减少网络资源的消耗

 

TCP本身是全双工通信,两端都可以自主选择关闭连接。客户端关闭连接代表客户端不再发送数据,服务器端关闭连接代表服务器端不再发送数据。客户端关闭连接,不代表服务器端也需要立即关闭连接。

 

由于网络存在着延迟,并且路由器可能会阻塞数据包的转发,导致一些老的数据包在互联网游荡。如果连接被快速关闭,这些老的数据包最终到达目的地后,可能会造成TCP错误的行为,因此需要保持TCP状态一段时间,以便优雅地去处理这些数据包。

 

操作系统TCP为了抵御潜在的网络攻击,有时会破坏RFC规则,自主选择某种超时关闭机制。这种机制在规避网络攻击的同时,也增加了解决问题的复杂性。

 

服务器端往往需要大量TCP连接,而TCP能够提供的资源是有限的,为了能够快速利用各种资源,服务器端可能会破坏一些规则,以达到高吞吐量的需求。

 

程序的不合理使用或者连接长久空闲,可能会导致网络资源消耗巨大,例如某些云平台为了降低自身资源消耗,会采取一定的措施来杀连接,给用户带来困扰。

 

连接关闭可以分为正常连接关闭和异常连接关闭,我们先讲述正常连接关闭是如何关闭的。

1、正常关闭连接

正常关闭分为四次挥手和三次挥手,四次挥手是需要4个数据包交互才能完成连接关闭,而三次挥手是需要3个数据包交互才能完成连接关闭。

1.1 四次挥手

 上图展示了四次挥手过程,客户端(client)首先关闭连接,发送FIN seq=x,ack=y数据包到服务器端。服务器端TCP接收到FIN数据包,把状态置为CLOSE_WAIT,发送ACK seq=y,ack=x+1给客户端,并通知上层应用客户端已经关闭连接。客户端接收到服务器端的ACK数据包,把TCP状态置为FIN_WAIT2,等待服务器端的FIN数据包过来。服务器端上层应用程序接收到TCP的关闭连接通知后,调用close()函数委托服务器TCP去负责连接关闭。服务器TCP把连接状态置为LAST_ACK,并发送FIN seq=y,ack=x+1到客户端。客户端TCP接收到服务器端发过来的Fin数据包,发送ACK seq=x+1,ack=y+1给服务器端, 把TCP状态置为TIME_WAIT(维持 2倍报文最大生存时间,即2MSL,Linux维持60秒)。服务器端TCP接收到客户端过来的ACK确认数据包,把TCP状态置为CLOSED状态,这时此连接在服务器端彻底关闭。等待一段时间后,客户端TCP把TIME_WAIT状态置为CLOSED状态,这时连接彻底关闭。

 

下图展示了服务器端先关闭连接的过程。


 下图从抓包文件里展示了服务器端先关闭连接的四次挥手过程。

1.2 三次挥手

三次挥手相比四次挥手,少了一次挥手。

下图展示了服务器端把第二次挥手和第三次挥手进行了合并。

下图展示了客户端合并了第二次和第三次挥手过程。

下图展示了抓包文件里的三次挥手过程。

第二次和第三次挥手合并的好处是节省了一个数据包,采用三次挥手必须满足的一个条件是上层应用能够及时去关闭连接。如果上层应用不能及时关闭连接,就很难有三次挥手过程,因为TCP无法自己发送FIN数据包。FIN数据包是应用层执行close操作后,委托给TCP去发送的。

2、异常关闭连接

四次挥手过程,如果某一个过程出现异常怎么办?永久处于那个TCP状态会引起资源的泄漏,而不永久关闭就可能违反TCP规范,这是一个两难的问题。

由于这方面的坑特别多,我们本节简单讲述异常关闭连接,后续章节会详细讲述更多的案例。

2.1 reset关闭连接 

上图展示了遇到reset数据包来异常终止连接的一些常见状态:

[if !supportLists]1.     [endif]SYN_RCVD

[if !supportLists]2.     [endif]SYN_SENT

[if !supportLists]3.     [endif]ESTABLISHED

[if !supportLists]4.     [endif]FIN_WAIT_1

[if !supportLists]5.     [endif]FIN_WAIT_2

[if !supportLists]6.     [endif]CLOSE_WAIT

 

处于这些状态,如果接收到reset数据包,会瞬间把TCP状态置为CLOSED状态,从而无法跟踪和查看TCP状态。

 

TCP reset数据包是非常有争议的数据包,这种数据包瞬间清零的做法,在防火墙、负载均衡器和路由器等设备程序中广泛存在,甚至在windows系统也时常用于关闭连接。

上图windows系统先发送FIN数据包到服务器,在服务器端还没有发送FIN数据包给客户端时(服务器相应的TCP连接处于CLOSE_WAIT),客户端又发送reset数据包给服务器来清理连接资源。

上图客户端先发送了FIN数据包,代表客户端不再发送数据到服务器端,服务器端TCP进行了ack确认,这时客户端处于FIN_WAIT2状态,服务器端TCP处于CLOSE_WAIT状态。服务器端TCP还在继续传递数据给客户端,最终客户端发送了reset数据包拒绝了接收数据,同时把服务器端的TCP状态置为了CLOSED状态。

上图端口9998是服务器端应用端口,42017端口是客户端端口,在reset数据包之前,双方连接处于ESTABLISHED状态,而后reset数据包把客户端连接状态瞬间置为CLOSED状态。

上述连接通过reset瞬间关闭的做法,在实际中很普遍,而这些做法多多少少违反了RFC规范,有时会给用户带来烦恼。

2.2 超时清理连接状态

连接关闭通常是由上层应用驱动的,应用程序如果不关闭连接或不及时关闭连接,会浪费资源,可能最终导致服务不可用。 

我们通过一个实验来查看Linux系统是如何通过超时来关闭连接的。 

通过MySQL客户端终端登入MySQL,将global interactive_timeout和global

wait_timeout均设置为120秒,如下图:

设置后退出MySQL客户端终端。 

利用MySQL客户端终端再次登入,查看变量是否生效,之后一直不操作,如下图:

 我们通过netstat来查看连接状态,连接已经是ESTABLISHED状态。

由于MySQL客户端终端长期不操作,MySQL会关闭连接,发送FIN数据包给客户端,而客户端由于不操作,不会去执行关闭操作,所以会一直处于CLOSE_WAIT状态,如下图:

在服务器端,我们查看相应状态已经为FIN_WAIT2状态。

 

过了1分钟,服务器端相应TCP状态消失,因为服务器端tcp_fin_timeout默认只有60秒。

抓包结果如下:

服务器发送FIN数据包给客户端后,客户端TCP进行了确认,但后续并没有发送FIN数据包给服务器端,所以状态一直处于CLOSE_WAIT。

这时客户端连接,如何关闭呢?要么服务器发送reset数据包(上面抓包文件显示没有),要么MySQL客户端终端自己执行关闭操作,否则连接一直处于CLOSE_WAIT状态。

这里讲述了Linux由于超时清理了服务器端的连接状态,而客户端连接一直僵持在CLOSE_WAIT状态。 

Linux不仅采用超时机制,还会采用keepalive机制来清理TCP状态("遇到大量FIN_WAIT1,怎么破"章节会有介绍),甚至还会因为内存紧张杀连接,如下图。

3、连接关闭与应用的关系

3.1正常关闭连接

正常关闭连接情况下,如果对端发送了FIN数据包过来,TCP会通知上层应用有读事件,应用去读取数据。如果读取的长度为0,意味着对端连接的关闭。这个时候可以选择继续传输数据,也可以选择关闭连接。大部分场景下,会选择关闭连接。

 

为防止对对端的数据还没有接收完就关闭连接,TCP提供了选项SO_LINGER来延缓连接关闭。

3.2异常关闭连接

下图展示了遇到reset数据包,Linux

TCP是如何工作的。

当TCP状态处于SYN_SENT(图中TCP_SYN_SENT),如果遇到reset,则会向上层应用报connection refused错误(ECONNREFUSED);当TCP状态处于CLOSE_WAIT状态,TCP会向上层报pipe错误(EPIPE);当TCP状态处于CLOSED状态,则不会做任何动作;其它情况下,TCP会向上层应用报connection reset错误(ECONNRESET)。

 

下图中,当发送第一次握手数据包后,客户端连接TCP状态为SYN_SENT,结果遇到了服务器的reset数据包,客户端TCP会向telnet上层应用报“Connection

refused”错误。

4、经典案例

下图是用户遇到的一个问题,服务器(443端口)先关闭连接,客户端TCP进行了ack确认,然后客户端继续传输Encrypted Alert数据包,接着客户端发送FIN数据包到服务器,但后续一直重传Encrypted Alert数据包。

看似连接关闭没有问题,为什么一直在重传呢?

我们看一下时间戳信息。

上图展示了在发送Encrypted Alert数据包时(第147个数据包),上一个ack确认数据包在50多分钟前发送。对服务器FIN数据包的确认,导致客户端处于CLOSE_WAIT状态,而服务器端处于FIN_WAIT2状态。操作系统为Linux系统,默认tcp_fin_timeout为1分钟,而时间过去了50多分钟,TCP连接状态FIN_WAIT2已被Linux超时机制变成CLOSED状态,即连接在服务器端已经不存在。

TCP连接,在客户端是CLOSE_WAIT状态,而在服务器已经不存在。在17:31:37,客户端发送Encrypted Alert数据包过去,按道理服务器TCP会发送reset数据包回来(根据reset发送规则,后续课程会有介绍),但从抓包文件来看,却没有reset数据包。我们可以推断,Encrypted Alert数据包和后面的FIN数据包,都被中途设备程序丢弃了,导致客户端TCP只能选择重传Encrypted Alert数据包。 

这个案例中,重传问题的产生有如下三大原因:

[if !supportLists]1.     [endif]客户端没有及时去关闭连接

[if !supportLists]2.     [endif]服务器过早通过超时机制来清理TCP连接状态

      服务器端保持连接FIN_WAIT2状态足够久且通过keepalive来保持连接活跃,就能解决问题

[if !supportLists]3.     [endif]中途设备程序没有转发数据包到服务器端

如果服务器没有通过tcp_fin_timeout来清理FIN_WAIT2状态,会导致服务器端积累大量FIN_WAIT2状态,进而可能导致服务器资源耗光,服务不可用。如果通过tcp_fin_timeout来清理FIN_WAIT2状态,则会导致客户连接还健在,服务器端连接不存在的问题。中途设备程序可能干涉数据包的发送(例如,中途设备程序进行了connection tracking,定期把不活跃的连接信息清除了,一旦路由的时候找不到数据包的相关连接信息,于是丢弃该数据包)。

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69935868/viewspace-3151366/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2019-07-25

  • 博文量
    16
  • 访问量
    11255