计算机网络LAB_1——数据链路层协议实现

本次实验的整理,包括LAB文档内容梳理和实验报告。

1 LAB文档内容梳理

1.1 样例程序分析

首先定义了帧的种类,在数据链路层传递的所有帧,分为数据帧、ACK(确认)帧、NAK(否定确认)帧

1
2
3
4
/* FRAME kind */
#define FRAME_DATA 1
#define FRAME_ACK 2
#define FRAME_NAK 3

之后给出了三种帧的具体结构,小括号内是该段信息的大小,单位为字节:

1
2
3
4
5
6
/*  
DATA Frame
+=========+========+========+===============+========+
| KIND(1) | SEQ(1) | ACK(1) | DATA(240~256) | CRC(4) |
+=========+========+========+===============+========+
*/
1
2
3
4
5
6
/*
ACK Frame
+=========+========+========+
| KIND(1) | ACK(1) | CRC(4) |
+=========+========+========+
*/
1
2
3
4
5
6
/*
NAK Frame
+=========+========+========+
| KIND(1) | ACK(1) | CRC(4) |
+=========+========+========+
*/

KIND: 1字节,表示这个帧的种类(可取1,2,3)

SEQ: 1字节,数据帧序号,如果类型为unsigned char,可表示的范围是 0 ~ 255

ACK: ACK帧或NAK帧的序号

DATA:数据帧中的数据段

CRC: 冗余码部分

1.2 事件

1.2.1 五种事件类型

1
2
3
4
5
#define NETWORK_LAYER_READY 0
#define PHYSICAL_LAYER_READY 1
#define FRAME_RECEIVED 2
#define DATA_TIMEOUT 3
#define ACK_TIMEOUT 4

NETWORK_LAYER_READY:
当网络层有新的分组需要发送并且未被链路层disable,会产生NETWORK_LAYER_READY事件;否则网络层自行缓冲待发送分组。此事件发生后才可以调用get_packet()得到网络层待发送的下一个分组。

PHYSICAL_LAYER_READY:
物理层发送队列的长度低于50字节。(3.3)

FRAME_RECEIVED:
物理层收到了一整帧 。

DATA_TIMEOUT:
超时发生时,产生 DATA_TIMEOUT事件, 并给出超时的定时器编号。发生超时的定时器的编号在参数 arg中返回。

ACK_TIMEOUT:
所设置的搭载ACK定时器超时。

1.2.2 程序流程示意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
enable_network_layer();
for (;;) {
event = wait_for_event(&arg);
switch (event) {
case EVENT_NETWORK_LAYER_READY:
len = get_packet(my_buf);
… …
break;
case EVENT_PHYSICAL_LAYER_READY:
… …
break;
case EVENT_FRAME_RECEIVED:
rbuf_len = recv_frame(rbuf, sizeof rbuf);
... ...
break;
case EVENT_ACK_TIMEOUT:
... ...
break;
case EVENT_DATA_TIMEOUT:
... ...
break;
}
if (...)
enable_network_layer();
else
disable_network_layer();
}

1.3 接口函数及库函数

1.3.1 初始化协议

1
void protocol_init(int argc, char **argv);

必须首先调用此函数对运行环境初始化。该函数的两个参数必
须传递 main()函数的两个同名参数。这样做的目的是从命令行参数中获取站点名及某些选项以提供一种配置系统参数的手段。这些 选项包括 重新 指定日志文件,指定 TCP端口号, 设定误码率,等等。当命令行中重新指定了新的参数值,默认值就不再起作用。

protocol_init()建立两个站点之间的 TCP连接,并且设定时间坐标的 参考 0点,通信的两个站点的时间坐标 0点在建立 TCP连接时被设置成相同的参考时间点 。

输出样例:

image-20210505142308591.png

1.3.2 网络层接口

1.3.2.1 网络层包长度

1
#define PKT_LEN 256

