计算机网络中的帧封装(C实现)
这段时间开始复习计算机网络,看到帧封装这一节,结合以前的课程设计,就用C写了个帧封装的程序,说实话C学的确实不怎么样,实现的时候对于文件操作那部分查了好多资料,下面说说帧封装是啥情况。
学过计算机网络的都知道,数据的传输都是以固定的格式进行传输,在计算机当中是以二进制的数据进行传输,在网络通信中, “帧” 指通信中的一个数据块。但是帧在数据链路层传输的时候是有讲究的,不是随便的封装和打包就可以传输,大小有限制,最小46字节,最大1500字节所以我们必须按照这个规则来封装,具体的原因有兴趣的可以参考谢希仁的计算机网络帧碰撞检测那部分,说的很详细,这里不再多说,如果帧长度下于46字节就用数据填充,直到46B为止,并且填充的字段不加入长度字段中,如果大于1500字节那必须分为两个帧进行传输。
要进行帧封装还必须知道帧的结构,不知道结构无从谈起。结合书上说的802.3标准的帧结构给大家画出来了:(需要说明这是802.3标准的帧结构,还有其他版本的,也还有的吧前导码和定界符放在一起8B,既是64位,都一样)
| 前导码 | 前定界符 | 目的地址 | 源目的地址 | 长度字段 | 数据字段 | 校验字段 |
| 7B | 1B | 6B | 6B | 2B | 46-1500 | 4B |
CRC校验:
在校验字段中,使用的是CRC校验。校验的范围包括目的地址字段、源地址字段、长度字段、数据字段。CRC是一种重要的线性分组码、编码和解码方法,有检错和纠错能力强等特点,不仅能检查出离散错误,还能检查出突发错误。
按照书上的描述:利用CRC进行检错的过程可简单描述如下:在发送端根据要传送的m位二进制码序列,以一定的规则产生一个校验用的r位监督码(CRC码),附在原始信息的后边,构成一个新的二进制码序列(共m+r位),然后发送出去。在接收端,根据信息码和CRC码之间所遵循的规则进行检验,以确定传送中是否出错。这个规则在差错控制理论中称为“生成多项式”具体代码:
int statusNum = dataTotalNum;
while(statusNum--) {
char temp;
//读1B的数据
temp = fgetc(fileOut);
//模拟数据除的二进制除法过程
for(unsigned char i = (unsigned char)0x80; i > ;i >>= ) {
if(crc & 0x80) { //当前余数最高位为1,需要进行除法运算。
crc <<= ;
//将输入数据相应位的值递补到余数末位
if(temp & i){
crc ^= 0x01;
}
//进行除法运算,即与除数的低位相异或。
crc ^= 0x07;
}else{
crc <<= ; //crc左移位,最低位补。
//输入数据相应位的值递补到余数末位
if(temp & i) crc ^= 0x01;//将输入数据相应位的值递补到余数末位。
}
}
}
运算符:
这里还要说一下运算符,待会CRC校验还要用到:& >>=
1:按位与运算 按位与运算符"&"是双目运算符。其功能是参与运算的两数各对应的二进位相与。只有对应的两个二进位均为1时,结
果位才为1 ,否则为0。参与运算的数以补码方式出现。
例如:9&5可写算式如下: 00001001 (9的二进制补码)&00000101 (5的二进制补码) 00000001 (1的二进制补码)可见9&5=1。
按位与运算通常用来对某些位清0或保留某些位。例如把a 的高八位清 0 , 保留低八位, 可作 a&255 运算 ( 255 的二进制数为0000000011111111)。
2:>>是右移运算符,就是将n的二进制表示向右移位。你这里的n>>=1表示将n的二进制表示向右移动一位再赋值给n.这里的
>>=与+=,-=,*=的用法是一样的,先做运算再赋值
3:^ 二进制位异或,双目操作符如果a与b中有且仅有一个为1时,a^b的值为1,其它情况下值为0
设计方法:
首先向输出文件写入前导码、帧前定界符、目的地址、源地址和长度字段。然后读取到封装的数据文件(以2进制方式读取),填充到数据位,然后用crc计算校验字段就可以了。
问题:
这里需要有两个要解决的问题一个是文件大小的读取(不是直接得到文件的大小而是得到读取量的大小),一个是循环读取,当帧长度大于1500字节后要循环读取在封装。
第一个问题解决方法:记录读取数据块的位置,然后利用读取文件内部指针的偏移计算读取的大小。
//得到最后一个数据块的位置
lastDataPacket = ftell(fileIn);
第二个问题解决办法:先计算总文件的大小,利用fou循环以此讲数据块封装,具体的实现如下:
/**
* 先把fpIn指针退回到文件结尾处。再得到文件位置指针
* 当前位置相对于文件首的偏移字节数,即可得到内容的长度
*/
fseek(fileIn,,SEEK_END);
offsetNum = ftell(fileIn);
//计算整1500数据包个数
int dataPacketNum = offsetNum/;
//把文件指针重新回到文件开头。
rewind(fileIn);
/**
* for循环处理大于1500B的情况,当大于1500时
* 自动转换到下一个数据帧中
*/
for(int j = ;j <= dataPacketNum; j++){
char data[MAXSIZE]; //数据临时存储数组
. . .for(i = ; i < ; i++){
fputc(sourceMac[i],fileOut); //写入源MAC
printf("%X ",sourceMac[i]);
}
//不是最后一个数据,则前面的数据都应该是1500,所以按最大数据数算
if(j != dataPacketNum){
//添加长度字段
. . .逻辑处理
}else{
. . . 逻辑处理
if(surplusLongs > ){
. . .逻辑处理
}else{
//多于或者等于46B,则正常读取
. . .逻辑处理
}
}
fseek(fileOut,(short)startCalibration,SEEK_SET); //将读指针指向开始校验的位置
}
再补充一下C的文件操作部分,都是常用到的函数:
fopen(打开文件) * fopen(const char * path,const char * mode);参数path字符串包含欲打开的文件路径及文件名,参数mode字符串则代表着流形态。
r 打开只读文件。r+ 打开可读写的文件,该文件必须存在。w 打开只写文件,若文件不存在则建立该文件。w+ 打开可读写文件,若文件不存在则建立文件。
a 以附加的方式打开只写文件。若文件不存在,则建立,如果文件存在,数据加到文件尾。a+ 以附加方式打开可读写的文件。若文件不存在,则建立,如果文件存在,数据加到文件尾后。
fputs(将一指定的字符串写入文件内) int fputs(const char * s,FILE * stream); 说明 fputs()用来将参数s所指的字符串写入到参数stream所指的文件内。
返回值 若成功则返回写出的字符个数,返回EOF则表示有错误发生。
fseek(移动文件流的读写位置)
int fseek(FILE * stream,long offset,int whence); 说明
fseek()用来移动文件流的读写位置。参数stream为已打开的文件指针,参数offset为根据参数whence来移动读写位置的位移数。
SEEK_SET从距文件开头offset位移量为新的读写位置。SEEK_CUR
以目前的读写位置往后增加offset个位移量。SEEK_END将读写位置指向文件尾后再增加offset个位移量。
putc(将一指定字符写入文件中)
int putc(int c,FILE * stream); putc()会将参数c转为unsigned
char后写入参数stream指定的文件中。虽然putc()与fputc()作用相同,但putc()为宏定义,非真正的函数调用。返回值
putc()会返回写入成功的字符,即参数c。若返回EOF则代表写入失败。
ftell(取得文件流的读取位置) long ftell(FILE * stream); 函数说明 ftell()用来取得文件流目前的读写位置。参数stream为已打开的文件指针。
返回值 当调用成功时则返回目前的读写位置,若有错误则返回-1,errno会存放错误代码。
总体来说就是这些,代码测试的时候需要注意,输出的是二进制文件,,查看的时候需要用编辑器打开比如UE编辑器,可以看到封装后的帧的结构。下面给出完整的代码,代码可运行,输出位置可以自己定:
/**
* 将fileinput文件中的数据封装成帧,封装好的帧写入到文件中.
* 如果数据长度小于46字节,则补全到46字节,如果数据长度大于1500,则封装成多个帧
* @Author: zhaoyafei
* @Time : 2015年7月7日
*/
#include <stdio.h>
#include <stdlib.h>
#define MAXSIZE 1500
/*
* 帧封装函数,完成把给
* 定的文件封装成帧的功能
*/
int frameEncapsulation(char *fileinput){
int i,lastDataPacket,offsetNum; //记录最后一个帧,记录偏移量
FILE *fileIn,*fileOut;
long startCalibration,endCalibration; //开始检验位置,结束检验位置
short dataTotalNum; //记录校验字节个数
if((fileIn = fopen(fileinput,"r+")) == NULL){
printf("%s","打开文件失败");
return ;
} if((fileOut = fopen("E:\\out.txt","wb+")) == NULL){
printf("%s","写入文件失败");
return ;
}
printf("\n封装后的文件保存地址:E:\\out.txt");
/**
* 先把fpIn指针退回到文件结尾处。再得到文件位置指针
* 当前位置相对于文件首的偏移字节数,即可得到内容的长度
*/
fseek(fileIn,,SEEK_END);
offsetNum = ftell(fileIn);
//计算整1500数据包个数
int dataPacketNum = offsetNum/;
//把文件指针重新回到文件开头。
rewind(fileIn); /**
* for循环处理大于1500B的情况,当大于1500时
* 自动转换到下一个数据帧中
*/
for(int j = ;j <= dataPacketNum; j++){
char data[MAXSIZE]; //数据临时存储数组
//写入帧前导码,十六进制0xaa
printf("\n帧的前导码:");
for(i = ;i < ; i++){
fputc(0xaa,fileOut);
printf("%X ",0xaa);
}
//写入帧定界符
fputc(0xab,fileOut);
printf("%X ",0xab);
char objectiveMac[] = {0xff,0xff,0xff,0xff,0xff,0xff};//模拟目的MAC地址
char sourceMac[] = {0x10,0x16,0x76,0xb4,0xe4,0x77};//模拟源MAC地址 //记录开始进行校验的位置,因为校验是从前导码以后开始的
startCalibration = ftell(fileOut);
printf("\n帧的目的MAC地址:");
for(i = ; i < ; i++){
fputc(objectiveMac[i],fileOut);//写入目的MAC
printf("%X ",objectiveMac[i]);
}
printf("\n帧的源MAC地址:");
for(i = ; i < ; i++){
fputc(sourceMac[i],fileOut); //写入源MAC
printf("%X ",sourceMac[i]);
}
//不是最后一个数据,则前面的数据都应该是1500,所以按最大数据数算
if(j != dataPacketNum){
//添加长度字段
fputc(char(/),fileOut);
fputc(char(%),fileOut); fread(data,sizeof(char),,fileIn);
fwrite(data,sizeof(char),,fileOut);
//记录开插入校验码的位置
endCalibration = ftell(fileOut);
fputc(0x00,fileOut); //添加辅助检验字段
dataTotalNum = short(ftell(fileOut)) - (short)startCalibration; //计算检验的字段长度
}else{
//得到最后一个数据块的位置
lastDataPacket = ftell(fileIn); //剩下有多少字节
int hasLongs = offsetNum - dataPacketNum * ;
//还有多少不够46B
int surplusLongs = - hasLongs; //记录长度字段
fputc(char(hasLongs/),fileOut);
fputc(char(hasLongs%),fileOut); //先读取剩余的所有数据
fread(data,sizeof(char),offsetNum - lastDataPacket,fileIn);
//如果不足,则填充
if(surplusLongs > ){
for(i = ;i < surplusLongs; i++){
//把不够的部分模拟补上
data[hasLongs++] = 0x00;
}
fwrite(data,sizeof(char),,fileOut); //写入数据
endCalibration = ftell(fileOut); //记录开插入校验码的位置
//添加辅助检验字段
fputc(0x00,fileOut);
dataTotalNum = short(ftell(fileOut)) - (short)startCalibration; //计算检验的字段长度
}else{
//多于或者等于46B,则正常读取
fwrite(data,sizeof(char),offsetNum - lastDataPacket,fileOut);
//记录开插入校验码的位置
endCalibration = ftell(fileOut);
fputc(0x00,fileOut);
dataTotalNum = short(ftell(fileOut)) - (short)startCalibration;
}
}
fseek(fileOut,(short)startCalibration,SEEK_SET); //将读指针指向开始校验的位置
unsigned char crc = ;
int statusNum = dataTotalNum;
while(statusNum--) {
char temp;
//读1B的数据
temp = fgetc(fileOut);
//模拟数据除的二进制除法过程
for(unsigned char i = (unsigned char)0x80; i > ;i >>= ) {
if(crc & 0x80) { //当前余数最高位为1,需要进行除法运算。
crc <<= ;
//将输入数据相应位的值递补到余数末位
if(temp & i){
crc ^= 0x01;
}
//进行除法运算,即与除数的低位相异或。
crc ^= 0x07;
}else{
crc <<= ; //crc左移位,最低位补。
//输入数据相应位的值递补到余数末位
if(temp & i) crc ^= 0x01;//将输入数据相应位的值递补到余数末位。
}
}
}
//将读指针指尾部,开始插入检验字段
fseek(fileOut,(short)endCalibration,SEEK_SET);
//若不足4B,补位至4B
printf("\n帧的校验和:");
if(sizeof(crc) == ){
fputc(0x00,fileOut);
fputc(0x00,fileOut);
fputc(0x00,fileOut);
printf("%X %X %X ",0x00,0x00,0x00);
printf("%X ",crc);
fputc(crc,fileOut);
}else if(sizeof(crc) == ){
fputc(0x00,fileOut);
fputc(0x00,fileOut);
printf("%X %X ",0x00,0x00);
printf("%X ",crc);
fputc(crc,fileOut);
}else {
fputc(0x00,fileOut);
printf("%X ",0x00);
printf("%X ",crc);
fputc(crc,fileOut);
}
printf("\n帧的数据部分:");
for(int m = ; m < dataTotalNum - ; m++){
printf("%X ",data[m]);
}
printf("\n");
}
//关闭文件资源
fclose(fileIn);
fclose(fileOut);
return ;
} //程序主函数
int main(){
int status;
char fileinput[]; //记录地址
while(){
printf("*******************************************************************************\n");
printf("%s","* 1.帧封装,2.退出 *\n");
printf("*******************************************************************************\n");
printf("%s","Please input selection number:");
scanf("%d",&status);
switch(status){
case :
//帧封装调用
printf("Please enter path to the file:");
scanf("%s",fileinput);
frameEncapsulation(fileinput);
break;
case :
exit();
default:
putchar('\a');
printf("%s","You can choose 1 or 2 !\n");
}
printf("%s","\n");
}
return ;
}
运行界面,封装的文件内容是 hello world:

