前文我们介绍了连接建立、拆包和装包等的工作原理,那么在实际运行当中TCP拆包和装包所得的Segment,以什么样的速度和数量发送出去的呢?
本文就来探讨一下具体的实现方法。
因为TCP是面向连接的,假如我们每次发出一个TCP段,等待接收方返回ACK后,再发送下一个,这样保证连接的可靠性是没有问题的,但是发送效率是比较低的,网络的使用率也是比较低的,如下图:
那么为了提升网络的使用率,我们提高TCP段的发送量,只要有发送任务就把所有数据发送出去,如下图:
上图中发送量是足够保证发送效率了,带宽也尽量占满了,而且可能会造成拥堵,因为当同时发送量超过带宽的容纳上限之后,就会造成主机网络瘫痪,其他应用也无法正常使用网络发送数据,缓存已全部存满,如下图:
这样也不符合实际的发送需求,需要控制实际单位时间内的发送量,并且保证每次发送出去的包,都能够收到ACK确认后,再发送下一次,如下图:
假如我们维护三个队列,分别用来存储未发送、发送中、已发送三种状态的数据,如上图,这样每次控制一定发送的量,根据返回状态确定下一次发送数据,可以满足我们的要求,就是操作起来不是那么方便,同时维护三个队列,并且控制三个队列内数据状态的更新,那么有没有更好的解决方式呢?
滑动窗口
让我们来看看,用滑动窗口怎么解决这个问题,达到更高效的目的。
我们将三个队列的结构合并成一个队列,然后设计一个滑动的窗,把每次发送的数据用窗括起来,也就是标记当前已发送出的数据,如果收到了接收方的ACK,我们标记成已发送,这样通过一个队列内标记状态的变化就能实现目的,参考下图:
如果接收到了ack,那么就标记为已接收,如下图:
如上图窗口内数据发送状态变化过程,逐渐接收到ACK,完成发送。
这时,窗口内左侧已接收的Segment已经练成一片,我们的窗口就可以向右侧移动两个单位如下图:
那么上面演示了正常的滑动窗口工作流程,那么如果窗口内的Segment不能按照顺序收到ack会怎样,如下图:
如上图,我们窗口内五个Segment有四个收到了ack,但不是按照顺序收到的,这时就涉及到TCP另一个工作机制——重传
那么当前窗口由于左侧只有一个Segment收到ack,所以只能向右移动一个单位,如下图:
这时,如果我们窗口内的5段一直收不到ack,发送方就会认为接收方一直没有收到这个TCP段,发送方会重新发送这个TCP段,
那么接收方如果想让发送方快速的重新发送未收到的TCP段,该怎样的操作呢?这时接收方就会连着发送未接收到段的3次ack,发送方接收到多次同一TCP段的ack,就会重新发送对应的TCP段,这叫做快速重传
有了滑动窗口,我们就可以对数据发送的效率进行控制,叫做流速控制
有一个关于数据在网络中发送一来一回时间的单位RTT(Round Trip Time),假设RTT时间为1ms,我们的窗口大小为1KB,网络带宽为1MB,那么1s刚好发送1MB的数据,刚好占满所有的带宽,但是实际生产中不可能将带宽完全占满,还有可能发生丢包、重传等情况,这就需要发送和接收双方根据实际情况来协调具体窗口大小。