阶段二:交互
文章目录
2.1 本阶段预期
2.1 本阶段预期
在数据链路层实现帧定位、差错检测、差错控制以及流量控制功能。
在应用层实现字符编码方案以及图片编码方案。
帧结构图:
2.2 帧定位
2.2.1 定义
帧定位技术是一种用来在一个比特流内分配或标记信道的技术。
2.2.2 方案
面向比特的首位字符界定法
笔者采用在帧头和帧尾各加 8 位标识的比特流 01111110。但是,仅仅如此的话会造成对帧头帧尾的误判,笔者考虑如果在数据中遇到连续的 6 个 1,那么将最后一个 1 替换为 0,但是仔细考虑,这样仍然 会造成误判,因此笔者采用:如果在发送端数据中遇到连续 5 个 1,那么在 最后一个 1 后面增添 1 个 0,例如:Data:11111100 -> 111110100。在接受端遇到 111110100 这种情况若判断出比特流为 9 位时,可去除连续 5 个 1 后 的 0,得到:11111100。
2.2.3 函数实现
发送机制:
char* get_frame(char* s); //成帧函数
bool define_fiveone(char* p, int index);//确定是否存在连续的 5个 1
接收机制:
char* locate_frame(char* s);//定位帧头帧尾,提取帧
char* de_frame(char* s);//提取 8 位比特流
2.2.4 测试
发送机制:
接收机制:
2.3 差错检测
笔者采用循环冗余校验来检验误码。
2.3.1 定义
CRC 是一种根据网络数据包或计算机文件等数据产生简短固定位 数校验码的一种信道编码技术,主要用来检测或校验数据传输或者保存后可能出现的错误。它是利用除法及余数的原理来作错误侦测的。
2.3.2 方案
- 编写函数计算 CRC 校验码,并添加至 8 位比特流后。 接收方通过重新计算 Q 来判断是否误码。 若出现误码, 则令
ERROR_FLAG=true(ERROR_FLAG 是初始化为 false 的误帧标志) (需要注意的是,这里我们并不是用 FCS
作为校验码,二而是 T/P 得 到的 Q 作为校验码,这样,接收方可以重新计算 Q 来进行比对,以发现是否出错。 - 此外,笔者为每个帧加入序号,可以通过序号差来判断是否有帧丢失,重复帧的情况发生。
2.3.3 函数实现
发送机制:
char* get_crc(char* s);//计算 CRC
char* add_crc(char* p, char* crc);将 CRC 插入 Data 后
int de_serial_number(char* p);//将10进制帧序号转为2进制
char* add_serial_number(char* p, char* serial);//插入帧序号
接收机制:
char* get_crc(char* s);//计算 CRC
char* add_crc(char* p, char* crc);将 CRC 插入 Data 后
int de_serial_number(char* p);//将10进制帧序号转为2进制
char* add_serial_number(char* p, char* serial);//插入帧序号
2.3.4 测试
发送机制:
接收机制:
2.4 差错控制
对于出现误码的帧,笔者采用停止等待协议进行差错的控制。
2.4.1 定义
停止等待协议用于通信系统中,两个相连的设备相互发送信息时使用,以确保信息不因丢包或包乱序而丢失,是最简单的自动重传请求方法。
2.4.2 方案
遵循以下原则:
- 收方对收到的帧进行差错检测以及帧序号差(与本地)的判断
- 收方只对成功接收的帧发送确认帧(ACK),其余一律丢弃
- 发放每发一帧必须暂存此帧并停止等待确认帧,超时则重发
关于确认帧:
格式:将源、目的地址颠倒,ACK标识置为1,数据位为11111111
在这里,考虑到反馈比特流可能 会出现误码,笔者规定,只要 8 位比特流内 1 的个数大于等于 4,则认为 不需要重传,8 位比特流内 1 的个数小于 4,则需要重传。这样就巧妙的解决了反馈信息误码造成的判断错误,毕竟误码率 p4 近似等于 0 了,几乎是不可能事件。
停等协议示意图:
2.4.3 函数实现
发送机制:
char* add_dirty(char* p);//加入ACK标识位
bool if_ack(char* s);//判断是否为确认帧
bool get_dirty(char* p);//获得重传标志
接收机制:
char* get_retrans_frame(char* p);//获得确认帧
2.4.4 测试
发送机制:
接收机制:
2.5 流量控制
起初并没有发现有什么地方需要进行流量控制的,但当发送多组数据时,接 收方并不能全部接收到,每隔一组数据都会漏接一个,这时候笔者意识到发送方向物理层灌输数据过快,一些数据被丢弃,于是笔者在网络层代码中,每次向物理层发送完数据后加 Sleep(10),实现了简单的流量控制。
2.6 函数封装
为了便于后续阶段的进行,笔者将交互阶段大部分功能封装在一个函数中,并且注释了不必要的打印信息。
发送机制:
char* encapsulate_frame(char* revData);//封装函数,获得最终向物理层发送的帧
接收机制:
char* decapsulate_frame(char* recbits);//解封函数,获得数据,并进行差错检测
测试:
2.7 字符编码
2.7.1 方案
采用ASCLL码进行对英文字母,标点,常用符号的编码及其解码。
2.7.2 函数实现
char* ascll_code(char ch);//将字符编码为8位比特流
char ascll_decode(char* p);//将8位比特流解码为字符
char* ten2two(int s);//ASCLL编码过程中用到的10进制转2进制函数
int two2ten(char* p);// ASCLL解码过程中用到的2进制转10进制函数
2.7.3 测试
发送机制:
接收机制:
(图中可能造成误解的地方:接收或发送仅显示了一帧,事实上有5帧,并不表明仅用一帧完成对“hello"的编码)
2.8 图片编码
2.8.1 方案
采用base64对图片进行编解码,发方先将图片编码为字符串,再将字符串编码为比特流,收方先将比特流译码为字符串,再用base64解码为图片并保存。
有关base64对图片编码的实现请参照基于base64的图片编解码方案—C语言实现
2.8.2 代码实现
const char* base64char = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";//base64编解码用到的字符
char* code_image(char* file_path);//图片编码
void decode_image(char* imageBase64, unsigned int imageSize);//图片解码
char* base64_encode(const unsigned char* bindata, char* base64, int binlength);//图片编码过程中使用的base64编码函数
int base64_decode(const char* base64, unsigned char* bindata) ;//图片解码过程中使用的base64解码函数
unsigned int get_imagesize(char* file_path);//获取图片大小(基于base64)
2.8.3 测试
发方检索用户输入的图片文件路径:”input.jpg”,进行编码发送
收方将收到的图片命名为”output.jpg”进行保存
*这种编码方式效率还是比较低的,本例中图片分辨率17×32,共发送27264帧,时长约270s.
2.9 心得体会
本阶段是整个项目的重中之重,务必要确保各种功能的稳健性,这样阶段三、四才能如鱼得水。
本文所提到的帧定位、差错检测以及差错控制是本项目的硬性要求,具体如何实现你可以参考现有的方案也可以自行设计,说到这,笔者当时凭空捏造停等协议,对于该协议的理解仅仅限于停止和等待两个关键词,最终花了一天半的时间才试错成功,却发现与现行的停等协议如出一辙,不过,不尝试犯错又怎知是错呢,虽然老师很建议参考现有的方案,但笔者认为经历过自己反复雕琢的东西才更有成就感不是吗。