1.3.2.2 流量控制函数

1
void enable_network_layer(void);void disable_network_layer(void);

对于发送方向来说,网络层和数据链路层的约定为:数据链路层在缓冲区满等条件下无法发送分组
时通过 disable_network_layer()通知网络层;在能够承接新的发送任务时执行
enable_network_layer()允许网络层发送 数据 分组

1.3.2.3 包处理函数

1
int get_packet(unsigned char *packet);

参数:将分组拷贝到指针 p指定的缓冲区中。

返回值:分组长度

1
void put_packet(unsigned char *packet, int len);

参数:存放收到分组的缓冲区首地址,分组长度

统计功能:

如果本次调用 put_packet()函数 比上次调用该函数的时间间隔超过 2秒,将给出一个接收方向的报告,格式如下所示:

480.484 …. 1784 packets received, 7611 bps, 95.14%, Err 38 (9.9e 006)

时间坐标为480.484秒,收到了 1784个分组,网络层有效数据传输率 7611bps,实际线路利用率95.14%,接收方向共检出 38个帧校验和错误,统计计算出实际误码率 9.9x10 6。

1.3.3 物理层接口

1.3.3.1 帧处理函数

1
void send_frame(unsigned char *frame, int len);

参数:将内存 frame处长度为 len的缓冲区块向物理层发送为一帧,每字节发送需要 1ms,帧与帧之间的边界保留 1ms。

1
int recv_frame(unsigned char *buf, int size);

参数:从物理层接收一帧, size为用于存放接收帧的缓冲区 buf的空间大小

返回值:收到帧的实际长度 。

1.3.3.2 查看队列长度

1
int phl_sq_len(void);

这个函数返回当前物理层队列的长度 。

1.3.3.3 流量控制机制

为协调数据链路层和物理层之间的流量,采用的机制是:只要在事件处理周期内至少一次调用过 send_frame()函数,那么,事件等待函数 wait_for_event()会在物理层发送队列低于50字节水平时,产生 PHYSICAL_LAYER_READY事件。

例如:物理层当前队列长度 20字节,链路层调用send_frame()发送一 7字节 帧,那么,随后的事件等待函数 wait_for_event()会立即产生PHYSICAL_LAYER_READY事件;再如:物理层当前队列长度 40字节,链路层调用 send_frame()发送一300字节字节帧帧,受限于信道受限于信道8000bps的带宽,需要发送的数据不可能瞬间发送到线路上,的带宽,需要发送的数据不可能瞬间发送到线路上,wait_for_event()会在物理层队列由会在物理层队列由340字节降为字节降为50字节以下(至少需要字节以下(至少需要290毫秒)毫秒)后才产生后才产生PHYSICAL_LAYER_READY事件。

在PHYSICAL_LAYER_READY事件后,如果 数据链路层 暂时没有需要发送的数据,因系统不会再次送来 PHYSICAL_LAYER_READY事件,应记录物理层状态,当有数据需要发送时直接发送。 物理层事件的这种处理方式类似于 硬件中的发送中断。

不顾物理层是否出于准备好状态而 调用 send_frame()发送多帧,受限于信道的 8000bps能力,会导致数据堆积在物理层发送队列的时间较长,等待物理层慢慢把数据发送出去。

物理层发送队列最多可以保留 64K字节。

1.3.4 CRC校验的产生和验证

本次实验采用的CRC校验方案为 CRC 32,与 IEEE 802.3以太网校验和生成多项式相同。生成多项式为:
x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x1+1

1
unsigned int crc32(unsigned char *buf, int len);

函数crc32()返回一 个 32比特整数。

设指针 p的定义为 char * p并且 p指向一个缓冲区,缓冲区内有 243字节数据,为这 243字节数据生成 CRC 32校验和,并且把这 32比特校验和附在 243字节之后,执行下面的语句:(unsigned int *)(p + 243) = crc32(p, 243);
注意:
p所指缓冲区必须至少有 247字节有效空间,以防内存访问越界。

