客户端主动断开 TCP 连接时需要四次挥手。
- 第一次挥手:客户端发出 连接释放报文,表示自己已经 停止发送数据
- 第二次挥手:服务器收到客户端的连接释放报文后,返回 客户端连接释放的确认报文,表示自己知道了,等我剩下的数据发送完就结束连接
- 第三次挥手:服务器发送完毕后,会再附带一个 连接释放报文,告诉客户端我发完了,已经停止发送数据了
- 第四次挥手:客户端收到服务器的连接释放报文,并发出一个确认报文,告诉服务器我收到了
- 服务器收到客户端的确认报文后 立即 关闭连接,而客户端则等待 2MSL 后进入连接关闭状态
TCP 报文头部有一个 FIN (finish) 配合 ACK 用来释放连接。
第一次挥手
客户端发出一个连接释放报文:报文中的 FIN 设为 1,表明这是一个释放连接的报文。
连接释放报文虽然可以不带有数据,但仍会占据一个序号,所以该报文的 seq 应该等于上一个发送的报文的 seq 值加 1。
第一次挥手就是为了使服务器知道客户端不会再发送数据了,但是服务器不一定发送完数据了,因此客户端还得接收数据。
发出连接释放报文后,客户端就从 ESTABLISHED 状态进入了 FIN-WAIT-1 状态。
第二次挥手
服务器收到客户端的连接释放请求,还需要给客户端一个答复,告诉客户端我收到你的连接释放请求了,但是我还有数据没发完,你得再等等。
由于是确认报文,所以 FIN = 0, ACK = 1
,同时附上对连接释放报文序列的确认序号 ack=u+1
和自己的序号 seq=v
。
服务器发送完确认报文,自己立马从 ESTABLISHED 状态进入 CLOSE-WAIT 状态。
整个 CLOSE-WAIT 状态是一个 半关闭状态:客户端不能向服务器发送数据了,但是服务器还可以向客户端发送数据。
客户端收到服务器的确认报文后,进入 FIN-WAIT-2 状态,等待服务器发送连接释放报文,再次期间如果服务器还有数据要发送,客户端必须接收。
第三次挥手
服务器在 CLOSE-WAIT 期间仍然会继续客户端发送数据,当剩余数据发送完毕后,服务器会再追加一个连接释放报文,告诉客户端自己已经发完了,将停止发送数据。
因为此时服务器发送的是连接释放报文,因此 FIN=1
,同时附上对上一次客户端发送的报文的确认 ACK=1;ack=u+1
和自己数据的序号 seq=w
。
服务器主动发送完连接释放报文后,从 CLOSE-WAIT 状态进入 LAST-ACK (最后确认) 状态,需要收到客户端的确认消息后才能真正断开连接。
这里,服务器发送的连接释放报文的确认序号等于客户端发送的连接释放报文的序列号加1,因为客户端在发送完连接释放报文后就再也不会发送其它数据了。
第四次挥手
客户端在 FIN-WAIT-2 状态一致等到服务器的连接释放报文,当收到该报文后,客户端立即给服务器发送一个确认报文,确认自己收到了服务器的连接释放消息。
标志位 FIN=0; ACK=1; seq=u+1; ack=w+1
。
发送完确认消息后,客户端立即进入 TIME-WAIT 状态,经过 2MSL 时间后,客户端自动进入 CLOSED 状态。
服务器收到客户端的确认消息后,立即进入 CLOSED 状态。
所以断开连接的四次挥手就是相互发送连接释放报文并确认的过程。
常见问题
☺ 为什么需要四次挥手?
为了确保数据能够完成传输。断开连接时,收到对方的 FIN 报文,仅仅表示对方不会再给你发数据了,但是此时你可能还需要发数据给对方,因此并不能立刻关闭连接,只能先给对方一个确认收到的消息(ACK),告诉我收到你的 FIN 报文了,我已经知道你停止发送数据了,但是我还没有发完呀,你得先等等,我发完了再通知你。
当服务器发送完成之后,会在发送一个 FIN 报文,告诉对方我也发完了,收到对方的确认之后,再断开连接。
☺ 客户端突然挂掉了怎么办?
客户端挂掉了,意味着客户端不会再向服务器发送数据了,服务器就只能设置一个定时器,如果空闲时间超过一定阈值,就开始向客户端发送 探测报文,如果所有探测报文发送完还是没收到客户端的数据,就说明客户端挂掉了,此时服务器就可以主动断开连接。计时器会在收到客户端数据时重置,它的作用是用来保证客户端连接的活性,所以又叫 保活计时器。
☺ 客户端为什么要等待 2MSL?
报文最大生存时间 (MSL, Maximum Segment Lifetime) 指的是报文在网络上存在的最长时间,超过这个时间的报文会被丢弃。
假设时间过了一个 MSL 服务器没有收到第四次握手客户端发来的确认报文,那么服务器就会认为这个报文丢失了,它就会再次重复第三次挥手的报文,并且理论上这个报文最多经过 MSL 时间就会到达客户端。也就是说,即使客户端的确认报文丢失,那么最多在 2MSL 时间内它就会再次收到服务器的连接释放报文。
如果客户端等待 2MSL 后没有收到任何服务器的数据,说明服务器已经正常断开连接了,那么客户端就可以从 TIME-WAIT 状态进入 CLOSED 状态。
评论区