计算机网络项目——最小网元设计(阶段二)

Posted Couldhelp

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了计算机网络项目——最小网元设计(阶段二)相关的知识,希望对你有一定的参考价值。

阶段目标

用链路层例程代码(LnkTester.sln)设计实现链路层上点到点之间的通信过程,具体包括:两点之间帧同步、、差错检测、差错控制、简单的流量控制。

设计描述

本阶段主要停留在链路层对等实体之间的通信,故我们设计时出于简便的测试的想法,先不考虑上下之间的数据传递带来的封装问题,将应用层融合为链路层。不过需要修改配置文件,将PHY和APP两层的配置文件改为PHY和LNK的两层文件,主要就是改一个APP层的名字,没有其他需求印象中是不太需要改其他参数的。

1、帧结构

不管是帧、数据包还是什么其他的数据形式也好,设计之初对于帧格式的设计一定是首尾的,这是一切的基础。在计算机网络的发展史中,协议的产生常常会伴随帧格式的改变。所以这是设计中的重头戏。

PS:设计采用面向位填充首尾定界法

01111110000000011001…011001111110
帧头定界符ACK标志位帧序号数据位CRC校验位帧尾定界符

2、帧定位

原理

帧定位采用面向位填充首尾定界法。帧头和帧尾标志固定为‘01111110’,在发送方封装成帧时通过get_frame()函数将帧内连续5个1后添加一个0,以便能够准确对帧进行定位。在接收方又通过读取bit流,读到首尾的“01111110”来进行定界,然后去除数据位中的连续5个1后的0.来还原真实数据位。(U8类型=char类型,此处这样定义,只是为了专门区别,用来表示单字节数据,这是指导书的说明,但我自己使用的时候则感觉多把他看成是1bit数据的形式。)

涉及函数

int get_frame(U8* s, int len);		//发送端成帧
int locate_frame(U8* s, int len,U8* bufSend);	//接收端提取帧

要点

  1. 需要对于连续8位数据的读取,判断首尾定界符,这是简单的for循环。
  2. 需要添加和提取5个1后面的0,这也是数据结构中基础的移位问题,要注意数据前移和后移时的到底是尾部先移动还是头部先移动。
  3. 除了简单的去除5个1后的0,可能要考虑因为去除CRC,ACK标志位和序列号时指针头的移动和len的变化问题,也可以简单在向上层转发函数中考虑。

3、差错检测

原理

差错检测采用CRC-4来产生校验码紧跟数据后面,其中CRC生成多项式采用固定G[5] = 1,0,0,1,1 利用crccode()函数来产生四位校验码,并在接收端用crcdecode()函数进行校验。校验错误则丢弃该帧,等待重传。(此处实际情况好像是多采用检错能力更强的CRC16,此处出于简便设计和运算速度,采用最短的CRC校验码)

涉及函数

int crccode(U8* s, int len);
//计算s的CRC校验位采用生成多项式G[5]=1,0,0,1,1
bool crcdecode(U8* s, int len);		//接收方校验数据

要点

  1. 添加和删除校验码的移位问题,以及注意改变数据的长度len
  2. CRC的计算原理是采用模2除法,区别于二进制除法

4、差错控制

原理

对于数据在信道上进行传输的过程中可能产生的误码进行差错控制设计,我们采用停等协议进行差错控制(虽然后续阶段的设计发现,停等协议这种一发一收的方式还是不够高效,但设计是最简单的,其实可以尝试滑窗的GB_N和SR协议),增加1位ACK标识符和4位序列(由于是停等协议序号只有0000和1111),ACK标志位判断当前帧是数据帧(0)还是确认帧(1),并且判断收到的是否为正确序号的帧(相应确认帧序号总是为期待收到的下一帧的序列号)。确认帧的数据位采用八位全1(11111111)作为数据部分。为提高效率,收方收到ACK为1的帧数据位中判断收到数据位中有超过四个1即判断收到确认帧;同时序号中有三个及三个以上相同数字的即自动纠错判断帧序号。在发生差错和丢包时,利用Timeout()内部的重传函数将缓存下来的数据进行定时帧重传,直到接收到正确的确认帧,然后计时器开始变量isTimeStart停止。

