跳至主要內容

TCP - 流量控制


重传机制

超时重传

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

超时触发重传存在的问题是,超时周期可能相对较长。

快速重传

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

快速重传的工作方式是当收到三个相同的 ACK 报文时,可以不用等到超时才重传,会直接重传丢失的报文段

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

举个例子,假设发送方发了 6 个数据,编号的顺序是 Seq1 ~ Seq6 ,但是 Seq2、Seq3 都丢失了,那么接收方在收到 Seq4、Seq5、Seq6 时,都是回复 ACK2 给发送方,但是发送方并不清楚是选择重传 Seq2 一个报文,还是重传 Seq2 之后已发送的所有报文呢(Seq2、Seq3、 Seq4、Seq5、 Seq6) 呢?

SACK 方法

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

SACK是将已收到的数据的信息发送给发送方,这样发送方就可以只重传丢失的数据

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

Duplicate SACK

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

滑动窗口

TCP 每发送一个数据,都要进行一次确认应答。当上一个数据包收到了应答了, 再发送下一个。如果是这样传输,那有一个缺点:数据包的往返时间越长,通信的效率就越低。

因此,TCP引入了滑动窗口。窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值。

窗口中有部分数据段没有收到ACK,也可以通过下一个ACK进行确认。这个模式就叫累计确认或者累计应答。

窗口的大小是由接收方的窗口大小来决定的。发送方发送的数据大小不能超过接收方的窗口大小,否则接收方就无法正常接收到数据。

接收窗口的大小是约等于发送窗口的大小的。因为滑动窗口并不是一成不变的。比如,当接收方的应用进程读取数据的速度非常快的话,这样的话接收窗口可以很快的就空缺出来。那么新的接收窗口大小,是通过 TCP 报文中的 Windows 字段来告诉发送方。那么这个传输过程是存在时延的,所以接收窗口和发送窗口是约等于的关系。

MSS限制

  • 链路层对一次能够发送的最大数据有限制,这个限制称之为 MTU(maximum transmission unit),不同的链路设备的 MTU 值也有所不同,例如
    • 以太网的 MTU 是 1500
    • FDDI(光纤分布式数据接口)的 MTU 是 4352
    • 本地回环地址的 MTU 是 65535 - 本地测试不走网卡
  • MSS 是最大段长度(maximum segment size),它是 MTU 刨去 tcp 头和 ip 头后剩余能够作为数据传输的字节数
    • ipv4 tcp 头占用 20 bytes,ip 头占用 20 bytes,因此以太网 MSS 的值为 1500 - 40 = 1460
  • TCP 在传递大量数据时,会按照 MSS 大小将数据进行分割发送
  • MSS 的值在三次握手时通知对方自己 MSS 的值,然后在两者之间选择一个小值作为 MSS

TCP粘包

粘包的问题出现是因为不知道一个用户消息的边界在哪,如果知道了边界在哪,接收方就可以通过边界来划分出有效的用户消息。

出现粘包的原因是多方面的,可能是来自接收方,也可能是来自发送方。

发送方原因

因为即使发送一个字节,也需要加入 tcp 头和 ip 头,也就是总字节数会使用 41 bytes,非常不经济。因此Nagle算法试图在发送一个分组之前,将大量的TCP数据绑定在一起,以提高网络效率。

  • 该算法是指发送端即使还有应该发送的数据,但如果这部分数据很少的话,则进行延迟发送
  • 如果 SO_SNDBUF 的数据达到 MSS,则需要发送
  • 如果 SO_SNDBUF 中含有 FIN(表示需要连接关闭)这时将剩余数据发送,再关闭
  • 如果 TCP_NODELAY = true,则需要发送
  • 已发送的数据都收到 ack 时,则需要发送
  • 上述条件不满足,但发生超时(一般为 200ms)则需要发送
  • 除上述情况,延迟发送

TCP默认使用Nagle算法(主要作用:减少网络中报文段的数量),而Nagle算法主要做两件事:

  • 只有上一个分组得到确认,才会发送下一个分组
  • 收集多个小分组,在一个确认到来时一起发送。

