网络知识入门,深入了解ACK控制位(五)

  上一篇文章讲了协议栈内部的组成,以及客户端与服务器建立连接时协议栈是如何工作的,本章将具体讲一下TCP控制信息里的控制位--ACK。

【网络知识入门,探索一次网页请求的旅程(一)】 https://blog.****.net/ck784101777/article/details/103741398

【网络知识入门,探讨DNS服务器在网页请求中的作用(二)】 https://blog.****.net/ck784101777/article/details/103741398

【网络知识入门,何为协议栈(三)】https://blog.****.net/ck784101777/article/details/103746921

【网络知识入门,深入探索协议栈(四)】https://blog.****.net/ck784101777/article/details/103761880

 

一、使用ACK确认网络包已经收到

 

1.数据包的序号和ack***

   
   到这里网络包已经装好数据并发往服务器了但数据发送操作还没有结束。TCP 具备确认对方是否成功收到网络包以及当对方没收到时进行重发的功能,因此在发送网络包之后接下来还需要进行确认操作。 我们先来看一下确认的原理(下图)。
 
     首先,TCP 模块在拆分数据时,会先算好每一块数据相当于从头开始的第几个字节,接下来在发送这一块数据时,将算好的字节数写在 TCP 头部中,“序号”字段就是派在这个用场上的然后发送数据的长度也需要告知接收方不过这个并不是放在
TCP 头部里面的因为用整个网络包的长度减去头部的长度就可以得到数据的长度,所以接收方可以用这种方法来进行计算有了上面两个数值, 我们就可以知道发送的数据是从第几个字节开始,长度是多少了。 通过这些信息,接收方还能够检查收到的网络包有没有遗漏例如, 假设上次接收到第 1460 字节那么接下来如果收到序号为 1461 的包说明中间没有遗漏;但如果收到的包序号为 2921那就说明中间有包遗漏了。像这样如果确认没有遗漏接收方会将到目前为止接收到的数据长度加起来,计算出一共已经收到了多少个字节然后将这个数值写入 TCP头部的 ACK 号中发送给发送方
 
    简单来说发送方说的是现在发送的是从第 ×× 字节开始的部分一共有 ×× 字节哦!”而接收方则回复说,“到第 ×× 字节之前的数据我已经都收到了哦!”这个返回 ACK 号的操作被称为确认响应,通过这样的方式发送方就能够确认对方到底收到
了多少数据
 
    然而,下图的例子和实际情况还是有些出入的在实际的通信中, 序号并不是从 1 开始的,而是需要用随机数计算出一个初始值,这是因为 如果序号都从 1 开始,通信过程就会非常容易预测,有人会利用这一点来发动攻击但是如果初始值是随机的那么对方就搞不清楚序号到底是从 多少开始计算的,因此需要在开始收发数据之前将初始值告知通信对象大家应该还记得在我们刚才讲过的连接过程中有一个将 SYN 控制位设为 1 并发送给服务器的操作就是在这一步将序号的初始值告知对方的
际上在将 SYN 设为 1 的同时还需要同时设置序号字段的值而这里的值就代表序号的初始值
 
                                   网络知识入门,深入了解ACK控制位(五)
 
 
 
2.数据包的确认是双向的
 
       前面介绍了通过序号和 ACK 号来进行数据确认的思路但仅凭这些还不够,因为我们刚刚只考虑了单向的数据传输,但 TCP 数据收发是双向的,在客户端向服务器发送数据的同时,服务器也会向客户端发送数据,因此必须要想办法应对这样的情况不过这其实也不难,上图中展示的客户端向服务器发送数据的情形,我们只要增加一种左右相反的情形就 可以了,如下图所示首先客户端先计算出一个序号然后将序号和数 据一起发送给服务器,服务器收到之后会计算 ACK 号并返回给客户端相反地,服务器也需要先计算出另一个序号然后将序号和数据一起发送给客户端,客户端收到之后计算 ACK 号并返回给服务器
  
              网络知识入门,深入了解ACK控制位(五)
 
 
 
 
     此外如图所示,客户端和服务器双方都需要各自计算序号,因此双方需要在连接过程中互相告知自己计算的序号初始值。明白原理之后我们来看一下实际的工作过程(如下图)。首先客户端在连接时需要计算出与从客户端到服务器方向通信相关的序号初始值,将这个值发送给服务器)。接下来服务器会通过这个初始值计算出 ACK 号并返回给客户端)。初始值有可能在通信过程中丢失,因此当服务器收到初始值后需要返回 ACK 号作为确认同时服务器也需要计算出与从服务器到客户端方向通信相关的序号初始值,并将这个值发送给客户端()。接下来像刚才一样客户端也需要根据服 务器发来的初始值计算出 ACK 号并返回给服务器)。到这里序号和 ACK 号都已经准备完成了接下来就可以进入数据收发阶段了数据收发操作本身是可以双向同时进行的,Web 中是先由客户端向服务器发送请求,序号也会跟随数据一起发送)。然后服务器收到数据后再返回 ACK  )。从服务器向客户端发送数据的过程则正好 相反(⑥⑦)。  

 

 

                网络知识入门,深入了解ACK控制位(五)
 
 
