面试题 / 网络与协议

TCP连接与可靠性

TCP 连接与断开

TCP(Transmission Control Protocol)是一种面向连接可靠的传输层协议。所谓“可靠”,通常体现在:按序交付、差错检测、丢包重传、流量控制与拥塞控制等。为了在不可靠的网络之上建立一条逻辑可靠的端到端连接,TCP 在传输数据前必须先完成连接建立过程,即 三次握手(Three-way Handshake)

建立连接-TCP 三次握手

TCP 三次握手图解

建立一个 TCP 连接需要“三次握手”,缺一不可:

  1. 第一次握手 (SYN): 客户端向服务端发送一个 SYN(Synchronize Sequence Numbers)报文段,其中包含一个由客户端随机生成的初始序列号(Initial Sequence Number, ISN),例如 seq=x。发送后,客户端进入 SYN_SENT 状态,等待服务端的确认。
  2. 第二次握手 (SYN+ACK): 服务端收到 SYN 报文段后,如果同意建立连接,会向客户端回复一个确认报文段。该报文段包含两个关键信息:
    • SYN:服务端也需要同步自己的初始序列号,因此报文段中也包含一个由服务端随机生成的初始序列号,例如 seq=y。
    • ACK (Acknowledgement):用于确认收到了客户端的请求。其确认号被设置为客户端初始序列号加一,即 ack=x+1。
    • 发送该报文段后,服务端进入 SYN_RCVD (也称 SYN_RECV)状态。
  3. 第三次握手 (ACK): 客户端收到服务端的 SYN+ACK 报文段后,会向服务端发送一个最终的确认报文段。该报文段包含确认号 ack=y+1。发送后,客户端进入 ESTABLISHED 状态。服务端收到这个 ACK 报文段后,也进入 ESTABLISHED 状态。

至此,双方都确认了连接的建立,TCP 连接成功创建,可以开始进行双向数据传输。

什么是半连接队列和全连接队列?

sequenceDiagram
  autonumber
  participant C as 客户端 Client
  participant K as 服务端内核 TCP
  box 服务端内核队列
    participant SQ as 半连接队列 SYN queue
    participant AQ as 全连接队列 Accept queue
  end
  participant App as 用户态应用 Server app

  C->>K: SYN
  K-->>C: SYN 加 ACK
  Note over SQ: 内核为该连接创建请求条目<br/>连接状态 SYN_RCVD<br/>放入 SYN queue

  C->>K: ACK 第三次握手
  Note over SQ,AQ: 内核收到 ACK 后完成握手<br/>将连接从 SYN queue 迁移到 Accept queue<br/>队列未满才可进入
  Note over AQ: 连接已完成 可被 accept<br/>连接状态 ESTABLISHED

  App->>K: accept
  K-->>App: 返回已就绪的 socket
  Note over AQ: 该连接从 Accept queue 移除

在 TCP 三次握手过程中,服务端内核通常会用两个队列来管理连接请求(不同操作系统/内核版本实现细节可能略有差异,下面以常见 Linux 行为为例):

  1. 半连接队列(也称 SYN Queue):
    • 保存“握手未完成”的请求:服务端收到 SYN 并回 SYN+ACK 后,连接进入 SYN_RCVD,等待客户端最终 ACK。
    • 如果一直收不到 ACK,内核会按重传策略重发 SYN+ACK,最终超时清理。
    • 常见相关参数:net.ipv4.tcp_max_syn_backlog;在 SYN Flood 场景下可配合 net.ipv4.tcp_syncookies
  2. 全连接队列(也称 Accept Queue):
    • 保存“握手已完成但应用还没 accept”的连接:服务端收到最终 ACK 后连接变为 ESTABLISHED,并进入 全连接队列,等待应用层 accept() 取走。
    • 队列容量受 listen(fd, backlog) 与系统上限 net.core.somaxconn 共同影响;实践中常见有效上限近似为 min(backlog, somaxconn)(具体行为与内核版本相关)。

总结:

