为什么TCP四路终止需要最后一个ACK

网络工程 通讯协议 第4层
2021-07-15 07:44:30

如题。对不起,如果这是一个重复的问题,但在网上的众多帖子中,我只能找到连接是如何断开的,但没有(令人信服地)为什么以这种方式完成。


1. A -----FIN-----> B
2. A <----ACK------ B
3. A <----FIN------ B
4. A -----ACK-----> B        <====== Why is this one necessary?

======if 3 or 4 lost=======
5. A <....FIN...... B
6. A .....ACK.....> B

似乎 B 一发出就可以关闭套接字,FIN(3)因为

  1. 如果FIN(3)没有丢失,一切都很好。
  2. 如果丢失,A 无论如何都可以超时并关闭连接。有人可能会争辩说,如果确实发生了这种情况,A 将不得不等待很长时间。但是 B 也承担了最后一个风险ACK(4)假设在 A 发出最后一个 之后连接突然断开(尽管可能性很小)ACK(4),导致 B 无法接收它。然后 B 也必须等待和超时。既然这两种方法都有缺陷,为什么不把这个留到最后ACK呢?

我猜

我认为,唯一的例外是,有数据包丢失的相当高的机会,使得两个FIN(3)ACK(4)不能按时到达。

如果FIN(3)丢失了,从 A 接收不到任何东西,B 将重新发送它的新FIN(5).

同样,如果ACK(4)丢失,B 会认为它FIN(3)丢失并重新发送FIN(5)A,虽然已经发出,但ACK(4)应该在TIME-WAIT状态,希望能收到这个新的FIN(5)并重新发送一个ACK(6)

似乎只有当上述情况发生得相当频繁时,最后ACK(4)一种情况才会产生重大影响。

3个回答
A -----FIN-----> B
FIN_WAIT_1       CLOSE_WAIT
A <----ACK------ B
FIN_WAIT_2

(B can send more data here, this is half-close state)

A <----FIN------ B
TIME_WAIT        LAST_ACK
A -----ACK-----> B
|                CLOSED
2MSL Timer
|
CLOSED

需要对 FIN 的 ACK,因为发送 FIN 的一端将重传它,直到它收到 ACK。所以,问题是,如果没有收到 ACK,为什么 TCP 一直发送 FIN?我的理解是,鉴于连接可能处于半关闭状态,接收最后一个 FIN(图中的 A)的一侧可能正在“无限期地”等待数据,从而在没有更多数据时浪费资源已收到。B 需要确保 A 收到 FIN(并关闭连接),因此它需要一个 ACK​​。

编辑

更准确地说是半关闭。在您的示例中,A 可以关闭其连接的一侧,发送第一个 FIN 并接收第一个 ACK​​,但 B 可以在关闭连接并发送最后一个 FIN 之前的任何时间段内随意发送更多数据。因此,第一个 FIN/ACK 序列和第二个序列之间的时间无法确定或超时。A 需要最后一个 FIN 以确保 B 已关闭其一侧的连接。

编辑 2

如果从 B 到 A 的最后一个 FIN 丢失了怎么办?然后,B 将不会收到 ACK 并将重传 FIN,直到它收到 ACK。因此,A 最终将获得 FIN 并转换到 TIME_WAIT 状态。

A -----FIN-----> B
FIN_WAIT_1       CLOSE_WAIT
A <----ACK------ B
FIN_WAIT_2

(B can send more data here, this is half-close state)

A  (Lost)X<--FIN------ B
FIN_WAIT_2       LAST_ACK
                 (timeout waiting for ACK)
A <----FIN------ B
TIME_WAIT
A -----ACK-----> B
|                CLOSED
2MSL Timer
|
CLOSED

如果最后一个 ACK​​ 丢失会发生什么?然后,B 会认为 A 没有收到 FIN 并会重传 FIN。从 B 的角度来看,这与 FIN 丢失是一样的,从 A 的角度来看,这是不同的,因为 A 现在处于 TIME_WAIT 或 CLOSED 状态。当 A 从 A 接收到新的 FIN 时,如果它处于 TIME_WAIT 状态,它将再次发送 ACK。

A -----FIN-----> B
FIN_WAIT_1       CLOSE_WAIT
A <----ACK------ B
FIN_WAIT_2

(B can send more data here, this is half-close state)

A <----FIN------ B
TIME_WAIT        LAST_ACK
A -----ACK-->X(Lost)   B
TIME_WAIT        LAST_ACK
                 (timeout waiting for ACK)
A <----FIN------ B
A -----ACK-----> B
|                CLOSED
2MSL Timer
|
CLOSED

如果 A 处于 CLOSED 状态,它将发送一个 RESET,在任何一种情况下,B 都将能够关闭其一侧的连接。

A -----FIN-----> B
FIN_WAIT_1       CLOSE_WAIT
A <----ACK------ B
FIN_WAIT_2

(B can send more data here, this is half-close state)

A <----FIN------ B
TIME_WAIT        LAST_ACK
A -----ACK-->X(Lost)   B
TIME_WAIT        LAST_ACK
|
2MSL Timer
|
CLOSED
                 (timeout waiting for ACK)
A <----FIN------ B
A -----RST-----> B
                 CLOSED

有 2 对 FIN/ACK,因为在 TCP 中,流的每个方向都是单独关闭的当 A 向 B 发送 FIN 时,意味着 A 不会再向 B 发送任何数据。连接可以保持打开状态,B 可以发送任意数量的数据。一旦 B 发送第二个 FIN,数据流在两个方向都关闭,连接可以终止。

这可能有用的一个例子是 HTTP(实际上很久以前是 HTTP,现代网页更复杂)。客户端向服务器发送包含请求 URL 的 HTTP 请求。然后客户端可以发送FIN。服务器知道它不会从客户端接收任何数据并清除 TCP 缓冲区/可能还有其他一些连接状态。然后服务器通过此连接发送网页。

为了处理丢失的 FIN 和相应的 ACK,TCP 需要在发送两个 FIN 后将某些连接状态保持一段预定义的时间(我认为大约 2 分钟,但它应该是 TCP 标准的)。特别是,如果 FIN 的发送方没有收到 ACK,它会重传 FIN。

我想我在 Linux 套接字 API 中看到了一个函数,它只关闭传出流,即您仍然可以从套接字读取但不能写入它。不记得叫什么了:(

TCP 提供可靠的传输服务。这包括每一方都知道它已收到另一方打算传输的所有数据。在您的示例中,A 在收到 FIN 之前不会知道它已收到 B 的所有数据。B 将要重新传输 FIN,以便它知道 A 知道所有数据都已传输。

作为旁注,这也意味着 A 在将其 ACK 发送到 B 的 FIN 后需要“留在此处”:当 B 重新传输 FIN 时,如果 A 消失了,B 将不会收到任何 ACK(可能还有 RST)等等不能保证 A 知道它已收到所有数据。A 在 TIME_WAIT 状态中停留一段时间以确认此类重新传输的 FIN。严格来说,它需要一直待在那里,直到它知道 B 收到了它的 ACK,但这是一个无法解决的问题,通常被称为“两军”问题。