TCP2

TCP2

TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。

TCP为什么可靠?

  • TCP通过三次握手、四次挥手保证连接的可靠性
  • 然后通过校验和、序列号确认机制、超时重传、流量控制、拥塞控制等机制保证数据的可靠传输

1. 重传机制

1.1 超时重传

重传机制的其中一个方式,就是在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的 ACK 确认应答报文,就会重发该数据,也就是我们常说的超时重传

RTT(Round-Trip Time 往返时延):数据发送时刻到接收到确认的时刻的差值

RTO(Retransmission Timeout 超时重传时间)

超时重传时间 RTO 的值应该略大于报文往返 RTT 的值

实际上「报文往返 RTT 的值」是经常变化的,因为我们的网络也是时常变化的。也就因为「报文往返 RTT 的值」 是经常波动变化的,所以「超时重传时间 RTO 的值」应该是一个动态变化的值

1.2 快速重传

快速重传机制

发送端同时收到三个ACK2,发送端就知道Seq2没有送到,于是就会立刻重传丢失的Seq2。快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。

快速重传解决了超时重传中需要计算超时时间的问题,但它也有别的问题——当Seq2Seq3都丢失了,发送端收到3个连续的ACK2,它是不知道Seq3是否丢失的,这时解决方法有两种:

  • 只重传Seq2一个报文,那么Seq3的重传还得等到发送端收到3个连续的ACK3,效率很低
  • 重传Seq2后的所有报文,虽然能够同时重传丢失的Seq2Seq3,但是后面没有丢失的报文还是会被重传一份,浪费资源

1.3 SACK (Selective Acknowledgment, 选择性确认)

既然快速重传中发送端不知道哪些报文丢失,那让接收方告诉它不就好了。

SACK允许接收方告诉发送方哪些报文已收到,哪些丢失了,以减少不必要的数据传输,解决了快速重传中的问题

这种方式需要在 TCP 头部「选项」字段里加一个 SACK 的东西,它可以将已收到的数据的信息发送给「发送方」,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据

选择性确认

1.4 Duplicate SACK

用于通知发送方哪些数据被重复接收了,有助于发送方了解是 数据丢失ACK丢失网络延迟导致的重传

2. 滑动窗口

2.1 发送方滑动窗口

SND.WND、SND.UN、SND.NXT

  • SND.WND:表示发送窗口的大小(大小是由接收方指定的);

  • SND.UNASend Unacknoleged):是一个绝对指针,它指向的是已发送但未收到确认的第一个字节的序列号,也就是 #2 的第一个字节。

  • SND.NXT:也是一个绝对指针,它指向未发送但可发送范围的第一个字节的序列号,也就是 #3 的第一个字节。

  • 指向 #4 的第一个字节是个相对指针,它需要 SND.UNA 指针加上 SND.WND 大小的偏移量,就可以指向 #4 的第一个字节了。

2.2 接收方滑动窗口

接收窗口

  • RCV.WND:表示接收窗口的大小,它会通告给发送方。
  • RCV.NXT:是一个指针,它指向期望从发送方发送来的下一个数据字节的序列号,也就是 #3 的第一个字节。
  • 指向 #4 的第一个字节是个相对指针,它需要 RCV.NXT指针加上 RCV.WND 大小的偏移量,就可以指向 #4 的第一个字节了。

2.3 发送方和接收方的滑动窗口大小是相等的吗?

并不是完全相等,接收窗口的大小是约等于发送窗口的大小的。

当接收方读取数据速度非常快时,它的窗口很快就会空出来,通过TCP报文段中的Windows字段告诉发送方自己的接收窗口大小,这个传输过程是有时延的,故两者关系是约等于。

3. 流量控制

发送端根据接收端的实际接收能力控制发送的数据量(由滑动窗口大小来保证)

3.1 窗口关闭

