tcp/ip 網絡編程自定義協議粘包處理

昨天問了某位美女姐姐一個邏輯問題。由於樓主嘴太笨,語言表達實在不怎麼的。到最後也沒有解釋清楚,最後放棄口頭敘述,特寫此博客,供美女姐姐參看。要是再不懂,說明我的文字表達也不怎麼的,不知到美女姐姐怎麼看。


linux下,tcp併發服務器接收數據時,如果網路阻塞,服務器來不處理接收到的數據,就會

出現網絡粘包的現象。那麼就需要將特定的數據包(應用層自定義協議)進行分包。

應用層數據包格式:

    每一包完整的數據是以7e開頭,7e結尾的。

    例如:7e....字母數字...7e     就是說頭尾7e中間是以字母數字組成的數據段,而且這些數據段中也不可能出現7e。數據包是以hex格式,也就是16進制傳輸。那麼服務器接收到數據之後,也就要以hex格式進行處理。 

    下面分析下數據包粘包的不同情況:

    ps:樓主太笨,想了這些情況不知到死了多少腦細胞,唉!

    一個或者兩個數據包粘包的情況:

    1.以7e開頭的數據包

         7e......7e            

         一個完整包 ,正常處理

         7e......              

         一個半包,保存起來等待下半包拼接

         7e......7e7e......  

         一個完整包,一個半包,保存半包,等待下半包拼接

         7e......7e..........  

         一個完整包,一個錯包(不知到什麼時候會發生)

         7e7e........ 

          兩個7e開頭,需要判斷上一次接受是否存儲有上半包,如果有將第一個7e拼接,如果沒有丟掉第一個7e,將第二個7e的半包進行存儲

         7e7e......7e

         兩個7e開頭,需要判斷上一次接受是否存儲有上半包,如果有將第一個7e拼接,如果沒有丟掉第一個7e,然後正常處理第二個包

    2.不是以7e開頭的數據包

         ..........7e           

         一個半包的情況,需要與上一次的半包拼接,如果上次沒有半包,就丟掉

         ..........7e7e...... 

         一個下半包,一個上半包,與前一次的上半包拼接成完整包後,保存第二個上半包(如果沒有上半包,就丟掉---比較坑!)

         ..........7e7e......7e 

         一個下半包和一個整包,與前一次的上半包拼接成完整包,接着正常處理第二個完整包(如果沒有上半包,就丟掉---這個更坑!)


 雖然樓主只是分析了,上面的最多兩個包粘連的情況。可是也可能有很多包粘在一起。樓主在程序處理的時候,用一個接口只處理拼包和整包。另外一個接口進行循環處理,如果第一此解析處理,數據段已經完了就退出。如果沒有就繼續循環進行處理。

下面附上代碼:

stick_bag.h

#ifndef _STICK_BAG_H_                                                     
#define _STICK_BAG_H_                                                     
#include "main.h"                                                         
                                                                          
/*粘包不同形式的變量*/                                                    
#define S1 1  /*一個完整的包,而且長度剛好*/                              
#define S2 2 /*一個完整的包,還有數據*/                                   
#define S3 3 /*7e開頭的半包*/                                             
#define S4 4 /*拼成一個完整包,下半包只有一個7e*/                          
#define S5 5 /*拼一個完整的包,還有數據*/                                 
#define SFAULT -1 /*錯誤包*/                                              
                                                                          
int Stick_Bag(char *recv_buffer,int len,char *half_bag,int *half_len);    
int Get_Bag(char *buffer,int len,char *bag,int *bag_len,char *half_bag,int
 *half_len);                                                              
                                                                          
#endif

stick_bag.c

#include "./include/stick_bag.h" 
#include "./include/print.h"

/*
 *      加入接收數據隊列
        對接收到的數據包進行分包出理,防止出現連包現象     
 */

