socket 粘包拆包處理

粘包皆愉、拆包?

客戶端或者服務(wù)端不斷的發(fā)送數(shù)據(jù)包時艇抠,接收的數(shù)據(jù)會出現(xiàn)兩個數(shù)據(jù)包粘在一起的情況幕庐,這就是TCP協(xié)議中經(jīng)常會遇到的粘包以及拆包的問題。

我們都知道TCP屬于傳輸層的協(xié)議家淤,傳輸層除了有TCP協(xié)議外還有UDP協(xié)議异剥。那么UDP是否會發(fā)生粘包或拆包的現(xiàn)象呢?答案是不會絮重。UDP是基于報文發(fā)送的冤寿,從UDP的幀結(jié)構(gòu)可以看出,在UDP首部采用了16bit來指示UDP數(shù)據(jù)報文的長度青伤,因此在應(yīng)用層能很好的將不同的數(shù)據(jù)報文區(qū)分開督怜,從而避免粘包和拆包的問題。

而TCP是基于字節(jié)流的狠角,雖然應(yīng)用層和TCP傳輸層之間的數(shù)據(jù)交互是大小不等的數(shù)據(jù)塊号杠,但是TCP把這些數(shù)據(jù)塊僅僅看成一連串無結(jié)構(gòu)的字節(jié)流,沒有邊界丰歌;另外從TCP的幀結(jié)構(gòu)也可以看出姨蟋,在TCP的首部沒有表示數(shù)據(jù)長度的字段辣吃,這種情況由于接收端不知道這兩個數(shù)據(jù)包的界限,所以對于接收端來說很難處理芬探。

粘包神得、拆包發(fā)生原因

發(fā)生TCP粘包或拆包有很多原因,現(xiàn)列出常見的幾點(diǎn)偷仿,可能不全面哩簿,歡迎補(bǔ)充,

1酝静、要發(fā)送的數(shù)據(jù)大于TCP發(fā)送緩沖區(qū)剩余空間大小节榜,將會發(fā)生拆包。

2别智、待發(fā)送數(shù)據(jù)大于MSS(最大報文長度)宗苍,TCP在傳輸前將進(jìn)行拆包。

3薄榛、要發(fā)送的數(shù)據(jù)小于TCP發(fā)送緩沖區(qū)的大小讳窟,TCP將多次寫入緩沖區(qū)的數(shù)據(jù)一次發(fā)送出去,將會發(fā)生粘包敞恋。

4丽啡、接收數(shù)據(jù)端的應(yīng)用層沒有及時讀取接收緩沖區(qū)中的數(shù)據(jù),將發(fā)生粘包硬猫。

粘包补箍、拆包解決辦法

通過以上分析,我們清楚了粘包或拆包發(fā)生的原因啸蜜,那么如何解決這個問題呢坑雅?解決問題的關(guān)鍵在于如何給每個數(shù)據(jù)包添加邊界信息,常用的方法有如下幾個:

1衬横、發(fā)送端給每個數(shù)據(jù)包添加包首部裹粤,首部中應(yīng)該至少包含數(shù)據(jù)包的長度,這樣接收端在接收到數(shù)據(jù)后冕香,通過讀取包首部的長度字段蛹尝,便知道每一個數(shù)據(jù)包的實(shí)際長度了。

2悉尾、發(fā)送端將每個數(shù)據(jù)包封裝為固定長度(不夠的可以通過補(bǔ)0填充)突那,這樣接收端每次從接收緩沖區(qū)中讀取固定長度的數(shù)據(jù)就自然而然的把每個數(shù)據(jù)包拆分開來。

3构眯、可以在數(shù)據(jù)包之間設(shè)置邊界愕难,如添加特殊符號,這樣,接收端通過這個邊界就可以將不同的數(shù)據(jù)包拆分開猫缭。

代碼分析:使用CocoaAsyncSocket

我們用第一種方案:(完整數(shù)據(jù)格式為數(shù)據(jù)長度+數(shù)據(jù)類型+數(shù)據(jù))
數(shù)據(jù)類型和數(shù)據(jù)長度分別占4byte,4byte
所以我們在發(fā)送數(shù)據(jù)時把數(shù)據(jù)包裝成Length+ type+data即可:

##2 數(shù)據(jù)類型枚舉
typedefNS_ENUM(NSUInteger, TMCommandType) {

    TMCommandTypeImg       =  1,

    TMCommandTypeText      =  2,

    TMCommandTypeVideo     =  3,

};
//發(fā)送時包裝數(shù)據(jù)
- (void)sendData:(NSData*)data type:(TMCommandType)type{

    NSMutableData *mData = [NSMutableData data];

    // 計算數(shù)據(jù)總長度 data

    unsignedintdataLength =4+4+(int)data.length;

    NSData*lengthData = [NSDatadataWithBytes:&dataLengthlength:4];

    [mData appendData:lengthData];

    // 數(shù)據(jù)類型 data

    // 2.拼接指令類型(4~7:指令)

    NSData *typeData = [NSData dataWithBytes:&type length:4];

    [mData appendData:typeData];

    // 最后拼接數(shù)據(jù)

    [mData appendData:data];

    NSLog(@"發(fā)送數(shù)據(jù)的總字節(jié)大小:%ld",mData.length);

    // 發(fā)數(shù)據(jù)

    [self.mSocket writeData:mData withTimeout:-1 tag:999];

}

//接收服務(wù)器返回來的數(shù)據(jù)  拆包
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
    
    NSLog(@"總共接收到tag = %ld : %ld 長度的數(shù)據(jù)",tag,data.length);
    if (data.length == 0) {
        return;
    }
    
    // 1.第一次接收數(shù)據(jù)
    if(self.mData.length == 0){
        //讀取前四個字節(jié) 數(shù)據(jù)包大小length
        NSData *totalSizeData = [data subdataWithRange:NSMakeRange(0, 4)];
        unsigned int totalSize = 0;
        [totalSizeData getBytes:&totalSize length:4];
        self.mTotalSize = totalSize;
        
        // 獲取指令類型Type
        NSData *commandIdData = [data subdataWithRange:NSMakeRange(4, 4)];
        unsigned int commandId = 0;
        [commandIdData getBytes:&commandId length:4];
        self.mCurrentCommandId = commandId;
    }
    //拼接數(shù)據(jù)
    [self.mData appendData:data];
    
    if (self.mData.length == self.mTotalSize) {
        //數(shù)據(jù)內(nèi)容
        NSData *data_data = [self.mData subdataWithRange:NSMakeRange(8, self.mData.length - 8)];
        NSLog(@"數(shù)據(jù)已經(jīng)接收完成");
        
        if (self.mCurrentCommandId == TMCommandTypeImg) {
          
            NSLog(@"接收到圖片");
            [self saveImage: data_data];
            
        }else if  (self.mCurrentCommandId == TMCommandTypeVideo){
            
            NSLog(@"接收到視頻");
            
        }else if  (self.mCurrentCommandId == TMCommandTypeText){
           
            NSLog(@"接收到文本");
        }
        // 清除數(shù)據(jù)
        self.mData = [NSMutableData data];
    };
    //-1表示永不超時
    [self.mSocket readDataWithTimeout:-1 tag:10086];
    
}

demo:https://github.com/TeeMoYan/Socket-.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末葱弟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子猜丹,更是在濱河造成了極大的恐慌芝加,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件射窒,死亡現(xiàn)場離奇詭異藏杖,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)脉顿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進(jìn)店門蝌麸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人艾疟,你說我怎么就攤上這事来吩。” “怎么了蔽莱?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵弟疆,是天一觀的道長。 經(jīng)常有香客問我碾褂,道長兽间,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任正塌,我火速辦了婚禮,結(jié)果婚禮上恤溶,老公的妹妹穿的比我還像新娘乓诽。我一直安慰自己,他們只是感情好咒程,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布鸠天。 她就那樣靜靜地躺著,像睡著了一般帐姻。 火紅的嫁衣襯著肌膚如雪稠集。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天饥瓷,我揣著相機(jī)與錄音剥纷,去河邊找鬼。 笑死呢铆,一個胖子當(dāng)著我的面吹牛晦鞋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼悠垛,長吁一口氣:“原來是場噩夢啊……” “哼线定!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起确买,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤斤讥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后湾趾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體周偎,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年撑帖,在試婚紗的時候發(fā)現(xiàn)自己被綠了蓉坎。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡胡嘿,死狀恐怖蛉艾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情衷敌,我是刑警寧澤勿侯,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站缴罗,受9級特大地震影響助琐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜面氓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一兵钮、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧舌界,春花似錦掘譬、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至藐握,卻和暖如春靴拱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背猾普。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工袜炕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人抬闷。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓妇蛀,卻偏偏與公主長得像耕突,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子评架,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評論 2 349