iOS GCDAsyncSocket簡單使用
阿新 • • 發佈:2019-12-25
接上篇文章用原生程式碼寫socket,現在這篇文章主要介紹GCDAsyncSocket的使用,後續將寫關於GCDAsyncSocket的原始碼分析。
GCDAsyncSocket使用
- 通過pod匯入
pod 'CocoaAsyncSocket'
- 匯入標頭檔案
#import <GCDAsyncSocket.h>
- 宣告變數 遵循代理
@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);
}
複製程式碼
除錯
按照上篇文章除錯方法,這裡就不再贅述。
粘包、分包(拆包)
概念
Socket通訊時會對傳送的位元組資料進行分包和粘包處理,屬於一種Socket內部的優化機制。
粘包:
當傳送的位元組資料包比較小且頻繁傳送時,Socket內部會將位元組資料進行粘包處理,既將頻繁傳送的小位元組資料打包成 一個整包進行傳送,降低記憶體的消耗。
分包:
當傳送的位元組資料包比較大時,Socket內部會將傳送的位元組資料進行分包處理,降低記憶體和效能的消耗。
例子解釋
當前傳送方傳送了兩個包,兩個包的內容如下:
123456789
ABCDEFGH
複製程式碼
我們希望接收方的情況是:收到兩個包,第一個包為:123456789,第二個包為:ABCDEFGH。但是在粘包和分包出現的情況就達不到預期情況。
粘包情況
兩個包在很短的時間間隔內傳送,比如在0.1秒內傳送了這兩個包,如果包長度足夠的話,那麼接收方只會接收到一個包,如下:
123456789ABCDEFGH
複製程式碼
分包情況
假設包的長度最長設定為5位元組(較極端的假設,一般長度設定為1000到1500之間),那麼在沒有粘包的情況下,接收方就會收到4個包,如下:
12345
6789
ABCDE
FGH
複製程式碼
處理方式
因為存在粘包和分包的情況,所以接收方需要對接收的資料進行一定的處理,主要解決的問題有兩個:
- 在粘包產生時,要可以在同一個包內獲取出多個包的內容。
- 在分包產生時,要保留上一個包的部分內容,與下一個包的部分內容組合。
處理方式: 在資料包頭部加上內容長度以及資料型別 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];
}
}
}
複製程式碼