iOS socket CocoaAsyncSocket使用 粘包的解決

公司項(xiàng)目棄用了第三方的IM埠忘,使用socket來實(shí)現(xiàn)。我在網(wǎng)上看了一些資料,決定使用CocoaAsyncSocket來實(shí)現(xiàn)莹妒∶總體來說,這個框架很輕量級旨怠,而且非常的好用渠驼、簡單易懂。
cocoaAsyncSocket github 地址
下面說一下它的用法:

初始化socket
//創(chuàng)建一個socket對象
if(!_socket) {
    dispatch_queue_t queue = dispatch_queue_create("SocketQueue", NULL);
    _socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:queue];
}else{
   [_socket setDelegate:self];
}
//連接
  NSError *error = nil;
  [self.socket connectToHost:addr onPort:port withTimeout:TIME_OUT error:&error]

這里創(chuàng)建socket的時傳入的隊(duì)列我自己創(chuàng)建了一個串行鉴腻,你也可以加入主隊(duì)列迷扇,但是不能是并發(fā)隊(duì)列。TIME_OUT 為超時時間爽哎,-1表示不設(shè)置超時蜓席,你也可以根據(jù)實(shí)際情況設(shè)置超時限制。

發(fā)送socket消息
- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag
關(guān)閉socket
[self.socket disconnect];
[self.socket setDelegate:nil];
代理方法
#pragma mark -socket的代理
#pragma mark 連接成功
-(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port {
    NSLog(@"Socket連接成功");
}
#pragma mark 斷開連接
-(void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err {
    NSLog(@"Socket連接斷開");
}
#pragma mark 數(shù)據(jù)發(fā)送成功
-(void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
   NSLog(@"Socket發(fā)送成功");
   [socket readDataWithTimeout:WRITE_TIME_OUT tag:tag];//手動調(diào)用讀取剛剛發(fā)送的數(shù)據(jù)
}
#pragma mark 讀取數(shù)據(jù)
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    //在這里處理接收到的socket數(shù)據(jù)
[self.socket readDataWithTimeout:READ_TIME_OUT buffer:nil bufferOffset:0 maxLength:MAX_BUFFER tag:0];
}

以上就是CocoaAsyncSocket的基本用法课锌,是不是很簡單明了厨内。實(shí)際開發(fā)中,我們會在這個的基礎(chǔ)上添加一些其他的邏輯比如:連接成功后與服務(wù)器做權(quán)限校驗(yàn)渺贤,發(fā)送數(shù)據(jù)加密雏胃,接收數(shù)據(jù)解碼、心跳等癣亚,這些和服務(wù)端協(xié)商好就可以了丑掺。本以為到此結(jié)束的時候,發(fā)現(xiàn)了一個問題:粘包述雾。

為什么會出現(xiàn)粘包 街州?

TCP(transport control protocol,傳輸控制協(xié)議)是面向連接的玻孟,面向流的唆缴,提供高可靠性服務(wù)。收發(fā)兩端(客戶端和服務(wù)器端)都要有一一成對的socket黍翎,因此面徽,發(fā)送端為了將多個發(fā)往接收端的包,更有效的發(fā)到對方匣掸,使用了優(yōu)化方法(Nagle算法)趟紊,將多次間隔較小且數(shù)據(jù)量小的數(shù)據(jù),合并成一個大的數(shù)據(jù)塊碰酝,然后進(jìn)行封包霎匈。這樣,接收端送爸,就難于分辨出來了铛嘱,必須提供科學(xué)的拆包機(jī)制暖释。 即面向流的通信是無消息保護(hù)邊界的。

UDP(user datagram protocol墨吓,用戶數(shù)據(jù)報(bào)協(xié)議)是無連接的球匕,面向消息的,提供高效率服務(wù)帖烘。不會使用塊的合并優(yōu)化算法亮曹,, 由于UDP支持的是一對多的模式,所以接收端的skbuff(套接字緩沖區(qū))采用了鏈?zhǔn)浇Y(jié)構(gòu)來記錄每一個到達(dá)的UDP包蚓让,在每個UDP包中就有了消息頭(消息來源地址乾忱,端口等信息),這樣历极,對于接收端來說窄瘟,就容易進(jìn)行區(qū)分處理了。 即面向消息的通信是有消息保護(hù)邊界的趟卸。

拆包

思路:預(yù)先設(shè)置一個全局的變量NSMutableData *cacheData蹄葱,每收到一個data數(shù)據(jù)先不馬上去解析它,而是把它拼接到cacheData中去锄列。這樣的話图云,如果包不完整,就可以保留數(shù)據(jù)不做處理等下一次拼接邻邮,然后處理;如果多個包粘一起竣况,可以循環(huán)處理。
區(qū)分每個包:每個包分為 head 和 body 兩部分筒严,當(dāng)多個包粘在一起時丹泉,我們從包的head 中取出這個包的長度。然后從cacheData中截取對應(yīng)長度鸭蛙,然后再用同樣的方法取下一個摹恨,就這樣一個一個的拆分,如果不夠讀取數(shù)據(jù)等下一次拼接娶视。