队列作用状态移出条件
半连接队列(SYN Queue)保存未完成握手连接SYN_RCVD收到 ACK / 超时重传失败
全连接队列(Accept Queue)保存已完成握手连接ESTABLISHED被应用层 accept() 取出

当全连接队列满时,net.ipv4.tcp_abort_on_overflow 会影响处理策略:

  • 0(默认):通常不会立刻让连接快速失败,给应用留缓冲时间(可能表现为客户端重试/超时)。
  • 1:直接对客户端回复 RST,让连接快速失败。

当半连接队列满时,如果开启了 tcp_syncookies,服务端可能不会为该连接在半连接队列中分配常规条目,而是计算并返回一个 SYN Cookie。只有当收到合法的最终 ACK 时,才“重建”必要的连接信息。这是抵御 SYN Flood 的核心手段之一。

为什么要三次握手?

TCP 三次握手的核心目的是为了在客户端和服务器之间建立一个可靠的全双工的通信信道。这需要实现两个主要目标:

1. 确认双方的收发能力,并同步初始序列号 (ISN)

sequenceDiagram
  autonumber
  participant C as 客户端 Client
  participant S as 服务端 Server

  Note over C,S: 目标 同步双方 ISN 并确认双向可达

  C->>S: SYN seq=ISN_C
  Note right of S: 服务端确认 客户端到服务端方向可达
  Note right of S: 服务端状态 SYN_RCVD

  S->>C: SYN 加 ACK seq=ISN_S ack=ISN_C+1
  Note left of C: 客户端确认<br/>1 服务端到客户端方向可达<br/>2 服务端已收到客户端 SYN<br/>3 获得 ISN_S

  C->>S: ACK seq=ISN_C+1 ack=ISN_S+1
  Note left of C: 客户端状态 ESTABLISHED
  Note right of S: 服务端确认 客户端已收到 SYN 加 ACK<br/>双方 ISN 同步完成
  Note right of S: 服务端状态 ESTABLISHED

  Note over C,S: 连接建立 可以开始传输数据

TCP 依赖序列号(SEQ)与确认号(ACK)保证数据有序、无重复、可重传。三次握手通过交换并确认双方的 ISN,使两端对“从哪一个序号开始收发数据”达成一致,同时让握手过程形成闭环,避免仅凭单向信息就进入已建立状态。

经过这三次交互,双方都确认了彼此的收发功能完好,并完成了初始序列号的同步,为后续可靠的数据传输奠定了基础。

三次握手能力确认速记:

  1. C→S:SYN → S 确认:C 能发,S 能收(C→S 通)。
  2. S→C:SYN+ACK → C 确认:S 能发,C 能收,且 S 已收到 C 的 SYN(对方 SEQ + 1)。
  3. C→S:ACK → S 确认:C 已收到 S 的 SYN+ACK,握手闭环,连接建立。

2. 防止已失效的连接请求被错误地建立

sequenceDiagram
    participant C as 客户端 (Client)
    participant S as 服务端 (Server)

    Note over C,S: 场景:旧的 SYN 报文在网络中滞留

    C->>S: 1. 发送 SYN (旧请求 - 滞留中)
    Note over C: 客户端超时,放弃该请求

    C->>S: 2. 发送 SYN (新请求)
    S-->>C: 3. 建立连接并正常释放...

    rect rgb(255, 240, 240)
        Note right of S: 此时,旧的 SYN 终于到达服务端
        S->>C: 4. 发送 SYN+ACK (针对旧请求)

        alt 如果是【两次握手】
            Note right of S: (假设服务端在回复 SYN+ACK 后即认为连接建立)
            Note right of S: ❌ 错误建立连接 (Ghost Connection)<br/>分配内存/资源,造成浪费
        else 如果是【三次握手】
            Note left of C: 客户端无该连接状态 / 非期望报文
            C->>S: 5. 发送 RST (重置报文) 或 直接丢弃

            Note right of S: 【服务端结果】<br/>收到 RST 立即清理;<br/>或未收到 ACK 则重传并最终超时清理
            Note right of S: ✅ 避免错误建连,保护资源
        end
    end

