TCP的滑动窗口机制,谈谈其设计演化过程

理解TCP滑动窗口是如何工作的,对于理解TCP的其他知识是至关重要的。

相比于更为简单,同为传输层协议的UDP而言,TCP提供了对传输数据的质量保证。

在可靠性上,TCP确保传输的数据不丢失、不重复,也不会产生乱序。

同时,TCP还提供了流量控制,用于控制数据发送的速度,防止较快主机导致较慢主机的缓冲区溢出。

接下来,从不可靠的协议开始说起,看看TCP面向流的滑动窗口机制是如何演化而来的。

不可靠的协议

对于不可靠的协议(例如IP、UDP),当数据发出去之后,可能到达目的地,也可能丢失。

如果没有提供消息反馈的机制,那么发送方将无法获知数据是否已成功的送达目的地,因而也无法对丢失的数据进行重传。

没有消息反馈机制,是造成传输不可靠,没有流控的一个主要原因。



支持重传的确认机制

为了能够在一个不可靠协议的基础之上,进行可靠的数据传输,引入了消息反馈机制。

消息反馈

发送方发送一条信息,当接收方收到数据之后,给发送方返回一个接收成功的反馈信息。

简单的实现流程如下:

  1. 主机A给主机B发送一条消息;
  2. 主机B收到后,给主机A发送一条确认消息,告诉A消息已经被成功的接收了;
  3. 主机A在收到B的确认消息之后,就知道该消息已发送成功。

但是这个简单的机制,可能存在2个问题:

  1. 由于IP协议是不可靠的,实际上消息可能没有到达目的地。这样,主机A将会处于等待消息确认的状态,可能永远也收不到确认。
  2. 另一方面,也有可能主机B在收到了从A发送过来的消息之后,返回了确认消息,但这个消息却意外的丢失了。

以上2种情况,都有可能导致主机A一直处于等待确认消息的状态,但却永远也等不到。

超时重传

为了解决这个问题,当主机A发送一条消息之后,可以同时启动一个计时器。这个计时器的时间要足够长,让消息能够送达主机B,同时让确认消息也能够返回给主机A。

如果在确认消息到达主机A之前,计时器超时了。那么,主机A会认为发送消息的过程中遇到了问题(可能是丢失了,或者网络堵塞导致包延迟),于是就重传该消息。

由于这种对消息的确认机制,既包含了对消息已收到的确认应答,也包含了发生超时后对消息的重传,因此被称为支持重传的确认机制(PAR,Positive Acknowledgment with Retransmission)



基于PAR机制,能够给数据传输提供基本的可靠性保证

但PAR机制存在一个缺点,就是在任何时候,只能有一条消息是未被确认的。这会使得系统变得非常慢,因为后续消息的发送都得等待前一条消息的确认。

消息标识和发送限制

PAR机制比较适用于传输少量数据,或者交互不是很频繁的协议。由于PAR传输效率低下,因此也不适用于像TCP这样的协议。

为了解决传输效率低下的问题,引入了消息标识。

消息标识

当发送方发送消息的时候,为这个消息增加一个唯一标识。接收方在收到消息之后,返回的确认消息也带上该消息标识。

这样,发送方就可以针对不同的消息使用不同的标识。发送方可以同时发送多条消息,在收到确认消息之后,只要根据消息标识进行匹配,确认对应的消息即可。

以上,只是从发送方的角度,解决了发送方的发送速率问题(在任何时候,只能有一条消息是未被确认的)。

发送限制

我们再换个角度,原来是发送一个消息,接收方处理完,返回确认消息后,等待下一条消息。这个过程,对于接收方来说,并不会存在什么问题。

在引入消息标识之后,发送方可以同时发送多条消息了。但如果发送过快,接收方可能由于处理不过来,被压垮了。

因此,对于接收方来说,需要保证在自己能够正常接收处理数据的情况下,通过一种机制,来通知发送方尽量不要发送得太快,发送的数据尽量按照自己的处理能力来。

比较容易想到的,就是在接收方返回确认消息的时候,将接收方的发送限制信息带上,传递给发送方

发送方在收到确认消息中的发送限制字段时,就可以按照接收方的要求来控制自己的发送速率。

有了这个发送限制,接收方就可以根据自己的实际负载情况,进行动态反馈以调整发送速率,最优化发送性能。

增加了消息标识和发送限制之后,可以认为是PAR机制的一个增强版。

