iOS | Socket & CocoaAsyncSocket 讀/寫操作以及粘包處理

本文主要介紹 CocoaAsyncSocket的 讀/寫 操作,以及如何處理TCP粘包的問題,CocoaAsyncSocket基本使用的請查看上一篇文章Socket & CocoaAsyncSocket介紹與使用

排隊 讀/寫 操作

CocoaAsyncSocket庫的最佳功能之一是“排隊 讀/寫 操作”。

  • 寫操作: 套接字未連接,但我還是可以開始寫它,庫將排隊我的所有寫操作,在套接字連接后,它將自動開始執(zhí)行我的寫操作!
NSError * err = nil ;
NSError *err = nil;
if (![self.clientSocket connectToHost:@"ip地址" onPort: "端口號" error:&err])  //異步!
{
    NSLog(@"I goofed: %@", err);
}

//此時套接字未連接。
//但我還是可以開始寫它!
//庫將排隊我的所有寫操作,
//在套接字連接后,它將自動開始執(zhí)行我的寫操作!
   [socket writeData: data1 withTimeout: - 1  tag: 1 ];
   [socket writeData: data2 withTimeout: - 1  tag: 2 ];

  • 排隊讀: 我們可以通過長度獲取到相應長度的數(shù)據(jù),可以很好解決粘包問題
[socket readDataToLength: datalength withTimeout: -1  tag: 0];

Tag參數(shù)了解

CocoaAsyncSocket中的tag參數(shù)是不通過套接字發(fā)送的,也不是從套接字讀取的。tag參數(shù)只需通過各種委托方法回顯給您。它旨在幫助簡化委托方法中的代碼。

  [socket writeData: data1 withTimeout: - 1  tag: 1 ];
  [socket writeData: data2 withTimeout: - 1  tag: 2 ];
//  當我們發(fā)送數(shù)據(jù)時候使用 tag 標記后,發(fā)送后可以在委托方法中根據(jù) tag 看到那條數(shù)據(jù)已經(jīng)發(fā)送出去了。
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag :( long)tag{
    if(tag == 1)
         NSLog(@"First request sent ");
    else  if(tag == 2)
         NSLog(@"Second request sent ");
}

在讀取時數(shù)據(jù)時,tag 也很有幫助:

[socket readDataWithTimeout:-1 tag:0];
// 讀取 tag 與上面方法的 tag 值是一一對應的。
#define TAG_WELCOME 10
#define TAG_CAPABILITIES 11
#define TAG_MSG 12
- (void)socket:(GCDAsyncSocket *)sender didReadData :( NSData *)data withTag :( long)tag{
    if(tag == TAG_WELCOME)
    {
        //  忽略歡迎信息
    }
    else  if(tag == TAG_CAPABILITIES)
    {
        [self  processCapabilities:data];
    }
    else if (tag == TAG_MSG)
    {
        [self  processMessage: data];
    }
}

Tcp 粘包

1. 什么是tcp粘包?

TCP是面向連接的,面向流的,提供高可靠性服務。收發(fā)兩端(客戶端和服務器端)都要有一一成對的socket,因此,發(fā)送端為了將多個發(fā)往接收端的包,更有效的發(fā)到對方,使用了優(yōu)化方法(Nagle算法),將多次間隔較小且數(shù)據(jù)量小的數(shù)據(jù),合并成一個大的數(shù)據(jù)塊,然后進行封包。這樣,接收端,就難于分辨出來了,就會出現(xiàn)粘包現(xiàn)象

2. TCP粘包解決方案

目前應用最廣泛的是在消息的頭部添加數(shù)據(jù)包長度,接收方根據(jù)消息長度進行接收;在一條TCP連接上,數(shù)據(jù)的流式傳輸在接收緩沖區(qū)里是有序的,其主要的問題就是第一個包的包尾與第二個包的包頭共存接收緩沖區(qū),所以根據(jù)長度讀取是十分合適的;

2.1 解決發(fā)送方粘包

方案一: 發(fā)送產(chǎn)生是因為Nagle算法合并小數(shù)據(jù)包,那么可以禁用掉該算法;
方案二: TCP提供了強制數(shù)據(jù)立即傳送的操作指令push,當填入數(shù)據(jù)后調(diào)用操作指令就可以立即將數(shù)據(jù)發(fā)送,而不必等待發(fā)送緩沖區(qū)填充自動發(fā)送;
方案三: 數(shù)據(jù)包中加頭,頭部信息為整個數(shù)據(jù)的長度(最廣泛最常用);

