1. 程式人生 > IOS開發 >iOS GCDAsyncSocket簡單使用

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.連線socket

#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,error);
    }
}
複製程式碼

7.關閉socket

- (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);
}
複製程式碼

除錯

按照上篇文章除錯方法,這裡就不再贅述。

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];
        }
    }
}
複製程式碼