对于数据的传输,现在可以提供可靠性、效率和基本的数据流控了。



TCP面向流的滑动窗口确认

在增加了消息标识和发送限制之后,整个消息的发送和确认过程,已经较为完善了。

但从传输的"数据"的角度来看,前面我们讲的,其实都是面向消息的。也就是说,发送和确认都是以消息为单位。

而实际上,TCP是面向字节流的,本身并没有消息的概念。TCP传输的数据,我们通常称之为报文段。具体如何识别并组装为一条完整的消息,是应用层的工作。

正因为如此,TCP是以字节为单位来处理数据流的。如果要对TCP传输的数据做标识,那就需要一个字节给一个标识。

这样一来,TCP每次就只能发送一个字节的数据,然后每次也只能确认一个字节的数据,这显然是不合理的。既浪费带宽,又降低了发送效率。

TCP的设计者确实也没有这么做,而是将数据划分为一个个数据段,每次可以发送一段数据

对于每一段数据,使用一个序号来进行标识。每次确认数据的时候,也是确认一段数据而不是一个字节。

事实上,序号是报文段中数据字节的偏移量,表示数据字节的位置。

TCP缓冲区

设想一个场景,主机A和主机B建立了一个新的连接:

  1. 主机A有一个很大的数据字节流需要传输,但是主机B没有办法全部接收;
  2. 所以主机B限制主机A报文段能够传输的字节数大小,直到已发送的字节被确认;
  3. 主机B允许主机A发送更多的字节。

所以,每台主机都需要跟踪哪些字节已经发送了,哪些字节还没有发送,哪些已经确认了。这个流程,和前面以消息为单位的发送限制机制很类似。

为此,TCP引入了缓冲区的概念,将发送方的缓冲区,划分为四个部分。

发送方的缓冲区



如图所示,发送方的缓冲区,被划分为四个部分,具体说明如下。

第一部分:已发送,并且已确认的字节

(Bytes Sent And Acknowledged)

对应于图中的 Category #1 部分,从第1到第31个字节,共31字节。

第二部分:已发送,但尚未确认的字节

(Bytes Sent But Not Yet Acknowledged)

对应于图中的 Category #2 部分,从第32到第45个字节,共14字节。

第三部分:尚未发送,但可以立即发送的字节

(Bytes Not Yet Sent For Which Recipient Is Ready)

对应于图中的 Category #3 部分,从第46到第51个字节,共6字节。

这部分,代表发送方当前还可以发送的字节数,也是接收方当前还可以接收的字节数。

第四部分:尚未发送,并且还不能发送的字节

(Bytes Not Yet Sent For Which Recipient Is Not Ready)

对应于图中的 Category #4 部分,从第52到第95个字节,共44字节。

这部分字节,已经超出了接收方可以接收的最大窗口大小,因此暂不能发送。

发送窗口(Send Window)

黑色方框部分,表示发送窗口(Send Window),通常也简单的称为窗口。

