計算機網絡中的幀封裝(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碼之間所遵循的規則進行檢驗,以肯定傳送中是否出錯。這個規則在差錯控制理論中稱爲「生成多項式」具體代碼:大數據

 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打開的):

相關文章
相關標籤/搜索