接上篇文章用原生代碼寫socket,如今這篇文章主要介紹GCDAsyncSocket的使用,後續將寫關於GCDAsyncSocket的源碼分析。緩存
pod 'CocoaAsyncSocket'
#import <GCDAsyncSocket.h>
@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;
}
複製代碼
//已經鏈接到服務器
- (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內部會將字節數據進行粘包處理,既將頻繁發送的小字節數據打包成 一個整包進行發送,下降內存的消耗。性能
當發送的字節數據包比較大時,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];
}
}
}
複製代碼