Reliable Data Transfer Protocol 的原理剖析

来源:互联网 发布:消防报警主机编程 编辑:程序博客网 时间:2024/04/28 02:36

引言

相信了解网络的人都知道,TCP 是一个 Reliable Data Transfer 的协议,那么在如此复杂的网络环境下,TCP 是如何实现这个功能的?在这篇文章中,我不会把 Reliable Data Transfer 局限在 TCP 上,我会介绍它的原理,以及实现这样的功能我们需要做哪些事情。为了更好地理解下面的内容,你需要下面的背景知识:

1、你需要看 Finite-state machine,从而了解它表示的含义

2、基本的网络知识,建议你读 Computer Networking: A Top-Down Approach 的第一章,如果是你第一次接触网络,它会对你帮助很大

3、了解1之后,理解下面这段话,你就可以完全理解下文中出现的 FSM

The event causing the transition is shown above the horizontal line labeling the transition, and the actions taken when the event occurs are shown below the horizontal line. When no action is taken on an event, or no event occurs and an action is taken, we’ll use the symbol Λ below or above the horizontal, respectively, to explicitly denote the lack of an action or event. The initial state of the FSM is indicated by the dashed arrow.

本文中的内容参考 Computer Networking: A Top-Down Approach,这是非常棒的关于计算机网络的书,相信读过之后,你一定会爱上它的。

Reliable Data Transfer over a Perfectly Reliable Channel

在现代这样复杂的网络环境下,我们发送的 packets 很可能丢失, corrupted(flipped from 0 to 1, or vice versa),或者被重排序,在我们这篇文章讨论的范围中,我们假设所有底层 channel 不会重排序 packets. 在这个小节中,我们假设底层 channel 是完全 reliable 的,我们可以用下面的 FSM 来描述 reliable data transfer 协议:

reliable data transfer

从上面的协议中可以看出,协议的 sending side 只需要接收应用层发来的数据,然后打包发给底层的这些协议; 而协议的 receiving side 只需要接收底层协议传来的 packet ,然后抽取出相应的数据并传递给上层的应用。这个协议只所以这么简单,是因为我们已经假设底层的 channel 不会丢失也不会 corrupt 传递的这些 packet.

Reliable Data Transfer over a Channel with Bit Errors

在这个小节中,我们会认为底层的 channel 有可能 corrupt 传递的 packet,因此我们需要一些额外的机制去消除这样的错误。在介绍如何解决这个问题之前,大家先想一想这个场景:你写一封情书给你所暗恋的人,假设她叫白洁,由于你很害羞,然后你通过一个中间人把这封信给白洁,那么现在问题来了,你如何确定这个中间人是否修改了你写的这封信?其实很简单,我们只需要下面3个能力就行:

1、Error detection:即白洁可以判断出这封信是否有问题,如何做到呢?假设你们2人都是天才程序员,你用一些手段把 信–>秘文1,并随着信一起发送过去,当白洁收到信时,她用同样的手段把 信–>秘文2,如果这个秘文1和秘文2不一致,证明这封信已经被修改过了

2、Receiver feedback:当白洁检查信是否有问题过后,她会写一个 positive (ACK:信没问题) or negative (NAK:信有问题) acknowledgment,然后找个中间人把这个信息传递回去

3、Retransmission:如果信有问题,你会把没有问题的信重新找个中间人发送给白洁

聪明的伙伴可能会发现在第2步中存在问题,如果中间人 corrupt acknowledgment ,那么 Retransmission 就不会发生,我们会在下面解决这个问题,现在我们假设先没有这个问题,因此 FSM 定义如下:

Reliable Data Transfer
Reliable Data Transfer

在上图所表达的协议中,有一点我需要强调的是:当 sender 在 wait-for-ACK-or-NAK 状态的时候,它不会接收上层应用发来的数据,即 rdt_send() 事件将不会触发,我们把这样的协议叫做 stop-and-wait protocols. 这样的协议会严重影响应用的性能,稍后我会介绍一个性能更高的协议,同时,它也会更复杂。

当 ACK 或 NAK 存在 corruption

上个小节中,我们提出了一个问题,就是中间人也有可能 corrupt ACK 或 NAK,那么我们应该如何解决这个问题呢?其实了有简单,我们只需要加 checksum bits 到 ACK/NAK packets 中,从而可以探测到这样的错误。这样 sender 就知道 ACK/NAK packets 是否存在问题,如果 packet 存在问题(它并需要知道是ACK还是NAK),sender 只需要 resend packet.

