参考文献

  • 编程必备基础 大话HTTP协议[慕课]
  • 趣谈网络协议
  • WireShark数据包分析实战(第三版)
  • TCP-RFC793
  • TCP/IP详解 卷1: 协议
  • 图解网络-小林coding
  • [UDP-RFC768]

TCP重传机制

  • TCP实现可靠传输的方式之一是通过序列号与确认应答.
    • 在TCP中,当发送端的数据到达接收主机时,接收端主机会返回一个确认应答消息,表示已收到消息.
  • 常见的重传机制有:
    • 超时重传
    • 快速重传
    • SACK
    • D-SACK

超时重传

  • 在发送数据时,设定一个定时器,当超过指定的时间后,没有收到对方的ACK确认应答报文,就会重发该数据.
  • TCP会在以下两种情况发生超时重传:
    • 数据包丢失: 数据包未成功发送
    • 确认应答丢失: 确认应答未成功发送
  • RTT(Round-Trip 往返时延): 数据从网络一端传送到另一端所需的时间,也就是包的往返时间.
  • 超时重传时间是以RTO(Retransmission Timeout 超时重传时间)表示
  • 在重传的情况下,超时时间RTO较长或较短会出现不同的情况
    • 当超时时间RTO较大时,重发就慢,丢了很久才重发,没效率,性能差;
    • 当超时时间RTO较小时,会导致可能并没有丢就重发,于是重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发.
  • 超时重传RTO的值应该略大于报文往返RTT的值.

TCP滑动窗口

  • 滑动窗口是 TCP 协议控制可靠性的核心.发送方将数据拆包,变成多个分组.然后将数据放入一个拥有滑动窗口的数组,依次发出,仍然遵循先入先出(FIFO)的顺序,但是窗口中的分组会一次性发送.窗口中序号最小的分组如果收到 ACK,窗口就会发生滑动;如果最小序号的分组长时间没有收到 ACK,就会触发整个窗口的数据重新发送.

  • 为了保证顺序性,每一个包都有一个ID.在建立连接的时候,会商定起始的ID是什么,然后按照ID一个个发送.为了保证不丢包,对于发送的包都要进行应答,但是这个应答也不是一个一个来的,而是会应答某个之前的ID,表示都收到了,这种模式称为累计确认或者累计应答(cumulative acknowledgment).

  • 为了记录所有发送的包和接收的包,TCP也需要发送端和接收端分别都有缓存来保存这些记录.发送端的缓存里是按照包的ID一个个排列,根据处理的情况分成四个部分.

    • 第一部分:发送了并且已经确认的.这部分就是你交代下属的,并且也做完了的,应该划掉的.
    • 第二部分:发送了并且尚未确认的.这部分是你交代下属的,但是还没做完的,需要等待做完的回复之后,才能划掉.
    • 第三部分:没有发送,但是已经等待发送的.这部分是你还没有交代给下属,但是马上就要交代的.
    • 第四部分:没有发送,并且暂时还不会发送的.这部分是你还没有交代给下属,而且暂时还不会交代给下属的.
  • 在TCP里,接收端会给发送端报一个窗口的大小,叫Advertised window.这个窗口的大小应该等于上面的第二部分加上第三部分,就是已经交代了没做完的加上马上要交代的.超过这个窗口的,接收端做不过来,就不能发送了.

    img

    • LastByteAcked:第一部分和第二部分的分界线
    • LastByteSent:第二部分和第三部分的分界线
    • LastByteAcked + AdvertisedWindow:第三部分和第四部分的分界线
  • 对于接收端来讲,它的缓存里记录的内容要简单一些.

    • 第一部分:接受并且确认过的.也就是我领导交代给我,并且我做完的.

    • 第二部分:还没接收,但是马上就能接收的.也即是我自己的能够接受的最大工作量.

    • 第三部分:还没接收,也没法接收的.也即超过工作量的部分,实在做不完.

    img

    • MaxRcvBuffer:最大缓存的量;

    • LastByteRead之后是已经接收了,但是还没被应用层读取的;

    • NextByteExpected是第一部分和第二部分的分界线.

    • 第二部分的窗口有多大呢?

      • NextByteExpectedLastByteRead的差其实是还没被应用层读取的部分占用掉的MaxRcvBuffer的量,我们定义为A.

      • AdvertisedWindow其实是MaxRcvBuffer减去A.

      • 也就是:AdvertisedWindow=MaxRcvBuffer-((NextByteExpected-1)-LastByteRead).

      • 那第二部分和第三部分的分界线在哪里呢?NextByteExpectedAdvertisedWindow就是第二部分和第三部分的分界线,其实也就是LastByteRead加上MaxRcvBuffer.

      • 其中第二部分里面,由于受到的包可能不是顺序的,会出现空挡,只有和第一部分连续的,可以马上进行回复,中间空着的部分需要等待,哪怕后面的已经来了.

