iOS GCDAsyncSocket簡單使用

接上篇文章用原生代碼寫socket,如今這篇文章主要介紹GCDAsyncSocket的使用,後續將寫關於GCDAsyncSocket的源碼分析。緩存

GCDAsyncSocket使用

  1. 經過pod導入 pod 'CocoaAsyncSocket'
  2. 導入頭文件 #import <GCDAsyncSocket.h>
  3. 聲明變量 遵循代理
@interface ViewController ()<GCDAsyncSocketDelegate>
@property (nonatomic, strong) GCDAsyncSocket *socket;
@end
複製代碼

4.鏈接socketbash

#pragma mark - 鏈接socket
- (IBAction)didClickConnectSocket:(id)sender {
    // 建立socket
    if (self.socket == nil)
        // 併發隊列,這個隊列將影響delegate回調,但裏面是同步函數!保證數據不混亂,一條一條來
        // 這裏最好是寫本身併發隊列
        self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
    // 鏈接socket
    if (!self.socket.isConnected){
        NSError *error;
        [self.socket connectToHost:@"127.0.0.1" onPort:8040 withTimeout:-1 error:&error];
        if (error) NSLog(@"%@",error);
    }
}
複製代碼

5.發送消息服務器

- (IBAction)didClickSendAction:(id)sender {
    
    NSData *data = [@"發送的消息內容" dataUsingEncoding:NSUTF8StringEncoding];
    [self.socket writeData:data withTimeout:-1 tag:10086];
}
複製代碼

6.重連併發

- (IBAction)didClickReconnectAction:(id)sender {
    // 建立socket
    if (self.socket == nil)
        self.socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_global_queue(0, 0)];
    // 鏈接socket
    if (!self.socket.isConnected){
        NSError *error;
        [self.socket connectToHost:@"127.0.0.1" onPort:8040 withTimeout:-1 error:&error];
        if (error) NSLog(@"%@",error);
    }
}
複製代碼

7.關閉socketapp

- (IBAction)didClickCloseAction:(id)sender {
    [self.socket disconnect];
    self.socket = nil;
}
複製代碼

GCDAsyncSocketDelegate

//已經鏈接到服務器
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(nonnull NSString *)host port:(uint16_t)port{
    NSLog(@"鏈接成功 : %@---%d",host,port);
    //鏈接成功或者收到消息,必須開始read,不然將沒法收到消息,
    //不read的話,緩存區將會被關閉
    // -1 表示無限時長 ,永久不失效
    [self.socket readDataWithTimeout:-1 tag:10086];
}

// 鏈接斷開
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err{
    NSLog(@"斷開 socket鏈接 緣由:%@",err);
}

//已經接收服務器返回來的數據
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{
    NSLog(@"接收到tag = %ld : %ld 長度的數據",tag,data.length);
    //鏈接成功或者收到消息,必須開始read,不然將沒法收到消息
    //不read的話,緩存區將會被關閉
    // -1 表示無限時長 , tag
    [self.socket readDataWithTimeout:-1 tag:10086];
}

//消息發送成功 代理函數 向服務器 發送消息
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag{
    NSLog(@"%ld 發送數據成功",tag);
}
複製代碼

調試

按照上篇文章調試方法,這裏就再也不贅述。 socket

image.png

粘包、分包(拆包)

內容摘取自函數

概念

Socket通訊時會對發送的字節數據進行分包和粘包處理,屬於一種Socket內部的優化機制。源碼分析

粘包:

當發送的字節數據包比較小且頻繁發送時,Socket內部會將字節數據進行粘包處理,既將頻繁發送的小字節數據打包成 一個整包進行發送,下降內存的消耗。性能

分包:

當發送的字節數據包比較大時,Socket內部會將發送的字節數據進行分包處理,下降內存和性能的消耗。優化

例子解釋
當前發送方發送了兩個包,兩個包的內容以下:
123456789
ABCDEFGH
複製代碼

咱們但願接收方的狀況是:收到兩個包,第一個包爲:123456789,第二個包爲:ABCDEFGH。可是在粘包和分包出現的狀況就達不到預期狀況。

粘包狀況

兩個包在很短的時間間隔內發送,好比在0.1秒內發送了這兩個包,若是包長度足夠的話,那麼接收方只會接收到一個包,以下:

123456789ABCDEFGH
複製代碼
分包狀況

假設包的長度最長設置爲5字節(較極端的假設,通常長度設置爲1000到1500之間),那麼在沒有粘包的狀況下,接收方就會收到4個包,以下:

12345
6789
ABCDE
FGH
複製代碼
處理方式

由於存在粘包和分包的狀況,因此接收方須要對接收的數據進行必定的處理,主要解決的問題有兩個:

  1. 在粘包產生時,要能夠在同一個包內獲取出多個包的內容。
  2. 在分包產生時,要保留上一個包的部份內容,與下一個包的部份內容組合。

處理方式: 在數據包頭部加上內容長度以及數據類型 1.發送數據

#pragma mark - 發送數據格式化
- (void)sendData:(NSData *)data dataType:(unsigned int)dataType{
    NSMutableData *mData = [NSMutableData data];
    // 1.計算數據總長度 data
    unsigned int dataLength = 4+4+(int)data.length;
    // 將長度轉成data
    NSData *lengthData = [NSData dataWithBytes:&dataLength length:4];
    // mData 拼接長度data
    [mData appendData:lengthData];
    
    // 數據類型 data
    // 2.拼接指令類型(4~7:指令)
    NSData *typeData = [NSData dataWithBytes:&dataType length:4];
    // mData 拼接數據類型data
    [mData appendData:typeData];
    
    // 3.最後拼接真正的數據data
    [mData appendData:data];
    NSLog(@"發送數據的總字節大小:%ld",mData.length);
    
    // 發數據
    [self.socket writeData:mData withTimeout:-1 tag:10086];
}

複製代碼

2.接收數據

- (void)recvData:(NSData *)data{
    //直接就給他緩存起來
    [self.cacheData appendData:data];
    // 獲取總的數據包大小
    // 整段數據長度(不包含長度跟類型)
    NSData *totalSizeData = [data subdataWithRange:NSMakeRange(0, 4)];
    unsigned int totalSize = 0;
    [totalSizeData getBytes:&totalSize length:4];
    //包含長度跟類型的數據長度
    unsigned int completeSize = totalSize  + 8;
    //必需要大於8 纔會進這個循環
    while (self.cacheData.length>8) {
        if (self.cacheData.length < completeSize) {
            //若是緩存的長度 還不如 咱們傳過來的數據長度,就讓socket繼續接收數據
            [self.socket readDataWithTimeout:-1 tag:10086];
            break;
        }
        //取出數據
        NSData *resultData = [self.cacheData subdataWithRange:NSMakeRange(8, completeSize)];
        //處理數據
        [self handleRecvData:resultData];
        //清空剛剛緩存的data
        [self.cacheData replaceBytesInRange:NSMakeRange(0, completeSize) withBytes:nil length:0];
        //若是緩存的數據長度仍是大於8,再執行一次方法
        if (self.cacheData.length > 8) {
            [self recvData:nil];
        }
    }
}
複製代碼
相關文章
相關標籤/搜索