iOS 處理socket粘包問題 (轉)


 

1.什么是粘包?

  粘包通常出現在TCP的協議里面,對於UDP來說是不會出現粘包狀況的,之所以出現這種狀況的原因,涉及到一種名為Nagle的算法。

  Nagle算法通過減少必須發送的封包的數量,提高網絡應用程序系統的效率,解決負載問題。通俗的講,就是在發包的時候會建立一個緩存區,發送的數據都會先進入這個緩存區,當上一條數據的接收被確認或者到達最大等待時間之后,才會將緩存區的數據一塊發送過去,如此反復。將小包進行整合,避免小包多次發送造成的傳輸速度慢等問題。

2.什么時候才需要處理粘包?

  理論上來講,只要是基於TCP的socket鏈接,都需要處理粘包的情況。

  可能有些人在測試的時候並沒有出現粘包的情況,認為並不需要對粘包進行處理,這種想法是錯誤的。

  首先,在測試的時候之所以沒有出現粘包情況,極有可能是因為網絡路由問題,導致TCP的MTU會有所變化,Internet上的標准MTU值為576,以太網MTU為1500。所以測試粘包一般以外網環境進行測試。

3.粘包解決方案

  由於Nagle算法已經成為了默認的執行方式,所以對於粘包,在Server端和Client端都需要解決粘包的問題,由於問題一致,所以解決方案也基本是通用的,只是相關語法需要變換一下。

  由於iOS的底層對於socket的封裝並不是那么完善,使用的話還得采用C語言,因此使用了一個名為CocoaAsyncSocket的第三方,將C的socket封裝成了適合OC面向對象的形式,github地址如下:https://github.com/robbiehanson/CocoaAsyncSocket

  導入成功后,正式開始處理粘包問題。

  首先,跟服務器確定數據頭的問題,對於粘包,一般有兩種解決方案,第一種就是服務器返回的字段中有可識別的頭和尾,我們可以根據可識別的頭和尾來拆包。第二種是服務器返回的數據只包含頭,頭里面有數據的長度,我們可以根據這個頭包含的數據長度來進行拆包。本文采用的便是第二種方案。

  服務器返回的數據為data形式的數據,打印出來的數據如下:

<fefd00f9 7b22736e 223a342c 22766572 73696f6e 223a2231 2e30222c 226e6574 466c6167 223a312c 22636d64 54797065 223a312c 22706475 223a7b22 70647554 79706522 3a343039 382c2264 65764461 7461223a 5b7b2264 65764964 223a2231 32333435 36373822 2c226465 764e616d 65223a22 e59b9ee8 b7afe68e a7e588b6 222c2270 4964223a 22343039 3833222c 22706172 616d223a 5b7b2274 79706522 3a383232 372c2276 616c7565 223a317d 2c7b2274 79706522 3a383232 382c2276 616c7565 223a307d 2c7b2274 79706522 3a383232 392c2276 616c7565 223a317d 2c7b2274 79706522 3a383233 302c2276 616c7565 223a317d 5d7d5d7d 7d>

  其中fefd00f9為服務器返回的頭,后面為具體的數據。其中fefd占兩個字節,00f9占兩個字節,fefd是固定的值不做任何處理,00f9就是數據的長度

  接下來,在鏈接成功之后,在回調方法里面創建一個緩存區

 
           
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
    self.readBuf = [[NSMutableData alloc] init];
  NSLog(@"鏈接成功后的其他操作");

}
 
          

  然后在收到服務器發送的數據的時候,對這個數據進行處理

復制代碼
//服務器發送的數據
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    //將數據存入緩存區
    [self.readBuf appendData:data];
    
    //數據中前面有4個字節的頭信息,其中前兩位是固定的頭長度(用處不大),后兩位才是數據的長度。
    //如果大於4個字節證明有消息,因為服務器只要發送數據,必定包含頭
    while (self.readBuf.length > 4) {
        
        //將消息轉化成byte,計算總長度 = 數據的內容長度 + 前面4個字節的頭長度
        Byte *bytes = (Byte *)[self.readBuf bytes];
        NSUInteger allLength = (bytes[2]<<8) + bytes[3] +4;
        
        //緩存區的長度大於總長度,證明有完整的數據包在緩存區,然后進行處理
        if (self.readBuf.length >= allLength) {
            NSMutableData *msgData = [[self.readBuf subdataWithRange:NSMakeRange(0, allLength)] mutableCopy];
            //提取出前面4個字節的頭內容,之所以提取出來,是因為在處理數據問題的時候,比如data轉json的時候,頭內容里面包含非法字符,會導致轉化出來的json內容為空,所以要先去掉再處理數據問題
            [msgData replaceBytesInRange:NSMakeRange(0, 4) withBytes:NULL length:0];
    
            NSLog(@"開始處理數據問題");

            //處理完數據后將處理過的數據移出緩存區
            _readBuf = [NSMutableData dataWithData:[_readBuf subdataWithRange:NSMakeRange(allLength, _readBuf.length - allLength)]];
        }else{
            //緩存區內數據包不是完整的,再次從服務器獲取數據,中斷while循環
            [self.clientSocket readDataWithTimeout:-1 tag:0];
            break;
        }
    }
    
    //讀取到服務端數據值后,能再次讀取
    [self.clientSocket readDataWithTimeout:-1 tag:0];
} 
復制代碼

  這樣處理之后,可以將粘包問題解決

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM