iOS-GCDAsyncSocket的使用

GCDAsyncSocket是第三方庫CocoaAsyncSocket其中的一個(gè)類,用于建立可靠的TCP連接。如果想建立UDP連接,可以用GCDAsyncUDPSocket。

用Cocoapods導(dǎo)入GCDAsyncSocket:(Podfile文件中添加下面這句就可以了)

pod 'CocoaAsyncSocket', '7.4.1'

1、創(chuàng)建Socket、并連接

//_connectStatus為socket的連接狀態(tài)
if (_connectStatus == DDSocketConnectStatusConnected){
        return;
    }
    
    if (self.socket == nil) {
//創(chuàng)建socket
        self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    }

    NSError *error = nil;
//設(shè)置socket的鏈接地址,并連接服務(wù)器(host是主機(jī)地址,port是端口號(hào))
    [self.socket connectToHost:host onPort:port withTimeout:timeout error:&error];
    _connectStatus = DDSocketConnectStatusConnecting;

2、連接成功的回調(diào)

如果建連成功之后,會(huì)收到socket成功的回調(diào),在里面你可以做一些事情,我是做了心跳的處理。

/**
 當(dāng)成功連接上,該方法會(huì)立刻返回
 @param sender socket套接字
 @param host 主機(jī)地址
 @param port 端口地址
 */
- (void)socket:(GCDAsyncSocket *)sender didConnectToHost:(nonnull NSString *)host port:(uint16_t)port
{
    DDLog(@"Socket連接成功 host: %@", host);
    _connectStatus = DDSocketConnectStatusConnected;
    _faliedCount = 0;
    //釋放重連定時(shí)器
    [self invalidateReconnect];
    //初始化發(fā)送心跳的定時(shí)器
    [self resumeHeartBeat];
    //通信的首個(gè)數(shù)據(jù)包 - 拆包
    [self clientSocketReadData];
    //連接成功回調(diào)
    [self onnectedSocketSucess:YES];
}

3、連接失敗的回調(diào)

如果連接失敗,就會(huì)回調(diào)下面的方法,一般會(huì)在里面做重連socket的操作,需要涉及到重連的時(shí)間和次數(shù)。

/**
 連接失敗,該方法會(huì)立刻返回
 @param sock socket套接字
 @param err 連接失敗的錯(cuò)誤回調(diào)
 */
- (void)socketDidDisconnect:(GCDAsyncSocket*)sock withError:(nullable NSError *)err
{
//未連接的狀態(tài)下進(jìn)行重連
    if (_connectStatus != DDSocketConnectStatusDisconnected || !_socket) {
        return;
    }
    //重連次數(shù)判斷
    if (_faliedCount < 0 || _faliedCount >= kBeatLimit) {
        [self invalidateReconnect];
        [self resetSocketStatus];
        
        return;
    }
    
    _faliedCount++;
    DDLog(@"重連次數(shù):%ld",(long)_faliedCount);
    
    if (_reconnectTimer == nil) {
        _reconnectTimer =  [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(connectSocket) userInfo:nil repeats:YES];
        _reconnectTimer.fireDate = [NSDate distantPast];  //啟動(dòng)reconnectTimer
    }
}

4、向服務(wù)端發(fā)送數(shù)據(jù)

這一步是建立在socket已經(jīng)建連的基礎(chǔ)上,socket連接成功后,你需要向服務(wù)端發(fā)送數(shù)據(jù)時(shí),調(diào)用下面方法:

[self.socket writeData:requestData withTimeout:-1 tag:0];

下面是你向服務(wù)端發(fā)送數(shù)據(jù)成功的回調(diào)方法:

/**
 消息發(fā)送成功

 @param sock socket套接字
 @param tag
 */
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag {
    DDLog(@"tcp 消息發(fā)送成功");
}

5、接受服務(wù)端的數(shù)據(jù)

這一步是建立在socket已經(jīng)建連的基礎(chǔ)上,socket連接成功后,服務(wù)器向你發(fā)送數(shù)據(jù)后,會(huì)調(diào)用下面方法:

/**
 接收消息的回調(diào)方法

 @param sock socket套接字
 @param data 接收到的二進(jìn)制數(shù)據(jù)
 @param tag 消息任務(wù)的類型, 可以自定義 , 根據(jù)定義可以判斷本次連接的類型
 */
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    [self.cachedResponseData appendData:data];
    [self handleReceivedData:self.cachedResponseData];
}