发送窗口由两部分组成,对应于发送缓冲区的第二部分(Category #2,灰色方框内)和第三部分(Category #3,红色方框内)。

如图所示,发送窗口大小为20字节,第二、三部分分别为14字节和6字节。

窗口的左边缘为窗口的第1个字节(第32字节),右边缘为窗口的最后一个字节(第51字节)。

窗口的左边缘位置,决定了窗口的右边缘位置。左边缘是第一和第二部分的分界线,右边缘是第三和第四部分的分界线。

发送窗口的大小,决定了发送方可以同时发送的字节数,即未被接收方确认的字节数。

可用窗口(Usable Window)

可用窗口(Usable Window),对应于缓冲区的第三部分(Category #3),即发送方当前仍可以发送的字节数。

发送消息之后窗口的变化

如果在可用窗口内,发送了一些数据,将会改变发送窗口的位置,以及各部分的大小。

在上面的例子中,发送方的可用窗口为6字节。如果发送方将6个字节的数据发送出去,那么这6个字节将会变成未确认状态,合并到第二部分。

第二部分的范围变为,从第32到第51个字节,共20个字节。

而可用窗口,即窗口的第三部分,将变为0。这通常被称为零窗口(Zero Window)

发送方需要等到有新的确认包到达,才会有可用的窗口可以继续发送数据。



确认消息之后窗口的变化

接收方成功接收了数据之后,会给发送方返回一个确认包。在确认包中,包含了一个确认序号,用于通知发送方,在这个序号之前的数据都已经成功接收了。(确认序号的主要作用,是用来解决不丢包问题。通知接收端已经接收了哪些数据。)

假设,已发送未确认部分(第32到第45字节),是由4个TCP报文段进行传输的。每个报文段的范围分别为:

  • 第一段为32~34字节;
  • 第二段为35~36字节;
  • 第三段为37~41字节;
  • 第四段为42~45字节。

假设第一段、第二段和第四段,都已经成功的发送给接收方了。但是,第三段还没有送达。

那么,接收方将只能确认第一段和第二段,即第32到第36字节已经接收成功了。而对于第四段,还不能进行确认。

这是因为,TCP采用的是累积确认的机制,确认序号表示在这个序号之前(确认序号值减去1)的数据都已经被成功接收。

由于第三段接收方还没有收到,因此不能越过这部分,去确认第四段。否则,发送方会认为第三段也已经被成功的接收了。

(事实上,现在的TCP支持SACK机制,如果启用了,可以支持确认非连续的数据块。)



发送方在接收到确认包之后,窗口将会向右移动5个字节(第32到36共确认5个字节)。而第32到36字节,将会合并到第一部分,变为已发送已确认状态。

由于确认了5个字节,窗口右移之后,又新建了一个5个字节的可用窗口(第52到56字节)。

这个过程会在每次发送方接收到确认包时发生,然后窗口会进行相应的移动,以调整缓冲区各部分的大小。(滑动窗口很形象)



有了滑动窗口机制,就可以使用一个确认序号来确认一整段数据。这为TCP面向字节流的服务,提供了可靠性支持,并且不需要为每一个序号的确认而耗费时间。

丢失确认之后窗口的变化

发送方在发送了第52到56字节的数据之后,就停下来不再发送数据了。因为被卡在了第37到41字节上,这个范围的字节仍然没有返回确认包。

当然,和PAR机制类似的,TCP也支持超时检测和重传数据包。

因此,在计时器超时之后,发送方将重发已丢失的报文段,并希望这一次能够到达目的地。

非常不幸的是,TCP的累积确认机制,仍存在一个缺点,它并不会独立的确认每个报文段。

这意味着,TCP有可能会重复发送那些实际已经被接收方成功接收的报文段。

在我们的例子中,第四段(第42到45字节)已经被成功接收。但只要第三段没有被确认,当超时进行重发时,第四段也会被重发。

(完)

作者:二进制之路链接:https://juejin.im/post/5e7b78ee6fb9a0099877fdd3

相关文章

3000字讲讲TCP协议,握手挥手不是你想的那么简单

专注于Java领域优质技术,欢迎关注作者: tobe 来自:tobe的呓语上一次讲了 UDP 协议,从这次开始,就要讲 TCP 协议了,因为 TCP 协议涉及到的东西很多,一篇文章概括不完,所以我把...

Java编程-TCP JAVA编程手机软件

1.1. Java的TCP面向连接, 数据安全, 区分服务器端和客户端.TCP分为Socket(客户端)和ServerSocket(服务端)需要分别建立客户端和服务器端客户端和服务端建立连接后,通过S...

Java网络编程---TCP通信 java的tcp通讯协议数据传输

TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象,从而在通信两端形成网络虚拟链路,一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信Java对基于TCP协议的网...

4000 字详解TCP超时与重传,看完没收获算我输

专注于Java领域优质技术,欢迎关注作者: tobe 来自:tobe的呓语上一篇介绍 TCP 的文章「TCP 三次握手,四次挥手和一些细节」反馈还不错,还是蛮开心的,这次接着讲一讲关于超时和重传那一部...

用 Wireshark 让你看见 TCP 到底是什么样

前言当你看到这篇文章时,你只能看到已经渲染好的文字和图像,而网络数据的交互对我们来说,却是看不见的,所以学习计算机网络原理的时候就会觉得非常的抽象,这一度让我苦恼。而且网络数据交换真实的模样,到底是不...

一文带你搞定TCP流量控制 tcp流量控制算法

摘要理想的流量控制实际的流量控制窗口关闭糊涂窗口综合征理想的流量控制什么是流量控制?流量控制就是发送方不能无脑的给接收方发送数据,它需要根据接收方的处理能力来发送数据。理想下的流量控制?理想意味着在实...