设想一个场景:客户端发送的第一个连接请求(SYN1)因网络延迟而滞留,于是客户端重发了第二个请求(SYN2)并成功建立了连接,数据传输完毕后连接被释放。此时,延迟的 SYN1 才到达服务端。

  • 如果是两次握手:服务端收到这个失效的 SYN1 后,会误认为是一个新的连接请求,并立即分配资源、建立连接。但这将导致服务端单方面维持一个无效连接,白白浪费系统资源,因为客户端并不会有任何响应。
  • 有了第三次握手:服务端收到失效的 SYN1 并回复 SYN+ACK 后,会等待客户端的最终确认(ACK)。由于客户端当前并没有发起连接的意图,它会忽略这个 SYN+ACK 或者发送一个 RST (Reset) 报文。这样,服务端就无法收到第三次握手的 ACK,最终会超时关闭这个错误的连接,从而避免了资源浪费。

因此,三次握手是确保 TCP 连接可靠性的最小且必需的步骤。它不仅确认了双方的通信能力,更重要的是增加了一个最终确认环节,以防止网络中延迟、重复的历史请求对连接建立造成干扰。

第 2 次握手传回了 ACK,为什么还要传回 SYN?

第二次握手里的 ACK 是为了确认“服务端确实收到了客户端的 SYN”(即确认 C→S 的请求到达)。而同时携带 SYN 是为了把服务端自己的 ISN 也同步给客户端,并要求客户端对其进行确认(即建立并确认 S→C 方向的建立过程)。只有双方的 ISN 都同步完成,后续的可靠传输(按序、重传、去重)才有共同起点。

简言之:ACK 用于“我收到了你的 SYN”,SYN 用于“我也要发起我的同步,请你确认”。

SYN 同步序列编号(Synchronize Sequence Numbers) 是 TCP/IP 建立连接时使用的握手信号。在客户机和服务端之间建立正常的 TCP 网络连接时,客户机首先发出一个 SYN 消息,服务端使用 SYN-ACK 应答表示接收到了这个消息,最后客户机再以 ACK(Acknowledgement)消息响应。这样在客户机和服务端之间才能建立起可靠的 TCP 连接,数据才可以在客户机和服务端之间传递。

三次握手过程中可以携带数据吗?

在 TCP 三次握手过程中,第三次握手是可以携带数据的(客户端发送完 ACK 确认包之后就进入 ESTABLISHED 状态了),这一点在 RFC 793 文档中有提到。也就是说,一旦完成了前两次握手,TCP 协议允许数据在第三次握手时开始传输。

如果第三次握手的 ACK 确认包丢失,但是客户端已经开始发送携带数据的包,那么服务端在收到这个携带数据的包时,如果该包中包含了 ACK 标记,服务端会将其视为有效的第三次握手确认。这样,连接就被认为是建立的,服务端会处理该数据包,并继续正常的数据传输流程。

断开连接-TCP 四次挥手

TCP 四次挥手图解

断开一个 TCP 连接则需要“四次挥手”,缺一不可:

  1. 第一次挥手 (FIN)
    (或任何一方)决定关闭连接时,它会向服务端发送一个 FIN(Finish)标志的报文段,表示自己已经没有数据要发送了。该报文段包含一个序列号 seq=u。发送后,客户端进入 FIN-WAIT-1 状态。
  2. 第二次挥手 (ACK)
    FIN 报文段后,会立即回复一个 ACK 确认报文段。其确认号为 ack=u+1。发送后,服务端进入 CLOSE-WAIT 状态。客户端收到这个 ACK 后,进入 FIN-WAIT-2 状态。此时,TCP 连接处于**半关闭(Half-Close)**状态:客户端到服务端的发送通道已关闭,但服务端到客户端的发送通道仍然可以传输数据。
  3. 第三次挥手 (FIN)
    ,它也会向客户端发送一个 FIN 报文段,表示自己也准备关闭连接。该报文段同样包含一个序列号 seq=y。发送后,服务端进入 LAST-ACK 状态,等待客户端的最终确认。
  4. 第四次挥手
    FIN 报文段后,会回复一个最终的 ACK 确认报文段,确认号为 ack=y+1。发送后,客户端进入 TIME-WAIT 状态。服务端在收到这个 ACK 后,立即进入 CLOSED 状态,完成连接关闭。客户端则会在 TIME-WAIT 状态下等待 2MSL(Maximum Segment Lifetime,报文段最大生存时间)后,才最终进入 CLOSED 状态。

