TCP 上篇:可靠数据传输原理
数据在信道传输的过程中,要考虑两种情况:
- 比特差错:分组能到达,但其中的比特可能受损;
- 丢包:分组可能被整个丢失,完全不能到达;
以下讨论中,我们暂且假设分组在发送方和接收方的信道中不会被重新排序,即发送方以 1,2 的顺序发包,接收方就不会在收到 2 之后才收到 1(如果先收到 2 说明 1 一定发生了丢包)。
针对比特差错的考虑
先考虑信道只出现比特差错的情况。为处理这种情况,引入 ARQ(Automatic Repeat reQuest)协议的概念,即发送方根据接收方的 ACK 和 NAK 反馈信息判断是否需要重传分组。ARQ 需要三种功能支持:
- 差错检测:通过差错检测和纠错技术判断分组的比特是否受损;
- 接收方反馈:接收方检查分组,未受损恢复 ACK,反之回复 NAK;
- 重传:接收方收到 NAK 或是受损的反馈分组都选择重传该分组;
由于任何追加信息的传输都要考虑受损,当 ACK 或 NAK 受损时,选择直接重传分组是明智的选择,但接收方因此需要判断自己收到的分组是否是上一个的重传。为此,给每个分组标上序号来辅助判断。在处理好当前分组再考虑下一个的“单周期”模式中,序号对 2 取模,只通过 0,1 判断是否为上一个包的重复即可。
事实上,我们可以让接收方在收到受损数据包时不回复 NAK,而是回复对上一个包的 ACK,这样也能告知发送方应当重传受损的数据包。于是,我们实现了一个无 NAK 的可靠数据传输协议。
针对丢包的考虑
如果信道还会发生丢包,那么上述协议就会出现某一方一直等待已丢失分组的情况。
如果让发送方来负责丢包的检测和处理,重传就是一种万能灵药。发送方可以在每一个数据包发送后设定一个定时器,在规定时间内没有收到 ACK 则直接选择重传。
接收方在收到重复的数据包之后,只需要照常回复 ACK 即可。
流水线可靠数据传输协议
在上面的讨论中,我们采用的思路是“处理完当前的包再发送下一个”。这种模式会导致 RTT 远大于将数据送入链路的时间,使得信道的利用率非常低下。因此,我们引入流水线的思想,即一次性写入多个分组,并为每个分组处理丢失、损失和延时过大等情况。
回退 N 步(GBN)
在这个协议中,我们给发送方引入一个长为 N 的环状区间,发送报文的序号就在这个区间之中。区间中有一分界线,左侧是已发送但未收到 ACK 的序号,右侧是未使用的序号。
当上层需要发送报文时,若分界线右侧仍有未使用序号,则用该序号发送报文;若发送方收到 ACK,则区间应向右滑动一次。
基于前面的底层信道有序假设,我们可以让接收方在收到预期之外的数据包时重复对上个正确接收包的 ACK 并丢失该数据包,所以发送方可以保证窗口滑动过程中包的 ACK 是按序收到的。
发送方仍然保持一个定时器,这个定时器会跟踪当前区间最左侧未收到 ACK 的已发送包;若该包已超时,考虑到接收方会把后续的包丢掉,所以发送方会将窗口分界线左侧的包全部重发,即“回退 N 步”。
选择重传(SR)
一个包的丢失或差错导致所有包的重复传输代价有些高,所以我们基于 GBN 的滑动窗口模型提出选择重传的协议。
在这个协议中,接收方也引入一个滑动窗口用于统计包的接收情况。此时接收方不会丢弃失序到达的数据包,而是在窗口中将其缓存,直到窗口左侧有连续的有序分组再将它们统一交付(并滑动窗口);现在发送方会为每个包设置一个定时器,并分别判断它们是否需要重传。
在这种情况下,ACK 不是有序到达的,接收方和发送方的窗口滑动也需要协调。假如接收方收到了窗口左侧的数据包,说明之前发送的 ACK 已经丢失,发送方选择了重传,这时候接收方必须重新发送 ACK 才能保证发送方的窗口能正常向右滑动。
另一个问题有关滑动窗口和序号空间的大小。考虑序号空间为 8,窗口大小为 5:当发送方和接收方窗口均处于 $2,3,4,5,6$ 时,发送方连续发送这五个序号的包,在极端情况下,接收方回复的 5 个 ACK 全部丢失,此时的窗口变为:
- 发送方:$2,3,4,5,6$
- 接收方:$7,0,1,2,3$
现在发送方把序号 2 重传时,接收方会认为这是一个新的包,从而造成误解。因此,SR 协议接收方的窗口大小必须小于等于序号空间的一半。