socket 粘包拆包處理

粘包、拆包?

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

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

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

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

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

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

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

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

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

粘包、拆包解決辦法

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

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

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

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

代碼分析:使用CocoaAsyncSocket

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

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

    TMCommandTypeImg       =  1,

    TMCommandTypeText      =  2,

    TMCommandTypeVideo     =  3,

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

    NSMutableData *mData = [NSMutableData data];

    // 計(jì)算數(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){
        //讀取前四個(gè)字節(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表示永不超時(shí)
    [self.mSocket readDataWithTimeout:-1 tag:10086];
    
}

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容