四次挥手期间连接可能处于半关闭(Half-Close)先发送 FIN 的一方不再发送应用数据,但另一方仍可继续发送剩余数据,直到它也发送 FIN 并完成后续 ACK。

为什么要四次挥手?

TCP 是全双工通信:两端的发送方向彼此独立。断开连接时,往往需要“我不发了”与“你也不发了”分别被对方确认,因此通常表现为四个报文段(FIN/ACK/FIN/ACK)。这也对应了现实世界的“双方分别确认挂断”的过程。

举个例子:A 和 B 打电话,通话即将结束后。

  1. 第一次挥手:A 说“我没啥要说的了”(A 发 FIN)
  2. 第二次挥手:B 回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话(B 回 ACK,但可能还有话要说)
  3. 第三次挥手:于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”(B 发 FIN)
  4. 第四次挥手:A 回答“知道了”,这样通话才算结束(A 回 ACK)。

为什么不能把服务端发送的 ACK 和 FIN 合并起来,变成三次挥手?

sequenceDiagram
  autonumber
  participant C as 客户端
  participant K as 服务端内核
  participant A as 服务端应用

  Note over C,K: 客户端发起关闭
  C->>K: FIN
  Note right of K: 内核立即回复 ACK 用于确认对端 FIN
  K-->>C: ACK
  Note right of K: 服务端状态变为 CLOSE_WAIT

  Note over K,A: 应用处理阶段
  K->>A: 通知本端应用对端已关闭发送方向 例如 read 返回 0
  A->>A: 读取和处理剩余数据
  A->>A: 发送最后响应
  A->>K: 调用 close 或 shutdown

  Note right of K: 发送本端 FIN 并进入 LAST_ACK
  K-->>C: FIN
  Note left of C: 客户端回复 ACK 并进入 TIME_WAIT
  C->>K: ACK
  Note right of K: 服务端收到最终 ACK 后进入 CLOSED

关键原因是:回复 ACK发送 FIN 的触发时机往往不同步。

  • 当服务端收到客户端 FIN 时,内核协议栈会立即回 ACK,用于确认“我收到了你要关闭的请求”。此时服务端进入 CLOSE_WAIT,等待本端应用把剩余事情处理完。
  • 只有当服务端应用处理完毕并调用 close()/shutdown() 后,内核才会发送本端的 FIN。
  • 因此“内核自动回 ACK”和“应用决定发 FIN”在时间上是解耦的,通常无法合并。只有在服务端恰好也准备立即关闭时,才可能出现 FIN+ACK 合并在一个报文段中的情况。

如果第二次挥手时服务端的 ACK 没有送达客户端,会怎样?

  • 客户端状态:客户端发送第一次 FIN 后进入 FIN_WAIT_1 并启动重传计时器。
  • 重传逻辑:若在超时时间内未收到对端对该 FIN 的确认 ACK,客户端会重传 FIN
  • 服务端处理:服务端若收到重复 FIN,通常会再次发送 ACK。如果由于网络问题 ACK 一直到不了,客户端在达到一定重试/超时阈值后可能报错或放弃(具体由实现与参数如 tcp_retries2 等影响)。

为什么第四次挥手客户端需要等待 2*MSL(报文段最长寿命)时间后才进入 CLOSED 状态?

第四次挥手时,客户端发送给服务端的 ACK 有可能丢失,如果服务端因为某些原因而没有收到 ACK 的话,服务端就会重发 FIN,如果客户端在 2*MSL 的时间内收到了 FIN,就会重新发送 ACK 并再次等待 2MSL,防止 Server 没有收到 ACK 而不断重发 FIN。

MSL(Maximum Segment Lifetime) : 一个片段在网络中最大的存活时间,2MSL 就是一个发送和一个回复所需的最大时间。如果直到 2MSL,Client 都没有再次收到 FIN,那么 Client 推断 ACK 已经被成功接收,则结束 TCP 连接。