验证校验和的方法,对上面的例子,只需要判断crc32(p, 243 + 4)是否为 0:
校验和正确为 0,否则不为 0。

1.3.5 定时器管理

1.3.5.1 get_ms

1
unsigned int get_ms(void);

get_ms()函数获取当前的时间坐标,单位为毫秒。

1.3.5.2 start_timer

1
void start_timer(unsigned int nr, unsigned int ms);

start_timer()用于启动一个定时器。

参数:计时器的编号,超时时间值。计时器的编号只允许在0~63之间,超时时间间隔的单位为毫秒。

超时发生时,产生 DATA_TIMEOUT事件, 并给出超时的定时器编号。发生超时的定时器的编号在参数 arg中返回。

例如: start_timer(3, 1200)启动 3号定时器,定时器的长度为 1.2秒 。

系统在把截止到调用函数start_timer()时刻为止已经放入物理层发送队列的数据发送完毕后才开始启动计时(不是从当前时间开始计时)。

例如:当前时间坐标为 5.100,物理层发送队列目前有 300字节,信道速率 8000bps,函数调用 start_timer(3, 1200)会导致 1.5秒之后 时间坐标 6.600处 3号定时器产生DATA_TIMEOUT事件 。

在定时器未超时之前直接对同一个编号的定时器执行start_timer()调用, 将按照新的时间设置产生超时事件。

1.3.5.3 stop_timer

1
void stop_timer(unsigned int nr);

stop_timer()中止一个定时器。

参数:要终止的定时器的编号

1.3.5.4 start_ack_timer

1
void start_ack_timer(unsigned int ms);

与start_timer(3.5.2)的不同之处:

  1. 少了一个参数:定时器编号。意味着只有一个ACK定时器
  2. ACK计时器启动时刻为当前时刻;
  3. 在ACK定时器未超时之前重新执行 start_ack_timer()调用,定时器将依然按照先前的时间设置产生超时事件 ACK_TIMEOUT。

1.3.5.5 stop_ack_timer

1
void stop_ack_timer(void);

终止ACK计时器。

1.3.6 跟踪、调试和输出

1.3.6.1 对printf的改良

lprintf()函数

1.3.6.2 debug输出

  1. debug_mask变量

    一个静态整型变量,用于调试信息的输出控制,默认值为0。

  2. dbg_frame, dbg_event, dbg_warning

    用 debug_mask的比特 0控制“事件”信息,比特 1控制“帧收发”信息:

    当 debug_mask的 0号比特为 0时,dbg_event()的所有输出被忽略;

    当 debug_mask的 1号比特为 0时,dbg_frame()的所有输出被忽略。

1.3.6.3 获取站点名

1
char *station_name(void);

此函数获取当前进程所对应的站点名,返回值为字符串 ”A”或者 ”B”。

1.4 命令行选项

image-20210505141428458.png

1.5 错误信息

image-20210505141514462.png

1.6 问题汇总

  • between函数,为什么那么设计

  • ack计时器,为什么只要一个,为什么send_frame之后立刻就stop_ack,start_ack又为什么是在收到帧的时候的操作,而不是发送帧的操作

    • ack的计时器,其实是等待捎带ack的计时器。一切都迎刃而解。
  • 关于滑动窗口的大小的计算,已经计算出滑动窗口大小<4,为什么还能设为7,设为63?7和63又是如何得到的?

2 实验报告

2.1 实验内容及实验环境描述

2.1.1 实验内容

​ 利用所学数据链路层原理,自己设计一个滑动窗口协议,在仿真环境下编程实现有噪音信道环境下两站点之间无差错双工通信。信道模型为8000bps 全双工卫星信道,信道传播时延270毫秒,信道误码率为10^-5,信道提供字节流传输服务,网络层分组长度固定为256字节。