我上面描述的方法引入 duplicate packets 到 sender-to-receiver channel,这个方法有一个难点在于:由于 receiver 并不知道它发送的 ACK/NAK packets 是否被 sender 正确收到,那么当它再次收到 sender 发送过来的 packet 时,它就不会知道这个 packet 是 new data 还是 retransmission!

为了解决这个问题,我们需要在 data packet 中加一个新的 field,这样 sender 会为每个它发送的 data packet 编号,从而把这个 sequence number 放入这个 field 中。由于增加了这样一种手段,我们的 FSM 会变得更加复杂,下图是 FSM 的定义:

Reliable Data Transfer Protocol
Reliable Data Transfer Protocol

上图初看起来还是很复杂的,但是仔细看看,你会发现它多出的2个状态正是由于我们为每个发送的 packet 标号所致,我们可以看到,当 receiver 收到一个 packet 时,它会用 has_seq0/1 方法去判断当前接收的 packet 是不是它想要的 packet.

简化 rdt2.1 中的 receiver

在 rdt2.1 中的 receiver,我们仔细看一看它的 Wait for 1 from below 状态,如果它接收的 packet 被 corrupt 了(它会发NAK),或者它目前想要接收1,而你给我发0(它会发ACK),它都会继续回到这个状态。这2个 event 有一个共同点就是:你给我发的 packet 错了!因此我们可以把这2个 event 简化成1个 event,如下图:

Reliable Data Transfer Protocol

从上图可以看到,我们去掉了 NAK,而为 ACK 也加了标识,现在仔细看一下上图中的 Wait for 1 from below 状态,当 sender 给我发送的 packet 被 corrupt 或者 给我的 packet 序号是0,我给它回个 ACK 0,证明对方错了,我需要的是 packet 1. 同时,sender 也要有能力识别这样的 ACK 序号,从而做出正确的选择,sender 的 FSM 如下,它与 rdt2.1 相比并没有改变什么,主要变化的就是我下面画红线的部分,它现在有能力识别 ACK 的序号了:

Reliable Data Transfer Protocol

Reliable Data Transfer over a Lossy Channel with Bit Errors

当底层 channel 不仅可以 corrupt packet,而且还有可能丢失 packet,我们应该如何处理这种情况呢?首先,我们考虑一下 packet 丢失的情况:

1、sender 发送的 packet 丢失

2、receiver 发送的 ACK 丢失

无论哪种情况发生,sender 都不会收到任何响应。因此,想要处理目前遇到的问题,我们应该解决下面2个问题。

1、如何去探测 packet 丢失?

2、当 packet 丢失以后我们需要做什么?

在想具体的解决办法之前,大家先想一想,是用 sender 还是 receiver 来进行探测 packet 丢失呢?毋庸置疑,肯定是 sender,这是因为 receiver 并不知道什么时候 sender 给我发 packet. 那么现在问题是 sender 如何来知道它发送的 packet 是否丢失呢?下面我给出一个方法:

sender 可以选择一个合理的时间段,如果在这个时间段内,它没有收到这个 packet 的 ACK,那么 sender 会 retransmitted. 这个方案有个问题是:如果这个时间段内没有收到 ACK,并不代表 packet 一定丢失了,也有可能是当前网络有比较大的延时,而 packet 正在传输的过程之中,因此我们的这种方案有可能会出现 duplicate data packets 在 sender-to-receiver channel 中,但是幸运的是,这个问题我们在 当 ACK 或 NAK 存在 corruption 小节中已经解决了,就是用 sequence numbers 去处理 duplicate packets 的情况。

对于 sender 来说,retransmission 就是灵丹妙药,它不需要具体知道没收到 ACK 的原因是因为它发送的 data packet 丢失,ACK丢失,还是这些包由于 delay 导致还在传输的过程中,它唯一需要做的就是:老子给你个具体的时间,在这个时间内你还没给老子回话,老子就认为出事了,然后 retransmit.

实现一个 time-based retransmission 机制 sender 需要一个 countdown timer,从而当到达一个给定时间的时候,它可以 interrupt sender. 因此 sender 需要以下3个功能:

1、Start the timer each time a packet (either a first-time packet or a retransmission) is sent
2、Respond to a timer interrupt (taking appropriate actions)
3、Stop the timer

加了这个新的功能以后,sender 的 FSM 定义如下:

Reliable Data Transfer

