iOS 基于GCDAsyncSocket實現的即時聊天

本文不討論技術選型擎颖,不介紹業(yè)務邏輯慢哈。
簡單地介紹下開發(fā)中會遇到的一些技術點蔓钟,解決的一些方案。

粘包

因為是基于TCP的卵贱,而TCP的是流式傳輸的滥沫,不像UDP數據報傳輸是有邊界的侣集,所以會有粘包問題。
然而這個問題是必須解決的兰绣,大致有三種方法世分。
1:固定包的長度,每次讀數據的時候缀辩,固定讀取字節(jié)臭埋。這在實際使用中基本不現實。
2:服務器每次發(fā)送消息的時候臀玄,給每個包添加上分隔符,如/r,/n瓢阴。GCDAsyncSocket也有方法按照此邏輯直接切割。但這個方案也非常不靠譜健无。
3:一般的處理方法是定義一個消息頭荣恐,消息頭中包含了一個包的長度,先拿到包長度再去讀取完整的包睬涧。

Socket通信定義
 頭信息:2字節(jié) 版本號
 功能代碼:2字節(jié)  功能代碼
 是否壓縮: 1個字節(jié) 0不壓縮募胃, 1壓縮
 消息長度:2字節(jié)
 消息實體:

功能代碼是指消息的類型,定義了許多許多畦浓, 因為每個項目定義都不一樣痹束,就不介紹了。
按照我們的通信定義讶请,我是這么處理粘包的:

    //解決粘包
    //思路是拆分包頭得到長度,判斷接得到的長度和包頭的長度是否一致祷嘶,不夠就繼續(xù)拼接,相等就返回夺溢,大于的話就自己做下拆分论巍。先
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    if (tag == 0) {
        self.readBufferData = [data mutableCopy];
         unsigned short dataLength;
        if(data.length >= 7){
            dataLength = [self getDataLength:data];
        }else{
            return;
        }
        [self.socketManager.socket readDataToLength:dataLength withTimeout:-1 tag:1];
    }else if(tag == 1){
        NSMutableData * completeData = self.readBufferData;
        self.readBufferData = nil;
        [completeData appendData:data];
        [self handleData:completeData];
        [self.socketManager.socket readDataToLength:7 withTimeout:-1 tag:0];
    }
}

簡單解釋下,上面的代理方法是GCDAsyncSocket讀取數據方法风响,tag可以區(qū)分我的讀數據請求嘉汰,如上tag==0 是我發(fā)起的讀消息頭的返回,再用消息頭中的字節(jié)長度去讀完整的包状勤,即tag==1的返回鞋怀。

乍看一下似乎沒有問題了,我也是這么以為的持搜。同事之前寫過量級比較大的通信密似,他遇到過問題,因為網卡緩沖區(qū)有個最大值葫盼,MTU残腌,如果一下子來N多消息,字節(jié)會溢出,有可能會導致字節(jié)的少讀多讀抛猫。所以要嚴格按照代碼以上的注釋寫代碼蟆盹,時間有限,我這邊先不上代碼邑滨。

生成數據報和解數據報

dataLength = [self getDataLength:data];

上面的代碼有這樣一個方法日缨。按照字節(jié)去拿數據钱反。之前不太清楚蘋果有API可以調用掖看。自己寫了2個壓縮字節(jié)的,估計還有些問題面哥。

/** 將數值轉成字節(jié)哎壳。編碼方式:低位在前,高位在后 */
- (NSData *)bytesFromValue:(NSInteger)value byteCount:(int)byteCount
{
    NSAssert(value <= 4294967295, @"bytesFromValue: (max value is 4294967295)");
    NSAssert(byteCount <= 4, @"bytesFromValue: (byte count is too long)");
    
    NSMutableData *valData = [[NSMutableData alloc] init];
    NSUInteger tempVal = value;
    int offset = 0;
    
    while (offset < byteCount) {
        unsigned char valChar = 0xff & tempVal;
        [valData appendBytes:&valChar length:1];
        tempVal = tempVal >> 8;
        offset++;
    }//while
    
    return [self dataWithReverse:valData];
}

正確的做法如下


/**
 *  生成數據報model
 */

-(NSData *)socketModelToData{
    NSString * bodyString = @"";
    if([self.body isKindOfClass:[NSDictionary class]]){
        bodyString = [self dictionnaryObjectToString:self.body];
    }
    NSData * dataBody = [bodyString dataUsingEncoding:NSUTF8StringEncoding];
    
    unsigned short bVersion = htons((short)self.version);
    NSMutableData *version = [[NSMutableData alloc] initWithBytes:&bVersion length:2];
    
    unsigned short vFunctionCode = htons((short)self.functionCode);
    [version appendBytes:&vFunctionCode length:2];
    
    unsigned short typeIsZip = htons((short)0);
    [version appendBytes:&typeIsZip length:1];
    
    unsigned short vDataBodyLength = htons((short)dataBody.length);
    [version appendBytes:&vDataBodyLength length:2];
    [version appendData:dataBody];

    return version;
}

/**
 *  解析數據報model
 */