​ 本次实验选用的滑动窗口协议为带NAK和不带NAK的回退N协议,以及选择重传协议。

2.1.2 实验环境

​ Windows10,Microsoft Visual Studio 2019,PowerShell

2.2 软件设计

2.2.1 数据结构

2.2.1.1 FRAME结构体

1
2
3
4
5
6
7
struct FRAME {
unsigned char kind; /*FRAME_DATA, FRAME_ACK, FRAME_NAK*/
unsigned char seq; /*数据帧序号*/
unsigned char ack; /*ACK帧或NAK帧的序号*/
unsigned char data[PKT_LEN]; /*数据帧里的数据部分*/
unsigned int padding; /*CRC冗余码部分*/
};

三种帧类型的定义如下:

1
2
3
4
/* FRAME kind */
#define FRAME_DATA 1
#define FRAME_ACK 2
#define FRAME_NAK 3

三种帧的具体结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
DATA Frame
+=========+========+========+===============+========+
| KIND(1) | SEQ(1) | ACK(1) | DATA(240~256) | CRC(4) |
+=========+========+========+===============+========+

ACK Frame
+=========+========+========+
| KIND(1) | ACK(1) | CRC(4) |
+=========+========+========+

NAK Frame
+=========+========+========+
| KIND(1) | ACK(1) | CRC(4) |
+=========+========+========+
*/

2.2.1.2 GBN协议宏定义和全局变量

  • 宏定义
1
2
3
#define MAX_SEQ 7         //帧序号最大值
#define DATA_TIMER 1500 //数据帧超时时间
#define ACK_TIMER 300 //等待捎带ACK的时间
  • 全局变量
1
2
3
4
5
6
7
static unsigned char frame_expected = 0;           //接受窗口期待接收的序号
static unsigned char buffer[MAX_SEQ + 1][PKT_LEN]; //发送窗口缓存区。
static unsigned char ack_expected = 0; //发送窗口下边界
static unsigned char next_frame = 0; //发送窗口上边界
static unsigned char nbuffered; //发送窗口缓冲区已使用的数量,已发送的帧数量
static int phl_ready = 0; //物理层就绪标志。(为 1 就绪)
int no_nak = 1; //NAK有效标志(为 1 有效)

其中:

① 在GBN协议中,接收窗口大小规定为 1,发送窗口大小 = 帧序号数量 - 1 = 帧序号最大值。

② 发送窗口缓冲区的作用是:将每个已发送未确认的帧缓存下来,若出现丢包或坏帧则可以重发。因此,缓冲区大小和发送窗口大小相同。

2.2.1.3 SR协议宏定义和全局变量

  • 宏定义
1
2
3
4
#define MAX_SEQ 63                  //帧序号最大值
#define NR_BUF ((MAX_SEQ + 1) / 2) //接收\发送窗口缓冲区大小
#define DATA_TIMER 3000 //数据帧超时时间
#define ACK_TIMER 1000 //ACK帧超时时间
  • 全局变量
1
2
3
4
5
6
7
8
9
10
static unsigned char out_buffer[NR_BUF][PKT_LEN];   //发送窗口缓冲区
static unsigned char in_buffer[NR_BUF][PKT_LEN]; //接收窗口缓冲区
static unsigned char ack_expected = 0; //发送窗口下边界
static unsigned char next_frame = 0; //发送窗口上边界
static unsigned char frame_expected = 0; //接收窗口下边界
static unsigned char too_far = NR_BUF; //接收窗口上边界
static unsigned char nbuffered; //发送窗口缓冲区已使用的数量,已发送的帧数量
static int phl_ready = 0; //物理层就绪标志。(为 1 就绪)
int no_nak = 1; //NAK有效标志(为 1 有效)
int arrv[NR_BUF]; //接收窗口缓冲区标志(为 1 表示已占用)

其中:接收\发送窗口缓冲区大小 = 发送窗口大小 = 接收窗口大小