比较一下 rdt3.0 和 rdt 2.2 的 sender,它们除了有个 timer 的不同之外,我们看到还有个不同的地方就是,当 sender 在等 ACK 的时候,当它收到一个被 corrupted 的或者不是它想要的 ACK (我们暂且把这个事件叫做A)的时候,rdt3.0 没有采取任何 actions,而 rdt2.2 采取的 action 是 udt_send(sndpkt),大家可以想一想这是为什么呢?其实如果你加上这个 action 也可以使协议正常运行。但是,加上这个 action 以后有可能会导致 packets 会无限制地增加。比如,当前 channel 不存在丢包的情况,只可能出现 bit error 或 超时的事件发生。你当前在 wait for ACK 1 这个状态,现在你收到了一个存在错误的 packet,然后你采取 udt_send(sndpkt) 事件,如果这个刚发出去的 packet 过一会在 sender 又收到了一个错误的 ACK,你又需要重新发,假设你收到了一个正确的 ACK,你切换到了 wait for ACK 0 的状态,由于你先前 retransmit 很多个 packet,你很有可能又收到了错误的 ACK 1,因此你又 retransmit packet 0,这样不断地循环下去,packet 将没有限制地增加下去。

由于我们没有对 receiver 增加任何额外的功能,所以 rdt2.2 中的 receiver 依然可以与 rdt3.0 中的 sender 共同使用。

提高 rdt3.0 中协议的性能

由于 rdt3.0 中采用的是 stop-and-wait 协议,所以它的性能会非常差,在这个小节中,我将会介绍 pipelined 协议,它可以大大提升性能,同样我会解释 pipelined 协议中遇到的一些问题,在讲解之前,大家看看下面的图片,先感受一下2个协议之间的差别:

Stop-and-wait versus pipelined protocol

pipelined 协议允许 sender 发送多个 packet 而不用等待 acknowledgments,由于 in-transit sender-to-receiver packets 可以被可视化成 filling a pipeline,因此这个技术叫做 pipelining. 实现这个技术有2种方法,它们分别是 Go-Back-Nselective repeat,下面来分别介绍这2种方法的原理。

Go-Back-N

在 Go-Back-N 协议中,sender 可以传送多个 packet 而不需要等待 acknowledgment,但是它被限制在 pipeline 中不能超过指定数量的 unacknowledged packets. 在我们具体定义 Go-Back-N 协议的 FSM 之前,我们首先来理解一下 sender 是如何标记它已经发送的 packet.

Sender’s view of sequence numbers in Go-Back-N

上图中有2个变量和1个固定的 Window size N,下面我来分别解释一下它们所表示的含义:

  • N:pipeline 中最多的 unacknowledged packets 数量
  • base:sequence number of the oldest unacknowledged packet
  • nextseqnum:smallest unused sequence number

通过这几个变量,我们可以把这一系列 sequence numbers 分成几个区间:

  • [0,base-1]:packets that have already been transmitted and acknowledged
  • [base,nextseqnum - 1]:packets that have been sent but not yet acknowledged
  • [nextseqnum,base + N - 1]:be used for packets that can be sent immediately
  • [base+N]:cannot be used until an unacknowledged packet currently in the pipeline (specifically, the packet with sequence number base) has been acknowledged

Go-Back-N 协议

下面我来定义一下 Go-Back-N 协议的 Extended finite-state machine:

Extended FSM description of GBN sender

Extended FSM description of GBN receiver

相信看到这的伙伴们已经很熟悉 FSM 了,上图都是自解释的,也就说你通过看图就完全可以明白 sender 和 receiver 之间是如何交互的,但是这里面有几个重点我需要强调一下:

(1)sender 只有1个 timer,它只为 oldest transmitted but not yet acknowledged packet 计时
(2)假设 receiver 期望收到的 packet sequence number 为 n,如果它收到的 packet 序列号为 n,packet 没有被 corrupted,并且这个 packet 是 in order(即它上次正确收到并且传递给上层应用的 packet sequence number 为 n - 1) 的,那么它会抽取数据传给上层应用,然后封装 ACK n,发回到 sender; 否则所有其它的情况下,receiver 丢弃收到的这个 packet,并 resend 最近一个正确收到的 packet 的 ACK,在这个例子中是ACK n-1
(3)由于 receiver 一次只 deliver 一个 packet 到上层应用,所以如果 packet k 被正确收到并且已经传递给上层应用,这就表示所有 sequence number 小于 k 的 packet 也已经被 delivered. 这同时也表明了,如果 sender 收到了 ACK k,即使小于 k 的 ACK 没有收到,它也可以认为这些 sequence number 小于 k 的 packet 也已经被 receiver 正确地收到

为了让大家可以更好地理解 Go-Back-N 协议中 sender 与 receiver 之间的交互,我建议大家自己去玩一下它:Go-Back-N Protocol,为了突出一下重点,大家按照我下面给的这些分类进行演示,演示的过程中和我们上面讲解的知识对比一下: 1、没有 packet 丢失 2、第1个发送的 packet 丢失 3、最后1个发送的 packet 丢失 4、第1个返回的 ACK 丢失 5、最后1个返回的 ACK 丢失

