TCP是一个可靠的传输协议,这个可靠是靠着众多富有智慧的设计保证的,而了解这其中的奥秘不仅仅是认识TCP的核心,而且对生活中协议的实现也有很多借鉴价值,首先就从最基础的停等协议开始吧。
停等协议
其实停等的概念在前面介绍TFTP的时候就介绍过,停等协议其实更加正统的名字应该是属于停止等待ARQ(Automatic Repeat-reQuest)的一部分,翻译成中文还真的一时想不到用什么比较贴切。为了更加能够符合大多数人的通用名称,在后面的文字中,我就用ARQ来代指停等这种过程了。就像在前面看到的,停止等待ARQ有两个重要的方面,一个是停止和等待的过程,另外一个是编号识别,首先还是从最简单的正常模式开始看看什么叫做停止等待ARQ。
这副图中水平箭头的两端标识两个通信端,上面的叫做S端,下面的叫做R端吧。还可以看到在S端和R端都有若干6个格子组成的长方形,这些长方形分为两个部分,在S端黄色的表示的是其接收缓冲区,白色的那一列是发送缓冲区。而下面的R端黄色的发送缓冲区,白色的是接收缓冲区,和S端相反。而每个格子表示一个包,也就是说S端有三个包要发送。
首先S端发送了1号包,在发送的过程中S端会拷贝一份这个1号包,用处在下面就会介绍,在S端的第二个长方形中用虚线表示了拷贝。而R端在接收到了这个包之后,会把1号包放到自己的接收缓冲区中,然后产生一个对1号包的ACK包,用黄色格子并且标号1表示。这个回复包会发送回S端作为对1号数据包的确认。至此,一次通信过程就完成了,后面的过程才会进行。S端等着R端的确认才进行下一次,这样保证了每一次的通信对于双方都是可靠的。
但是,由于网路本身并不是可靠的,发送出去的1号包可能会因为网络网路本身的问题就消失了,这个消失并不会通知到S端,在如此一个复杂的网络里要设计出这样一个机制不仅费时而且太占用资源。所以在TCP中,超时重发是一个最简单而又有效的办法,虽然会浪费一些时间,但是相比于前面所述的机制成本要小太多。
除了包发着发着就消失了,另外一种可能出现的情况是包在图中迷路了,花了好多时间才到达对端,这个时间比发送端的重传计时器都长,这个叫做延迟,这个和网游里网络延迟不是一个概念。下面就要详细的看看这些异常情况,只有理解了这些异常情况才能继续进一步理解TCP中的ARQ。
首先第一种情况是发送端的包在网络中丢失了,同样,我还是画了图。假设在接下来的发送过程中,2号包在发送的过程中丢失了,这个时候TCP实现中的拷贝包就有帮助了,在等待了一段时间之后,也就是重传定时器到期了之后,再次重新发送这个2号包。注意,这个重传计时器的时间肯定要比一个包在信道中往返时间(RTT)要长一些。为什么?因为如果不是这样,发送端如何确认是丢包了还是只是等待的人还没有来?这个知识点,在面试的时候问人能答上来的不会超过一半。所以在图中,这个空隙我故意画个大的,在这里假设第二次传输就成功了,在经过第二次重传之后,就回到上面一种情况。
下面介绍第二种可能发生的错误的情况,假设发送端的包正确的到达了接收端,但是接收端传回来的确认却丢失了。如果是R端的回复丢失,其实站在S端这边,他是不可能知道到底是3号包丢失了还是回送的3号包的应答包丢失了,在S端,这两种行为没有任何区别。所以在重传计数器到期之后,S端会重传其保存的3号副本。而在这个包再次到达对端之后,R端的接收缓冲区中已经有了3号包了,这时又一次到达了3号包,这个时候R端就会直接丢弃掉这个重复到达的3号包,因为R端已经拥有了。此时其实R端就能知道自己回复丢失了,因为受到了重复包,所以他再一次的发送这个对3号包的应答。
上面所说的都是包在传输过程中丢失的情况,除了这种情况,还会有一种包走着走着就迷路了,但是经过了一段时间之后,它又在各种路由协议的指导下找到了正确的道路,到达了对端。而偏偏就在这个迷路的时间里,S端因为半天没有收到对端的回复以为自己的包丢失了,所以又重发了一遍的包。这个情况用图来表示更加清晰。
在这个图中R端的3号应答包迷失了,在重传定时器到期之后S端又重传了3号包,这个包让R端知道自己的3号应答包因为某种原因没有到达对端。此时,R端再次发送3号应答包,并且成功到达了对端。可过了一段时间之后,那个迷路的3号应答包又一次的到达了S端,而这个时候S端的接收缓冲区中已经有了3号应答包,S端会简单的丢弃掉这个重复的3号应答包。
等待的过程总是漫长的
上面说的等待停止ARQ确实可以保证数据可靠的在一个不可靠的信道上传输,但是有一个很现实的问题,对于这种每次发送一个包然后再确认一个包效率实在是太低,上面所画的只是一次通信的过程,如果我们按照上面的方式把6次成功的过程画在一个图上就是下面 这张图的上面的样子。
而下面这个图就是现在要介绍的连续ARQ协议了,连续ARQ相对于停等ARQ,其信道的利用率大大提高,同样发送6个包,前面的停等ARQ比连续ARQ要花了更多的时间。说了这么多,那么什么叫做连续ARQ呢?连续ARQ就是不要那么快的发送确认,等接收端接收到几个包之后再发送确认,这样做的好处,第一明显的是减小了通信的流量,其次也省去了很多时间,这一点, 从图中的长短也能看出来其变化。
在实际的实现过程中,TCP会维护一个被称之为“窗口”的东西和累积确认的机制来实现这个连续ARQ。用数学的概念来描述,可以理解为是一个将要发送所有数据的一个子集,这个子集中包括的是可以发送的数据包范围。抽象成一个图的话就如下面一样:
比如说要发送的数据包一共有8个,这些包都存在于TCP发送端的发送缓冲区中,而图中绿色的就是目前可以一次发送最大的数据包的范围,称之为“滑动窗口”,具体的来说就是在窗口内的数据包可以一次性发出去而不需要等待对端的确认,而不是像停等模式那样非要一包一包的发然后等待对方的确认。
而对端会根据自身的情况选择可以确认的数据包,以上图为例,对端在收到2号数据包之后发送了对编号为2的ACK包,在TCP的设计中,ACK包的中的确认序号标识该序号之前的包都已经收到,这种机制叫做“累积确认”。发送端在接收到2号ACK包之后,就可以更改最大可以一次性发送的数据包范围,因为这个时候发送端已经知道有1,2号包已经妥善的投递到对端。这种操作就像我们推窗户一样,“窗户”向后面“滑动”,是不是很形象?所以有时候计算机工作者也很艺术很生活的。
正如前面所述的,任何异常情况都是TCP这样一个声称“可靠”的协议需要考虑和解决的。在上面的“窗口”机制中,“窗口"中的所有数据包可以一次性发出而不需要等待任何确认。那么假设这么一种情况,如果1-5的包一次性发出去了,但是2,3号包丢失了,最后1,4,5号包到了,对端会怎么样?按照前面的“累积确认"机制,对端不可能发送编号为5的ACK包,因为这样会导致发送端认为5号包以前的都妥善的接收了。实际上,对端只能发送对1号包的ACK包,因为这样才能不打破“累积确认"的规则。那么发送端的窗口只能向后移动一个包,这种机制叫做"Go-back-N",俗成"滚回未确定的时候",在这个情况下,发送端只能 go back 到2号包,重新发送2-6号包。
在TCP中,除了Go-back-N 这种机制外,还有一种叫selective repeat ARQ,这种在TCP中只是一个optional部分,不要求实现。他和Go-back-N不同之处在于如果发生丢包,不会要求回到最小序号的数据包,然后只能从该最小序号的数据包开始发送,哪怕后面的包已经妥善的收到。再通俗点说就是selective repeat ARQ,哪个包丢失就重传哪个包。以上面的为例,在2,3号包丢失之后,只需要重传2,3号包,窗口照样向后滑动。这样的好处就是进一步的节省网络资源,缺点就是你得为每一个包维护一个重传计时器,而Go-back-N只需要为一起发出去的一组包设置一个重传计时器,实现起来更加困难,也需要更多维护。所以一般TCP中都是采用的Go-back-N的机制。
滑动窗口的概念在TCP中十分的经典,在下一篇中会更加详细的讲述这个著名的"滑动窗口"。