本文介紹了CocoaAsyncSocket庫中GCDAsyncSocket類的使用蜂奸、粘包處理以及時間延遲測試.
一.CocoaAsyncSocket介紹
CocoaAsyncSocket中主要包含兩個類: GCDAsyncSocket针饥、GCDAsyncUdpSocket
1)GCDAsyncSocket
用GCD搭建的基于TCP/IP協(xié)議的socket網(wǎng)絡(luò)庫
GCDAsyncSocket is a TCP/IP socket networking library built atop Grand Central Dispatch. -- 引自CocoaAsyncSocket.
2)GCDAsyncUdpSocket
用GCD搭建的基于UDP/IP協(xié)議的socket網(wǎng)絡(luò)庫.
GCDAsyncUdpSocket is a UDP/IP socket networking library built atop Grand Central Dispatch..-- 引自CocoaAsyncSocket.
二.下載CocoaAsyncSocket
點擊CocoaAsyncSocket下載
拷貝下面兩個文件到項目
GCDAsyncSocket.h
GCDAsyncSocket.m
三.客戶端介紹
先講解客戶端創(chuàng)建過程井佑,大部分項目已經(jīng)有服務(wù)端socket。
步驟:
1).繼承GCDAsyncSocketDelegate協(xié)議.
2).聲明屬性
// 客戶端socket
@property (strong, nonatomic) GCDAsyncSocket *clientSocket;
3).創(chuàng)建socket并指定代理對象為self,代理隊列必須為主隊列.
self.clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
4).連接指定主機的對應(yīng)端口.
NSError *error = nil;
self.connected = [self.clientSocket connectToHost:self.addressTF.text onPort:[self.portTF.text integerValue] viaInterface:nil withTimeout:-1 error:&error];
5).成功連接主機對應(yīng)端口號.
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
// NSLog(@"連接主機對應(yīng)端口%@", sock);
[self showMessageWithStr:@"鏈接成功"];
[self showMessageWithStr:[NSString stringWithFormat:@"服務(wù)器IP: %@-------端口: %d", host,port]];
// 連接成功開啟定時器
[self addTimer];
// 連接后,可讀取服務(wù)端的數(shù)據(jù)
[self.clientSocket readDataWithTimeout:- 1 tag:0];
self.connected = YES;
}
注意:
The host parameter will be an IP address, not a DNS name.(連接的主機為IP地址,并非DNS名稱.) -- 引自GCDAsyncSocket
6).發(fā)送數(shù)據(jù)給服務(wù)端
// 發(fā)送數(shù)據(jù)
- (IBAction)sendMessageAction:(id)sender
{
NSData *data = [self.messageTextF.text dataUsingEncoding:NSUTF8StringEncoding];
// withTimeout -1 : 無窮大,一直等
// tag : 消息標記
[self.clientSocket writeData:data withTimeout:- 1 tag:0];
}
注意:
*發(fā)送數(shù)據(jù)主要通過- (void)writeData:(NSData )data withTimeout:(NSTimeInterval)timeout tag:(long)tag寫入數(shù)據(jù)的.
7).讀取服務(wù)端數(shù)據(jù)
/**
讀取數(shù)據(jù)
@param sock 客戶端socket
@param data 讀取到的數(shù)據(jù)
@param tag 本次讀取的標記
*/
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
NSString *text = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
[self showMessageWithStr:text];
// 讀取到服務(wù)端數(shù)據(jù)值后,能再次讀取
[self.clientSocket readDataWithTimeout:- 1 tag:0];
}
注意:
有的人寫好代碼,而且第一次能夠讀取到數(shù)據(jù),之后,再也接收不到數(shù)據(jù).那是因為,在讀取到數(shù)據(jù)的代理方法中,需要再次調(diào)用[self.clientSocket readDataWithTimeout:- 1 tag:0];方法,框架本身就是這么設(shè)計的.
8).客戶端socket斷開連接.
/**
客戶端socket斷開
@param sock 客戶端socket
@param err 錯誤描述
*/
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
[self showMessageWithStr:@"斷開連接"];
self.clientSocket.delegate = nil;
self.clientSocket = nil;
self.connected = NO;
[self.connectTimer invalidate];
}
注意:
sokect斷開連接時,需要清空代理和客戶端本身的socket.
self.clientSocket.delegate = nil;
self.clientSocket = nil;
9).建立心跳連接.
// 計時器
@property (nonatomic, strong) NSTimer *connectTimer;
// 添加定時器
- (void)addTimer
{
// 長連接定時器
self.connectTimer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(longConnectToSocket) userInfo:nil repeats:YES];
// 把定時器添加到當前運行循環(huán),并且調(diào)為通用模式
[[NSRunLoop currentRunLoop] addTimer:self.connectTimer forMode:NSRunLoopCommonModes];
}
// 心跳連接
- (void)longConnectToSocket
{
// 發(fā)送固定格式的數(shù)據(jù),指令@"longConnect"
float version = [[UIDevice currentDevice] systemVersion].floatValue;
NSString *longConnect = [NSString stringWithFormat:@"123%f",version];
NSData *data = [longConnect dataUsingEncoding:NSUTF8StringEncoding];
[self.clientSocket writeData:data withTimeout:- 1 tag:0];
}
注意:
心跳連接中發(fā)送給服務(wù)端的數(shù)據(jù)只是作為測試代碼,根據(jù)你們公司需求,或者和后臺商定好心跳包的數(shù)據(jù)以及發(fā)送心跳的時間間隔.因為這個項目的服務(wù)端socket也是我個人寫的,所以,我自定義心跳包協(xié)議.客戶端發(fā)送心跳包,服務(wù)端也需要有對應(yīng)的心跳檢測,以此檢測客戶端是否在線.
代理方法如下:
/socket成功連接到才會服務(wù)器調(diào)用
-(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port;
//接受到新的socket連接才會調(diào)用
- (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket;
//讀取數(shù)據(jù),有數(shù)據(jù)就會調(diào)用
- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag;
//直到讀到這個長度的數(shù)據(jù),才會觸發(fā)代理
- (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag;
//直到讀到data這個邊界位衩,才會觸發(fā)代理
- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag;
//有socket斷開連接調(diào)用
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err;
四.模擬服務(wù)端
1.繼承GCDAsyncSocketDelegate協(xié)議.
2.聲明屬性
3.創(chuàng)建socket并指定代理對象為self,代理隊列必須為主隊列.
4.開放服務(wù)端的指定端口.
5.連接上新的客戶端socket
6.發(fā)送數(shù)據(jù)給客戶端
7.讀取客戶端的數(shù)據(jù)
8.建立檢測心跳連接.
心跳檢測方法只提供部分思路:
1.懶加載一個可變字典,字典的鍵作為客戶端的標識.如:客戶端標識為123456.
*2.在- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData )data withTag:(long)tag方法中,將讀取到的數(shù)據(jù)或者數(shù)據(jù)中的部分字符串作為鍵.字典的值為系統(tǒng)當前時間.服務(wù)端第一次讀取數(shù)據(jù)時,字典中沒有數(shù)據(jù),所以,直接添加到可變字典中,之后每次讀取數(shù)據(jù)時,都用字典的setObject: forKey:方法添加字典,若存儲的鍵相同,即客戶端標識相同,鍵會被覆蓋,再使用系統(tǒng)的當前時間作為值.
3.在- (void)checkLongConnect中,獲取此時的當前時間,遍歷字典,將每個鍵的值和當前時間進行比較即可.判斷的延遲時間可以寫8秒.時間自定.之后,再根據(jù)自己的需求進行后續(xù)處理.
五.數(shù)據(jù)粘包處理
粘包解決思路:
思路1)
發(fā)送方將數(shù)據(jù)包加上包頭和包尾,包頭冀续、包體以及包尾用字典形式包裝成json字符串,接收方,通過解析獲取json字符串中的包體,便可進行進一步處理.
思路2)
添加前綴.和包內(nèi)容拼接成同一個字符串.
思路3)
如果最終要得到的數(shù)據(jù)的長度是個固定長度,用一個字符串作為緩沖池,每次收到數(shù)據(jù),都用字符串拼接對應(yīng)數(shù)據(jù),每當字符串的長度和固定長度相同時,便得到一個完整數(shù)據(jù),處理完這個數(shù)據(jù)并清空字符串,再進行下一輪的字符拼接.
六.檢驗測試.
1)測試配置
測試時,兩端需要處于同一WiFi下.客戶端中的IP地址為服務(wù)端的IP地址,具體信息進入Wifi設(shè)置中查看.
2)測試所需環(huán)境.
將客戶端程序安裝在每個客戶端,讓一臺服務(wù)端測試機和一臺客戶端測試機連接mac并運行,這兩臺測試機可以看到打印結(jié)果,所有由服務(wù)端發(fā)送到客戶端的數(shù)據(jù),通過客戶端再回傳給服務(wù)端,在服務(wù)端看打印結(jié)果.
3)進行延遲差測試.
延遲差即服務(wù)端發(fā)送數(shù)據(jù)到第一臺客戶端和服務(wù)端發(fā)送數(shù)據(jù)到最后一臺客戶端的時間差.根據(jù)服務(wù)端發(fā)送數(shù)據(jù)給不同數(shù)量的客戶端進行測試.而且,發(fā)送數(shù)據(jù)時,是隨機發(fā)送.
4)單次信息收發(fā)測試.
讓服務(wù)端給每個客戶端隨機發(fā)送200次數(shù)據(jù).并計算服務(wù)端發(fā)送數(shù)據(jù)到某一客戶端,完整的一次收發(fā)時間情況.
學習資料查詢?nèi)缦拢?/p>
https://www.cnblogs.com/letougaozao/p/3842113.html
http://www.cocoachina.com/ios/20170615/19529.html
https://www.cnblogs.com/XYQ-208910/p/5169209.html
iOS開發(fā)項目- 基于WebSocket的聊天通訊(2)
iOS開發(fā)項目- 基于WebSocket的聊天通訊(1)