這段時間開始複習計算機網絡,看到幀封裝這一節,結合之前的課程設計,就用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碼之間所遵循的規則進行檢驗,以肯定傳送中是否出錯。這個規則在差錯控制理論中稱爲「生成多項式」具體代碼:大數據
1 int statusNum = dataTotalNum; 2 while(statusNum--) { 3 char temp; 4 //讀1B的數據 5 temp = fgetc(fileOut); 6 //模擬數據除的二進制除法過程 7 for(unsigned char i = (unsigned char)0x80; i > 0;i >>= 1) { 8 if(crc & 0x80) { //當前餘數最高位爲1,須要進行除法運算。 9 crc <<= 1; 10 //將輸入數據相應位的值遞補到餘數末位 11 if(temp & i){ 12 crc ^= 0x01; 13 } 14 //進行除法運算,即與除數的低位相異或。 15 crc ^= 0x07; 16 }else{ 17 crc <<= 1; //crc左移位,最低位補。 18 //輸入數據相應位的值遞補到餘數末位 19 if(temp & i) crc ^= 0x01;//將輸入數據相應位的值遞補到餘數末位。 20 } 21 } 22 }
運算符:編碼
這裏還要說一下運算符,待會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,其它狀況下值爲0spa
設計方法:計算機網絡
首先向輸出文件寫入前導碼、幀前定界符、目的地址、源地址和長度字段。而後讀取到封裝的數據文件(以2進制方式讀取),填充到數據位,而後用crc計算校驗字段就能夠了。設計
問題:
這裏須要有兩個要解決的問題一個是文件大小的讀取(不是直接獲得文件的大小而是獲得讀取量的大小),一個是循環讀取,當幀長度大於1500字節後要循環讀取在封裝。
第一個問題解決方法:記錄讀取數據塊的位置,而後利用讀取文件內部指針的偏移計算讀取的大小。
1 //獲得最後一個數據塊的位置 2 lastDataPacket = ftell(fileIn);
第二個問題解決辦法:先計算總文件的大小,利用fou循環以此講數據塊封裝,具體的實現以下:
/** * 先把fpIn指針退回到文件結尾處。再獲得文件位置指針 * 當前位置相對於文件首的偏移字節數,便可獲得內容的長度 */ fseek(fileIn,0,SEEK_END); offsetNum = ftell(fileIn); //計算整1500數據包個數 int dataPacketNum = offsetNum/1500; //把文件指針從新回到文件開頭。 rewind(fileIn); /** * for循環處理大於1500B的狀況,當大於1500時 * 自動轉換到下一個數據幀中 */ for(int j = 0;j <= dataPacketNum; j++){ char data[MAXSIZE]; //數據臨時存儲數組
. . .for(i = 0; i < 6; i++){ fputc(sourceMac[i],fileOut); //寫入源MAC printf("%X ",sourceMac[i]); } //不是最後一個數據,則前面的數據都應該是1500,因此按最大數據數算 if(j != dataPacketNum){ //添加長度字段 . . .邏輯處理 }else{ . . . 邏輯處理 if(surplusLongs > 0){ . . .邏輯處理 }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編輯器,能夠看到封裝後的幀的結構。下面給出完整的代碼,代碼可運行,輸出位置能夠本身定:
1 /** 2 * 將fileinput文件中的數據封裝成幀,封裝好的幀寫入到文件中. 3 * 若是數據長度小於46字節,則補全到46字節,若是數據長度大於1500,則封裝成多個幀 4 * @Author: zhaoyafei 5 * @Time : 2015年7月7日 6 */ 7 #include <stdio.h> 8 #include <stdlib.h> 9 #define MAXSIZE 1500 10 /* 11 * 幀封裝函數,完成把給 12 * 定的文件封裝成幀的功能 13 */ 14 int frameEncapsulation(char *fileinput){ 15 int i,lastDataPacket,offsetNum; //記錄最後一個幀,記錄偏移量 16 FILE *fileIn,*fileOut; 17 long startCalibration,endCalibration; //開始檢驗位置,結束檢驗位置 18 short dataTotalNum; //記錄校驗字節個數 19 if((fileIn = fopen(fileinput,"r+")) == NULL){ 20 printf("%s","打開文件失敗"); 21 return 1; 22 } 23 24 if((fileOut = fopen("E:\\out.txt","wb+")) == NULL){ 25 printf("%s","寫入文件失敗"); 26 return 1; 27 } 28 printf("\n封裝後的文件保存地址:E:\\out.txt"); 29 /** 30 * 先把fpIn指針退回到文件結尾處。再獲得文件位置指針 31 * 當前位置相對於文件首的偏移字節數,便可獲得內容的長度 32 */ 33 fseek(fileIn,0,SEEK_END); 34 offsetNum = ftell(fileIn); 35 //計算整1500數據包個數 36 int dataPacketNum = offsetNum/1500; 37 //把文件指針從新回到文件開頭。 38 rewind(fileIn); 39 40 /** 41 * for循環處理大於1500B的狀況,當大於1500時 42 * 自動轉換到下一個數據幀中 43 */ 44 for(int j = 0;j <= dataPacketNum; j++){ 45 char data[MAXSIZE]; //數據臨時存儲數組 46 //寫入幀前導碼,十六進制0xaa 47 printf("\n幀的前導碼:"); 48 for(i = 0;i < 7; i++){ 49 fputc(0xaa,fileOut); 50 printf("%X ",0xaa); 51 } 52 //寫入幀定界符 53 fputc(0xab,fileOut); 54 printf("%X ",0xab); 55 char objectiveMac[6] = {0xff,0xff,0xff,0xff,0xff,0xff};//模擬目的MAC地址 56 char sourceMac[6] = {0x10,0x16,0x76,0xb4,0xe4,0x77};//模擬源MAC地址 57 58 //記錄開始進行校驗的位置,由於校驗是從前導碼之後開始的 59 startCalibration = ftell(fileOut); 60 printf("\n幀的目的MAC地址:"); 61 for(i = 0; i < 6; i++){ 62 fputc(objectiveMac[i],fileOut);//寫入目的MAC 63 printf("%X ",objectiveMac[i]); 64 } 65 printf("\n幀的源MAC地址:"); 66 for(i = 0; i < 6; i++){ 67 fputc(sourceMac[i],fileOut); //寫入源MAC 68 printf("%X ",sourceMac[i]); 69 } 70 //不是最後一個數據,則前面的數據都應該是1500,因此按最大數據數算 71 if(j != dataPacketNum){ 72 //添加長度字段 73 fputc(char(1500/256),fileOut); 74 fputc(char(1500%256),fileOut); 75 76 fread(data,sizeof(char),1500,fileIn); 77 fwrite(data,sizeof(char),1500,fileOut); 78 //記錄開插入校驗碼的位置 79 endCalibration = ftell(fileOut); 80 fputc(0x00,fileOut); //添加輔助檢驗字段 81 dataTotalNum = short(ftell(fileOut)) - (short)startCalibration; //計算檢驗的字段長度 82 }else{ 83 //獲得最後一個數據塊的位置 84 lastDataPacket = ftell(fileIn); 85 86 //剩下有多少字節 87 int hasLongs = offsetNum - dataPacketNum * 1500; 88 //還有多少不夠46B 89 int surplusLongs = 46 - hasLongs; 90 91 //記錄長度字段 92 fputc(char(hasLongs/256),fileOut); 93 fputc(char(hasLongs%256),fileOut); 94 95 //先讀取剩餘的全部數據 96 fread(data,sizeof(char),offsetNum - lastDataPacket,fileIn); 97 //若是不足,則填充 98 if(surplusLongs > 0){ 99 for(i = 0;i < surplusLongs; i++){ 100 //把不夠的部分模擬補上 101 data[hasLongs++] = 0x00; 102 } 103 fwrite(data,sizeof(char),46,fileOut); //寫入數據 104 endCalibration = ftell(fileOut); //記錄開插入校驗碼的位置 105 //添加輔助檢驗字段 106 fputc(0x00,fileOut); 107 dataTotalNum = short(ftell(fileOut)) - (short)startCalibration; //計算檢驗的字段長度 108 }else{ 109 //多於或者等於46B,則正常讀取 110 fwrite(data,sizeof(char),offsetNum - lastDataPacket,fileOut); 111 //記錄開插入校驗碼的位置 112 endCalibration = ftell(fileOut); 113 fputc(0x00,fileOut); 114 dataTotalNum = short(ftell(fileOut)) - (short)startCalibration; 115 } 116 } 117 fseek(fileOut,(short)startCalibration,SEEK_SET); //將讀指針指向開始校驗的位置 118 unsigned char crc = 0; 119 int statusNum = dataTotalNum; 120 while(statusNum--) { 121 char temp; 122 //讀1B的數據 123 temp = fgetc(fileOut); 124 //模擬數據除的二進制除法過程 125 for(unsigned char i = (unsigned char)0x80; i > 0;i >>= 1) { 126 if(crc & 0x80) { //當前餘數最高位爲1,須要進行除法運算。 127 crc <<= 1; 128 //將輸入數據相應位的值遞補到餘數末位 129 if(temp & i){ 130 crc ^= 0x01; 131 } 132 //進行除法運算,即與除數的低位相異或。 133 crc ^= 0x07; 134 }else{ 135 crc <<= 1; //crc左移位,最低位補。 136 //輸入數據相應位的值遞補到餘數末位 137 if(temp & i) crc ^= 0x01;//將輸入數據相應位的值遞補到餘數末位。 138 } 139 } 140 } 141 //將讀指針指尾部,開始插入檢驗字段 142 fseek(fileOut,(short)endCalibration,SEEK_SET); 143 //若不足4B,補位至4B 144 printf("\n幀的校驗和:"); 145 if(sizeof(crc) == 1){ 146 fputc(0x00,fileOut); 147 fputc(0x00,fileOut); 148 fputc(0x00,fileOut); 149 printf("%X %X %X ",0x00,0x00,0x00); 150 printf("%X ",crc); 151 fputc(crc,fileOut); 152 }else if(sizeof(crc) == 2){ 153 fputc(0x00,fileOut); 154 fputc(0x00,fileOut); 155 printf("%X %X ",0x00,0x00); 156 printf("%X ",crc); 157 fputc(crc,fileOut); 158 }else { 159 fputc(0x00,fileOut); 160 printf("%X ",0x00); 161 printf("%X ",crc); 162 fputc(crc,fileOut); 163 } 164 printf("\n幀的數據部分:"); 165 for(int m = 0; m < dataTotalNum - 15; m++){ 166 printf("%X ",data[m]); 167 } 168 printf("\n"); 169 } 170 //關閉文件資源 171 fclose(fileIn); 172 fclose(fileOut); 173 return 0; 174 } 175 176 //程序主函數 177 int main(){ 178 int status; 179 char fileinput[20]; //記錄地址 180 while(1){ 181 printf("*******************************************************************************\n"); 182 printf("%s","* 1.幀封裝,2.退出 *\n"); 183 printf("*******************************************************************************\n"); 184 printf("%s","Please input selection number:"); 185 scanf("%d",&status); 186 switch(status){ 187 case 1: 188 //幀封裝調用 189 printf("Please enter path to the file:"); 190 scanf("%s",fileinput); 191 frameEncapsulation(fileinput); 192 break; 193 case 2: 194 exit(0); 195 default: 196 putchar('\a'); 197 printf("%s","You can choose 1 or 2 !\n"); 198 } 199 printf("%s","\n"); 200 } 201 return 0; 202 }
運行界面,封裝的文件內容是 hello world:
封裝後的幀結構(我用UE打開的):