自顶向下 | 可靠数据传输

884 阅读5分钟

网络层服务(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的三种可能。

  1. 发方询问,收方回复。但这之间依旧可能分组受损。
  2. 增加足够的比特使发方可以修复受损的比特,这对不会丢失分组的信道来说是可行的。
  3. 重传该分组,但接收方不知道收到的是新分组还是重传分组。

可能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有时被称为比特交替协议。

本文提到了多种保证可靠数据传输的机制:检验和、序号、定时器、肯定确认,每种机制都在协议的运行中起到了必不可少的作用,至此我们得到了一个可靠数据传输协议。