面试回答TCP四次挥手问题及相关问题
TCP三次握手的原理参考怎么回答TCP的三次握手问题
四次挥手的原因
TCP断开连接需要四次挥手,相对于建立TCP连接的三次握手多了一次。这由TCP的半关闭(half-close)造成的。所谓的半关闭,其实就是TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。所以断开连接的四次挥手不能像建立连接的三次握手一样,将其中的两次握手操作合为一次。(TCP三次挥手中,合并两次挥手的原理可查看链接)
四次挥手的流程
在socket的流程中,任意一方执行close()函数便会申请关闭连接,即产生四次挥手操作。假设客户端向服务器发起关闭请求。最开始双方都处于ESTABLISHED状态。
第一次挥手:客户端发送一个FIN报文,报文中会指定一个序列号。此时客户端处于 FIN_WAIT1 状态。 即发出连接释放报文段(FIN=1,序号seq=x),并停止再发送数据,主动关闭TCP连接,进入FIN_WAIT1状态,等待服务端的确认。
第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态。 即服务端收到连接释放 报 文 段 后 即 发 出 确 认 报 文 段 ( ACK=1 , 确 认 号 ack=x+1 , 序 号 seq=y) , 服 务 端 进 入CLOSE_WAIT状态,此时的TCP处于半关闭状态,客户端到服务端的连接释放。客户端收到服务端的确认后,进入FIN_WAIT2状态,等待服务端发出的连接释放报文段。
第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。 即服务端没有要向客户端发出的数据,服务端发出连接释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=x+1),服务端进入LAST_ACK状态,等待客户端的确认。
第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 +1作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。 即客户端收到服务端的连接释放报文段后,对此发出确认报文段(ACK=1,seq=x+1,ack=w+1),客户端进入TIME_WAIT状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL后,客户端才进入CLOSED状态。
TCP四次挥手的相关问题
挥手为什么需要四次?
因为当服务端收到客户端的SYN连接请求报文后(TCP三次握手),可以直接发送SYN+ACK报文(将两次合为一次,所以只需要3次)。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭OCKET,所以只能先回复一个ACK报文,告诉客户端,“你发的FIN报文我收到了”。只有等到我服务端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四次挥手。
2MSL等待状态?
TIME_WAIT状态也被称为2MSL等待状态。每个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime),它是任何报文段被丢弃前在网络内的最长时间。这个时间是有限的,因为TCP报文段以IP数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段。
对一个具体实现所给定的MSL值,处理的原则是:当TCP执行一个主动关闭,并发回最后一个ACK,该连接必须在TIME_WAIT状态停留的时间为2倍的MSL。这样可让TCP再次发送最后的ACK以防这个ACK丢失(另一端超时并重发最后的FIN)。
这种2MSL等待的另一个结果是这个TCP连接在2MSL等待期间,定义这个连接的插口(客户的IP地址和端口号,服务器的IP地址和端口号)不能再被使用。这个连接只能在2MSL结束后才能再被使用。
四次挥手释放连接时,等待2MSL的意义?
MSL是Maximum Segment Lifetime的英文缩写,可译为“最长报文段寿命”,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。等待2MSL有两个意义:
- 为了保证客户端发送的最后一个ACK报文段能够到达服务器。因为这个ACK有可能丢失,从而导致处在LAST-ACK状态的服务器收不到对FIN-ACK的确认报文。服务器会超时重传这个FIN-ACK,接着客户端再重传一次确认,重新启动时间等待计时器。最后客户端和服务器都能正常的关闭。假设客户端不等待2MSL,而是在发送完ACK之后直接释放关闭,一但这个ACK丢失的话,服务器就无法正常的进入关闭连接状态。
- 防止“已失效的连接请求报文段”出现在本连接中。 客户端在发送完最后一个ACK报文段后,再经过2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文段。
服务器出现大量close_wait状态?
出现close_wait状态可能是由于当客户端申请断开连接后,第二次挥手时服务器发送了ACK报文进入CLOSE_WAIT状态后,并没有发送FIN报文。可能的原因是服务器在处理一些业务操作。比如关闭数据库操作,将一些临时数据同步到数据库中。
该问题的出现可以从程序逻辑的修改中解决。比如当客户端申请断开连接时,服务器将业务操作给到任务队列中,给线程池处理。然后服务器就可以直接先调用close(fd),发送FIN报文,关闭连接。
客户端长时间处于fin_wait_2状态?
原因:可能服务器在close_wait状态时断电了。或者服务器延迟调用close()
解决:超时会终止,心跳。fin_wait2无法通过程序释放,只能通过kill,os系统回收。
CLOSING状态
这种状态比较特殊,实际情况中应该是很少见,属于一种比较罕见的例外状态。正常情况下,当你发送FIN后,按理来说是应该先收到(或同时收到)对方的ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报文,反而却收到了对方的FIN报文。什么情况下会出现此种情况呢?其实细想一下,也不难得出结论:那就是如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也就会出现CLOSING状态,表示双方都正在关闭SOCKET连接。
大量Time_wait,产生原因?解决方案?
主动方调用close(),则主动端会有time_wait状态,若服务器出现大量的time_wait,则可能是服务器代码主动调用了close导致的。
解决方案:
1 这是一种不正常的逻辑,查逻辑bug
2: 若代码逻辑正常,则可用setsockopt()函数设置为reuse重用状态,只是一定程度的减少