网络层服务(IP服务)是不可靠的,IP不保证数据报的交付,不保证数据报的按序交付,也不保证数据报中数据的完整性和正确性。运输层报文段是被IP数据报携带着在网络中传输的,因此运输层的报文段具有同样的问题。
我们试着用伪代码来实现可靠数据传输(reliable data transfer)。
rdt1.0
前提:下层信道是完全可靠的,且收发速率一致。
在这个简单协议中,接收端不需要提供任何反馈信息给发送方,因为下层信道是完全可靠的。也不需要请求发送方慢一点,因为收发速率是一致的。
发送端
for {
data = accept(); // 阻塞函数,等待上层数据
message = pack(data); // 封装上层数据
transfer(message); // 将数据报交付给下层信道
}
接收端
for {
data = accept(); // 阻塞函数,等待下层数据
message = unpack(data); // 拆分下层数据
deliver(message); // 将数据报交付给上层应用
}
rdt2.0
前提:分组可能会出现比特受损,但依旧保证分组有序到达。
我们需要引入控制报文,让发送方知道哪些内容被正确接收,哪些内容有误并因此需要重复。基于这样重传机制的可靠数据传输协议称为自动重传请求(Automatic Repeat reQuest, ARQ)协议。
ARQ协议有三种机制
- 差错检测:tcp/udp报文段有一个16bits字段称为检验和。
- 接收方反馈:肯定确认(ACK)/否定确认(NAK)。
- 重传:接收方收到出错分组(发送方收到NAK),重传该分组。
需要注意的是:当发送方处于等待ACK或NAK的状态时(recv),它不能从上层获取到更多的数据,由于这种行为,rdt2.0这样的协议被称为停等协议。
发送端
for {
data = accept(); // 阻塞函数,等待上层数据
message = pack(data); // 封装上层数据
transfer(message); // 将数据报交付给下层信道
for {
result = recv(); // 阻塞函数,等待收方反馈
if result {
break; // ACK
} else {
transfer(message); // NAK
}
}
}
接收端
for {
data = accept(); // 阻塞函数,等待下层数据
if check(data) {
message = unpack(data); // 拆分下层数据
deliver(message); // 将数据报交付给上层应用
reply = pack(ACK);
} else {
reply = pack(NAK);
}
transfer(reply); // 回复确认报文
}
rdt2.1
rdt2.0的一个问题是,如果确认报文受损,发送方无法知道接收方是否正确接收了刚刚发送的数据。考虑处理受损ACK/NAK的三种可能。
- 发方询问,收方回复。但这之间依旧可能分组受损。
- 增加足够的比特使发方可以修复受损的比特,这对不会丢失分组的信道来说是可行的。
- 重传该分组,但接收方不知道收到的是新分组还是重传分组。
可能3的解决办法是引入序号机制,对于停等协议来说,1bit的序号字段就足够了(接收到和上个分组一样序号的分组,就是重传分组,序号不一样的分组就是新分组)。
发送端
number = 1;
for {
data = accept(); // 阻塞函数,等待上层数据
number ^= 1;
message = pack(data, number); // 封装上层数据
transfer(message); // 将数据报交付给下层信道
for {
result = recv(); // 阻塞函数,等待收方反馈
if result {
break; // ACK
} else {
transfer(message); // NAK
}
}
}
接收端
number = 0;
for {
data = accept(); // 阻塞函数,等待下层数据
if check(data) {
message = unpack(data); // 拆分下层数据
if message.number != number {
deliver(message); // 将数据报交付给上层应用
number = message.number;
}
reply = pack(ACK);
} else {
reply = pack(NAK);
}
transfer(reply); // 回复确认报文
}
rdt2.2
rdt2.1引入序号机制后,可以利用ACK实现和NAK一样的效果,当发送方接收到的是上一个分组序号的ACK,那么说明接收方没有成功接收当前分组。
rdt2.2是在有比特差错信道上实现的一个无NAK的可靠数据传输协议。
发送端
number = 1;
for {
data = accept(); // 阻塞函数,等待上层数据
number ^= 1;
message = pack(data, number); // 封装上层数据
transfer(message); // 将数据报交付给下层信道
for {
result = recv(); // 阻塞函数,等待收方反馈
if result == number {
break; // ACK
} else {
transfer(message); // duplicate ACK
}
}
}
接收端
number = 0;
for {
data = accept(); // 阻塞函数,等待下层数据
reply = pack(ACK, number);
if check(data) {
message = unpack(data); // 拆分下层数据
if message.number != number { // 无损且新分组
deliver(message); // 将数据报交付给上层应用
reply = pack(ACK, message.number);
number = message.number;
}
}
transfer(reply); // 回复确认报文
}
rdt3.0
前提:分组可能会出现比特受损,且下层信道还会丢包。
无论是发送的分组或是响应的分组丢失,都会导致发送方收不到响应,发送方就会阻塞在result = recv();
这里。因此无论是数据分组丢失,还是ACK分组丢失,发送方在等待一段时间(一个往返时延+接收方处理一个分组的时间)后就该重传分组。
为了实现基于时间的重传机制,需要一个定时器,每次发送一个分组(新分组或重传分组)便启动一个定时器,定时器在给定的时间量到达后中断发送方。
number = 1;
for {
data = accept(); // 阻塞函数,等待上层数据
number ^= 1;
message = pack(data, number); // 封装上层数据
transfer(message); // 将数据报交付给下层信道
timer.start();
for {
result = recv(timeout = [](){
transfer(message); // 超时重传
timer.start();
}); // 阻塞函数,等待收方反馈
if result == number {
timer.stop();
break; // ACK
}
}
}
因为分组序号在0和1之间交替,因此rdt3.0有时被称为比特交替协议。
本文提到了多种保证可靠数据传输的机制:检验和、序号、定时器、肯定确认,每种机制都在协议的运行中起到了必不可少的作用,至此我们得到了一个可靠数据传输协议。