补充:数据包中途丢失怎么办?
 
    TCP 采用这样的方式确认对方是否收到了数据在得到对方确认之前,发送过的包都会保存在发送缓冲区中如果对方没有返回某些包对应 的 ACK 那么就重新发送这些包。 这一机制非常强大。通过这一机制我们可以确认接收方有没有收到某个包,如果没有收到则重新发送这样一来无论网络中发生任何错误, 我们都可以发现并采取补救措施(重传网络包)。反过来说有了这一机制,我们就不需要在其他地方对错误进行补救了。 因此,网卡集线器路由器都没有错误补偿机制一旦检测到错误就直接丢弃相应的包。应用程序也是一样因为采用 TCP 传输即便发生一些错误对方最终也能够收到正确的数据,所以应用程序只管自顾自地发送这些数据就好了。不过如果发生网络中断服务器宕机等问题那么 无论 TCP 怎样重传都不管用这种情况下无论如何尝试都是徒劳因此 TCP 会在尝试几次重传无效之后强制结束通信并向应用程序报错。
 
 
 

二、网络传输缓慢的毒瘤:ACK等待时间                        

 
1.一传一应答的方式
 
    前面说的只是一些基本原理实际上网络的错误检测和补偿机制非常复杂。下面来说几个关键的点。
 
    首先是返回 ACK 号的等待时间这个等待时间叫超时时间)。
 
    当网络传输繁忙时就会发生拥塞,ACK 号的返回会变慢这时我们就必须将等待时间设置得稍微长一点,否则可能会发生已经重传了包之后, 前面的 ACK 号才姗姗来迟的情况这样的重传是多余的看上去只是多发一个包而已,但它造成的后果却没那么简单因为 ACK 号的返回变慢大多是由于网络拥塞引起的,因此如果此时再出现很多多余的重传对于本来就很拥塞的网络来说无疑是雪上加霜。那么等待时间是不是越长越好呢?也不是如果等待时间过长那么包的重传就会出现很大的延迟也会导致网络速度变慢。 看来等待时间需要设为一个合适的值,不能太长也不能太短但这谈何容易。根据服务器物理距离的远近ACK 号的返回时间也会产生很大的波动,而且我们还必须考虑到拥塞带来的影响
 
    例如在公司里的局域网环境下,几毫秒就可以返回 ACK 但在互联网环境中当遇到拥塞时需要几百毫秒才能返回 ACK 号也并不稀奇。正因为波动如此之大,所以将等待时间设置为一个固定值并不是一个好办法。因此TCP 采用了动态调整等待时间的方法这个等待时间是根据 ACK 号返回所需的时间来判断的具体来说TCP 会在发送数据的过程中持续测量 ACK 号的返回时间如果 ACK 号返回变慢则相应延长等待时间;相对地如果 ACK 号马上就能返回则相应缩短等待时间
 
 
2.多传非固定应答方式:滑动窗口
 

 

    如下图所示每发送一个包就等待一个 ACK 号的方式是最简单也最容易理解的但在等待 ACK 号的这段时间中,如果什么都不做那实在太浪费了。为了减少这样的浪费TCP 采用图b这样的滑动窗口方式来管理数据发送和 ACK 号的操作所谓滑动窗口就是在发送一个包之后,不等待 ACK号返回而是直接发送后续的一系列包这样一来,等待 ACK 号的这段时间就被有效利用起来了。
 
             网络知识入门,深入了解ACK控制位(五)
 
    虽然这样做能够减少等待 ACK 号时的时间浪费但有一些问题需要注意。在一来一回方式中接收方完成接收操作后返回 ACK 然后发送方收到 ACK 号之后才继续发送下一个包因此不会出现发送的包太多接收方处理不过来的情况。但如果不等返回 ACK 号就连续发送包,就有可能会出现发送包的频率超过接收方处理能力的情况
 
 