Nagle算法造成了发送方可能会出现粘包问题。对于发送方造成的粘包问题,可以通过关闭Nagle算法来解决,使用TCP_NODELAY选项来关闭算法。

接收方原因

TCP接收到数据包时,并不会马上交到应用层处理,或者说应用层并不会立即处理。实际上,TCP将接收到的数据包保存在接收缓存里,然后应用程序主动从缓存读取收到的分组。这样一来,如果TCP接收数据报到缓存的速度岛屿应用程序从缓存中读取数据报的速度,多个包就会被缓存,应用程序就有可能读取到多个收尾相接粘到一起的包

接收方没有办法来处理粘包问题,只能将问题交给应用层来处理

解决方式

一般有三种方式分包的方式(可以同时解决发送方和接收方的问题):

  1. 固定长度的消息:这种是最简单方法,即每个用户消息都是固定长度的,比如规定一个消息的长度是 64 个字节,当接收方接满 64 个字节,就认为这个内容是一个完整且有效的消息
  2. 特殊字符作为边界:比如HTTP 通过设置回车符、换行符作为 HTTP 报文协议的边界。
  3. 自定义消息结构,如TLV 格式,即 Type 类型、Length 长度、Value 数据,类型和长度已知的情况下,就可以方便获取消息大小,分配合适的 buffer,缺点是 buffer 需要提前分配,如果内容过大,则影响 server 吞吐量
    • Http 1.1 是 TLV 格式
    • Http 2.0 是 LTV 格式

流量控制

发送方不能无脑的发数据给接收方,要考虑接收方处理能力。

流量控制就是根据接收方的滑动窗口的大小来控制的。

比如服务端繁忙,无法及时的处理掉接收的数据,就会减小接收窗口的大小

拥塞控制

流量控制是避免「发送方」的数据填满「接收方」的缓存

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

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

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

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

慢启动

慢启动算法:当发送方每收到一个 ACK,拥塞窗口 cwnd 的大小就会加 1。

这个大小是指能同时发送数据的数量,比如一开始拥塞窗口为1,表示可以传1个;发送方收到一个 ACK 确认应答后,cwnd 增加 1,于是一次能够发送 2 个;以此类推,显然慢启动时的拥塞窗口大小是呈指数增长的

慢启动算法增长到哪是根据慢启动门限 ssthresh (slow start threshold)来决定的:

  • 当 cwnd < ssthresh 时,使用慢启动算法。
  • 当 cwnd >= ssthresh 时,就会使用拥塞避免算法

拥塞避免

拥塞避免算法:每当收到一个 ACK 时,cwnd 增加 1/cwnd。

现假定 ssthresh 为 8,当到门限时,当 8 个 ACK 应答确认到来时,每个确认增加 1/8,8 个 ACK 确认 cwnd 一共增加 1

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

拥塞发生

当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种,两种使用的拥塞发送算法是不同的

  • 超时重传
  • 快速重传

超时重传

  • ssthresh 设为 cwnd/2
  • cwnd 重置为 1 (是恢复为 cwnd 初始化值,我这里假定 cwnd 初始化值 1)

接着,就重新开始慢启动,慢启动会突然减少数据流,

快速重传

当接收方发现丢了一个中间包的时候,发送三次前一个包的 ACK,于是发送端就会快速地重传,不必等待超时再重传。

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

进入快速恢复算法

快速恢复

算法如下:

  1. 拥塞窗口 cwnd = ssthresh + 3 ( 3 的意思是确认有 3 个数据包被收到了);
  2. 重传丢失的数据包;
  3. 如果再收到重复的 ACK,那么 cwnd 增加 1;
    如果收到新数据的 ACK 后,把 cwnd 设置为第一步中的 ssthresh 的值,原因是该 ACK 确认了新的数据,说明从 duplicated ACK 时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态;

也就是没有像「超时重传」一夜回到解放前,而是还在比较高的值,后续呈线性增长

参考链接

来源:https://www.xiaolincoding.com/ ,Seven进行了部分补充完善

seven97官方微信公众号
seven97官方微信公众号