昨天問了某位美女姐姐一個邏輯問題。由於樓主嘴太笨,語言表達實在不怎麼的。到最後也沒有解釋清楚,最後放棄口頭敘述,特寫此博客,供美女姐姐參看。要是再不懂,說明我的文字表達也不怎麼的,不知到美女姐姐怎麼看。
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開頭的,樓主就把數據包丟掉了。這裏可能會丟掉太多有用的包,所以比較坑。希望大家能夠提出意見,不喜請大噴,噴噴更健康。