顺序问题和丢包问题

  • 超时重试: 也即对每一个发送了,但是没有ACK的包,都有设一个定时器,超过了一定的时间,就重新尝试.但是这个超时的时间如何评估呢?这个时间不宜过短,时间必须大于往返时间RTT,否则会引起不必要的重传.也不宜过长,这样超时时间变长,访问就变慢了.
  • 估计往返时间,需要TCP通过采样RTT的时间,然后进行加权平均,算出一个值,而且这个值还是要不断变化的,因为网络状况不断的变化.除了采样RTT,还要采样RTT的波动范围,计算出一个估计的超时时间.由于重传时间是不断变化的,我们称为自适应重传算法(Adaptive Retransmission Algorithm).
  • 有需要重传的时候,TCP的策略是超时间隔加倍.每当遇到一次超时重传的时候,都会将下一次超时时间间隔设为先前值的两倍.两次超时,就说明网络环境差,不宜频繁反复发送.
  • 超时触发重传存在的问题是,超时周期可能相对较长.那是不是可以有更快的方式呢?
  • 有一个可以快速重传的机制,当接收方收到一个序号大于下一个所期望的报文段时,就检测到了数据流中的一个间格,于是发送三个冗余的ACK,客户端收到后,就在定时器过期之前,重传丢失的报文段.
    • 例如,接收方发现6、8、9都已经接收了,就是7没来,那肯定是丢了,于是发送三个6的ACK,要求下一个是7.客户端收到3个,就会发现7的确又丢了,不等超时,马上重发.
  • 还有一种方式称为Selective Acknowledgment (SACK).这种方式需要在TCP头里加一个SACK的东西,可以将缓存的地图发送给发送方.例如可以发送ACK6、SACK8、SACK9,有了地图,发送方一下子就能看出来是7丢了.

TCP拥塞控制

  • 接收端流量控制在局域网内做流量控制是可行的,但是在公网上就会出现问题.网络数据包在传输过程中,要经过很多路由器的转发,而这些路由器的带宽和缓冲区大小是不确定的.路由器的缓冲区太小,会造成数据包大量堆积甚至拥塞,而接收端端的缓冲区一般很大,此时就会造成大量的网络拥塞,从而加剧整个网络的拥塞,甚至早上整个互联网的不可用.
  • 为了解决这个问题,TCP发送方需要确认连接双方的线路的数据最大QPS是多少,这就是所谓的拥塞窗口

慢启动算法拥塞窗口的原理

  • TCP发送方首先会发送一个或者数个数据报文段,然后等待对方的回应,ACK回应后就把这个窗口值的大小加倍,然后连续发送两个数据报,对方回应以后,再把这个窗口加倍,这个机制就是发送端拥塞控制的慢启动算法.

  • 对慢启动算法的简单理解: 先发送少量的数据报文段,得到确认后,再将发送报文段的个数增加,直到出现超过错误或者丢包;发送端因此了解到网络的承载能力,也就确定了拥塞窗口的大小,发送方就根据这个拥塞窗口的大小发送数据.

TCP半关闭

  • TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力

TCP故障模式总结