- (DXSocketModel *)dataToSocketModel:(NSData *)data{
    
    DXSocketModel * socketModel = [[DXSocketModel alloc] init];
    unsigned short  version;
    unsigned short  functionCode;
    unsigned short  isGzip;
    unsigned short  dataLength;
   
    [data getBytes:&version range:NSMakeRange(0,2)];
    [data getBytes:&functionCode range:NSMakeRange(2,2)];
    [data getBytes:&isGzip range:NSMakeRange(4,1)];
    [data getBytes:&dataLength range:NSMakeRange(5,2)];
 
    
    socketModel.version = ntohs(version);
    socketModel.functionCode = ntohs(functionCode);
    socketModel.isGzip =  ntohs(isGzip);
    socketModel.dataLength = ntohs(dataLength);
    
//   if(socketModel.dataLength + 7 == data.length){
    NSData * jsonData =  [data subdataWithRange:NSMakeRange(7,socketModel.dataLength)];
    NSString * jsonStr = [[NSString alloc]initWithData:jsonData encoding:NSUTF8StringEncoding];
    socketModel.body = [self dictionaryWithJsonString:jsonStr];
//    }else{
//        NSLog(@"包頭提示長度和實際長度不一樣");
//    }
    ;
    return socketModel;
}

網絡字節(jié)序和主機字節(jié)序

如果你認真看了的話尚卫,你會發(fā)現生成數據和解析數據的時候归榕,我用了這兩個ntohs htons函數,主要是生成的字節(jié)高低位和傳輸中的字節(jié)高低位不同吱涉。需要轉換刹泄。
大端和小端(網絡字節(jié)序和主機字節(jié)序)
大端(Big Endian):即網絡字節(jié)序。
小端(Littile Endian):即主機字節(jié)序怎爵。
這里不得不感嘆下API的豐富特石,方便了我們一批API調用者。
詳解大端模式和小端模式

其他

至于一些比較基本的問題:心跳包鳖链、斷線重連(斷線重連要區(qū)分是自己主動斷開還是網絡異常)姆蘸、GCDAsyncSocket的一些代理方法,百度太多見芙委,就不重復介紹了逞敷。

事后看該方案存在一個問題:少包

如果接收區(qū)不夠大,或者已經被占滿灌侣,讀不到剩下所有的包內容推捐,需要增加一個buffer,保留不完整的包侧啼,繼續(xù)去讀取剩余的包內容牛柒。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市慨菱,隨后出現的幾起案子焰络,更是在濱河造成了極大的恐慌,老刑警劉巖符喝,帶你破解...
    沈念sama閱讀 217,657評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件闪彼,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機畏腕,發(fā)現死者居然都...
    沈念sama閱讀 92,889評論 3 394
  • 文/潘曉璐 我一進店門缴川,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人描馅,你說我怎么就攤上這事把夸。” “怎么了铭污?”我有些...
    開封第一講書人閱讀 164,057評論 0 354
  • 文/不壞的土叔 我叫張陵恋日,是天一觀的道長。 經常有香客問我嘹狞,道長岂膳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評論 1 293
  • 正文 為了忘掉前任磅网,我火速辦了婚禮谈截,結果婚禮上,老公的妹妹穿的比我還像新娘涧偷。我一直安慰自己簸喂,他們只是感情好,可當我...
    茶點故事閱讀 67,562評論 6 392
  • 文/花漫 我一把揭開白布燎潮。 她就那樣靜靜地躺著喻鳄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪跟啤。 梳的紋絲不亂的頭發(fā)上诽表,一...
    開封第一講書人閱讀 51,443評論 1 302
  • 那天,我揣著相機與錄音隅肥,去河邊找鬼竿奏。 笑死,一個胖子當著我的面吹牛腥放,可吹牛的內容都是我干的泛啸。 我是一名探鬼主播,決...
    沈念sama閱讀 40,251評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼秃症,長吁一口氣:“原來是場噩夢啊……” “哼候址!你這毒婦竟也來了?” 一聲冷哼從身側響起种柑,我...
    開封第一講書人閱讀 39,129評論 0 276
  • 序言:老撾萬榮一對情侶失蹤岗仑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后聚请,有當地人在樹林里發(fā)現了一具尸體荠雕,經...
    沈念sama閱讀 45,561評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡稳其,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,779評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了炸卑。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片既鞠。...
    茶點故事閱讀 39,902評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖盖文,靈堂內的尸體忽然破棺而出嘱蛋,到底是詐尸還是另有隱情,我是刑警寧澤五续,帶...
    沈念sama閱讀 35,621評論 5 345
  • 正文 年R本政府宣布洒敏,位于F島的核電站,受9級特大地震影響返帕,放射性物質發(fā)生泄漏桐玻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,220評論 3 328
  • 文/蒙蒙 一荆萤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧铣卡,春花似錦链韭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蝉仇,卻和暖如春旋讹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背轿衔。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評論 1 269
  • 我被黑心中介騙來泰國打工沉迹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人害驹。 一個月前我還...
    沈念sama閱讀 48,025評論 2 370
  • 正文 我出身青樓鞭呕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親宛官。 傳聞我的和親對象是個殘疾皇子葫松,可洞房花燭夜當晚...
    茶點故事閱讀 44,843評論 2 354

推薦閱讀更多精彩內容