参考

TCP 可靠传输

TCP 如何保证传输的可靠性?

  1. 基于数据块传输:应用数据被分割成 TCP 认为最适合发送的数据块,再传输给网络层,数据块被称为报文段或段。
  2. 对失序数据包重新排序以及去重:TCP 为了保证不发生丢包,就给每个包一个序列号,有了序列号能够将接收到的数据根据序列号排序,并且去掉重复序列号的数据就可以实现数据包去重。
  3. 校验和 : TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段。
  4. 重传机制 : 在数据包丢失或延迟的情况下,重新发送数据包,直到收到对方的确认应答(ACK)。TCP 重传机制主要有:基于计时器的重传(也就是超时重传)、快速重传(基于接收端的反馈信息来引发重传)、SACK(在快速重传的基础上,返回最近收到的报文段的序列号范围,这样客户端就知道,哪些数据包已经到达服务器了)、D-SACK(重复 SACK,在 SACK 的基础上,额外携带信息,告知发送方有哪些数据包自己重复接收了)。关于重传机制的详细介绍,可以查看详解 TCP 超时与重传机制这篇文章。
  5. 流量控制 : TCP 连接的每一方都有固定大小的缓冲空间,TCP 的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议(TCP 利用滑动窗口实现流量控制)。
  6. 拥塞控制 : 当网络拥塞时,减少数据的发送。TCP 在发送数据的时候,需要考虑两个因素:一是接收方的接收能力,二是网络的拥塞程度。接收方的接收能力由滑动窗口表示,表示接收方还有多少缓冲区可以用来接收数据。网络的拥塞程度由拥塞窗口表示,它是发送方根据网络状况自己维护的一个值,表示发送方认为可以在网络中传输的数据量。发送方发送数据的大小是滑动窗口和拥塞窗口的最小值,这样可以保证发送方既不会超过接收方的接收能力,也不会造成网络的过度拥塞。

TCP 如何实现流量控制?

TCP 利用滑动窗口实现流量控制。流量控制是为了控制发送方发送速率,保证接收方来得及接收。 接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。

为什么需要流量控制? 这是因为双方在通信的时候,发送方的速率与接收方的速率是不一定相等,如果发送方的发送速率太快,会导致接收方处理不过来。如果接收方处理不过来的话,就只能把处理不过来的数据存在 接收缓冲区(Receiving Buffers) 里(失序的数据包也会被存放在缓存区里)。如果缓存区满了发送方还在狂发数据的话,接收方只能把收到的数据包丢掉。出现丢包问题的同时又疯狂浪费着珍贵的网络资源。因此,我们需要控制发送方的发送速率,让接收方与发送方处于一种动态平衡才好。

这里需要注意的是(常见误区):

  • 发送端不等同于客户端
  • 接收端不等同于服务端

TCP 为全双工(Full-Duplex, FDX)通信,双方可以进行双向通信,客户端和服务端既可能是发送端又可能是服务端。因此,两端各有一个发送缓冲区与接收缓冲区,两端都各自维护一个发送窗口和一个接收窗口。接收窗口大小取决于应用、系统、硬件的限制(TCP 传输速率不能大于应用的数据处理速率)。通信双方的发送窗口和接收窗口的要求相同

TCP 发送窗口可以划分成四个部分

  1. 已经发送并且确认的 TCP 段(已经发送并确认);
  2. 已经发送但是没有确认的 TCP 段(已经发送未确认);
  3. 未发送但是接收方准备接收的 TCP 段(可以发送);
  4. 未发送并且接收方也并未准备接受的 TCP 段(不可发送)。

TCP 发送窗口结构图示

TCP发送窗口结构

  • SND.WND:发送窗口。
  • SND.UNA:Send Unacknowledged 指针,指向发送窗口的第一个字节。
  • SND.NXT:Send Next 指针,指向可用窗口的第一个字节。

可用窗口大小 = SND.UNA + SND.WND - SND.NXT

