logo  

运维备忘录

运维备忘录
作者: 陈安廉

摘要:软件开发进阶系列


finwait2和closewait状态累积问题


2021-08-19 19:30:02


TCP连接是一种可靠的连接,在这里可以这么理解:既要确认本机发出的包得到确认,又要确认收到的任何消息都已告知连接的对端。

TCP双工的这个特性使得连接的正常关闭需要四次握手,其含义为:主动端关闭了发送的功能;被动端认可;被动端也关闭了发送的功能;主动端认可。

但还存在程序异常的情形,此时,则通过异常的那端发送RST复位报文通知另一端关闭连接。


TCP连接是一种双工的连接,何谓双工?即连接双方可以并行的发送或者接收消息,而无须顾及对方此时到底在发还是收消息。这样,关闭连接时,就存在3种情形:完全关闭连接;关闭发送消息的功能;关闭接收消息的功能。其中,后两者就叫做半关闭,由shutdown实现(所以 shutdown多出一个参数正是控制关闭发送或者关闭接收),前者由close实现。


close:

客户端作为主动方:

如果有未读消息,发送rst通知对方有异常发生。

如果没有未读消息,则对于对方来说不一定是异常,可以正常关闭,发送fin报文。





shutdown:







TCP连接的关闭是双向的,必须通知对方自己不再发送数据,关闭出流量的方向;然后等待对方通知自己可以关闭入流量。如果不这样做,对方可能还要数据发送,而对方并不知道你已经关闭了接收,这就会造成错误。


所以处于finwait1状态的socket如果等不到ack,需要重发fin,知道超时错误。

处于finwait2状态的socket在等待对方的fin,这种情况稍微复杂,如果对方因为故障,一直不发送fin呢?








一方进程被杀死,系统会主动向对方发送fin,进入finwait1,在收到对方的ack后进入finwait2。

被动关闭方收到fin,发送ack,进入closewait。


此时主动关闭方进程已死,已不可能向被动关闭方发送消息。被动关闭方进程还在,而双方连接都还在。结束等待状态的情况只有以下几种

  1. 被动方写数据,不报错,主动方会返回rst,并从finwait2进入close;被动方收到rst后进入colse

    已经关闭的情况下。被动方读数据,会报异常Connection reset。

    已经关闭的情况下。写数据不报错,但是flush()会报错:Broken pipe

2.系统重启。

3.配置finwait2和closewait的超时。


4.否则连接会一直存在并占用资源。客户端会占用一个端口,而服务端占用一个fd。



finwait2的超时

PROC文件tcp_fin_timeout默认为60秒,内核中相应的变量为init_net.ipv4.sysctl_tcp_fin_timeout,不过其以jiffies表示,默认值为TCP_FIN_TIMEOUT,即(60 * HZ)。此值表示一个不再被应用层使用(执行了close调用)的TCP连接处于FIN_WAIT_2状态的时长,如果在此时间内未能接收到对端的FIN结束报文,内核将复位此连接。但是除此之外,如果应用层是执行shutdown(SHUT_WR)操作关闭了套接口的发送,TCP连接还可进行接收操作,此种情况下的TCP连接处于FIN_WAIT_2状态不受tcp_fin_timeout的时间限制,将会永久的等待对端去关闭连接或者本地使用close关闭。


超时定时器设置

FIN_WAIT_2状态的超时定时器设置分两种情况,其一是由应用层的shutdown系统调用所引发;其二是由close系统调用引发。

先看第一种情况,shutdown可关闭接收或者发送方向的流量(仅关闭发送方向时触发FIN报文发送),导致套接口处于半关闭状态,并且套接口状态走到FIN_WAIT_1(关闭接收方向套接口状态不变),等待对端响应ACK报文。如果对端不响应ACK报文,本端FIN报文会进行超时重传,直到出错处理。


反之,当接收到对端回应的ACK报文时,处理流程进入函数tcp_rcv_state_process的TCP_FIN_WAIT1分支。由于此时应用层并没有close套接口,其SOCK_DEAD标志未设置,仅是将套接口的状态设置为TCP_FIN_WAIT2,退出处理流程,留待本端应用层close系统调用去结束连接;或者对端发送FIN报文来结束连接。


套接口的SOCK_DEAD标志没有置位还有一种可能是,应用层调用了close接口,但是设置了套接口的SOCK_LINGER选项,注意其与TCP_LINGER2不同,设置了此选项后,tcp_close函数不会立即置位SOCK_DEAD,而是等待sk_lingertime规定的时长,所以即使应用层调用了tcp_close操作,如果还在sk_lingertime时长内,SOCK_DEAD标志也还没有设置。通常应用层未设置SOCK_LINGER选项,这不是默认情况。如果是应用层进程在退出时自动调用了套接口的close函数,内核将忽略SOCK_LINGER选项设置不执行等待。


但是,如果本地的应用层直接使用close调用完全关闭双向的连接而不是shutdown,看一下tcp_rcv_state_process函数接下来的处理。如果用户层设置的套接口linger2值小于零,内核将不会在FIN_WAIT_2状态等待,直接销毁套接口。如果FIN_WAIT_2的超时时间(tmo)大于TCP_TIMEWAIT_LEN(60秒)的时长,启动keepalive定时器,定时时长为二者之差(tmo - TCP_TIMEWAIT_LEN)。以上对tcp_fin_time函数的介绍可知,默认情况下其值等于TCP_TIMEWAIT_LEN的值,内核默认不执行此分支。


如果完全关闭的套接口在FIN_WAIT_1状态时,接收到的是一个带有FIN标志的报文(FIN+ACK报文)或者此处的套接口还未被应用层释放。启动keepalive定时器,定时时长为FIN_WAIT_2的超时时间。需要注意的是sock_owned_by_user能够成立的条件十分苛刻:仅在tcp_close函数释放套接口owner前,release_sock函数的spin_lock_bh未获得锁而等待时发生。


对于HTTP服务端来说,如果客户端发送了FIN报文结束连接,HTTP服务端通常回复FIN+ACK报文,实现3个报文结束连接。所以通常情况下客户端会启动tmo时长的keepalive定时器。



除了以上情况之外,如果FIN_WAIT_2的超时时间小于等于TCP_TIMEWAIT_LEN的值,并且接收到的报文不带有FIN标志(仅ACK),此种情况为TCPIP协议中定义的FIN_WAIT_2状态。启动TIME_WAIT定时器。定时时长设置为FIN_WAIT_2的超时时间,如果其小于当前连接的重传超时时间RTO的话,使用RTO时间,以允许FIN报文至少重传一次。tcp_time_wait函数最后销毁TCP套接口。


最后,接着半连接的套接口流程。如果应用层使用close系统调用主动关闭此半连接时,套接口的状态已经处于TCP_FIN_WAIT2了(tcp_rcv_state_process函数中设置)。如果TCP套接口的linger2小于零,直接发送重置reset报文;否则,根据FIN_WAIT_2的超时时间与TCP_TIMEWAIT_LEN的大小关系,启动keepalive定时器或者time_wait定时器,防止对端一直不发送FIN结束报文导致本端套接口不能释放。


但是,如果本地应用层不close此半连接,并且对端也不结束连接,不发送FIN报文,此连接将一直存在。



https://blog.csdn.net/sinat_20184565/article/details/88562876

https://blog.csdn.net/zhangxiao93/article/details/52078784