這段時間開始復習計算機網絡,看到幀封裝這一節,結合以前的課程設計,就用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,其它情況下值為0
設計方法:
首先向輸出文件寫入前導碼、幀前定界符、目的地址、源地址和長度字段。然后讀取到封裝的數據文件(以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打開的):