TCP 接收窗口可以划分成三个部分

  1. 已经接收并且已经确认的 TCP 段(已经接收并确认);
  2. 等待接收且允许发送方发送 TCP 段(可以接收未确认);
  3. 不可接收且不允许发送方发送 TCP 段(不可接收)。

TCP 接收窗口结构图示

TCP接收窗口结构

接收窗口的大小是根据接收端处理数据的速度动态调整的。 如果接收端读取数据快,接收窗口可能会扩大。 否则,它可能会缩小。

另外,这里的滑动窗口大小只是为了演示使用,实际窗口大小通常会远远大于这个值。

TCP 的拥塞控制是怎么实现的?

在某段时间,若对网络中某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要变坏。这种情况就叫拥塞。拥塞控制就是为了防止过多的数据注入到网络中,这样就可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机,所有的路由器,以及与降低网络传输性能有关的所有因素。相反,流量控制往往是点对点通信量的控制,是个端到端的问题。流量控制所要做到的就是抑制发送端发送数据的速率,以便使接收端来得及接收。

TCP的拥塞控制

为了进行拥塞控制,TCP 发送方要维持一个 拥塞窗口(cwnd) 的状态变量。拥塞控制窗口的大小取决于网络的拥塞程度,并且动态变化。发送方让自己的发送窗口取为拥塞窗口和接收方的接受窗口中较小的一个。

TCP 的拥塞控制采用了四种算法,即 慢开始拥塞避免快重传快恢复。在网络层也可以使路由器采用适当的分组丢弃策略(如主动队列管理 AQM),以减少网络拥塞的发生。

  • 慢开始: 慢开始算法的思路是当主机开始发送数据时,如果立即把大量数据字节注入到网络,那么可能会引起网络阻塞,因为现在还不知道网络的负荷情况。经验表明,较好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是由小到大逐渐增大拥塞窗口数值。cwnd 初始值为 1,每经过一个传播轮次,cwnd 加倍。
  • 拥塞避免: 拥塞避免算法的思路是让拥塞窗口 cwnd 缓慢增大,即每经过一个往返时间 RTT 就把发送方的 cwnd 加 1.
  • 快重传与快恢复: 在 TCP/IP 中,快速重传和恢复(fast retransmit and recovery,FRR)是一种拥塞控制算法,它能快速恢复丢失的数据包。没有 FRR,如果数据包丢失了,TCP 将会使用定时器来要求传输暂停。在暂停的这段时间内,没有新的或复制的数据包被发送。有了 FRR,如果接收机接收到一个不按顺序的数据段,它会立即给发送机发送一个重复确认。如果发送机接收到三个重复确认,它会假定确认件指出的数据段丢失了,并立即重传这些丢失的数据段。有了 FRR,就不会因为重传时要求的暂停被耽误。  当有单独的数据包丢失时,快速重传和恢复(FRR)能最有效地工作。当有多个数据信息包在某一段很短的时间内丢失时,它则不能很有效地工作。

ARQ 协议了解吗?

自动重传请求(Automatic Repeat-reQuest,ARQ)是 OSI 模型中数据链路层和传输层的错误纠正协议之一。它通过使用确认和超时这两个机制,在不可靠服务的基础上实现可靠的信息传输。如果发送方在发送后一段时间之内没有收到确认信息(Acknowledgements,就是我们常说的 ACK),它通常会重新发送,直到收到确认或者重试超过一定的次数。

ARQ 包括停止等待 ARQ 协议和连续 ARQ 协议。

停止等待 ARQ 协议

停止等待协议是为了实现可靠传输的,它的基本原理就是每发完一个分组就停止发送,等待对方确认(回复 ACK)。如果过了一段时间(超时时间后),还是没有收到 ACK 确认,说明没有发送成功,需要重新发送,直到收到确认后再发下一个分组;

在停止等待协议中,若接收方收到重复分组,就丢弃该分组,但同时还要发送确认。

1) 无差错情况:

发送方发送分组,接收方在规定时间内收到,并且回复确认.发送方再次发送。

2) 出现差错情况(超时重传):

