ARQ自动重传详解

ARQ简介

ARQ称为Automatic Repeat Request,叫做自动重传

ARQ是TCP实现可靠传输的重要协议。

自动重传的两个触发条件
1、定时器倒计时结束还未收到ack包。
2、连续收到三个相同的ack包。

ARQ分类
ARQ有以下三种实现,分别对于滑动窗口的三种状态:
1、停止-等待协议
发送窗口大小 = 1 ,接收窗口大小 = 1

发送方每发送一个数据包,就要等待接收方返回ack包。如果在定时器内没有收到ack包,就重新发送数据包。

在这里插入图片描述

在这里插入图片描述

缺点: 发送效率慢,串行发送

2、回退N步协议

发送窗口大小 n > 1,接收窗口 = 1

发送方最多可以连续发送n个数据包,不必等待接收方的ack包。

接收窗口必须按照顺序来接收数据包,如果接收到无序数据包的时候,会发送当前最小的有序数据包的序列号+1作为ack回应包。

接收窗口采用累计确认的方式来发送ack包。

进一步加快了发送效率

在这里插入图片描述

从上图就可以看出,当接收到重复的ack包的时候,发送方会重传将所有大于ack包的处于发送窗口中的数据包重传,这就是回退N步。

缺点:每次都要重新传送N个数据包,造成冗余。

3、选择重传协议

发送窗口大小 > 1,接收窗口大小 > 1

在这里插入图片描述
相较于回退n步协议,选择重选只会重传那些丢失和顺坏的数据包。

比如上面的图片中的接收窗口,200~299的数据包丢失了

ack是无法满足选择重传的要求的,接收窗口发送一个ack = 200的ack包,只能通知发送端 序列号小于200的数据包都已经被接收了。当过了超时时间的时候,发送窗口 会将大于200的所有数据包都重新发送。

ack无法满足选择重传的要求,所以引入了sack (Selective Acknowledgement)

SACK存储在TCP头部中的选项字段中,选项是一个可变长度。
在这里插入图片描述

下面是具体例子:
在这里插入图片描述

如上图所示,SACK会将接收窗口中的缓存信息发送给发送端,让发送端只重传丢失的数据。

选择重传的时候,接收窗口可以无序的接收数据包。

总结

方法窗口大小是否有序接收做法
停止-等待协议发送:接收=1:1有序发送窗口每次只能发送一个数据包,然后就停止等待ack包。接收窗口有序的接收数据包,接收成功后发送ack包给发送窗口,如果收到的数据包是无序的,就直接丢弃
后退N步协议发送:接收=N:1有序发送窗口每次最多一次性发送n个数据包,接收窗口有序的接收数据包,当接收到有序的数据包后,发送ack包给发送窗口,如果收到的数据包时无序的,就直接丢弃。当数据包丢失的时候,会将发送窗口中的后面的所有数据包都重新发送
选择重传协议发送:接收=N:N无序发送窗口每次最多一次性发送n个数据包,接收窗口无序的接收数据包,当接收到数据包后,发送ack包给发送窗口,ack中会携带SACK信息,也就是接收窗口中的缓存信息。发送端会根据SACK信息来只重传丢失的数据包
  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
n-ARQ(n次自动重传请求)是一种数据传输协议,可以在数据传输过程中检测丢失的数据包并进行重传,以确保数据的完整性和可靠性。下面是一个简单的C语言实现n-ARQ协议的示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define MAX_SEQ 1 #define MAX_PKT 1024 #define TIMEOUT 5 typedef enum {frame_arrival, timeout, crc_err} event_type; typedef struct { char data[MAX_PKT]; } packet; typedef struct { int seq; int ack; int crc; packet info; } frame; static int phl_ready = 0; int between(int a, int b, int c) { if(((a <= b) && (b < c)) || ((c < a) && (a <= b)) || ((b < c) && (c < a))) { return 1; } else { return 0; } } void send_data(int frame_nr, int frame_expected, packet buffer[]) { frame s; s.info = buffer[frame_nr % MAX_SEQ]; s.seq = frame_nr; s.ack = (frame_expected + MAX_SEQ - 1) % MAX_SEQ; s.crc = 0; for(int i = 0; i < sizeof(frame); i++) { s.crc += *((char*)&s + i); } printf("Sending frame %d\n", frame_nr); phl_ready = 0; send_frame(s); start_timer(frame_nr % MAX_SEQ); } void protocol5(void) { int ack_expected = 0; int next_frame_to_send = 0; int frame_expected = 0; int too_far = MAX_SEQ; packet buffer[MAX_SEQ]; frame r; int nbuffered = 0; int i; for(i = 0; i < MAX_SEQ; i++) { buffer[i].data[0] = '0' + i; } while(1) { wait_for_event(&event); switch(event.type) { case(frame_arrival): recv_frame(&r); if(r.seq == frame_expected) { printf("Receiving frame %d\n", frame_expected); if(r.crc != 0) { printf("CRC error\n"); send_data((ack_expected + MAX_SEQ - 1) % MAX_SEQ, frame_expected, buffer); } else { nbuffered--; frame_expected = (frame_expected + 1) % MAX_SEQ; ack_expected = (ack_expected + 1) % MAX_SEQ; stop_timer(frame_expected % MAX_SEQ); } } else { printf("Receiving frame %d\n", r.seq); } while(between(ack_expected, r.ack, next_frame_to_send)) { nbuffered--; stop_timer(ack_expected % MAX_SEQ); ack_expected = (ack_expected + 1) % MAX_SEQ; } break; case(timeout): printf("Timeout %d\n", frame_expected); send_data((ack_expected + MAX_SEQ - 1) % MAX_SEQ, frame_expected, buffer); break; case(crc_err): printf("CRC error\n"); send_data((ack_expected + MAX_SEQ - 1) % MAX_SEQ, frame_expected, buffer); break; default: break; } if(nbuffered < MAX_SEQ && phl_ready) { printf("Buffering packet %d\n", next_frame_to_send); send_data(next_frame_to_send, frame_expected, buffer); next_frame_to_send = (next_frame_to_send + 1) % MAX_SEQ; nbuffered++; } if(between(ack_expected, r.ack, next_frame_to_send)) { printf("Acknowledge %d\n", r.ack); } } } int main(int argc, char const *argv[]) { protocol5(); return 0; } ``` 在这个示例中,我们使用了一个简单的环形缓冲区来存储数据包。我们使用一个循环变量`next_frame_to_send`来追踪下一个要发送的数据包,使用`frame_expected`来追踪下一个期望接收的数据包,使用`ack_expected`来追踪下一个期望接收的确认帧。 在主程序中,我们调用了`protocol5`函数来实现n-ARQ协议。在该函数中,我们使用一个无限循环来等待事件的发生。当一个帧到达时,我们首先检查它的序号是否与期望的一致,如果一致,则说明这是我们期望的帧。我们首先检查CRC校验是否通过,如果通过,则将帧的数据包存入缓冲区,然后更新期望的帧号和确认帧号,并停止计时器。如果CRC校验失败,则重新发送上一个确认帧。 如果我们收到的不是期望的帧,则说明它是一个老的或未来的帧,我们只需简单地丢弃即可。 当一个计时器超时时,我们重新发送上一个确认帧。如果我们收到了一个CRC校验错误,则也重新发送上一个确认帧。 如果我们的缓冲区中有空闲空间,并且物理层准备好了发送下一个帧,则我们将下一个帧发送出去,并更新`next_frame_to_send`和`nbuffered`变量。 最后,如果我们收到了一个确认帧,则我们更新`ack_expected`变量并打印一条确认消息。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值