二:用电信号传输TCP/IP数据-3.2-ACK号的管理

上一节讲了数据收发的大概过程,实际上网络的错误检测和补偿机制非常复杂,这一节讲三个关键点。

一、返回ACK号的等待时间

返回ACK号的等待时间叫超时时间。
当网络传输繁忙时ACK号的返回会变慢,这时就要将等待时间设置得长一点,不然可能已经重传了,ACK号才到达。这样的重传是多余的,虽然有序号在并不影响接收方对数据完整性的判断和组合,但因为ACK返回变慢源于网络堵塞,如果此时再出现很多多余的重传,对于网络来说无疑是雪上加霜。
但等待时间也不是越长越好,如果等待时间过长,真正需要重传的情况下就会出现很大的延迟,给人的体验就是网速变慢。
所以,返回ACK号的等待时间不能太长也不能太短。
一个合适的等待时间不能是一个固定的值,因为根据物理服务器的远近,ACK号的返回时间也会产生很大的波动。比如,在小型局域网内,几毫秒就可以返回ACK号,但如果在互联网环境中,返回ACK号可能需要几十毫秒到几百毫秒不等。
因此,TCP采用了动态调整等待时间的方法,这个等待时间是根据ACK号返回所需的时间来判断的。
具体来说,TCP会在发送数据的过程中持续测量ACK号的返回时间,如果慢,就延长;如果快,就缩短。

二、使用窗口有效管理ACK号


如上图a所示,每发送一个包就等待一个ACK号的方式是最简单也最容易理解的,但在等待ACK号的这段时间里,如果什么都不做,既浪费时间又浪费资源。对时间和资源的极限利用是计算机的基本世界观。
所以,TCP采用图b中的滑动窗口方式来管理数据发送和ACK号的操作。
所谓滑动窗口,就是在发送一个包之后,不等待ACK返回,而是直接发送后续的一系列包。这样,等待ACK号的这段时间就被有效利用起来了。

这样的方式下,有个问题要注意。
在一来一回的方式下,接收方完成接收操作后返回ACK号,然后发送方收到ACK号之后才继续发送下一个包,因此不会出现发送太多而接收方无法处理的情况。
但如果不等返回ACK号,也就是不管接收方是否完成接收就连续发送的话,有可能出现发送的频率超过接收方处理能力的情况。
下面将这种情况展开来讲下。
当接收方的TCP收到包后,会先将数据存放到接收缓冲区中。
然后接收方计算ACK号,将数据块组装起来还原成原本的数据并传递给应用程序,如果这些操作还没完成下一个包就到了,也会被暂存在缓冲区。
如果数据到达的速度比处理这些数据并传递给应用程序的速度还要快,那么接收缓冲区中的数据就会越堆越多,直到溢出。
溢出之后,后面的数据就进不来了,因为没有空间存放。
因此,要避免这种超过接收方处理能力的情况。

解决方法是:接收方告诉发送方自己最多还能接收多少数据,发送方根据这个值对发送操作进行控制。
最多能接收多少数据就是“窗口”,由于这个值一直在变,所以叫“滑动窗口”。

如上图,接收方将数据暂存到缓冲区中并执行接收操作,当接收操作完成后,缓冲区中的空间会被释放出来,也就可以接收更多的数据了。这时接收方会通过TCP头部中的窗口字段将自己能接收的数据量告知对方。
图中有一点容易误解,就是会让人觉得接收方似乎在缓冲区被填满之前什么都没做,就静静地等着填满后再取出。实际上并不是这样,图中是为了方便讲解而演示接收方来不及处理导致缓冲区被填满的情况。实际情况中,接收方在收到数据后马上就会开始处理,如果处理速度比到达速度快,缓冲区会被立即清空并通过窗口字段告知对方。
图中近演示了单向的操作,实际上和序号、ACK号一样,窗口字段的发送也是双向进行的。

窗口大小是TCP调优参数中非常常用的一个。

三、ACK与窗口的合并

要提高收发数据的效率,还需要考虑另外一个问题,那就是返回ACK号和更新窗口的时机。
现在假定这两个参数是相互独立的,分别用两个单独的包来发送,结果会如何呢?
1、窗口大小的更新时机
当收到的数据刚刚开始填入缓冲区时,其实没必要每次都向发送方更新窗口大小,因为发送方可以用前面的窗口大小减去已经发送的数据长度就能自行计算出当前剩余的窗口大小。
更新窗口大小的时机应该是接收方从缓冲区中取出数据交给应用程序的时候。为什么是这个时候?因为这个操作是接收方的应用程序发出请求时才会进行,而发送方无从得知,需要接收方主动告知。

2、ACK号的更新时机
理想情况下,当接收方接收到数据后,如果确认没有问题,就应该向发送方返回ACK号,可以认为ACK号的更新操作应该在收到数据之后马上进行。
但是如果把窗口大小的更新和ACK号这两个要素结合起来看,接收方收到数据确认无误马上返回ACK号,随后数据传递给应用程序后又要更新窗口大小,这样的话每收到一个包就要向发送方分别发送ACK号和窗口大小更新两个单独的包。
毫无疑问,这样会导致网络效率下降。

因此,接收方在发送ACK号或窗口更新时,并不会马上把包发送出去,而是会等待,在这个过程中如果出现其他的通知操作,就可以把两者合并到一个包里发送。
比如,等待发送ACK号时正好需要更新窗口,这时两个通知就可以放在一个包里发送。
当需要连续发送多个ACK号时,只需要发送最后一个ACK号就可以了。因为ACK号表示已收到的数据量,也就是告诉发送方目前已接收的数据的最后位置在哪里,因此中间的ACK号可以全部省略而只发送最后一个。
当需要连续发送多个窗口更新时也是如此。

posted @ 2023-05-22 15:48  GPL-技术沉思录  阅读(20)  评论(0编辑  收藏  举报