当接收方接收窗口rwnd = 0时,窗口关闭,发送方不再发送数据。当接收方处理完数据,给发送方发送ACK并告知发送方自己的接收窗口rwnd增大了,但如果此时ACK丢失了,发送方就不知道接收方窗口已非0,接收方也还在等待发送方发送数据,造成死锁的现象。

解决方案:TCP 为每个连接设有一个持续定时器,只要 TCP 连接一方收到对方的零窗口通知,就启动持续计时器。如果持续计时器超时,就会发送窗口探测 ( Window probe ) 报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小。

4. 拥塞控制

流量控制是防止发送方数据填满接收方缓存,但它无法控制整个网络,当网络拥堵时,继续发送大量数据会导致数据包延时、丢失等,拥塞控制就是为了防止发送方的数据填满网络

发送方通过维护一个拥塞窗口cwnd,该窗口随网络拥堵情况而动态变化。由于我们前面已知swnd约等于rwnd,那么加入拥塞窗口后,swnd = min(cwnd, rwnd)

下面给出拥塞控制算法:

4.1 慢启动

TCP连接建立后,先不要发大量的数据,初始化cwnd = 1MSS,每收到一个ACKcwnd += 1MSS,直到cwnd达到慢启动阈值ssthresh,进入拥塞避免算法

慢启动过程窗口cwnd的增长是指数级别的

慢启动算法

4.2 拥塞避免算法

进入拥塞避免算法后,它的规则是:每当收到一个 ACK 时,cwnd 增加 1/cwnd

接上前面的慢启动的例子,现假定 ssthresh8

  • 当 8 个 ACK 应答确认到来时,每个确认增加 1/8,8 个 ACK 确认 cwnd 一共增加 1,于是这一次能够发送 9 个 MSS 大小的数据,变成了线性增长。

就这么一直增长着后,网络就会慢慢进入了拥塞的状况了,于是就会出现丢包现象,这时就需要对丢失的数据包进行重传。当触发了重传机制,也就进入了「拥塞发生算法」

拥塞避免

4.3 拥塞发生算法

重传机制主要有两种:

  • 超时重传:ssthresh = cwnd / 2,cwnd = 1,进入慢启动

    拥塞发送 —— 超时重传

  • 快速重传:cwnd = cwnd / 2,ssthresh = cwnd,进入快速恢复算法

4.4 快速恢复算法

快速重传和快速恢复算法一般同时使用,快速恢复算法是认为,你还能收到 3 个重复 ACK 说明网络也不那么糟糕,所以没有必要像超时重传那么强烈。

进入快速恢复之前,cwndssthresh已经被更新了:

  • cwnd = cwnd/2 ,也就是设置为原来的一半;
  • ssthresh = cwnd;

然后进入快速恢复:

  • cwnd = ssthresh + 3

    +3的原因

    发送方收到3个重复的ACK,说明有3个数据已经被成功发送到接收方,它们已经离开网络了,这3个报文段不再消耗网络资源而是停留在接收方的接收缓存中,可见现在网络中不是堆积了报文段而是减少了3个报文段,因此可以适当把拥塞窗口扩大些

  • 重传丢失的数据(即被重复的ACK

  • 若再收到重复的ACKcwnd = cwnd + 1(+1的道理同上)

  • 若收到新的ACK,说明恢复过程已结束,令cwnd = ssthresh再次进入拥塞避免算法

    为什么要cwnd = ssthresh

    如果不将拥塞窗口设置回ssthresh的话,而是在原先的基础上进行拥塞避免,线性增长的话(起点较高,因为原先就是在这个节点附近触发的拥塞),会容易再次导致网络拥塞,导致丢包,又再次触发相应的快速重传快速恢复机制,导致发生概率较高,这就严重拖慢了协议的性能,因此需要将拥塞窗口先回退到一个较低的水平,一次先少发点数据,这样尽量避免拥塞重传,提高性能。

快速重传和快速恢复


TCP2
http://example.com/2025/05/19/TCP2/
作者
Kon4tsu
发布于
2025年5月19日
许可协议