CocoaAsyncSocket + Protobuf 處理粘包和拆包問題,cocoaasyncsocket粘包
在上一篇文章《iOS之ProtocolBuffer搭建和樣本demo》分享環境的搭建, 我們和伺服器進行IM通訊用了github有名的架構CocoaAsynSocket, 然後和伺服器之間的資料媒介是ProtoBuf。然後後面在開發的過程中也碰到了拆包和粘包問題,這方面網上資料很少,曲折了一下才解決,這裡分享一下問題的解決過程!
首先描述下碰到的問題:
1、伺服器發送內容很長的資料過來的時候,GCDAsyncSocket監聽收到的一個包解析不了,一直要接收好幾個包拼接才是這條資料的完整包,即所謂的拆包;
2、伺服器快速發送多條資料過來,傳到用戶端這邊的時候幾條資料合成了一個包,即所謂的粘包。所以想解析這些粘在一起的資料,必須知道每條資料的長度,才能正確切割解析。
先上關鍵代碼,解決讀取每條資料的頭部位元組,根據頭部位元組讀取這條資料的內容長度。這樣才能完美的解決粘包問題。由於根據資料的長度不一樣,導致頭部位元組佔用的長度也會不一樣,比如說我這裡反覆測試的結果頭部佔用位元組一般為1和2,短內容資料頭部佔用位元組長度為1,長內容資料頭部佔用位元組長度為2。這裡的代碼參考了Google提供的Protobuf的objectivec版的源碼。
/** 關鍵代碼:擷取data資料的內容長度和頭部長度: index --> 頭部佔用長度 (頭部佔用長度1-4個位元組) */- (int32_t)getContentLength:(NSData *)data withHeadLength:(int32_t *)index{ int8_t tmp = [self readRawByte:data headIndex:index]; if (tmp >= 0) return tmp; int32_t result = tmp & 0x7f; if ((tmp = [self readRawByte:data headIndex:index]) >= 0) { result |= tmp << 7; } else { result |= (tmp & 0x7f) << 7; if ((tmp = [self readRawByte:data headIndex:index]) >= 0) { result |= tmp << 14; } else { result |= (tmp & 0x7f) << 14; if ((tmp = [self readRawByte:data headIndex:index]) >= 0) { result |= tmp << 21; } else { result |= (tmp & 0x7f) << 21; result |= (tmp = [self readRawByte:data headIndex:index]) << 28; if (tmp < 0) { for (int i = 0; i < 5; i++) { if ([self readRawByte:data headIndex:index] >= 0) { return result; } } result = -1; } } } } return result;}/** 讀取位元組 */- (int8_t)readRawByte:(NSData *)data headIndex:(int32_t *)index{ if (*index >= data.length) return -1; *index = *index + 1; return ((int8_t *)data.bytes)[*index - 1];}
解決了讀取每條資料的頭部佔用位元組,和內容的長度,粘包問題就好解決了。
再上比較完整的代碼:從用戶端監聽伺服器發送過來的資料到處理拆包和粘包問題,然後解析成自訂的protobuf模型類。
/** 監聽來自伺服器的訊息代理方法 */- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{ [self.receiveData appendData:data]; //讀取data的頭部佔用位元組 和 從頭部讀取內容長度 //驗證結果:資料比較小時頭部佔用位元組為1,資料比較大時頭部佔用位元組為2 int32_t headL = 0; int32_t contentL = [self getContentLength:self.receiveData withHeadLength:&headL]; if (contentL < 1){ [sock readDataWithTimeout:-1 tag:0]; return; } //拆包情況下:繼續接收下一條訊息,直至接收完這條訊息所有的拆包,再解析 if (headL + contentL > self.receiveData.length){ [sock readDataWithTimeout:-1 tag:0]; return; } //當receiveData長度不小於第一條訊息內容長度時,開始解析receiveData [self parseContentDataWithHeadLength:headL withContentLength:contentL]; [sock readDataWithTimeout:-1 tag:tag];} #pragma mark - private methods 輔助方法/** 解析位元據:NSData --> 自訂模型對象 */- (void)parseContentDataWithHeadLength:(int32_t)headL withContentLength:(int32_t)contentL{ NSRange range = NSMakeRange(0, headL + contentL); //本次解析data的範圍 NSData *data = [self.receiveData subdataWithRange:range]; //本次解析的data GPBCodedInputStream *inputStream = [GPBCodedInputStream streamWithData:data]; NSError *error; ChatMsg *obj = [ChatMsg parseDelimitedFromCodedInputStream:inputStream extensionRegistry:nil error:&error]; if (!error){ if (obj) [self saveReceiveInfo:obj]; //儲存解析正確的模型對象 [self.receiveData replaceBytesInRange:range withBytes:NULL length:0]; //移除已經解析過的data } if (self.receiveData.length < 1) return; //對於粘包情況下被合并的多條訊息,迴圈遞迴直至解析完所有訊息 headL = 0; contentL = [self getContentLength:self.receiveData withHeadLength:&headL]; [self parseContentDataWithHeadLength:headL withContentLength:contentL]; //繼續解析下一條}/** 擷取data資料的內容長度和頭部長度: index --> 頭部佔用長度 (頭部佔用長度1-4個位元組) */- (int32_t)getContentLength:(NSData *)data withHeadLength:(int32_t *)index{ int8_t tmp = [self readRawByte:data headIndex:index]; if (tmp >= 0) return tmp; int32_t result = tmp & 0x7f; if ((tmp = [self readRawByte:data headIndex:index]) >= 0) { result |= tmp << 7; } else { result |= (tmp & 0x7f) << 7; if ((tmp = [self readRawByte:data headIndex:index]) >= 0) { result |= tmp << 14; } else { result |= (tmp & 0x7f) << 14; if ((tmp = [self readRawByte:data headIndex:index]) >= 0) { result |= tmp << 21; } else { result |= (tmp & 0x7f) << 21; result |= (tmp = [self readRawByte:data headIndex:index]) << 28; if (tmp < 0) { for (int i = 0; i < 5; i++) { if ([self readRawByte:data headIndex:index] >= 0) { return result; } } result = -1; } } } } return result;}/** 讀取位元組 */- (int8_t)readRawByte:(NSData *)data headIndex:(int32_t *)index{ if (*index >= data.length) return -1; *index = *index + 1; return ((int8_t *)data.bytes)[*index - 1];}/** 處理解析出來的資訊 */- (void)saveReceiveInfo:(ChatMsg *)obj{ //...}
View Code
樣本demo:https://github.com/xiaotanit/Tan_ProtocolBuffer
原文連結:http://www.cnblogs.com/tandaxia/p/6718695.html