TCP 核心问题剖析之 断连我只想 “三次挥手” 你凭什么让我 “挥手四次” ?
想必看过我的 三握 文章的同学应该对 ”握手“ 和 ”挥手“ 这个概念有所理解了,其实就是数据交互。就是我给你发条消息,你接受处理,回你条消息,你接受处理如此而已。
那怎么到了断开连接的时候,就要 ”挥手四次“ 了呢
- A 说:我要断开连接
- B 说:收到断吧
- A 说:收到我断了
这样是不也就 ok 了呢?感觉好像没问题?本文从将从这里开始
照例
本文将带你学会
- 什么是 TCP 四次挥手
- 断开连接,我只想挥手三次,你凭什么让我挥手四次?
- 二次挥手 ACK 对方跑路了咋办?
- CLOSE_WAIT 在等啥?
- TIME_WATI 2MSL 等待两个最大报文存活时长意欲何为?
TCP ”四次挥手“ 断连
聊为什么之前,先看看是什么?
172.16.26.177 是 Client 客户端地址,192.168.26.144 是 Server 服务提供方地址。
可以看到,经过一段时间 keep-alive 之后,服务端觉得,闹够了!我要断连!流程如下
一次挥手 :172.16.26.177 --> 192.168.26.144 我要断连! 发送标志位 FIN ACK ,这里 Ack 的值是上次来包的 seq + 1 (即 6572 C 发给 S 的 seq )
图中 Seq = 430 Ack = 295
看网上大部分都只说了 FIN 标志位,那其实 ACK 标志位也是 1 ,所以准确说应该是 FIN ACK
二次挥手:192.168.26.144 --> 172.16.26.177 我收到了你的断链请求。ACK 这次挥手是确认第一次挥手的 FIN ACK 收到了 (注意此时不是告知你可以断开连接了)
此时 Seq = 295 Ack = 431
复习一下,Seq 为本段报文数据第一个字节编号, Ack 为期望下次收到下个报文 Seq 值 = 要确认报文 Seq 值 + 要确认报文数据字节数
三次挥手:192.168.26.144 --> 172.16.26.177 好我这没问题,可以断连。FIN ACK 第三次挥手是被断连方发送确认可断连报文,此时的 Seq 和 Ack 和 二次挥手一样
四次挥手:172.16.26.177 --> 192.168.26.144 好的收到。ACK 告诉被断连方,我收到了你的断连确认。
此时 Seq = 431 Ack = 296
通常来说都会给一个状态流程图来方便理解,这里我也画了一个来辅助认识
为啥是 四次 不是 三次
我们看看 “三次挥手” 行不行,也就是说 四挥手确认 不要了。
似乎不太行,这样我无法确认 三挥手 FIN ACK 已经被收到,如果三次挥手的 FIN ACK 包在网络中丢失,那我可能会长时间处于 FIN_WAIT2 状态。
仔细观察,其实所谓的 “四次挥手” 我们主要的是两次的 FIN ACK 断连报文,而剩下的两次是对这两次的 FIN ACK 的确认。
看着减少不行,那我能不能多挥几次?当然可以,但没有必要,对于这种通信,我们追求的是在最少的通信次数内做好,即只做必要的事。
好,那我不删 第四次,我删 第二次 好吧?让你断你就断,分什么 ACK + FIN ACK
好,这里没了 CLOSE_WATI 状态,那我们就要解释下这个状态的用处
CLOSE_WAIT 在等啥?
我们发起断连请求时,对方可能存在有些数据包没发完,或者有些包没收到确认,那么等所有包都确认送达后,我才会告诉你可以断开了。故而这里的 ACK 也不能去。
这是可能有人(TMD 是谁?!)就说了:诶?不对!我这也行啊,我等确认收完再直接发 FIN ACK 不就行了?
行,你可真行,真是个小机灵鬼。
这里我认为是这样的:首先 TCP 要求每个报文都要有 ACK 确认,这个是设计原则不可变,那么没有收到 ACK 就会重试发包直到确认收到。
可以理解为这是个通用的机制,而上面的这个操作,你等全部确认发完,可能会有一段时间,这个会影响到通用机制不停的重发,所以为了保证我们的机制,要接收到报文后立刻确认我收到,故而有了,FIN_WAIT1 和 FIN_WAIT2 两个状态。
你说他们有本质区别吗?我觉得没有,这是一个机制设计的问题,取舍后保留较为合理的,更加可读易懂的。
TIME_WATI 2MSL 等待两个最大报文存活时长意欲何为?
细心的同学应该发现,我们 断连发起方,在四次挥手 ACK 后,仍然等待了两个 MSL (最大报文存活时长)才进入了 CLOSED 状态。
协议规定 MSL 为 2 分钟,实际应用中常用的是 30 秒,1 分钟和 2 分钟等
这是因为,我们要确认 被断连方 收到了我们的 ACK ,为了防止无限的 ACK 下去,我们这里兜底等待 2MSL 时长,如果被断连方没收到 ACK 它会重新发送 FIN ACK 可断报文,此时我们可以补发 ACK。
如果超过了这个时长,我就不管了,已经仁至义尽,之后来的我们直接返回 RST 重置报文,让他重置连接吧。
二次挥手 ACK 对方跑路了咋办
可能你又有疑问了,我们的网络环境纷繁复杂,啊……我可不确认明天和意外谁先来临,难道我 FIN ACK 了对方,对方就一定能回复我么?
我看未必,服务宕机,浏览器关闭,电脑挂机,机房着火,如果意外可能存在,那么它必定存在。
咋办,那我就一直 FIN_WAIT 吗?如何处理?
自然而然,想到了超时,我设置个超时,如果长时间没有收到 ACK ,我也不管你了,我就直接自己断了,你爱咋咋吧。
确实可以,然而 TCP 没有为我们提供这种机制,好在 Linux 为我们提供了 tcp_fin_timeout 这个参数,设置超时时间来处理这种情况。
感谢 Linux ,否则我可要等死了。