head 中的信息是和服務(wù)端協(xié)議的晒哄,按照具體情況定

代碼:

#pragma mark 讀取數(shù)據(jù)
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
   
   //由于獲取data數(shù)據(jù)可能為多個數(shù)據(jù)的拼接或是不完整 先加入cachedata中再解析
 
   [self analyticalData:data];
   
   [self.socket readDataWithTimeout:READ_TIME_OUT buffer:nil bufferOffset:0 maxLength:MAX_BUFFER tag:0];
}

#pragma mark 解析數(shù)據(jù)
- (void)analyticalData:(NSData *)data {
     
      if (data.length > 0) {
         
         [_cacheData appendData:data];
         
      }
      //我們這邊定義的head的長度為16個字節(jié),按具體情況肪获。如果大于16個字節(jié)就循環(huán)取包
      while (_cacheData.length > 16) {
          
         NSData *head = [_cacheData subdataWithRange:NSMakeRange(0, 16)];//取得頭部數(shù)據(jù) 
        //head中定義前4個字節(jié)為包的長度
         NSData *lengthData = [head subdataWithRange:NSMakeRange(0, 4)]; 
        
         NSInteger packageLength = [[[NSString alloc] initWithData:lengthData encoding:NSUTF8StringEncoding] integerValue];
         
         //從head中取出和服務(wù)器協(xié)商的消息協(xié)議
         NSData *operation = [head subdataWithRange:NSMakeRange(8, 4)];; 
          //如果包的長度沒有實(shí)際包的長度寝凌,讀取數(shù)據(jù)等下次拼接
         if (packageLength > _cacheData.length) {
            [self.socket readDataWithTimeout:-1 tag:0];
            return;
         }
          取出boday
          NSData *bodyData = [_cacheData subdataWithRange:NSMakeRange(16, packageLength)];
        
         //從cacheData中去掉已讀取的數(shù)據(jù)
         _cacheData = [NSMutableData dataWithData:[_cacheData subdataWithRange:NSMakeRange(packageLength, _cacheData.length - packageLength)]];

          __weak typeof(self) weakSelf = self;
         dispatch_async(dispatch_get_global_queue(0, 0), ^{
            //異步處理數(shù)據(jù),根據(jù)不同數(shù)據(jù)類型處理不同的事件
            [weakSelf functionWithOperation:operation jsonData:bodyData];
            
         });
      }
  
}
一些問題:

讀取數(shù)據(jù)

-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{

在這個方法中要手動調(diào)用readDataWithTimeout方法孝赫,不然會出現(xiàn)只有第一次讀到數(shù)據(jù)硫兰。讀取消息的TIME_OUT 要設(shè)置為-1,不然會頻繁的斷開寒锚。

因?yàn)槲覀兊捻?xiàng)目中解析數(shù)據(jù)要先將NSData 轉(zhuǎn)成byte數(shù)組劫映,然后進(jìn)去解碼。會用到Byte *byte = malloc() 方法刹前,malloc 方法會在堆中分配內(nèi)存泳赋,每次收到消息都要調(diào)用,內(nèi)存就會一直漲需要使用完byte后要調(diào)用free(byte)釋放喇喉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末祖今,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子拣技,更是在濱河造成了極大的恐慌千诬,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件膏斤,死亡現(xiàn)場離奇詭異徐绑,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)莫辨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門傲茄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人沮榜,你說我怎么就攤上這事盘榨。” “怎么了蟆融?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵草巡,是天一觀的道長。 經(jīng)常有香客問我型酥,道長山憨,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任冕末,我火速辦了婚禮萍歉,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘档桃。我一直安慰自己枪孩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布藻肄。 她就那樣靜靜地躺著蔑舞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嘹屯。 梳的紋絲不亂的頭發(fā)上攻询,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機(jī)與錄音州弟,去河邊找鬼钧栖。 笑死低零,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拯杠。 我是一名探鬼主播掏婶,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼潭陪!你這毒婦竟也來了雄妥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤依溯,失蹤者是張志新(化名)和其女友劉穎老厌,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體黎炉,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡枝秤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了拜隧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宿百。...
    茶點(diǎn)故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖洪添,靈堂內(nèi)的尸體忽然破棺而出垦页,到底是詐尸還是另有隱情,我是刑警寧澤干奢,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布痊焊,位于F島的核電站,受9級特大地震影響忿峻,放射性物質(zhì)發(fā)生泄漏薄啥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一逛尚、第九天 我趴在偏房一處隱蔽的房頂上張望垄惧。 院中可真熱鬧,春花似錦绰寞、人聲如沸到逊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽觉壶。三九已至,卻和暖如春件缸,著一層夾襖步出監(jiān)牢的瞬間铜靶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工他炊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留争剿,地道東北人已艰。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像秒梅,于是被迫代替她去往敵國和親旗芬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容