众所周知,TCP协议是一个面向连接,可靠的传输层的协议,而IP协议是不可靠的网络层协议,IP协议能做到的就是“尽力而为的”交付分组,即不能确保分组一定能从发送方到达接受方,那么TCP协议是如何在不能信赖的网络层上建立可靠的连接确保对方能准确无误的收到分组呢?
在深入了解TCP协议之前,需要先熟悉一些基本的可靠数据传输原理,本文是笔者在阅读《计算机网络:自顶向下方法》中的传输层后做下的笔记总结。注意,TCP协议只是以这些原理为基础,实际还会在这些原理中做出一些改进。
在了解可靠数据传输原理之前,相信大家都会有这样的一个疑问,为什么需要可靠数据传输原理呢?难道底层的信道不能直接做到准确的数据传送么?在数据传送过程中数据会发生哪些损坏?
笔者首先列出,当数据在传送过程中会发生哪些损坏:
因此,可靠数据传输原理就是为了解决上述的问题的。
现在,假设将发送方和接受方分为三层模型,其中,分组通过底层信道传输,底层信道不会对分组进行重新排序,发送方模型如下:
现在假设底层信道完全可信,即上面笔者所说的三种分组损坏情况都不会发生,那么分组只要直接从发送方通过底层信道送往接收方,接收方也不需要给发送方回应,因为分组准确地到达并且不会发生任何差错。
分组的数据在传输中出现比特差错是非常常见的,因此,发送方需要一个来自接收方的确认信息来告诉发送方传送的分组数据是否有差错,那么,当数据无误的时候,发送方发送一个ACK(肯定确认)的回应表示数据没有问题,而如果数据错误,则发送一个NAK(否定确认)的回应表示数据有问题需要重传。其中,发送方在发送分组的时候需要为分组计算一个检验和,当分组到达以后,接收方用同样的计算方法对分组进行计算,将得到的检验和与发送方的检验和进行核对以检验分组数据是否传输出错。
情况流程如下:
其中,在发送方与接收方的交互中,大家可以看到使用了差错检测,接收方反馈,重传这三个功能,基于这三个功能以及还有超时功能的可靠数据传输协议被称为ARQ协议。(维基百科传送门:ARQ)
笔者在上面加粗了发送方在发送完分组数据以后的状态,这是因为当发送方没有连续发送分组而是处于等待接收方回应的状态时,此时发送方与接收方交互的协议被称为停等协议。
当发送方接收到来自接收方的反馈分组时,反馈分组出现了比特差错,发送方无法辨认反馈是ACK还是NAK,因此,一个简单的解决方法是,发送方只要重传当前分组就行了。但是,又有另一个问题发生了,接收方如何知道收到的分组是一个新的分组还是说是一个重传的分组呢?因此,还需要给分组准备一个简单的序列号(用一个比特位来表示,即0和1),使接收方能返回不同分组的回馈。
笔者重点描述一下如果接收方返回的回馈发生了比特差错的情况:
在上述的流程中,大家可以注意到,无论接收方向发送方返回一个正确传达的NAK,还是返回一个破损的回馈分组,发送方都需要重传当前分组,因此,当接收方收到一个破损的分组,可以向发送方发送上一个已经正确接收的分组的ACK,表示需要重传接收方已经正确接收分组的下一个分组(即发送方的当前分组,不过在传送过程中破损了),这样就可以通过一个ACK来确认多种情况。
假设现在分组有可能被发送方与接收方之间的中转路由器丢弃了,而此时发送方与接收方根本不知道分组被丢弃了,于是双方都陷入了一个死循坏的等待中。因此,一个简单的解决方法是,发送方需要设置一个定时器,当发送分组的时候开始计时,如果在一定时间内(传播时延+接收方处理分组时间)没有收到接收方的回馈分组,则重传当前分组。
需要注意的是,由于发送方使用了计时器,所以当发送方收到了当前分组的上一个分组的ACK的时候(即当前分组传输损坏)或者回馈分组损坏的时候(这两个操作都不会重置计时器),并不会立即发送当前分组,而是等到计时器超时才重新发送当前分组。
在解决了上面三种分组数据损坏的问题后,得到的的确是一个可靠的数据传输协议,但是,大家应该注意到上述的可靠数据传输协议采用的是停等协议,这必然造成了效率的地下,不能对链路带宽充分的利用。因此,有没有方法使发送方不断的发送分组,而无需等待发送分组的确认呢?
那么,如果要实现流水线发送分组而无需确认分组,需要做到:
假设在序号空间内,划分一个长度为N的子区间,这个区间内包含了已经被发送但未收到确认的分组的序号以及可以被立即发送的分组的序号,这个区间的长度就被称为窗口长度。(随着发送方方对ACK的接收,窗口不断的向前移动,并且窗口的大小是可变的,因此GBN协议也被称为滑动窗口协议)
笔者描述一下当上层调用发生时,发送方的窗口变化:
那么,为什么要限制窗口长度为N,而不直接设置窗口长度为整个发送方的缓存长度呢?这是为了给TCP协议提供流量控制的功能(注意,流量控制与差错控制要区分)。
GBN协议还采取了累积确认,当发送方收到一个对分组n的ACK的时候,即表明接收方对于分组n以及分组n之前的分组全部都收到了。
对于超时的触发,GBN协议会将当前所有已发送但未被确认的分组重传,换句话说,如果当前窗口内都是已发送但未被确认的分组,一旦定时器发现窗口内的第一个分组超时,则窗口内所有分组都要被重传。每次当发送方收到一个ACK的时候,定时器都会被重置。
GBN协议对于接收方则相对比较简单,接收方只需要按序接收分组,对于比当前分组序号还要大的分组则直接丢弃。假设接收方正在等待接收分组n,而分组n+1却已经到达了,于是,分组n+1被直接丢弃,正是因为这种处理,所以发送方并不会出现在连续发送分组n,分组n+1之后,而分组n+1的ACK却比分组n的ACK更早到达发送方的情况。
那么,GBN协议对超前到达的分组直接丢弃的做法会不会有点过于浪费呢?大家可以注意一下GBN的超时机制,即使分组n+1已经预先到达了接收方,但只要分组n没有到达接收方,则很有可能导致发送方的定时器超时,而一旦定时器超时,则所有已发送但未被确认的分组都会被重传,其中就包括了分组n+1,因此,接收方只要简单的丢弃提前到达的分组n+1就可以了。
在了解了GBN协议之后,或许有人对GBN的超时重传机制感到困惑,有必要因为一个分组的超时而重传所有的已发送而未被确认的分组么?SR协议允许接收方对于超前分组的到达返回ACK,则发送方就会出现在收到分组n的ACK之前接收到分组n+1的ACK的情况。
那么,对于SR协议来说,发送方需要做到:
接收方需要做到:
需要注意的是,接收窗口内不能出现两个分组的相同序号,否则接收方无法辨认需要对一个冗余分组的ACK还是对一个新的分组的ACK,因此,接受窗口长度必须小于或者等于序号空间的一半。
最后,笔者在查阅滑动窗口协议的时候发现了根据发送窗口与接收窗口的长度来区分停等协议,GBN协议,SR协议的方法:滑动窗口协议 。