2)第二次握手:服务器B收到SYN包,必须发生一个ACK包,来确认客户A的SYN(ACK=j+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器B进入SYN_RECV状态。
3)第三次握手:客户端A收到服务器B的SYN+ACK包,向服务器B发送确认包ACK(ACK=k+1),此包发送完毕,客户端A和服务器B进入ESTABLISHED状态,完成三次握手(注意,主动打开方的最后一个ACK包中可能会携带了它要发送给服务端的数据)。
总结:三次握手,其实就是主动打开方,发送SYN,表示要建立连接,然后被动打开方对此进行确认,表示可以,然后主动方收到确认之后,对确认进行确认;
5.2 四次挥手断开连接:
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭,TCP的双方都要向对方发送一次 FIN 包,并且要对方对次进行确认。根据两次FIN包的发送和确认可以将四次挥手分为两个阶段:
第一阶段:主要是主动闭方方发生FIN,被动方对它进行确认;
1)第一次挥手:主动关闭方,客户端发送完数据之后,向服务器发送一个FIN(M)数据包,进入 FIN_WAIT1 状态;
被动关闭方服务器收到FIN(M)后,进入 CLOSE_WAIT 状态;
2)第二次挥手:服务端发生FIN(M)的确认包ACK(M+1),关闭服务器读通道,进入 LAST_ACK 状态;客户端收到ACK(M+1)后,关闭客户端写通道,
进入 FIN_WATI2 状态;此时客户端仍能通过读通道读取服务器的数据,服务器仍能通过写通道写数据。
第二阶段:主要是被动关闭方发生FIN,主动方对它进行确认;
3)第三次挥手:服务器发送完数据,向客户机发送一个FIN(N)数据包,状态没有变还是 LAST_ACK;客户端收到FIN(N)后,进入 TIME_WAIT 状态
4)第四次挥手:客户端返回对FIN(N)的确认段ACK(N+1),关闭客户机读通道(还是TIME_WAIT状态);
服务器收到ACK(N+1)后,关闭服务器写通道,进入CLOSED状态。
总结:
四次挥手,其本质就是:
主动关闭方数据发生完成之后 发生FIN,表示我方数据发生完,要断开连接,被动方对此进行确认;
然后被动关闭方在数据发生完成之后 发生FIN,表示我方数据发生完成,要断开连接,主动方对此进行确认;
5.3 CLOSE_WAIT状态的原因和处理方法:
由上面的TCP四次挥手断开连接的过程,可以知道 CLOSE_WAIT 是主动关闭方发生FIN之后,被动方收到 FIN 就进入了 CLOSE_WAIT状态,此时如果被动方没有调用 close() 函数来关闭TCP连接,那么被动方服务器就会一直处于 CLOSE_WAIT 状态(等待调用close函数的状态);
所以 CLOSE_WAIT 状态很多的原因有两点:
1)代码中没有写关闭连接的代码,也就是程序有bug;
2)该连接的业务代码处理时间太长,代码还在处理,对方已经发起断开连接请求; 也就是客户端因为某种原因先于服务端发出了FIN信号,导致服务端被动关闭,若服务端不主动关闭socket发FIN给Client,此时服务端Socket会处于CLOSE_WAIT状态(而不是LAST_ACK状态)。
CLOSE_WAIT 的特性:
由于某种原因导致的CLOSE_WAIT会维持至少2个小时的时间(系统默认超时时间的是7200秒,也就是2小时)。如果服务端程序因某个原因导致系统造成一堆 CLOSE_WAIT消耗资源,那么通常是等不到释放那一刻,系统就已崩溃。CLOSE_WAIT 的危害还包括是TOMCAT失去响应等等。
要解决 CLOSE_WAIT 过多导致的问题,有两种方法:
1)找到程序的bug,进行修正;
2)修改TCP/IP的keepalive的相关参数来缩短CLOSE_WAIT状态维持的时间;
TCP连接的保持(keepalive)相关参数:
1> /proc/sys/net/ipv4/tcp_keepalive_time 对应内核参数 net.ipv4.tcp_keepalive_time
含义:如果在该参数指定的秒数内,TCP连接一直处于空闲,则内核开始向客户端发起对它的探测,看他是否还存活着;
2> /proc/sys/net/ipv4/tcp_keepalive_intvl 对应内核参数 net.ipv4.tcp_keepalive_intvl
含义:以该参数指定的秒数为时间间隔,向客户端发起对它的探测;
3> /proc/sys/net/ipv4/tcp_keepalive_probes 对应内核参数 net.ipv4.tcp_keepalive_probes
含义:内核发起对客户端探测的次数,如果都没有得到相应,那么就断定客户端不可达或者已关闭,内核就关闭该TCP连接,释放相关资源;
所以 CLOSE_WAIT 状态维持的秒数 = tcp_keepalive_time + tcp_keepalive_intvl * tcp_keepalive_probes
所以适当的 降低 tcp_keepalive_time, tcp_keepalive_intvl,tcp_keepalive_probes 三个值就可以减少 CLOSE_WAIT:
修改方法:
sysctl -w net.ipv4.tcp_keepalive_time=600
sysctl -w net.ipv4.tcp_keepalive_probes=3
sysctl -w net.ipv4.tcp_keepalive_intvl=5
sysctl -p