6、斷開連接

[self.socket disconnect];

socket開發(fā)中需要注意

在開發(fā)中前端和后端會(huì)約定一個(gè)固定的數(shù)據(jù)(消息)格式,按照這個(gè)格式來讀取數(shù)據(jù),就能把每組數(shù)據(jù)劃分出來,也就較好的解決了 粘包 掉包 的問題,數(shù)據(jù)不完整時(shí)也能獲知數(shù)據(jù)的缺失。

在開發(fā)中前端和后端會(huì)約定一個(gè)固定的數(shù)據(jù)(消息)格式,按照這個(gè)格式來讀取數(shù)據(jù),就能把每組數(shù)據(jù)劃分出來,也就較好的解決了 粘包 掉包 的問題,數(shù)據(jù)不完整時(shí)也能獲知數(shù)據(jù)的缺失。
比如,下面是一個(gè)表格,為規(guī)定好的消息格式:

屏幕快照 2018-05-24 下午8.22.12.png

注:以上的消息格式分成3塊,數(shù)據(jù)和校驗(yàn)符在這里稱為身體部分
1.消息頭部:起始符 + 目標(biāo)地址 + 原地址 + 數(shù)據(jù)長度 = 7Byte,(頭部包含了那么多信息,并且長度是固定的,所以我們接收消息的時(shí)候,要先從頭部開始入手)
2.消息體:要發(fā)送或者接收到的數(shù)據(jù),長度為dataLength
3.校驗(yàn)符:用來檢驗(yàn)接收的數(shù)據(jù)是否完整一致(開發(fā)中可能沒有)

下面是數(shù)據(jù)返回,處理粘包 掉包問題:
//包返回狀態(tài)
typedef NS_ENUM(NSInteger, DDSocketReturnDataStatus) {
    DDSocketReturnDataStatusSucceed              = 1,      // 完美返回一個(gè)完整包的數(shù)據(jù)
    DDSocketReturnDataStatusNoHead               = 2,      // 包頭不匹配或者不存在(舍棄操作,不處理)
    DDSocketReturnDataStatusHeadNoFrist          = 3,      // 包頭存在但是不在第一個(gè)索引位
    DDSocketReturnDataStatusNoComplete           = 4,      // 不滿足基礎(chǔ)數(shù)據(jù)長度(需要等待繼續(xù)返回)
    DDSocketReturnDataStatusLessProtocolBuffers  = 5,      // protocolBuffers數(shù)據(jù)與長度小于返回長度 (需要等待繼續(xù)返回)
    DDSocketReturnDataStatusMoreProtocolBuffers  = 6,      // protocolBuffers數(shù)據(jù)與長度大于返回長度
};
/**
 處理包以及返回的方法,根據(jù)寶返回?cái)?shù)據(jù)的長度,判斷狀態(tài),做相應(yīng)處理

 @param indexData 本次連接 接收到的二進(jìn)制數(shù)據(jù)
 */
- (void)handleReceivedData:(NSData *)indexData
{
    NSUInteger tempFirstHeaderIndex = 0;
    SInt32 tempFirstDataCount = 0;
    DDSocketReturnDataStatus returnDataStatus = [GCDAsynSocketModel validationDataPacket:self.cachedResponseData firstHeaderIndex:&tempFirstHeaderIndex firstDataLength: &tempFirstDataCount];
    //記錄上一次包頭位置(粘包時(shí)使用)
    self.firstHeaderIndex = tempFirstHeaderIndex;
    //記錄上一次數(shù)據(jù)的長度(粘包時(shí)使用)
    self.firstDataCount = tempFirstDataCount;

    switch (returnDataStatus) {
        case DDSocketReturnDataStatusSucceed:
        {
            [self didJustReceivedWholeDataPackage];
        }
            break;
            
        case DDSocketReturnDataStatusLessProtocolBuffers:
        case DDSocketReturnDataStatusNoComplete:
        {
            [self clientSocketReadData];
        }
            break;
        
        case DDSocketReturnDataStatusNoHead:
        {
            [self clientSocketReadData];
        }
            break;
        
        case DDSocketReturnDataStatusHeadNoFrist:
        {
            [self resetPackageHeaderLocation];
        }
            break;
        
        case DDSocketReturnDataStatusMoreProtocolBuffers:
        {
            [self extractWholeDataPackage];
        }
            break;
    
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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