2.2.2 模块结构

2.2.2.1 子程序说明

两个协议的子程序原理、作用及参数等大致相同,不再分开阐述

  • static int between(unsigned char a, unsigned char b, unsigned char c)

    • 函数作用:判断帧序号是否落在接收\发送窗口内,以此决定接下来的操作(缓存、发送ACK或者丢弃等)
    • 参数:
      • unsigned char a, unsigned char c:窗口的边界
      • unsigned char b:帧序号或ACK序号
    • 返回值:int 类型,若在窗口内,返回true(1),否则返回false(0)
  • char inc(char c)

    • 函数作用:用于序号的循环有界递增。例如,帧序号最大值为3时,序号只能取 0,1,2,3,0,1,2…
    • 参数:char c:需要递增的序号值
    • 返回值:char类型,递增之后的结果
  • static void put_frame(unsigned char* frame, int len)

    • 函数作用:为一帧添加CRC校验和,并将这一帧发送给物理层
    • 参数:
      • unsigned char* frame:指向这一帧的指针
      • int len:这一帧的长度,单位为字节
    • 无返回值
  • static void send_data_frame(void)

    • 函数作用:发送数据帧

    • 无参数

      这里与教材中是不同的,由于本协议实现中使用了大量全局变量,在传参方面省略了很多功夫

    • 无返回值

  • static void send_ack_frame(void)

    • 函数作用:发送单独ACK帧
  • static void send_nak_frame(void)

    • 函数作用:发送单独的NAK帧

2.2.2.2 程序调用关系图

image-20210505182426979.png

2.2.2.3 响应分析

  • GBN协议

    image-20210505171554501.png

  • SR协议

    image-20210505171638295.png

2.2.2.4 算法流程图

image-20210505182001929.png

2.3 实验结果分析

1.描述你所实现的协议软件是否实现了有误码信道环境中无差错传输功能。

实现了有误码信道环境中的无差错传输功能,采用了CRC校验和重传技术使错误得以发现和纠正

2.程序的健壮性如何,能否可靠地长时间运行。

程序健壮性强,在高负荷和高误码率等条件下均未出现过死锁现象,能够正常运行。线下测试时运行30min左右没有问题。

3.协议参数的选取:滑动窗口的大小,重传定时器的时限,ACK 搭载定时器的时限,这些参数是怎样确定的?根据信道特性数据,分组层分组的大小,以及你的滑动窗口机制,给出定量分析,详细列举出选择这些参数值的具体原因。

根据实验模型:8000bps全双工卫星信道,分组长度固定为 256 字节,单向传输时延为270ms,信道误码率(默认)10^-5,帧间间隔1ms。我对两个协议的窗口大小、重传定时器时限和ACK定时器时限进行了理论计算和实际测试。

滑动窗口的大小:

滑动窗口大小直接涉及到信道利用率和数据拥塞问题,若窗口太小,将导致信道利用率过低,信道中长时间没有数据传送;若窗口太大,数据发送过快,将造成接收方被数据淹没,发生拥塞现象导致数据丢失,出错率增加。

  • 首先计算得到滑动窗口的最小值,设滑动窗口的大小 W,信道传输时延 t,数据率 c,帧长度 l 。则应有关系式如下:

根据自定义的帧的数据结构可知,一个数据帧最长包括3字节的帧头(kind,seq,ack)、256字节的数据和4字节的 CRC 冗余码,共263字节。即此处l = 263 * 8 bit。代入其他数据计算可得,W >= 4.05。

  • 而滑动窗口的最大值与帧序列有关,设发送窗口大小 Ws,接收窗口大小为Wr,帧序列的二进制位数为 n。则有关系式如下:

在帧结构中,帧序列seq为unsigned char类型,大小为1字节,即n = 8。
在GBN协议中,Wr = 1,所以其Ws <= 255 。
在SR协议中,Wr = Ws时效率最高,从而,Wr = Ws <= 128

  • 通过实际测试的结果分析得到合适的 W 值,最终发现,在 GBN 协议中,W 取 7 效率最高;在 SR 协议中,W 取 63 效率最高

重传定时器时限和ACK定时器时限

重传计时器的时限涉及到重传的响应时间,若太大,将导致重传等待的时间过久;若太小,将导致较为频繁的重传,两种情况均将导致信道利用率下降。

  • 估算重传定时器时限的下界 t ,考虑如下几个时间:

    • 每一帧的帧长为263B,由信道传输速率为 8000bps 可得数据发送延迟时间 Td 为 263ms 。
    • 传播时延 Ts 固定为 270ms 。
    • 接收方接收完数据帧后,既可能以捎带 ACK 的方式发送 ACK ,也可能由于 ACK定时器超时而发送单独的 ACK 帧。这里显然应取:对方从接收完数据帧,到开始发送 ACK 的时间间隔的上界进行计算,不难看出,这个上界就是ACK定时器的时长 ACK_TIMER
    • ACK 帧在物理层队列中排队的等待时间,同样以最坏情况进行计算,即在 ACK 帧之前存在等待发送的普通数据帧(已发送0字节)、重传数据帧和 NAK 帧,总计 263 + 263 + 6 = 532 字节,需要花费 532ms 才能发送完成。
    • 发送这个ACK 帧的时间为6ms,它在信道上的传输时延为270ms 。
    • 物理层在发送数据帧时会在帧与帧之间添加 1ms 的时间间隔,最坏情况下增加 4ms 的时间。

      综上有:

  • 由此可得,重传定时器时限 t 与ACK定时器时限 ACK_TIMER 有关。我们知道,ACK定时器时限的一个最低的下界为数据链路层从网络层获得一个数据包的时间,经过多次的测试和分析,我们发现这个下界大约为200ms。而在实际测试中,当ACK定时器时限高于这个下界时可取得较高的效率。

  • 最终,我们通过多次实验观察的方式,确定了 t 的最优值 :

    • 在 GBN 协议中令ACK定时器时限为 240 ms,在SR协议中,令ACK定时器时限为1000 ms
    • 重传定时器在 GBN 协议中取 2800ms 最优,在SR协议中取 3000ms 最优。

4.理论分析:根据所设计的滑动窗口工作机制(Go-Back-N 或者选择重传),推导出在无差错信道环境下分组层能获得的最大信道利用率;推导出在有误码条件下重传操作及时发生等理想情况下分组层能获得的最大信道利用率。给出理论推导过程。理论推导的目的是得到信道利用率的极限数据。为了简化有误码条件下的最大利用率推导过程,可以对问题模型进行简化,比如:假定超时重传的数据帧的回馈ACK 帧可以100%正确传输,但是简化问题分析的这些假设必须不会对整个结论产生较大的误差。

  • 在无差错信道上,由于需要携带3字节的控制信息和4字节的校验位,因此最大的信道利用率为

  • 在误码率为 1e-5 的信道上(即每传送 100000 个比特平均会发生 1 个错误)

    假设信道上始终有数据需要传送,则可以传送 100000/((256+3+4)*8) 约为 47 个数据包,即每 47 个数据包会有一个出错。

    假设超时重传的数据帧的回馈ACK 帧可以100%正确传输,出错的是最后一个数据包,且每出错一次,在限定时间内可以正确重传该帧。

    则每传送 47 个数据包需要传送 47+1+1 = 49 次。于是此时的信道利用率为

    但由于程序设计并不能够达到理想状态,当一个数据包超时后,往往需要重复多次重传,造成信道浪费。若重传 K 次,则信道利用率为

    若平均重传10次,信道利用率约为 78.88% 。

5.实验结果分析:你的程序运行实际达到了什么样的效率,比对理论推导给出的结论,有没有差距?给出原因。有没有改进的办法?如果没有时间把这些方法付诸编程实施,介绍你的方案。