涉及函数

int add_ack_seq(U8* s, int len);
//加入1位ACK标识位和4位序列号——————确认帧的ACK为1,并且ACK的数据bit流部分为11111111
bool isAck(U8* s);			//判断是否为确认帧
int get_ack_frame(U8* s, U8* ack_frame);	
//生成s对应的确认帧(帧序号为期待下一次接收到的帧序号)

---------------------重要变量---------------------------

--超时重传--
int TickTack = 0;//全局变量在Timeout()用来差错控制中的重传
bool isTimerStart = false;//全局计时器启动标志

--缓存区--
U8* buffer = NULL;//全局的缓冲区,发送前用来装载可能重传的数据
int buflen = 0;//缓冲区储存的字符串长度

--帧序号--
U8 SEQ[4] =  0,0,0,0 ;//四位序列号为全局变量,只有0000和1111,停等协议中异或交替出现

要点

  1. 在数据位头部,添加ACK标志位和帧序号涉及到的移位和len变化
  2. 帧序号由于是采用停等协议,故异或用1111和0000两个序号足矣,并且多位数据可以采用前向纠错(和数据位8位全1能够前向纠错同理),减少重传次数,提高传输效率;虽然停等协议效率本来就很低。
  3. 缓存区的全局变量在清空的时候建议采用提前分配一块较大的空间,然后不要采用free()释放的方式表示某次数据的缓存清空,而采用buflen=0长度清0来表示。因为在实测过程中因为前后次传输数据过程中,可能会由于超时时间的数值过小,导致出现连续多次释放同一块空间,导致以下报错。(而且反复申请和反复清空的操作本身就是一种很费资源的方式,参考数据栈清空时是因为真的不会再用那一块数据了,才清空的)。
    报错截图:(这个困扰了我很久,网上只知道是释放了空内存,后来花费很久才找到上面的解决办法)

5、流量控制

在本例中,对于停等协议来说其实流量控制的意义不大,因为一发一收的机制本身就不会出现流量一股脑全怼进去的情况。但还是可以做流量控制,可以仿照重传,在Timeout()函数中设置一个阈值,和一个类似Ticktack的时钟变量,然后当达到阈值的时候,在sendtolower()前sleep一定的时间,达到流量控制的效果。(由于当时有点摸鱼,只随手写了个sleep,上面的说法才应该是正确的流控)

6、长帧传输——分片

这是当时自己没有做的一项拓展功能,但在后面传输图片或者文件的功能实现中,发现这是极其必要的。因为如果传输的数据过长,会导致数据产生错误而重传的概率提高,可能导致反复重传而传不过去。因此将数据进行分片多次传输,减小每次传输的帧长度,就有必要了。
虽然没有具体实现,但后续想了一下实现思路,还是比较简单:可以将数据按定长分段存入一个循环队列中,然后对每个小帧进行帧头和帧长的记录,然后依次封装发送。可以用一个标志变量记录当发送总数据量等于从上层接收到的帧长的时候,来表示一个完整的帧已经传输成功;或者可以用更多的帧序号加以区分。接收方则需要按帧序号重新组装数据放入一个新的内存即可。

测试情况

1、点到点数据传输情况

发送方:

接收方:

2、重传情况

发送方:

接收方:

其他想说的话

阶段二应该是后面两个阶段的基础,个人觉得阶段二主要是学会如何阅读源码,然后学会如何使用已有的一些例程函数,去方便我们的代码编写。(然后在调试过程中,最重要的就是学会看内存数据的变化,这是很重要的一点,能提高解决bug的效率)
不过好在这一阶段的代码,有老师的讲解视频可以参考,所以学着参考视频,放开手大胆去做,克服畏难情绪,好的结果才能回应这样一个好的开端!

以上是关于计算机网络项目——最小网元设计(阶段二)的主要内容,如果未能解决你的问题,请参考以下文章

计算机网络项目——最小网元设计(阶段二)

计算机网络项目——最小网元设计(阶段二)

计算机网络项目——最小网元设计(阶段三)

计算机网络项目——最小网元设计(阶段三)

计算机网络项目——最小网元设计(阶段三)

计算机网络项目——最小网元设计(阶段三)