//  `方案三`發(fā)送方解決粘包的代碼部分:
- (void)sendData:(NSData *)data{
        
        NSMutableData *sendData = [NSMutableData data];
        // 獲取數(shù)據(jù)長度
        NSInteger datalength = data.length;
        //  NSInteger長度轉(zhuǎn) NSData
        NSData *lengthData = [NSData dataWithBytes:&datalength length:sizeof(datalength)];
        // 長度幾個字節(jié)和服務器協(xié)商好。這里我們用的是4個字節(jié)存儲長度信息
        NSData *newLengthData = [lengthData subdataWithRange:NSMakeRange(0, 4)];
        // 拼接長度信息
        [sendData appendData:newLengthData];
        //拼接數(shù)據(jù)
        [sendData appendData:data];
        // 發(fā)送加了長度信息的包
        [self.clientSocket writeData:[sendData copy] withTimeout:-1 tag:0];
}

2.2解決接收方粘包
  1. 解析數(shù)據(jù)包頭部信息,根據(jù)長度來接收;(最廣泛最常用)
/**
 數(shù)據(jù)緩沖區(qū)
 */
@property (nonatomic, strong) NSMutableData *dataBuffer;;

//  讀取客戶端發(fā)送的數(shù)據(jù),通過包頭長度進行拆包
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
    
        //  數(shù)據(jù)存入緩沖區(qū)
        [self.dataBuffer appendData:data];
        
        // 如果長度大于4個字節(jié),表示有數(shù)據(jù)包。4字節(jié)為包頭,存儲包內(nèi)數(shù)據(jù)長度
        while (self.dataBuffer.length >= 4) {
            
            NSInteger  datalength = 0;
            // 獲取包頭,并獲取長度
            [[self.dataBuffer subdataWithRange:NSMakeRange(0, 4)] getBytes:&datalength length:sizeof(datalength)];
            //  判斷緩存區(qū)內(nèi)是否有包
            if (self.dataBuffer.length >= (datalength+4)) {
                // 獲取去掉包頭的數(shù)據(jù)
                NSData *realData = [[self.dataBuffer subdataWithRange:NSMakeRange(4, datalength)] copy];
                // 解析處理
                [self handleData:realData socket:sock];
                
                // 移除已經(jīng)拆過的包
                self.dataBuffer = [NSMutableData dataWithData:[self.dataBuffer subdataWithRange:NSMakeRange(datalength+4, self.dataBuffer.length - (datalength+4))]];
            }else{
                break;
            }
        }
        [sock readDataWithTimeout:-1 tag:0];
}
  1. 自定義數(shù)據(jù)格式:在數(shù)據(jù)中放入開始、結(jié)束標識;解析時根據(jù)格式抓取數(shù)據(jù),缺點是數(shù)據(jù)內(nèi)不能含有開始或結(jié)束標識;
  2. 短連接傳輸,建立一次連接只傳輸一次數(shù)據(jù)就關(guān)閉;(不推薦)

注:以上代碼僅提供粘包的解決思路,具體如何解包以及包頭數(shù)據(jù)結(jié)構(gòu)可以和服務器進行商定

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

相關(guān)閱讀更多精彩內(nèi)容

  • 一、網(wǎng)絡各個協(xié)議:TCP/IP、SOCKET、HTTP等 網(wǎng)絡七層由下往上分別為物理層、數(shù)據(jù)鏈路層、網(wǎng)絡層、傳輸層...
    杯水救車薪閱讀 2,362評論 0 17
  • 2016.7.4 今天晚上對項目頂層文件(daemon)進行了分析,對其中的TCP連接進行具體的代碼級分析。 1、...
    zuoerfeng閱讀 2,076評論 0 4
  • 網(wǎng)絡編程 一.楔子 你現(xiàn)在已經(jīng)學會了寫python代碼,假如你寫了兩個python文件a.py和b.py,分別去運...
    go以恒閱讀 2,249評論 0 6
  • 在開發(fā)階段,隨著js框架和庫的引入,頁面js的加載個數(shù)就越來越多,嚴重影響頁面的響應速度。于是我們就需要對js和c...
    挨踢的菜鳥閱讀 909評論 0 0
  • 故事如下:惠能流浪到廣州法性寺,聽見一僧道“風吹幡動”,又聽一僧說“幡動而知風吹”,惠能卻道:“非風動,非幡動,仁...
    月漸星闌閱讀 105評論 0 0

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