停止等待协议中超时重传是指只要超过一段时间仍然没有收到确认,就重传前面发送过的分组(认为刚才发送过的分组丢失了)。因此每发送完一个分组需要设置一个超时计时器,其重传时间应比数据在分组传输的平均往返时间更长一些。这种自动重传方式常称为 自动重传请求 ARQ 。另外在停止等待协议中若收到重复分组,就丢弃该分组,但同时还要发送确认。

3) 确认丢失和确认迟到

  • 确认丢失:确认消息在传输过程丢失。当 A 发送 M1 消息,B 收到后,B 向 A 发送了一个 M1 确认消息,但却在传输过程中丢失。而 A 并不知道,在超时计时过后,A 重传 M1 消息,B 再次收到该消息后采取以下两点措施:1. 丢弃这个重复的 M1 消息,不向上层交付。 2. 向 A 发送确认消息。(不会认为已经发送过了,就不再发送。A 能重传,就证明 B 的确认消息丢失)。
  • 确认迟到:确认消息在传输过程中迟到。A 发送 M1 消息,B 收到并发送确认。在超时时间内没有收到确认消息,A 重传 M1 消息,B 仍然收到并继续发送确认消息(B 收到了 2 份 M1)。此时 A 收到了 B 第二次发送的确认消息。接着发送其他数据。过了一会,A 收到了 B 第一次发送的对 M1 的确认消息(A 也收到了 2 份确认消息)。处理如下:1. A 收到重复的确认后,直接丢弃。2. B 收到重复的 M1 后,也直接丢弃重复的 M1。

连续 ARQ 协议

连续 ARQ 协议可提高信道利用率。发送方维持一个发送窗口,凡位于发送窗口内的分组可以连续发送出去,而不需要等待对方确认。接收方一般采用累计确认,对按序到达的最后一个分组发送确认,表明到这个分组为止的所有分组都已经正确收到了。

  • 优点: 信道利用率高,容易实现,即使确认丢失,也不必重传。
  • 缺点: 不能向发送方反映出接收方已经正确收到的所有分组的信息。 比如:发送方发送了 5 条 消息,中间第三条丢失(3 号),这时接收方只能对前两个发送确认。发送方无法知道后三个分组的下落,而只好把后三个全部重传一次。这也叫 Go-Back-N(回退 N),表示需要退回来重传已经发送过的 N 个消息。

超时重传如何实现?超时重传时间怎么确定?

当发送方发送数据之后,它启动一个定时器,等待目的端确认收到这个报文段。接收端实体对已成功收到的包发回一个相应的确认信息(ACK)。如果发送端实体在合理的往返时延(RTT)内未收到确认消息,那么对应的数据包就被假设为已丢失并进行重传。

  • RTT(Round Trip Time):往返时间,也就是数据包从发出去到收到对应 ACK 的时间。
  • RTO(Retransmission Time Out):重传超时时间,即从数据发送时刻算起,超过这个时间便执行重传。

RTO 的确定是一个关键问题,因为它直接影响到 TCP 的性能和效率。如果 RTO 设置得太小,会导致不必要的重传,增加网络负担;如果 RTO 设置得太大,会导致数据传输的延迟,降低吞吐量。因此,RTO 应该根据网络的实际状况,动态地进行调整。

RTT 的值会随着网络的波动而变化,所以 TCP 不能直接使用 RTT 作为 RTO。为了动态地调整 RTO,TCP 协议采用了一些算法,如加权移动平均(EWMA)算法,Karn 算法,Jacobson 算法等,这些算法都是根据往返时延(RTT)的测量和变化来估计 RTO 的值。

参考

  1. 《计算机网络(第 7 版)》
  2. 《图解 HTTP》
  3. https://www.9tut.com/tcp-and-udp-tutorial
  4. https://github.com/wolverinn/Waking-Up/blob/master/Computer%20Network.md
  5. TCP Flow Control—https://www.brianstorti.com/tcp-flow-control/
  6. TCP 流量控制(Flow Control):https://notfalse.net/24/tcp-flow-control
  7. TCP 之滑动窗口原理 : https://cloud.tencent.com/developer/article/1857363

来源引用