TCP重传, 滑动窗口, 流量控制, 拥塞控制

TCP重传, 滑动窗口, 流量控制, 拥塞控制

1. 重传机制

TCP 实现可靠传输的方式之一,是通过序列号与确认应答

在 TCP 中,当发送端的数据到达接收主机时,接收端主机会返回一个确认应答消息,表示已收到消息。

针对数据可能丢失的情况, 用重传机制来解决, 四种常见的重传机制:

  • 超时重传
  • 快速重传
  • SACK
  • D-SACK
1.1 超时重传

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

分为两种:

  • 数据包丢失
  • 确认应答丢失

在这里插入图片描述

超时时间:

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

RTO(Retransmission Timeout 超时重传时间)

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

在这里插入图片描述

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

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

1.2 快读重传

不以时间为驱动,而是以数据驱动重传

在这里插入图片描述

在上图,发送方发出了 1,2,3,4,5 份数据:

  • 第一份 Seq1 先送到了,于是就 Ack 回 2;
  • 结果 Seq2 因为某些原因没收到,Seq3 到达了,于是还是 Ack 回 2;
  • 后面的 Seq4 和 Seq5 都到了,但还是 Ack 回 2,因为 Seq2 还是没有收到;
  • 发送端收到了三个 Ack = 2 的确认,知道了 Seq2 还没有收到,就会在定时器过期之前,重传丢失的 Seq2。
  • 最后,收到了 Seq2,此时因为 Seq3,Seq4,Seq5 都收到了,于是 Ack 回 6 。

快速重传的工作方式是当收到三个相同的 ACK 报文时,会在定时器过期之前,重传丢失的报文段。

快速重传机制只解决了一个问题,就是超时时间的问题,但是它依然面临着另外一个问题。就是重传的时候,是重传一个,还是重传所有的问题。

1.3 SACK方法

实现重传机制的方式叫:SACK( Selective Acknowledgment), 选择性确认

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

如下图,发送方收到了三次同样的 ACK 确认报文,于是就会触发快速重发机制,通过 SACK 信息发现只有 200~299 这段数据丢失,则重发时,就只选择了这个 TCP 段进行重复。

在这里插入图片描述

1.4 Duplicate SACK

主要使用了 SACK 来告诉「发送方」有哪些数据被重复接收了。

分为ACK丢包和网络延时

2. 滑动窗口

流量控制可以让发送端根据接收端的实际接受能力控制发送的数据量。它的具体操作是,接收端主机向发送端主机通知自己可以接收数据的大小,于是发送端会发送不会超过该大小的数据,该限制大小即为窗口大小,即窗口大小由接收端主机决定。

TCP 首部中,专门有一个字段来通知窗口大小,接收主机将自己可以接收的缓冲区大小放在该字段中通知发送端。当接收端的缓冲区面临数据溢出时,窗口大小的值也是随之改变,设置为一个更小的值通知发送端,从而控制数据的发送量,这样达到流量的控制。这个控制流程的窗口也可以称作滑动窗口。

3. 流量控制

在这里插入图片描述

左边发送端, 右边接收端, 白色空余位置, 粉色有数据但是未发送(为读到内存中), 灰色已发送

从三次握手 -> 数据通信 -> 四次挥手全过程:

在这里插入图片描述

# fast sender: 客户端
# slow recerver: 服务器
# win: 滑动窗口大小
# mss: maximum segment size, 单条数据的最大长度

第4~9步: 客户端给服务器发送数据

  • 1(1024):1 (1-0)表示之前一共给服务器发送了1个字节,(1024)表示这次要发送的数据量为 1k
  • 1025(1024):1025(1025-0)表示之前一共给服务器发送了1025个字节,(1024)表示这次要发送的数据量为 1k

第10步:

  • ack6145: 服务器给客户端回复数据,6145是确认序号, 代表实际接收的字节数

    服务器实际接收的字节数 = 确认序号 - 客户端生成的随机序号 ===> 6145 = 6145 - 0

  • win2048:服务器告诉客户端我的缓存还有2k,也就是还有4k还在缓存中没有被读走

第11步:win4096表示滑动窗口变为4k,代表还可以接收4k数据,还有2k在缓存中

第12步:客户端又给服务器发送了1k数据

第13步: 第一次挥手,FIN表示客户端主动和服务器断开连接,并且发送了1k数据到服务器端

第14步: 第二次挥手,回复ACK, 同意断开连接

第15, 16步: 服务器端从读缓冲区中读数据, 第16步数据读完, 滑动窗口变成最大的6k

第17步:

  • FIN: 服务器请求和客户端断开连接
  • 8001(0): 服务器一共给客户端发送的字节数 8001 - 8000 = 1个字节,携带的数据量为0(FIN不计算在内)
  • ack8194: 服务器收到了客户端的多少个字节: 8194 - 0 = 8194个字节

第18步: 第四次挥手

  • ACK: 客户端同意了服务器断开连接的请求
  • 8002: 确认序号, 可以计算出服务器给客户端发送了多少数据,8002 - 8000 = 2 个字节

4. 拥塞控制

在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但是一重传就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大…

所以,TCP 不能忽略网络上发生的事,它被设计成一个无私的协议,当网络发送拥塞时,TCP 会自我牺牲,降低发送的数据量。

于是,就有了拥塞控制,控制的目的就是避免「发送方」的数据填满整个网络。

为了在「发送方」调节所要发送数据的量,定义了一个叫做「拥塞窗口」的概念。

拥塞窗口 cwnd是发送方维护的一个的状态变量,它会根据网络的拥塞程度动态变化的

我们在前面提到过发送窗口 swnd 和接收窗口 rwnd 是约等于的关系,那么由于加入了拥塞窗口的概念后,此时发送窗口的值是swnd = min(cwnd, rwnd),也就是拥塞窗口和接收窗口中的最小值。

拥塞窗口 cwnd 变化的规则:

  • 只要网络中没有出现拥塞,cwnd 就会增大;
  • 但网络中出现了拥塞,cwnd 就减少;

其实只要「发送方」没有在规定时间内接收到 ACK 应答报文,也就是发生了超时重传,就会认为网络出现了拥塞。