TCP之滑动窗口原理 | 您所在的位置:网站首页 › 滑动窗口的作用及工作原理图 › TCP之滑动窗口原理 |
![]() 在我们当初学习网络编程的时候,都接触过TCP,在TCP中,对于数据传输有各种策略,比如滑动窗口、拥塞窗口机制,又比如慢启动、快速恢复、拥塞避免等。通过本文,我们将了解滑动窗口在TCP中是如何使用的。 滑动窗口实现了TCP流控制。首先明确滑动窗口的范畴: TCP是双工的协议,会话的双方都可以同时接收和发送数据。会话的双方都各自维护一个发送窗口和一个接收窗口。各自的接收窗口大小取决于应用、系统、硬件的限制(TCP传输速率不能大于应用的数据处理速率)。各自的发送窗口则要求取决于对端通告的接收窗口,要求相同。滑动窗口解决的是流量控制的的问题,就是如果接收端和发送端对数据包的处理速度不同,如何让双方达成一致。接收端的缓存传输数据给应用层,但这个过程不一定是即时的,如果发送速度太快,会出现接收端数据overflow,流量控制解决的是这个问题。 发送端窗口![]() 上图是发送端滑动窗口的简图。我们可以将数据分为4个部分: 发送和已确认的字节(蓝色部分)已发送但尚未确认的字节(黄色部分)未发送的字节和接收方准备接收的字节,即在缓冲区buffer中(绿色部分)未发送且接收方未准备接收的字节,即已经在缓冲区,但是该部分数据还未被处理(灰色部分)其中第三部分,也就是绿色部分,也称为可用窗口,因为这是发送方可以使用的窗口。 发送窗口由黄色和绿色部分组成。这些字节要么已经发送,要么可以发送。 当发送方发送21-25字节并使用可用窗口中的所有字节时,可用窗口可能为空,发送窗口保持不变(如下图)。 ![]() 当发送方收到第16-19字节的 ACK 时,发送窗口向右滑动 4 个字节。更新的可用窗口可用于队列中的以下字节(如下图)。 ![]() 为了便于理解,我们后续将窗口名使用简称,即: SND.WND,代表发送窗口SND.UNA, 代表Send Unacknowledged指针,指向发送窗口的第一个字节SND.NXT, 代表Send Next指针,指向可用窗口的第一个字节使用简写后,如下图所示: ![]() 基于这些定义,我们可以用公式表示可用的窗口大小。 代码语言:javascript复制可用窗口(可用窗口)大小 = SND.UNA + SND.WND - SND.NXT 接收端窗口![]() 接收窗口有三种: 1、接收并且已经向发送端发送确认ACK2、尚未接收但允发送端发送数据,即可用接收窗口3、尚未接收且不允许发送端发送数据,即已经在缓冲区中,但还未被处理第二种称为接收窗口,也称为RCV.WND。类似于发送窗口,指针RCV.NXT,代表Receive Next指针,指向接收窗口的第一个字节。 ![]() 接收窗口不是静态的。如果服务端性能高,读取数据快,接收窗口可能会扩大。否则,它可能会缩小。 接收方通过在TCP段报头中的窗口字段中指示大小来传达其接收窗口。当发送方收到它时,这个窗口大小就成为可用窗口。 发送和接收数据需要时间。因此,接收窗口不等于特定时刻的可用窗口。 下面,为了更好的理解滑动窗口在TCP中的使用,我们将使用一个简单的例子进行模拟说明。 示例(大小不变)我们模拟一个请求和响应,以更好地理解滑动窗口的工作原理。为了模拟起来简单,我们尽可能的简化里面的过程,比如: 我们忽略最大段大小 (MSS)。MSS 因选择的网络路由而不同。使接收窗口等于可用窗口,并且在此过程中两者保持不变。![]() 上图示例中,有10个步骤。客户端请求资源,服务器分三段响应: 1、一个 50 字节的包头2、一个 80 字节的数据13、一个 100 字节的数据2每一方都可以同时是发送方和接收方。 我们假设客户端的发送窗口 (SND.WND) 是 300 字节,接收窗口 (RCV.WND) 是 150 字节。因此,服务器的 SND.WND 为 150 字节,RCV.WND 为 300 字节。 ![]() 上图客户端的起始状态。 我们假设它之前已经从服务器接收了300个字节,所以RCV.NXT指向301。由于它还没有发送任何东西,SND.UNA和SND.NXT都指向1。 代码语言:javascript复制可用窗口(可用窗口)大小 = SND.UNA + SND.WND - SND.NXT根据这个公式,客户端的可用窗口大小为 1 + 300 - 1 = 300。 ![]() 这是服务端的起始状态,镜像另一端即客户端的状态。 因为它已经发送了300个字节,所以SND.UNA和SND.NXT都指向301。 RCV.NXT指向1,因为客户端尚未发送任何请求。服务器的可用窗口是301 + 150 - 301 = 150。 现在,我们从步骤1开始: ![]() 客户端发送它的第一个100字节请求。 此刻,窗户发生了变化。 这 100 个字节已发送,但尚未收到 ACK。因此,SND.NXT 向右滑动 100 个字节。其他指针保持不变。可用窗口更改为 1 + 300 - 101 = 200。 ![]() 在第 2 步,我们的焦点转移到服务器上,从服务端的角度来分析。 当服务器收到请求时,RCV.NXT 向右滑动 100 个字节。然后它发送一个对于50字节的ACK回复。这 50 个字节的确认ACK已发送至发送端,即客户端,但尚未收到回复ACK,因此 SND.NXT 向右移动 50 个字节。SND.UNA不动。可用窗口大小变为301 + 150 - 351 = 100。 ![]() 让我们现在继续转向客户端。 当收到50字节的回复时,RCV.NXT向右移动50字节。SND.UNA 在收到前一个发送的 100 个字节的 ACK 时向右滑动。SND.NXT保持不变,因为客户端不发送任何数据。可用窗口更改为101 + 300 - 101 = 300。 ![]() 再次移动到服务器端。 可用窗口为 100 字节。服务器可以发送 80 字节的段。 SND.NXT 向右滑动 80 个字节。SND.UNA 保持不变,因为上一次的50 字节尚未得到确认。RCV.NXT 保持不变,因为服务器没有收到任何数据。可用窗口更改为 301 + 150 - 431 = 20。 ![]() 客户端收到数据的第一部分并立即发送ACK。 当客户端接收到 80 字节的数据时,RCV.NXT 向右移动。其他部分不变。可用窗口大小仍为300。 ![]() 此时,服务器在发送 50 字节的回复时收到了第 2 步的 ACK。 SND.UNA 向右移动 50 个字节。其他部分保持不变。可用窗口大小变为351 + 150 - 431 = 70。 ![]() 当服务器发送数据1即80字节部分时,再次收到第4步的另一个ACK。 SND.UNA 向右移动 80 个字节。其他部分保持不变。可用窗口大小变为431 + 150 - 431 = 150。 ![]() 在第 8 步,服务器数据2,大小为100字节。 SND.NXT向右移动 100 个字节。其他部分保持不变。可用窗口大小变为431 + 150 - 531 = 50。 ![]() 继续转到客户端。 当客户端收到 100 字节时,RCV.NXT 向右移动 100 字节。其他部分保持不变。可用窗口大小保持不变。 ![]() 最后,服务器收到前一个响应的 ACK。 SND.UNA向右移动100个字节。其他部分保持不变。可用窗口大小变为531 + 150 - 531 = 150。 至此,对于滑动窗口不变的示例,讲解完毕,那么对于滑动窗口大小变化的呢?在TCP中又是如果实现的呢? 示例(大小变化的窗口)在前面的示例中,我们假设发送窗口和接收窗口保持不变。这个假设本身在实际中就是不成立的,因为不存在。 两个窗口中的字节都存在于操作系统缓冲区中,可以对其进行调整。例如,当我们的应用程序没有足够快地从中读取字节时,缓冲区中的可用空间就会缩小。 我们来介绍一下这种情况下的窗口变化,看看它是如何影响可用窗口的。 ![]() 我们简化了这种情况以将可用窗口集中在客户端上。在这个例子中,客户端始终是发送方,而服务器是接收方。 ![]() 当服务器发送 ACK 时,它也会在其中包含更新后的窗口大小。 ![]() 一开始,客户端发送第一个150字节的请求。 这 150 个字节已发送,但尚未发送 ACK。可用窗口缩小到 150 字节。发送窗口保持在300字节。 ![]() 当服务器收到请求时,应用程序读取前 50 个字节,还有 100 个字节仍在缓冲区中,从接收窗口中占用 100 个字节的可用空间。因此,接收窗口缩小到 200 字节。 接下来,服务器发送带有更新的 200 字节接收窗口的 ACK。 ![]() 客户端收到 ACK 并将其发送窗口大小更新为 200。 此时,可用窗口与发送窗口相同,因为所有 150 个字节都被确认。 ![]() 客户端再次发送另一个 200 字节的请求,使用可用窗口中的所有可用空间。 ![]() 服务器接收到 200 字节后,应用程序仍然运行缓慢,总共只读取了 70 字节,并在缓冲区中留下了 280 字节。 这会导致接收窗口再次缩小。现在,我们只剩下 20 个字节了。 在 ACK 消息中,服务器与客户端共享更新的窗口大小。 ![]() 同样,客户端在收到 ACK 后将其发送窗口更新为 20 字节。可用窗口也变为 20 字节。 在这种情况下,客户端停止发送任何大于 20 字节的请求,直到它收到以下消息中的另一个窗口更新。 如果没有更多来自服务器的消息,我们会被困在 20 字节的可用窗口吗? 我们不会。为了避免这种情况,客户端的 TCP 会定期检测窗口大小。一旦释放更多空间,可用窗口就会扩大,并且可以发送更多数据。 结语可用窗口的计算是理解TCP滑动窗口的关键。 要学习可用窗口的计算,我们需要了解 3 个指针——SND.UNA、SND.NXT 和 RCV.NXT。 假设一个永不改变的窗口大小可以帮助我们了解进度。 |
CopyRight 2018-2019 实验室设备网 版权所有 |