TCP 作为一个传输层协议,最核心的能力是传输。传输需要保证可靠性,还需要控制流速,这两个核心能力均由滑动窗口提供。
一、请求/响应模型
TCP 中每个发送的请求都需要响应。如果一个请求没有收到响应,发送方就会认为这次发送出现了故障,会触发重发。
如下图:每一个请求收到响应之后,再发送下一个请求,吞吐量会很低。因为这样的设计,会产生网络的空闲时间,浪费带宽。带宽没有用满,意味着可以同时发送更多的请求,接收更多的响应。
改进:让发送方有请求就发送出去,而不是等待响应。通过这样的处理方式,发送的数据连在了一起,响应的数据也连在了一起,吞吐量就提升了。但是如果可以同时发送的数据真的非常多呢?比如成百上千个 TCP 段都需要发送,这个时候带宽可能会不足。
二、排队(Queuing)
在 TCP 层实现一个队列。新元素从队列的一端(左侧)排队,作为一个未发送的数据封包。开始发送的数据封包,从队列的右侧离开。
存在的问题:这样做就需要多个队列,我们要将未发送的数据从队列中取出,加入发送中的队列。然后再将发送中的数据,收到 ACK 的部分取出,放入已接收的队列。而发送中的封包,何时收到 ACK 是一件不确定的事情,这样使用队列似乎也有一定的问题。
三、滑动窗口(Sliding Window)
- 深绿色代表已经收到 ACK 的段
- 浅绿色代表发送了,但是没有收到 ACK 的段
- 白色代表没有发送的段
- 紫色代表暂时不能发送的段
假设我们最多同时发送 5 个封包,也就是窗口大小 = 5。窗口中的数据被同时发送出去,然后等待 ACK。如果一个封包 ACK 到达,我们就将它标记为已接收(深绿色)。
如下图所示,有两个封包的 ACK 到达,因此标记为绿色。
这个时候滑动窗口可以向右滑动,如下图所示:
四、重传
如果发送过程中,部分数据没能收到 ACK 可能发生重传。
如果发生下图这样的情况,段 4 迟迟没有收到 ACK。
这个时候滑动窗口只能右移一个位置,如下图所示:
在这个过程中,如果后来段 4 重传成功(接收到 ACK),那么窗口就会继续右移。如果段 4 发送失败,还是没能收到 ACK,那么接收方也会抛弃段 5、段 6、段 7。这样从段 4 开始之后的数据都需要重发。
五、快速重传
在 TCP 协议中,如果接收方想丢弃某个段,可以选择不发 ACK。发送端超时后,会重发这个 TCP 段。而有时候,接收方希望催促发送方尽快补发某个 TCP 段,这个时候可以使用快速重传能力。
例如段 1、段 2、段 4 到了,但是段 3 没有到。 接收方可以发送多次段 3 的 ACK。如果发送方收到多个段 3 的 ACK,就会重发段 3。这个机制称为快速重传。这和超时重发不同,是一种催促的机制。
为了不让发送方误以为段 3 已经收到了,在快速重传的情况下,接收方即便收到发来的段 4,依然会发段 3 的 ACK(不发段 4 的 ACK),直到发送方把段 3 重传。
实际操作中,每个 TCP 段的大小不同,限制数量会让接收方的缓冲区不好操作,因此实际操作中窗口大小单位是字节数。
六、流速控制
发送、接收窗口的大小可以用来控制 TCP 协议的流速。窗口越大,同时可以发送、接收的数据就越多,支持的吞吐量也就越大。当然,窗口越大,如果数据发生错误,损失也就越大,因为需要重传越多的数据。
实际操作中,也不可以真的把带宽用完,所以最终我们会使用折中的方案,在延迟、丢包率、吞吐量中进行选择,毕竟鱼和熊掌不可兼得。
七、总结
为了提高传输速率,TCP 协议选择将多个段同时发送,为了让这些段不至于被接收方拒绝服务,在发送前,双方要协商好发送的速率。但是我们不可能完全确定网速,所以协商的方式,就变成确定窗口大小。
八、题目
滑动窗口和流速控制是怎么回事?
滑动窗口是 TCP 协议控制可靠性的核心。发送方将数据拆包,变成多个分组。然后将数据放入一个拥有滑动窗口的数组,依次发出,仍然遵循先入先出(FIFO)的顺序,但是窗口中的分组会一次性发送。窗口中序号最小的分组如果收到 ACK,窗口就会发生滑动;如果最小序号的分组长时间没有收到 ACK,就会触发整个窗口的数据重新发送。