封装后的帧结构(我用UE打开的):

计算机网络中的帧封装(C实现)的更多相关文章
- 《计算机网络 自顶向下方法》 第8章 计算机网络中的安全 Part2
SSL(使 TCP 连接安全) SSL(Secure Socket Layer),即安全套接字层,是对 TCP 的强化 HTTPS 使用 SSL,而 HTTP 不使用 SSL 通过采用机密性.数据完整 ...
- 利用三层交换机实现VLAN间路由(计算机网络中速率、带宽、吞吐量的概念)
1.速率 速率是指计算机网络中的主机在数字信道上,单位时间内从一端传送到另一端的数据量,即数据传输率,也称数据率或比特率.比特(bit)是数据量的最小单位,s(秒)是时间的最小单位.所以速率单位为bi ...
- 计算机网络中的TCP/UDP协议到底是怎么回事(二)
上一篇博客阐述了TCP/IP五层网络结构模型以及一些关于TCP.UDP的基础知识,这篇博客会接着写一些关于TCP拥塞控制的算法以及对TCP中常有的疑问进行解答. TCP拥塞控制 首先了解几个概念,为下 ...
- 计算机网络中的TCP/UDP协议到底是怎么回事(一)
TCP/IP五层网络结构模型 物理层:物理层建立在物理通信介质的基础上,作为系统和通信介质的接口,用来实现数据链路实体间透明的比特 (bit) 流传输.只有该层为真实物理通信,其它各层为虚拟通信 数据 ...
- 计算机网络中的TCP/IP模型
Transmission Control Protocol/Internet Protocol的简写,中译名为传输控制协议/因特网互联协议,又名网络通讯协议,是Internet最基本的协议.Inter ...
- 以QQ举例 说明计算机网络中的一些概念区别(TCP与UDP,广播与单播)
QQ 中的 广播与单播 今天简单地学习了一下 广播和多播(组播) 的知识.关于 单播和多播 的概念,可以用 QQ 中的一些例子来解释. 单播,就像 两个人聊QQ 一样,信息的接收和传递只在两个节点之间 ...
- 计算机网络中七层,五层,四层协议;IP 地址子网划分
七层协议: 7 应用层(http) 6 表示层(上层用户可以相互识别的数据:jpg) 5 会话层(不同主机不同线程间的通信) 4 运输层(tcp/ip:传输层提供端到端的透明数据服务)/差错控制和流量 ...
- 计算机网络中IP地址和MAC地址
计算机 网络中的网络地址有I P 地址和物理地址之分,对 于主机间的通信时,它们的作用也不一样 . l I P 地址 为 了保证 I n t e r n e t 网上主机通信时能够相互识别 ,不引 ...
- 使用Vue制作了一个计算机网络中子网划分部分的简陋计算工具
前端新手请多关照~~~~ 上个学期学校开了计算机网络的课, 上到子网划分部分时, 各种计算虽然不然但是足够让人眼花缭乱 于是就想着自己写一个子网划分的小工具来辅助一下, 在一些简单的题目测试后没什么问 ...
随机推荐
- 1125mysqbinlog日志
-- 认真分析mysqbinlog的日志,其中前半部分使用的binlog_format='STATEMENT',后半部分使用binlog_format='ROW';-- 所谓二进制文件,就是可以直接执 ...
- 入门Webpack,看这篇就够了
来源于:http://www.jianshu.com/p/42e11515c10f 写在前面的话 阅读本文之前,先看下面这个webpack的配置文件,如果每一项你都懂,那本文能带给你的收获也许就比较有 ...
- linux dd 命令详解
dd 是 Linux/UNIX 下的一个非常有用的命令,作用是用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换. 名称: dd 使用权限: 所有使用者dd 这个指令在 manual 里的定义是 ...
- Android Studio 工具插件
1.Android Studio 翻译插件,可以将英文翻译为中文. https://github.com/Skykai521/ECTranslation 2.Android Studio之Androi ...
- 【51Nod 1501】【算法马拉松 19D】石头剪刀布威力加强版
http://www.51nod.com/onlineJudge/questionCode.html#!problemId=1501 dp求出环状不连续的前缀和,剩下东西都可以算出来,比较繁琐. 时间 ...
- 【UOJ #244】【UER #7】短路
http://uoj.ac/contest/35/problem/244 对其他人来说好简单的一道题,我当时却不会做TWT 注定滚粗啊 题解很好的~ #include<cstdio> #i ...
- 【原创】cs+html+js+css模式(六):改造ajax.js,从原来的原生态js修改为依赖于jquery插件
由于原有的ajax可能在性能上,对于jquery的支持不够并且不够方便,开发人员使用的时候需要知道我们内部指定的后缀文件的设置,基于这个前提我们进行了js的改造 // 使用闭包开发插件 ( ...
- HTML+CSS知识点总结
转自:http://blog.csdn.net/qiushi_1990/article/details/40260447?utm_source=tuicool&utm_medium=refer ...
- PHP实现linux命令tail -f
PHP实现linux命令tail -f 今天突然想到之前有人问过我的一个问题,如何通过PHP实现linux中的命令tail -f,这里就来分析实现下. 这个想一想也挺简单,通过一个循环检测文件,看文件 ...
- 【CityHunter】服务器端设计思路
设计服务端程序首先我考虑到的是通讯传输方式的设计,按照CityHunter的特殊性,其具有两种使用场景: 仅用于查看当前信息状态.搜索周边环境.对信息的实时性要求不高的一些场景: 用于攻略藏宝图或Ch ...