Selective Repeat

GBN 协议的出现是用来解决 stop-and-wait 协议的性能问题,但是 GBN 本身也存在性能问题,尤其当 window size 和 bandwidth-delay product 都很大的时候,假设你的 window size 是100,第1个发送的 packet 由于网络原因丢失了,即使剩下的99个 packet 正确收到,也会完全被 receiver 丢弃,从而使得1个 packet 出现错误的情况下,GBN 会 retransmit 大量不需要再次传送的 packet. 在这个小节中,我将会介绍 Selective Repeat 协议的原理,剖析它是如何解决 GBN 协议中存在的这个问题,正如它的名字一样,selective-repeat 协议只会 retransmit 一些 receiver 没有正确收到的 packet.

Selective Repeat 协议中的 receiver 会 acknowledge 正确收到的 packet,不管它是否 in order,Out-of-order packets 将被 buffered. 假设现在 sender 一下发送5个 packet,它们的 sequence numbers 分别是0,1,2,3,4,不幸的是 packet 0 在传送的过程中 loss 了,其它4个 packet 被正确收到了,与 GBN 不同的是,SR 协议会在 receiver 端 buffer 这4个正确收到的 packet 并发送相应的 ACK 给 sender,然后等待 packet 0,一旦 packet 0 正确收到以后,它会把这5个 packet 一起发给上层应用。一图胜千言,强烈建议大家自己玩一下这个协议,看看它在丢包时是如何处理的,Selective Repeat Protocol

上面的过程有一点需要强调的是,不像 GBN 协议,如果 sender 正确收到了 packet 4 的 ACK,但是这不能代表 packet 0/1/2/3 也被正确收到了,因此 sender 必须要正确收到所有这些 packet 的 ACK,它的 window size 才会继续向前,确切地说,只有 send_base 对应的 packet 的 ACK 正确收到,它才会向前滑动。因此对于下图中的 receiver 来说,即使它正确收到了 send_base 对应的 packet,并向前滑动了 window size,但是如果 sender 没有正确收到 send_base 对应的 packet 的 ACK,那么它就会停止不前,因此receiver 应该 reacknowledges 序列号小于 rcv_base 的 packet.

Selective-repeat (SR) sender and receiver views of sequence-number space

现在我们还剩下一个重要的问题需要讨论,也就是 sequence number 和 windows size 之间的选择问题,如果 sequence number 有限,windows size 可以随意选择吗?现在考虑下面2个场景,如下面 2 幅图:

SR receiver dilemma with too-large windows: A new packet or a retransmission

上图中 receiver 已经正确收到了 sender 发送的序列号为0,1,2的 packet,但是它发给 sender 的 ACK 0,1,2全部丢失了,这时 receiver 的 window size 向前滑动,重点是由于我们的 sequence number 很有限,它的这次滑动竟然覆盖了 sequence number 0 和 1,这会导致出现什么样的问题呢?由于 sender 这面没有正确地收到相应的 ACK,所以超时后它会 retransmit 序列号为 0,1,2的 packet,sender 发送的 packet 0 和 1 是上次它认为对方没有正确收到的 packet,而 receiver 期望收到的 packet 0 和 1 应该是新的数据,而不是它上次已经收到过的。

SR receiver dilemma with too-large windows: A new packet or a retransmission

与第1幅图不同的是,上图中发送的 packet 0 是包含的数据是新的,这个 packet 正是 receiver 期望收到的。从上面我描述的2个场景来看,并没有办法来区分是 first packet 的 retransmission 还是 fifth packet 的 original transmission. 由此可看,window size 的大小只比 sequence number 空间小1是不够的,那么 window size 到底多大才好呢? 对于 SR 协议来说, window size 必须要 sequence number space 的一半。

总结

整篇文章中,我们从一个完美的底层 channel 开始,逐渐引入现实 channel 中会遇到的各种问题,同时我也提出相应的技术去解决引入的问题。这篇文章并没有谈具体的 TCP 协议是如何实现 Reliable Data Transfer Protocol 的,但是我可以负责任的告诉大家,TCP 几乎用了我上面提到的所有技术来实现它的 Reliable Data Transfer.

大家还应该记住非常重要的一点就是:Reliable Data Transfer 不仅仅可以实现在传输层,它完全可以实现在应用层,网络层,和链路层,这是因为我们已经了解了 Reliable Data Transfer Protocol 所需要的各种技术,我们完全可以在这些层面上去实现 acknowledgments, timers, retransmissions, and sequence numbers.

原创粉丝点击