int Stick_Bag(char *recv_buffer,int len,char *half_bag,int *half_len)
{
    char bag[1024] = {'\0'};        
    int bag_len = 0;
    int off = 0;
    int ret ,i = 0;
    while(i < len){
        ret = Get_Bag(recv_buffer + off,len - off,bag,&bag_len,half_bag,half_len);    
        if(ret == S1){
            printf("一個完整的包\n");
            printf("bag_len:%d\n",bag_len);
            print(bag,bag_len);
            memset(half_bag,'\0',1024);
            *half_len = 0;
            return 0;
        }else if(ret == S2){
            off = off + bag_len;
            i = off;
            printf("一個完整的包,還有數據\n");
            /*加入消息隊列*/
            print(bag,bag_len);
            memset(bag,'\0',bag_len);
            memset(half_bag,'\0',1024);
            *half_len = 0;
        }else if(ret == S3){/*半包保存起來*/
            printf("半包保存起來\n");
            return 0;
        }else if(ret == S4){
            off = off + 1;        
            i = off;
            /*加入數據隊列*/    
            printf("拼成一個包,下半包只剩一個0x7e:\n");
            print(bag,bag_len);
            memset(bag,'\0',bag_len);
            memset(half_bag,'\0',1024);
            *half_len = 0;
        }else if(ret == S5){
            off = off + bag_len - (*half_len);/*拼完包,還有數據,偏移量*/    
            i = off;
            /*加入數據隊列*/
            print(bag,bag_len);
            memset(bag,'\0',bag_len);
            memset(half_bag,'\0',1024);
            *half_len = 0;
        }else{
            return -1;/*close socket,error bag*/
        }
        
    }        
}


/*處理分包,一個包取出,剩下的繼續解析*/
int Get_Bag(char *buffer,int len,char *bag,int *bag_len,char *half_bag,int *half_len)
{
    int i = 0;
    if(*half_len != 0){
        if((buffer[0] == 0x7e) && (buffer[1] == 0x7e)){
            printf("buffer[0]:%x\n",buffer[0]);
            half_bag[*half_len] = buffer[0];            
            *bag_len = *half_len + 1;
            memcpy(bag,half_bag,*bag_len);
            
            return S4;/*拼成一個完整包,下半包只有一個7e*/
        }
    }

    if(buffer[i] == 0x7e){/*7e 開頭*/
        bag[0] = buffer[0];            
        i ++;
        while(i < len){
            bag[i] = buffer[i];
            /*一個完整的包,而且長度剛好*/
            if((buffer[i] == 0x7e) && (i + 1 == len)){
                *bag_len = len;    

                return S1;
            }else if((i+1 == len) && (buffer[i] != 0x7e)){
                memcpy(half_bag,buffer,len);    
                *half_len = len;
                return S3;/*7e開頭的半包*/
        
            }else if((buffer[i] == 0x7e) && (i < len)){/*一個完整的包,還有數據*/
                *bag_len = i + 1;
                return S2;
            }
            i ++;    
        }
    }else{/*非7e開頭*/
        printf("非7e開頭\n");
        if(*half_len != 0){
            char tmp[1024] = {'\0'};
            while(i < len){
                tmp[i] = buffer[i];
                if((buffer[i] == 0x7e) && ((i + 1) == len)){
                    memcpy(half_bag+(*half_len),tmp,len);        
                    memcpy(bag,half_bag,(*half_len)+len);
                    *bag_len = (*half_len) + len;
                    memset(half_bag,'\0',(*bag_len));
                    return 1;
                }else if((buffer[i] == 0x7e) && (i + 1 < len)){
                    memcpy(half_bag+(*half_len),tmp,i+1);    
                    memcpy(bag,half_bag,(*half_len) + i + 1);
                    *bag_len = (*half_len) + i + 1;
                    memset(half_bag,'\0',*bag_len);
                    return 5;/*拼一個完整的包,還有數據*/
                }else if((i + 1 == len) && (buffer[i] != 0x7e)){

                    printf("錯誤包\n");
                    return SFAULT;/*錯誤包*/
                }
                i++;
            }    
        }else{
            printf("錯誤包\n");
            return SFAULT;
        }
    }        
}

 樓主的開發環境:

    系統 centos6.4    編譯器  gcc4.4.7 

以上代碼樓主親測可用,這裏就不附測試結果。樓主覺得還有很多不合理的地方,暫時不知道怎麼解決。比如:如果一次接受數據沒有半包,而這一次接收數據之後不以7e開頭的,樓主就把數據包丟掉了。這裏可能會丟掉太多有用的包,所以比較坑。希望大家能夠提出意見,不喜請大噴,噴噴更健康。     

        




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章