# 简述

​ 众所周知,TCP 是传输层中一种面向连接的、可靠的、基于字节流的通信协议,是网络通信中十分重要的桥梁。本文集中于对可靠的数据传输、流量控制以及拥塞控制三个内容的简单描述,需要读者对 TCP 有一定了解。

# 可靠的数据传输

# 超时重传、快速重传、sack 重传

​ 我们都知道,在 TCP 中,当发送端的数据到达接收端时,接收端会返回一个确认应答消息,表示已收到消息。但过程往往不会这么顺利,数据可能在错综复杂的网络中丢失。此时,就需要利用重传机制解决。

# 超时重传:

​ 当发送端发出一个数据包后,会启动一个定时器(超时重传时间 RTO ),,等待接收端确认收到这个数据包。如果没有在定时范围内收到接收端的确认报文,发送端将重发数据包。这里有两种情况:发送的数据包丢失或者确认应答丢失。

​ 显然, RTO 应该略大于正常情况下发送端数据发送时刻与发送端收到确认报文时刻的差值(包的往返时间 RTT ),但由于网络环境的不确定性, RTT 在不断变化,因此 RTO 的值实际上需要很复杂的计算才能得以确定,这里不多做展开。

超时间隔加倍:每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍。两次超时,就说明网络环境差,不宜频繁反复发送。

# 快速重传:

超时重传也有一些问题,报文段丢失后的等待重传时间相对较长,效率较低。因此又衍生出了快速重传

​ 先介绍一下累计确认机制:当接收端收到比期望序号大的报文段时,会重复发送最近一次确认的报文段的确认信号,称之为冗余 ACK(duplicate ACK)。如图所示,报文段 1 成功接收并被确认 ACK 2,接收端的期待序号为 2,当报文段 2 丢失,报文段 3 失序到来,与接收端的期望不匹配,接收端重复发送冗余 ACK 2。

​ 这样,如果在 RTO 范围内,发送端会收到连续的三个重复冗余 ACK(实际上收到 4 个相同 ACK,第一个是正常的,后三个才是冗余的),发送端便知晓哪个报文段在传输过程中丢失了,于是重发该报文段,不需要等待 RTO 再重传,提高了效率。这便是快速重传机制

​ 这里采用三次冗余 ACK 的原因也很耐人寻味,我们知道 TCP 包是封装在 IP 包内的,IP 包在传输时会乱序,因此 TCP 包到达接收端也是乱序的,这也会造成接收端发送冗余 ACK 给发送端。因此我们不能仅凭某一两次的冗余 ACK 就进行重传。选取三次是经过统计所得出的一个估计值,均衡考虑的最优结果。

# SACK 重传

​ 快速重传解决了超时时间较长的问题,我们还有另外一个问题:重传的时候,是重传一个,还是重传所有。传一个会导致多次触发快速重传,每个丢失的报文都需要三次冗余 ACK,传所有报文时,如果后面的部分报文已经收到,会造成重复发送,导致资源浪费。因此有了 ** SACK 方法 **。

​ 在 TCP 头部选项字段里加一个 SACK 的东西,发送端可以根据它知道哪些数据收到了,哪些数据没收到,从而只重传丢失的数据。

​ 基于 SACK 方法,还有一种 ** Duplicate SACK ** 机制又称 D-SACK ,它使用 SACK 方法来告诉「发送方」有哪些数据被重复接收了。

​ 当连续发送报文时,如果一个报文因网络而延迟到达(已经触发快速重传继续传输后续报文时接收端收到之前延时的报文),回应的 ack 中的 sack 就会告诉发送端接收端重复接收了之前已经接收到的报文(即知道该报文延迟)。

​ 通过这种方式,发送方还可以知道是自己发送的报文丢失还是接收方回应的 ACK 确认报文丢失

# 流量控制

​ 由于接收方和发送方的条件不同会会出现发送方数据发送过快,但接收方来不及接受,导致数据丢失的问题,基于此, TCP 出现了流量控制机制。

​ TCP 依靠滑动窗口进行流量控制,在 TCP 中有一个字段叫 window ,用来让接收端告诉发送端自己还有多少缓冲区可以接收数据。这样发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。类似于一个先进先出的队列。同时发送方也会有一个滑动窗口,表示自己发送的数据包是否已经收到了接收方的确认报文。双方通过报文的 window 字段告诉对方自己目前的窗口大小。

发送方的滑动窗口示意:

接收方的滑动窗口示意:

# 拥塞控制

​ 在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但是一重传就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,导致恶性循环,因此有了拥塞控制来避免发送端的数据填满网络造成拥堵。主要通过三个算法来实现,分别是:慢开始 (slow-start)拥塞避免 (congestion avoidance)、和快恢复 (fast recovery)。。

​ 首先我们需要一个状态变量来表示网络的拥堵状况 —— 拥塞窗口cwnd ),只要网络中没有出现阻塞,窗口就会增大,网络中出现阻塞,窗口就会减小。一个传输轮次传输 cwnd 个报文。

慢启动

​ 当发送端每收到 1 个确认 ACK(一轮可能收到多个),拥塞窗口的大小就会加 1。这使得拥塞窗口呈指数级增长( cwnd 为 2 时,一轮收到 2 个 ACK, cwnd + 2 = 4; cwnd 为 4 时,一轮收到 4 个 ACK , cwnd + 4 = 8)。

​ 当 cwnd 的值超过慢启动门限(ssthresh 一般情况下为 65535 字节)时,慢启动算法停止,使用拥塞避免算法

拥塞避免

​ 每收到一个 ACK 报文,拥塞窗口 cwnd 增加 1/cwnd 。这使得拥塞窗口呈线性增长( cwnd 为 10 时,一轮收到 10 个 ACK, cwnd + 10/10 = 11; cwnd 为 11 时,一轮收到 11 个 ACK , cwnd + 11/11 = 12)。

​ 当超时重传拥塞发生以后, ssthresh 设置为 cwnd/2cwnd 重置为 1。

​ 当快速重传拥塞发生以后, cwnd = cwnd/2ssthresh = cwnd 进入快速恢复算法,

快恢复算法

​ 进入快恢复算法时,已经发生了快速重传拥塞cwndssthresh 已被更新,之后进行如下操作:

  1. 拥塞窗口 cwnd = ssthresh + 3 (表示有三个数据包被收到)。

  2. 重传丢失的数据包。

  3. 如果收到的 ACK 是重复的,cwnd 增加 1。

  4. 如果收到新数据的 ACK,把拥塞窗口设置为第 1 步中 ssthresh 的值,因为 ACK 已经确认了新数据,快速恢复过程可以结束,可以再次进入拥塞避免阶段。

    下图表示了所有的拥塞算法。

image-20230215210457706