CocoaAsyncSocket + Protobuf 處理粘包和拆包問題,cocoaasyncsocket粘包

來源:互聯網
上載者:User

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

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.