缓冲区溢出
 
    下面来具体解释一下。当接收方的 TCP 收到包后会先将数据存放到接收缓冲区中。然后接收方需要计算 ACK 将数据块组装起来还原成原本的数据并传递给应用程序,如果这些操作还没完成下一个包就到了也不用担心,因为下一个包也会被暂存在接收缓冲区中如果数据到达的速率比处理这些数据并传递给应用程序的速率还要快,那么接收缓冲区中的数据就会越堆越多,最后就会溢出缓冲区溢出之后后面的数据就进不来了,因此接收方就收不到后面的包了这就和中途出错的结果是一样的,也就意味着超出了接收方处理能力
 
滑动窗口基本思路
 
     我们可以通过下面的方法来避免这种情况的发生。首先接收方需要告诉发送方自己最多能接收多少数据,然后发送方根据这个值对数据发送操作进行控制,这就是滑动窗口方式的基本思路。关于滑动窗口的具体工作方式,还是看图更容易理解,如下图所示在这张图中,接收方将数据暂存到接收缓冲区中并执行接收操作当接收操作完成后,接收缓冲区中的空间会被释放出来也就可以接收更多的数据了, 这时接收方会通过 TCP 头部中的窗口字段将自己能接收的数据量告知发送 方。这样一来发送方就不会发送过多的数据导致超出接收方的处理能力了。
 
 
TCP调优之滑动窗口缓冲区
 
     此外单从图上看大家可能会以为接收方在等待接收缓冲区被填满之前似乎什么都没做,实际上并不是这样这张图是为了讲解方便故意体现一种接收方来不及处理收到的包,导致缓冲区被填满的情况实际上, 接收方在收到数据之后马上就会开始进行处理,如果接收方的性能高处理速度比包的到达速率还快,缓冲区马上就会被清空并通过窗口字段告知发送方。 还有,中只显示了从右往左发送数据的操作实际上和序号、 ACK 号一样发送操作也是双向进行的。前面提到的能够接收的最大数据量称为窗口大小,它是 TCP 调优参数中非常有名的一个。
 
 
              网络知识入门,深入了解ACK控制位(五)
 

 

返回ACK和更新滑动窗口的时机

 
     要提高收发数据的效率还需要考虑另一个问题那就是返回 ACK 号和更新窗口的时机。如果假定这两个参数是相互独立的,分别用两个单独的包来发送,结果会如何呢
 
首先什么时候需要更新窗口大小呢
     
    当收到的数据刚刚开始填入缓冲区时,其实没必要每次都向发送方更新窗口大小,因为只要发送方在每次发送数据时减掉已发送的数据长度就可以自行计算出当前窗口的剩余长度因此,更新窗口大小的时机应该是接收方从缓冲区中取出数据传递给应用程序的时候这个操作是接收方应用程序发出请求时才会进行的而发送方不知道什么时候会进行这样的操作,因此当接收方将数据传递给应用程序,导致接收缓冲区剩余容量增加时就需要告知发送方这就是更新窗口大小的时机。
 
那么 ACK 号又是什么情况呢
   
   当接收方收到数据时如果确认内容没有问题,就应该向发送方返回 ACK 因此我们可以认为收到数据之后马上就应该进行这一操作。如果将前面两个因素结合起来看,首先发送方的数据到达接收方,在接收操作完成之后就需要向发送方返回 ACK 而再经过一段时间 ,当数据传递给应用程序之后才需要更新窗口大小。但如果根据这样的设计来实现,每收到一个包就需要向发送方分别发送 ACK 号和窗口更新这两个单独的包。这样一来,接收方发给发送方的包就太多了,导致网络效率下降。
 
 
    因此,接收方在发送 ACK 号和窗口更新时,并不会马上把包发送出去,而是会等待一段时间,在这个过程中很有可能会出现其他的通知操作, 这样就可以把两种通知合并在一个包里面发送了。
   
    举个例子在等待发送ACK 号的时候正好需要更新窗口这时就可以把 ACK 号和窗口更新放在一个包里发送,从而减少包的数量当需要连续发送多个 ACK 号时也可以减少包的数量,这是因为 ACK 号表示的是已收到的数据量也就是说,它是告诉发送方目前已接收的数据的最后位置在哪里因此当需要连续发送 ACK 号时只要发送最后一个 ACK 号就可以了中间的可以全部省略当需要连续发送多个窗口更新时也可以减少包的数量,因为连续发生窗口更新说明应用程序连续请求了数据,接收缓冲区的剩余空间连续增加。这种情况和 ACK 号一样可以省略中间过程只要发送最终的结果就可以了。