目录
一. 四次挥手
(1)过程描述
(2)为什么是四次挥手?
二、相关问题
1. 第一次挥手丢失了,会发生什么?
2. 第二次挥手丢失了,会发生什么?
补充:close 函数 和 shutdown 函数的不同
3. 第三次挥手丢失了,会发生什么?
4. 第四次挥手丢失了,会发生什么?
5. 为什么需要 TIME_WAIT 状态?
(1)防止历史连接中的数据,被后面相同四元组的连接错误的接收
(2)保证「被动关闭连接」的一方,能被正确的关闭
6. 什么情况会出现三次挥手?
什么是 TCP 延迟确认机制?
一. 四次挥手
双方 都可以 主动断开 连接,断开连接后 主机中的「资源」将被释放。
(1)过程描述
- ① 客户端 打算 关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态。
- ② 服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSE_WAIT 状态。
- ③ 客户端 收到 服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。
- ④ 等待 服务端 处理完数据后,也向 客户端 发送 FIN 报文,之后 服务端 进入 LAST_ACK 状态。
- ⑤ 客户端 收到 服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态。
- ⑥ 服务端收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此 服务端已经 完成连接的关 闭。
- ⑦ 客户端在经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此 客户端 也完成 连接的关闭。
这里 每个方向 都需要一个 FIN 和 一个 ACK,因此 通常被 称为 四次挥手。这里一点 需要注意是:主动关闭连接的,才有 TIME_WAIT 状态。
(2)为什么是四次挥手?
- 关闭 连接时,客户端 向 服务端 发送 FIN 时,仅仅 表示客户端 不再发送 数据了 但是还能 接收数据。
- 服务端 收到 客户端的 FIN 报文时,先回一个 ACK 应答报文,而 服务端 可能 还有 数据需要处理 和 发送,等 服务端 不再发送数据 时,才发送 FIN 报文 给客户端 来表示 同意现在 关闭连接。
从 上面过程 可知,服务端 通常需要 等待完成 数据的 发送和处理,所以 服务端 的开发送,因此是 需要 四次挥手。ACK 和 FIN 一般 都会 分开发送,因此是 需要 四次挥手。
二、相关问题
1. 第一次挥手丢失了,会发生什么?
当客户端(主动关闭方)调用 close 函数后,就会向服务端发送 FIN 报文,试图与服务端断开连接,此时客户端的连接进入到 FIN_WAIT_1状态。正常情况下,如果 能 及时 收到服务端(被动 关闭方)的 ACK,则会很快变为 FIN_WAIT_2 状态。
如果 第一次挥手 丢失了,那么 客户端迟迟 收不到 被动方的 ACK 的话,也就会 触发 超时重传机制,重传 FIN 报文,重发次数由 tcp_orphan_retries 参数 控制。当客户端重传 FIN 报文的次数超过 tcp_orphan_retries 后,就不再发送 FIN 报文,则会在等待一段时间(时间为上一次超时时间的 2 倍),如果 还是没能 收到 第二次挥手,那么 直接进入到 close 状态。
举个例子,假设 tcp_orphan_retries 参数值 3,当 第一次挥手一直 丢失时,发生的 过程如下图:
当客户端 超时重传 3 次 FIN 报文后,由于 tcp_orphan_retries 为 3,已 达到 最大重传 次数,于是 再等待一段时间(时间为 上一次 超时时间的 2 倍),如果 还是没能 收到 服务端的 第二次 挥手(ACK 报文),那么 客户端就会 断开连接。
2. 第二次挥手丢失了,会发生什么?
当服务端 收到客户端的 第一次挥手后,就会先回一个 ACK 确认 报文,此时 服务端 的 连接进入到 CLOSE_WAIT 状态。
ACK 报文是不会重传的,所以 如果 服务端的 第二次挥手 丢失了,客户端 就会 触发超时重传机制,重传 FIN 报文,直到 收到 服务端的 第二次挥手,或者 达到最大的 重传次数。
举个例子,假设 tcp_orphan_retries 参数值 2,当 第二次挥手 一直丢失 时,发生的 过程如下图:
当客户端 超时重传 2 次 FIN 报文后,由于 tcp_orphan_retries 为 2,已达到 最大重传次数,于是再 等待一段时间(时间为 上一次 超时时间的 2 倍),如果 还是没能 收到 服务端 的 第二次挥手(ACK 报文),那么 客户端 就会 断开连接。
补充:close 函数 和 shutdown 函数的不同
当 客户端 收到 第二次挥手,也就是 收到服务端 发送的 ACK 报文后,客户端 就会处于 FIN_WAIT_2 状态,在 这个状态 需要 等服务端 发送 第三次挥手,也就是 服务端的 FIN 报文。
对于 close 函数关闭的 连接,由于 无法再 发送 和 接收数据,所以 FIN_WAIT_2 状态 不可以 持续太久,而 tcp_fin_timeout 控制了 这个状态下 连接的 持续时长,默认值是 60 秒。
这 意味着 对于 调用 close 关闭的 连接,如果在 60 秒 后还 没有收到 FIN 报文,客户端(主动关闭方)的连接就会 直接关闭,如下图:
如果 主动关闭方 使用 shutdown 函数 关闭连接,指定了 只关闭 发送方向,而 接收方向 并没有 关闭,那么 意味着 主动关闭方 还是 可以 接收数据的。此时,如果 主动关闭方 一直没 收到 第三次挥手,那么 主动关闭方的 连接 将会一直 处于 FIN_WAIT_2 状态(tcp_fin_timeout 无法控制 shutdown 关闭的连接)。如下图:
3. 第三次挥手丢失了,会发生什么?
当 服务端(被动关闭方)收到 客户端(主动关闭方)的 FIN 报文后,内核会 自动回复 ACK,同时 连接处于 CLOSE_WAIT 状态,顾名思义,它表示 等待 应用进程 调用 close 函数 关闭连接。此时,内核 是 没有权利 替代 进程 关闭连接,必须 由进程 主动 调用 close 函数来 触发 服务端 发送 FIN 报文。
服务端 处于 CLOSE_WAIT 状态时,调用了 close 函数,内核 就会 发出 FIN 报文,同时 连接进入LAST_ACK 状态,等待 客户端 返回 ACK 来 确认连接 关闭。如果 迟迟 收不到这个 ACK,服务端就会重发 FIN 报文,重发次数 仍然 由 tcp_orphan_retries 参数 控制,这与 客户端 重发 FIN 报文的 重传次数 控制方式是 一样的。
举个例子,假设 tcp_orphan_retries = 3,当 第三次挥手 一直丢失 时,发生的 过程如下图:
- 当服务端 重传 第三次挥手 报文的 次数达到了 3 次 后,由于 tcp_orphan_retries 为 3,达到了 重传 最大次数,于是 再等待 一段时间(时间为 上一次 超时时间的 2 倍),如果 还是 没能 收到客户端的 第四次挥手(ACK 报文),那么 服务端 就会 断开连接。
- 客户端 因为是 通过 close 函数 关闭连接的,处于 FIN_WAIT_2 状态是 有时长 限制的,如果tcp_fin_timeout 时间内 还是没能 收到服务端的 第三次挥手(FIN 报文),那么 客户端就会 断开连接。
4. 第四次挥手丢失了,会发生什么?
当 客户端 收到服务端的 第三次挥手的 FIN 报文后,就会 回 ACK 报文,也就是 第四次挥手,此时 客户端 连接 进入 TIME_WAIT 状态。
在 Linux 系统,TIME_WAIT 状态 会持续 2MSL 后才会 进入 关闭状态。然后,服务端(被动关闭方)没有收到 ACK 报文前,还是处于 LAST_ACK 状态。如果 第四次挥手 的 ACK 报文 没有 到达服务端,服务端 就会 重发 FIN 报文,重发次数 仍然由 tcp_orphan_retries 参数 控制。
举个例子,假设tcp_orphan_retries 为 2,当 第四次挥手 一直丢失时,发生的过程如下:
- 当 服务端 重传 第三次挥手 报文达到 2 时,由于 tcp_orphan_retries 为 2,达到了 最大重传次数,于是 再等待一段时间(时间为 上一次 超时时间的 2 倍),如果 还是没能 收到 客户端的 第四次挥手(ACK 报文),那么 服务端 就会 断开连接。
- 客户端 在收到 第三次挥手后,就会进入 TIME_WAIT 状态,开启 时长为 2MSL 的定时器,如果途中 再次 收到 第三次挥手(FIN 报文)后,就会 重置定时器,当 等待 2MSL 时长后,客户端 就会断开连接。
5. 为什么需要 TIME_WAIT 状态?
(1)防止历史连接中的数据,被后面相同四元组的连接错误的接收
先来了解序列号(SEQ)和初始序列号(ISN):
- 序列号,是 TCP 一个 头部字段,标识了 TCP 发送端 到 TCP 接收端的 数据流的 一个字节,因为TCP 是 面向字节流的 可靠协议,为了 保证消息的 顺序性和可靠性,TCP 为 每个传输方向上的 每个字节 都 赋予了一个 编号,以 便于 传输成功后 确认、丢失 后重传 以及在 接收端保证 不会乱序。序列号是一个 32 位的 无符号数,因此 在到达 4G 之后 再循环回到 0。
- 初始 序列号,在 TCP 建立连接的 时候,客户端和服务端 都会 各自生成一个 初始序列号,它是 基于 时钟生成的 一个 随机数,来 保证 每个连接 都拥有 不同的 初始序列号。初始化 序列号 可被视为 一个 32 位的 计数器,该 计数器 的数值 每 4 微秒 加 1,循环 一次需要 4.55 小时。
序列号和初始化 序列号 并不是 无限递增的,会 发生 回绕为 初始值的 情况,这意味着 无法 根据序列号来 判断 新老数据。
假设 TIME-WAIT 没有等待时间或时间过短,被延迟的数据包抵达后会发生什么呢?
- 服务端 在 关闭连接 之前 发送的 SEQ =301 报文,被 网络 延迟了。
- 接着,服务端 以相同的 四元组 重新打开了 新连接,前面 被延迟的 SEQ=301 这时 抵达了 客户端,而且 该数据报文的 序列号 刚好在 客户端 接收窗口内,因此 客户端会 正常接收 这个 数据报文,但是 这个数据报文是 上一个 连接残留下来 的,这样就 产生 数据错乱 等 严重的问题。
为了 防止历史 连接中的 数据,被 后面 相同四元组的 连接错误的 接收,因此 TCP 设计了 TIME_WAIT 状态,状态 会 持续 2MSL 时长,这个 时间 足以让 两个方向上的 数据包都 被丢弃,使得 原来 连接的 数据包 在网络中 都 自然消失,再 出现的 数据包 一定都是 新建立 连接 所产生的。
(2)保证「被动关闭连接」的一方,能被正确的关闭
TIME-WAIT 作用是 等待足够的时间 以确保 最后的 ACK 能让 被动关闭方 接收,从而 帮助其 正常关闭。
如果 客户端(主动关闭方)最后一次 ACK 报文(第四次挥手)在 网络中 丢失了,那么 按照 TCP 可靠性原则,服务端(被动关闭方)会 重发 FIN 报文。
假设 客户端 没有 TIME_WAIT 状态,而是在 发完 最后一次 回 ACK 报文就 直接进入 CLOSE 状态,如果该 ACK 报文 丢失了,服务端 则 重传的 FIN 报文,而这时 客户端 已经 进入到 关闭状态了,在 收到服务端 重传的 FIN 报文后,就会回 RST 报文。
服务端 收到 这个 RST 并将其 解释为一个 错误(Connection reset by peer),这对于 一个 可靠的协议 来说 不是一个 优雅的 终止方式。为了 防止这种 情况出现,客户端 必须 等待足够长的 时间,确保 服务端 能够收到 ACK,如果 服务端 没有 收到 ACK,那么 就会触发 TCP 重传机制,服务端会 重新 发送一个 FIN,这样 一去一来 刚好 两个 MSL 的时间。
客户端在 收到 服务端 重传的 FIN 报文时,TIME_WAIT 状态的 等待时间,会重置回 2MSL。
6. 什么情况会出现三次挥手?
当 被动 关闭方(上图的 服务端)在 TCP 挥手过程中,「没有数据要发送」并且「开启了 TCP 延迟确认机制」,那么 第二 和 第三次 挥手 就会 合并传输,这样就 出现了 三次挥手。(TCP 延迟确认机 制 默认开启)
什么是 TCP 延迟确认机制?
当 发送 没有携带 数据的 ACK,它的 网络效率 也是 很低的,因为 它也有 40 个字节的 IP 头 和TCP 头,但却 没有 携带 数据报文(浪费)。
为了解决 ACK 传输效率低 问题,所以就 衍生出了 TCP 延迟确认。TCP 延迟确认的策略:
- 当 有响应数据 要发送 时,ACK 会 随着 响应数据 一起 立刻 发送给对方。
- 当 没有 响应数据 要发送时,ACK 将会 延迟 一段时间,以 等待 是否有 响应 数据 可以一起 发送。
- 如果 在 延迟等待 发送 ACK 期间,对方的 第二个 数据报文 又到达了,这时 就会 立刻发送 ACK。