实际达到的效率如下表所示:

image-20210505210958256

协议效率大部分达到了参考效率,但有一些数据与理想效率有差距,比如无误码情况下的97.3%,以及高误码率下的效率。考虑其原因,猜想主要有以下几方面:

  • 不能保证数据链路层和物理层之间没有延迟,ACK和重传的帧也不能100%正确,与理想的假设情况有区别
  • 在高误码率的环境下,NAK帧的丢失有可能会导致一连串的重传,降低传输效率
  • 所选的窗口大小和超时时间还不是最优的

据此,提出以下方案:

  • 修改回退N步协议,添加NAK帧。根据实测结果(见上述表格),这种方法对回退N的效率有一定提升,但不明显。
  • 对选择重传协议的NAK帧进行一定修改,拓宽其服务对象,设置NAK数组,对每一个缓冲区内的帧都可以单独发送NAK帧,这样可以缓解高误码率下NAK帧丢失、坏帧之后带来的一系列超时重传问题。
  • 针对数据链路层与物理层之间的延迟,考虑取消PHYSICAL_LAYER_READY事件对NAK帧、ACK帧、超时重传的数据帧的限制,一旦产生这些帧,无论PHYSICAL_LAYER_READY事件是否发生,都可以直接发送。
  • 通过有设计的实验,对误码率、传输速率、窗口大小、超时时间等量进行定量分析,借助dbg输出以及相关统计工具,从统计学角度对实验结果进行更加合理严谨的分析,通过图像、函数、求导等手段,得出理论上最优超时时间以及窗口大小。

2.4 研究和探索的问题

(1) CRC校验能力

CRC校验码的检错能力很强,它除了能检查出离散错外,还能检查出突发错误。本次实验采用的CRC校验方案为CRC-32,与IEEE802.3以太网校验和生成多项式相同。生成多项式为:

x32+x26+x23+x22+x16+x12+x11+x10+x8+x7+x5+x4+x2+x1+1

从其检错能力来看,不能发现传输错误的概率为2的-32次方,几乎接近于0,因此没有必要增加成本再去达到理论上为0的错误率。

另外一个方面,如果CRC校验错误而导致给网络层传输了错误数据,那么网络层也可以通过它的校验方式发现错误,采取重传,因此能够进一步保证传输正确性。

(2) 程序设计方面的问题

① 协议软件跟踪功能为协议的调试提供了优质的方法,通过调用dbg_event和dbg_frame函数,可以清楚地观察协议的运行过程,可以非常自由的输出运行中的调试信息,并且跟踪到相应的代码段。我的程序多处调用dbg函数,实现了协议软件跟踪调试。

② C语言的time.h当中提供了一些关于时间操作的函数可以用来实现get_ms()函数,如clock_t clock()。该函数返回程序开始执行后占用的处理器时间,如果无法获得占用时间则返回-1。只需在开始通信时,设置一个静态变量start_time。然后在每次调用get_ms()后,获取当前的时间current_time。然后再返回start_time-current_time即可。

③ 如果本次实验提供的程序库中不包含log_printf和lprintf函数,可自己实现。

④ Start_timer()函数用于给发出的数据帧计时,如果超时还未收到确认ACK,便重发缓存中的数据帧。因此,在重新调用时应重新开始计时。Start_ack_timer()函数用于等待捎带确认的反向数据帧,若超时还没有反向数据帧,那就需要单独发送一个ACK帧。Start_ack_timer()在重新调用上的特点,就是为了避免ACK定时器时限过长使得发送方超时重发。

(3)对等协议实体之间的流量控制

我的协议解决了流量控制问题。作为滑动窗口协议,窗口本身就是一个流量控制,由于窗口大小的限制,发送方不会一次性发送过多信息导致接收方被数据所淹没或信息丢失。