轉載:http://www.cocoachina.com/ios/20170615/19529.html
參考:http://www.jb51.net/article/83941.htm
參考:http://www.lxweimin.com/p/321bc95d077f
http://www.cocoachina.com/ios/20170206/18619.html
本文介紹了CocoaAsyncSocket庫中GCDAsyncSocket類的使用、粘包處理以及時間延遲測試.
一.CocoaAsyncSocket介紹
CocoaAsyncSocket中主要包含兩個類:
1.GCDAsyncSocket.
1
2用GCD搭建的基于TCP/IP協議的socket網絡庫
GCDAsyncSocket?is?a?TCP/IP?socket?networking?library?built?atop?Grand?Central?Dispatch.?--?引自CocoaAsyncSocket.
2.GCDAsyncUdpSocket.
1
2用GCD搭建的基于UDP/IP協議的socket網絡庫.
GCDAsyncUdpSocket?is?a?UDP/IP?socket?networking?library?built?atop?Grand?Central?Dispatch..--?引自CocoaAsyncSocket.
二.下載CocoaAsyncSocket
首先,需要到這里下載CocoaAsyncSocket.
下載后可以看到文件所在位置.
文件路徑
這里只要拷貝以下兩個文件到項目中.
TCP開發使用的文件
三.客戶端
因為,大部分項目已經有服務端socket,所以,先講解客戶端創建過程.
步驟:
1.繼承GCDAsyncSocketDelegate協議.
2.聲明屬性
1
2//?客戶端socket
@property?(strong,?nonatomic)?GCDAsyncSocket?*clientSocket;
3.創建socket并指定代理對象為self,代理隊列必須為主隊列.
1
self.clientSocket?=?[[GCDAsyncSocket?alloc]?initWithDelegate:self?delegateQueue:dispatch_get_main_queue()];
4.連接指定主機的對應端口.
1
2NSError?*error?=?nil;
self.connected?=?[self.clientSocket?connectToHost:self.addressTF.text?onPort:[self.portTF.text?integerValue]?viaInterface:nil?withTimeout:-1?error:&error];
5.成功連接主機對應端口號.
-?(void)socket:(GCDAsyncSocket?*)sock?didConnectToHost:(NSString?*)host?port:(uint16_t)port
{
//????NSLog(@"連接主機對應端口%@",?sock);
[self?showMessageWithStr:@"鏈接成功"];
[self?showMessageWithStr:[NSString?stringWithFormat:@"服務器IP:?%@-------端口:?%d",?host,port]];
//?連接成功開啟定時器
[self?addTimer];
//?連接后,可讀取服務端的數據
[self.clientSocket?readDataWithTimeout:-?1?tag:0];
self.connected?=?YES;
}
注意:
The host parameter will be an IP address, not a DNS name. -- 引自GCDAsyncSocket
連接的主機為IP地址,并非DNS名稱.
6.發送數據給服務端
//?發送數據
-?(IBAction)sendMessageAction:(id)sender
{
NSData?*data?=?[self.messageTextF.text?dataUsingEncoding:NSUTF8StringEncoding];
//?withTimeout?-1?:?無窮大,一直等
//?tag?:?消息標記
[self.clientSocket?writeData:data?withTimeout:-?1?tag:0];
}
注意:
發送數據主要通過- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag寫入數據的.
.讀取服務端數據
/**
讀取數據
@param?sock?客戶端socket
@param?data?讀取到的數據
@param?tag?本次讀取的標記
*/
-?(void)socket:(GCDAsyncSocket?*)sock?didReadData:(NSData?*)data?withTag:(long)tag
{
NSString?*text?=?[[NSString?alloc]initWithData:data?encoding:NSUTF8StringEncoding];
[self?showMessageWithStr:text];
//?讀取到服務端數據值后,能再次讀取
[self.clientSocket?readDataWithTimeout:-?1?tag:0];
}
注意:
有的人寫好代碼,而且第一次能夠讀取到數據,之后,再也接收不到數據.那是因為,在讀取到數據的代理方法中,需要再次調用[self.clientSocket readDataWithTimeout:- 1 tag:0];方法,框架本身就是這么設計的.
.客戶端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.
1
2self.clientSocket.delegate?=?nil;
self.clientSocket?=?nil;
.建立心跳連接.
//?計時器
@property?(nonatomic,?strong)?NSTimer?*connectTimer;
//?添加定時器
-?(void)addTimer
{
//?長連接定時器
self.connectTimer?=?[NSTimer?scheduledTimerWithTimeInterval:5.0?target:self?selector:@selector(longConnectToSocket)?userInfo:nil?repeats:YES];
//?把定時器添加到當前運行循環,并且調為通用模式
[[NSRunLoop?currentRunLoop]?addTimer:self.connectTimer?forMode:NSRunLoopCommonModes];
}
//?心跳連接
-?(void)longConnectToSocket
{
//?發送固定格式的數據,指令@"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];
}
注意:
心跳連接中發送給服務端的數據只是作為測試代碼,根據你們公司需求,或者和后臺商定好心跳包的數據以及發送心跳的時間間隔.因為這個項目的服務端socket也是我寫的,所以,我自定義心跳包協議.客戶端發送心跳包,服務端也需要有對應的心跳檢測,以此檢測客戶端是否在線.
四.服務端
步驟:
1.繼承GCDAsyncSocketDelegate協議.
2.聲明屬性
1
2//?服務端socket(開放端口,監聽客戶端socket的連接)
@property?(strong,?nonatomic)?GCDAsyncSocket?*serverSocket;
3.創建socket并指定代理對象為self,代理隊列必須為主隊列.
1
2//?初始化服務端socket
self.serverSocket?=?[[GCDAsyncSocket?alloc]initWithDelegate:self?delegateQueue:dispatch_get_main_queue()];
4.開放服務端的指定端口.
1
BOOL?result?=?[self.serverSocket?acceptOnPort:[self.portF.text?integerValue]?error:&error];
.連接上新的客戶端socket
//?連接上新的客戶端socket
-?(void)socket:(GCDAsyncSocket?*)sock?didAcceptNewSocket:(nonnull?GCDAsyncSocket?*)newSocket
{
//?保存客戶端的socket
[self.clientSockets?addObject:?newSocket];
//?添加定時器
[self?addTimer];
[self?showMessageWithStr:@"鏈接成功"];
[self?showMessageWithStr:[NSString?stringWithFormat:@"客戶端的地址:?%@?-------端口:?%d",?newSocket.connectedHost,?newSocket.connectedPort]];
[newSocket?readDataWithTimeout:-?1?tag:0];
}
.發送數據給客戶端
//?socket是保存的客戶端socket,?表示給這個客戶端socket發送消息
-?(IBAction)sendMessage:(id)sender
{
if(self.clientSockets?==?nil)return;
NSData?*data?=?[self.messageTextF.text?dataUsingEncoding:NSUTF8StringEncoding];
//?withTimeout?-1?:?無窮大,一直等
//?tag?:?消息標記
[self.clientSockets?enumerateObjectsUsingBlock:^(id??_Nonnull?obj,?NSUInteger?idx,?BOOL?*?_Nonnull?stop)?{
[obj?writeData:data?withTimeout:-1?tag:0];
}];
}
.讀取客戶端的數據
/**
讀取客戶端發送的數據
@param?sock?客戶端的Socket
@param?data?客戶端發送的數據
@param?tag?當前讀取的標記
*/
-?(void)socket:(GCDAsyncSocket?*)sock?didReadData:(NSData?*)data?withTag:(long)tag
{
NSString?*text?=?[[NSString?alloc]initWithData:data?encoding:NSUTF8StringEncoding];
[self?showMessageWithStr:text];
//?第一次讀取到的數據直接添加
if(self.clientPhoneTimeDicts.count?==?0)
{
[self.clientPhoneTimeDicts?setObject:[self?getCurrentTime]?forKey:text];
}
else
{
//?鍵相同,直接覆蓋,值改變
[self.clientPhoneTimeDicts?enumerateKeysAndObjectsUsingBlock:^(id??_Nonnull?key,?id??_Nonnull?obj,?BOOL?*?_Nonnull?stop)?{
[self.clientPhoneTimeDicts?setObject:[self?getCurrentTime]?forKey:text];
}];
}
[sock?readDataWithTimeout:-?1?tag:0];
}
.建立檢測心跳連接.
//?檢測心跳計時器
@property?(nonatomic,?strong)?NSTimer?*checkTimer;
//?添加計時器
-?(void)addTimer
{
//?長連接定時器
self.checkTimer?=?[NSTimer?scheduledTimerWithTimeInterval:10.0?target:self?selector:@selector(checkLongConnect)?userInfo:nil?repeats:YES];
//?把定時器添加到當前運行循環,并且調為通用模式
[[NSRunLoop?currentRunLoop]?addTimer:self.checkTimer?forMode:NSRunLoopCommonModes];
}
//?檢測心跳
-?(void)checkLongConnect
{
[self.clientPhoneTimeDicts?enumerateKeysAndObjectsUsingBlock:^(id??_Nonnull?key,?id??_Nonnull?obj,?BOOL?*?_Nonnull?stop)?{
//?獲取當前時間
NSString?*currentTimeStr?=?[self?getCurrentTime];
//?延遲超過10秒判斷斷開
if(([currentTimeStr?doubleValue]?-?[obj?doubleValue])?>?10.0)
{
[self?showMessageWithStr:[NSString?stringWithFormat:@"%@已經斷開,連接時差%f",key,[currentTimeStr?doubleValue]?-?[obj?doubleValue]]];
[self?showMessageWithStr:[NSString?stringWithFormat:@"移除%@",key]];
[self.clientPhoneTimeDicts?removeObjectForKey:key];
}
else
{
[self?showMessageWithStr:[NSString?stringWithFormat:@"%@處于連接狀態,連接時差%f",key,[currentTimeStr?doubleValue]?-?[obj?doubleValue]]];
}
}];
}
心跳檢測方法只提供部分思路:
1.懶加載一個可變字典,字典的鍵作為客戶端的標識.如:客戶端標識為13123456789.
2.在- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag方法中,將讀取到的數據或者數據中的部分字符串作為鍵.字典的值為系統當前時間.服務端第一次讀取數據時,字典中沒有數據,所以,直接添加到可變字典中,之后每次讀取數據時,都用字典的setObject: forKey:方法添加字典,若存儲的鍵相同,即客戶端標識相同,鍵會被覆蓋,再使用系統的當前時間作為值.
3.在- (void)checkLongConnect中,獲取此時的當前時間,遍歷字典,將每個鍵的值和當前時間進行比較即可.判斷的延遲時間可以寫8秒.時間自定.之后,再根據自己的需求進行后續處理.
五.數據粘包處理.
1.粘包情況.
例如:包數據為:abcd.
2.粘包解決思路.
思路1:
發送方將數據包加上包頭和包尾,包頭、包體以及包尾用字典形式包裝成json字符串,接收方,通過解析獲取json字符串中的包體,便可進行進一步處理.
例如:
{
//?head:包頭,body:包體,end:包尾
NSDictionary?*dict?=?@{
@"head":?@"phoneNum",
@"body":?@(13133334444),
@"end":?@(11)};
}
思路2:
添加前綴.和包內容拼接成同一個字符串.
例如:當發送數據是13133334444,如果出現粘包情況只屬于完整型:
13133334444
1313333444413133334444
131333344441313333444413133334444...
可以將ab作為前綴.則接收到的數據出現的粘包情況:
ab13133334444
ab13133334444ab13133334444
ab13133334444ab13133334444ab13133334444...
使用componentsSeparatedByString:方法,以ab為分隔符,將每個包內容存入數組中,再取對應數組中的數據操作即可.
思路3:
如果最終要得到的數據的長度是個固定長度,用一個字符串作為緩沖池,每次收到數據,都用字符串拼接對應數據,每當字符串的長度和固定長度相同時,便得到一個完整數據,處理完這個數據并清空字符串,再進行下一輪的字符拼接.
例如:處理上面的不完整型.創建一個長度是4的tempData字符串作為數據緩沖池.第1次收到數據,數據是:ab,tempData拼接上ab,tempData中只能再存儲2個字符,第2次收到數據,將數據長度和2進行比較,第2次的數據是:cda,截取前兩位字符,即cd,tempData繼續拼接cd,此時,tempData為abcd,就是我們想要的數據,我們可以處理這個數據,處理之后并清空tempData,將第2次收到數據的剩余數據,即cda中的a,再與tempData拼接.之后,再進行類似操作.
核心代碼
/**
處理數據粘包
@param?readData?讀取到的數據
*/
-?(void)dealStickPackageWithData:(NSString?*)readData
{
//?緩沖池還需要存儲的數據個數
NSInteger?tempCount;
if(readData.length?>?0)
{
//?還差tempLength個數填滿緩沖池
tempCount?=?4?-?self.tempData.length;
if(readData.length?<=?tempCount)
{
self.tempData?=?[self.tempData?stringByAppendingString:readData];
if(self.tempData.length?==?4)
{
[self.mutArr?addObject:self.tempData];
self.tempData?=?@"";
}
}
else
{
//?下一次的數據個數比要填滿緩沖池的數據個數多,一定能拼接成完整數據,剩余的繼續
self.tempData?=?[self.tempData?stringByAppendingString:[readData?substringToIndex:tempCount]];
[self.mutArr?addObject:self.tempData];
self.tempData?=?@"";
//?多余的再執行一次方法
[self?dealStickPackageWithData:[readData?substringFromIndex:tempCount]];
}
}
}
調用
//?存儲處理后的每次返回數據
@property?(nonatomic,?strong)?NSMutableArray?*mutArr;
//?數據緩沖池
@property?(nonatomic,?copy)?NSString?*tempData;
/**?第四次測試?--?混合型**/
self.mutArr?=?nil;
/*
第1次?:?abc
第2次?:?da
第3次?:?bcdabcd
第4次?:?abcdabcd
第5次?:?abcdabcdab
*/
//?數組中的數據代表每次接收的數據
NSArray?*testArr4?=?[NSArray?arrayWithObjects:@"abc",@"da",@"bcdabcd",@"abcdabcd",@"abcdabcdab",?nil];
self.tempData?=?@"";
for(NSInteger?i?=?0;?i?<?testArr4.count;?i++)
{
[self?dealStickPackageWithData:testArr4[i]];
}
NSLog(@"testArr4?=?%@",self.mutArr);
結果:
92017-06-09?00:49:12.932976+0800?StickPackageDealDemo[10063:3430118]?testArr4?=?(
abcd,
abcd,
abcd,
abcd,
abcd,
abcd,
abcd
)
數據粘包處理Demo在文末.
六.測試.
1.測試配置.
測試時,兩端需要處于同一WiFi下.客戶端中的IP地址為服務端的IP地址,具體信息進入Wifi設置中查看.
IP和端口描述
2.測試所需環境.
將客戶端程序安裝在每個客戶端,讓一臺服務端測試機和一臺客戶端測試機連接mac并運行,這兩臺測試機可以看到打印結果,所有由服務端發送到客戶端的數據,通過客戶端再回傳給服務端,在服務端看打印結果.
當年的圖
3.進行延遲差測試.
延遲差即服務端發送數據到第一臺客戶端和服務端發送數據到最后一臺客戶端的時間差.根據服務端發送數據給不同數量的客戶端進行測試.而且,發送數據時,是隨機發送.
延遲差測試結果:
延遲差測試
由圖所知,延遲差在200毫秒以內的比例基本保持在99%以上.所以符合開發需求(延遲在200毫秒以內).
4.單次信息收發測試.
讓服務端給每個客戶端隨機發送200次數據.并計算服務端發送數據到某一客戶端,完整的一次收發時間情況.
單次信息收發測試結果:
單次信息收發測試
由圖所知,一次收發時間基本在95%以上,這個時間會根據網絡狀態和數據包大小波動.不過,可以直觀看到數據從服務端到客戶端的時間.
GitHub:
CocoaAsyncSocket客戶端Demo(含粘包解決和測試)
CocoaAsyncSocket服務端Demo(含粘包解決和測試)
iOS應用中使用AsyncSocket庫處理Socket通信的用法講解
這篇文章主要介紹了iOS應用中使用AsyncSocket庫處理Socket通信的用法講解,AsyncSocket同時支持TCP和UDP,文中展示了其建立斷開連接及發送接收消息的操作,very好用,需要的朋友可以參考下
用socket可以實現像QQ那樣發送即時消息的功能。客戶端和服務端需要建立長連接,在長連接的情況下,發送消息。客戶端可以發送心跳包來檢測長連接。
在iOS開發中使用socket,一般都是用第三方庫AsyncSocket,不得不承認這個庫確實很強大。下載地址CocoaAsyncSocket
。
特性
AsyncSocket類是支持TCP的。
AsyncUdpSocket是支持UDP的。
AsyncSocket是封裝了CFSocket和CFSteam的TCP/IP socket網絡庫。它提供了異步操作,本地cocoa類的基于delegate的完整支持。主要有以下特性:
隊列的非阻塞的讀和寫,而且可選超時。你可以調用它讀取和寫入,它會當完成后告知你。
自動的socket接收。如果你調用它接收連接,它將為每個連接啟動新的實例,當然,也可以立即關閉這些連接。
委托(delegate)支持。錯誤、連接、接收、完整的讀取、完整的寫入、進度以及斷開連接,都可以通過委托模式調用。
基于run loop的,而不是線程的。雖然可以在主線程或者工作線程中使用它,但你不需要這樣做。它異步的調用委托方法,使用NSRunLoop。委托方法包括socket的參數,可讓你在多個實例中區分。
自包含在一個類中。你無需操作流或者socket,這個類幫你做了全部。
支持基于IPV4和IPV6的TCP流。
AsyncUdpSocket是UDP/IP socket網絡庫,包裝自CFSocket。它的工作很像TCP版本,只不過是用于處理UDP的。它包括基于非阻塞隊列的發送接收操作,完整的委托支持,基于runloop,自包含的類,以及支持IPV4和IPV6。
使用AsyncSocket的時候可以做一層封裝,根據需求提供幾個接口出來。比如:連接、斷開連接、發送消息等等。還有接受消息,接受到的消息可以通過通知、代理、block等傳出去。
下面簡單介紹一下對AsyncSocket使用.一般來說,一個用戶只需要建立一個socket長連接,所以可以用單例類方便使用。
定義單列類:LGSocketServe
LGSocketServe.h
復制代碼代碼如下:
//
//? LGSocketServe.h
//? AsyncSocketDemo
//
#import
#import "AsyncSocket.h"
@interface LGSocketServe : NSObject
+ (LGSocketServe *)sharedSocketServe;
@end
LGSocketServe.m
復制代碼代碼如下:
//
//? LGSocketServe.m
//? AsyncSocketDemo
//
#import "LGSocketServe.h"
@implementation LGSocketServe
static LGSocketServe *socketServe = nil;
#pragma mark public static methods
+ (LGSocketServe *)sharedSocketServe {
@synchronized(self) {
if(socketServe == nil) {
socketServe = [[[self class] alloc] init];
}
}
return socketServe;
}
+(id)allocWithZone:(NSZone *)zone
{
@synchronized(self)
{
if (socketServe == nil)
{
socketServe = [super allocWithZone:zone];
return socketServe;
}
}
return nil;
}
@end
建立socket長連接
LGSocketServe.h
復制代碼代碼如下:
@property (nonatomic, strong) AsyncSocket???????? *socket;?????? // socket
//? socket連接
- (void)startConnectSocket;
LGSocketServe.m
//自己設定
#define HOST @"192.168.0.1"
#define PORT 8080
//設置連接超時
#define TIME_OUT 20
- (void)startConnectSocket
{
self.socket = [[AsyncSocket alloc] initWithDelegate:self];
[self.socket setRunLoopModes:[NSArray arrayWithObject:NSRunLoopCommonModes]];
if ( ![self SocketOpen:HOST port:PORT] )
{
}
}
- (NSInteger)SocketOpen:(NSString*)addr port:(NSInteger)port
{
if (![self.socket isConnected])
{
NSError *error = nil;
[self.socket connectToHost:addr onPort:port withTimeout:TIME_OUT error:&error];
}
return 0;
}
宏定義一下HOST、PORT、TIME_OUT,實現startConnectSocket方法。這個時候要設置一下AsyncSocket的代理AsyncSocketDelegate。當長連接成功之后會調用:
復制代碼代碼如下:
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
{
//這是異步返回的連接成功,
NSLog(@"didConnectToHost");
}
心跳
LGSocketServe.h
復制代碼代碼如下:
@property (nonatomic, retain) NSTimer???????????? *heartTimer;?? // 心跳計時器
LGSocketServe.m
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
{
//這是異步返回的連接成功,
NSLog(@"didConnectToHost");
//通過定時器不斷發送消息,來檢測長連接
self.heartTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(checkLongConnectByServe) userInfo:nil repeats:YES];
[self.heartTimer fire];
}
// 心跳連接
-(void)checkLongConnectByServe{
// 向服務器發送固定可是的消息,來檢測長連接
NSString *longConnect = @"connect is here";
NSData?? *data? = [longConnect dataUsingEncoding:NSUTF8StringEncoding];
[self.socket writeData:data withTimeout:1 tag:1];
}
在連接成功的回調方法里,啟動定時器,每隔2秒向服務器發送固定的消息來檢測長連接。(這個根據服務器的需要就可以了)
斷開連接
1,用戶手動斷開連接
LGSocketServe.h
復制代碼代碼如下:
// 斷開socket連接
-(void)cutOffSocket;
LGSocketServe.m
-(void)cutOffSocket
{
self.socket.userData = SocketOfflineByUser;
[self.socket disconnect];
}
cutOffSocket是用戶斷開連接之后,不在嘗試重新連接。
2,wifi斷開,socket斷開連接
LGSocketServe.m
復制代碼代碼如下:
- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err
{
NSLog(@" willDisconnectWithError %ld?? err = %@",sock.userData,[err description]);
if (err.code == 57) {
self.socket.userData = SocketOfflineByWifiCut;
}
}
wifi斷開之后,會回調onSocket:willDisconnectWithError:方法,err.code == 57,這個時候設置self.socket.userData = SocketOfflineByWifiCut。
重新連接
socket斷開之后會回調:
LGSocketServe.m
復制代碼代碼如下:
- (void)onSocketDidDisconnect:(AsyncSocket *)sock
{
NSLog(@"7878 sorry the connect is failure %ld",sock.userData);
if (sock.userData == SocketOfflineByServer) {
// 服務器掉線,重連
[self startConnectSocket];
}
else if (sock.userData == SocketOfflineByUser) {
// 如果由用戶斷開,不進行重連
return;
}else if (sock.userData == SocketOfflineByWifiCut) {
// wifi斷開,不進行重連
return;
}
}
在onSocketDidDisconnect回調方法里面,會根據self.socket.userData來判斷是否需要重新連接。
發送消息
LGSocketServe.h
復制代碼代碼如下:
// 發送消息
- (void)sendMessage:(id)message;
LGSocketServe.m
//設置寫入超時 -1 表示不會使用超時
#define WRITE_TIME_OUT -1
- (void)sendMessage:(id)message
{
//像服務器發送數據
NSData *cmdData = [message dataUsingEncoding:NSUTF8StringEncoding];
[self.socket writeData:cmdData withTimeout:WRITE_TIME_OUT tag:1];
}
//發送消息成功之后回調
- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag
{
}
發送消息成功之后會調用onSocket:didWriteDataWithTag:,在這個方法里可以進行讀取消息。
接受消息
LGSocketServe.m
復制代碼代碼如下:
//設置讀取超時 -1 表示不會使用超時
#define READ_TIME_OUT -1
#define MAX_BUFFER 1024
//發送消息成功之后回調
- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag
{
//讀取消息
[self.socket readDataWithTimeout:-1 buffer:nil bufferOffset:0 maxLength:MAX_BUFFER tag:0];
}
//接受消息成功之后回調
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
//服務端返回消息數據量比較大時,可能分多次返回。所以在讀取消息的時候,設置MAX_BUFFER表示每次最多讀取多少,當data.length < MAX_BUFFER我們認為有可能是接受完一個完整的消息,然后才解析
if( data.length < MAX_BUFFER )
{
//收到結果解析...
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
NSLog(@"%@",dic);
//解析出來的消息,可以通過通知、代理、block等傳出去
}
[self.socket readDataWithTimeout:READ_TIME_OUT buffer:nil bufferOffset:0 maxLength:MAX_BUFFER tag:0];
接受消息后去解析,然后可以通過通知、代理、block等傳出去。在onSocket:didReadData:withTag:回調方法里面需要不斷讀取消息,因為數據量比較大的話,服務器會分多次返回。所以我們需要定義一個MAX_BUFFER的宏,表示每次最多讀取多少。當data.length < MAX_BUFFER我們認為有可能是接受完一個完整的消息,然后才解析 。
出錯處理
LGSocketServe.m
復制代碼代碼如下:
- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err
{
NSData * unreadData = [sock unreadData]; // ** This gets the current buffer
if(unreadData.length > 0) {
[self onSocket:sock didReadData:unreadData withTag:0]; // ** Return as much data that could be collected
} else {
NSLog(@" willDisconnectWithError %ld?? err = %@",sock.userData,[err description]);
if (err.code == 57) {
self.socket.userData = SocketOfflineByWifiCut;
}
}
}
socket出錯會回調onSocket:willDisconnectWithError:方法,可以通過unreadData來讀取未來得及讀取的buffer。
使用
導入#import “LGSocketServe.h”
復制代碼代碼如下:
LGSocketServe *socketServe = [LGSocketServe sharedSocketServe];
//socket連接前先斷開連接以免之前socket連接沒有斷開導致閃退
[socketServe cutOffSocket];
socketServe.socket.userData = SocketOfflineByServer;
[socketServe startConnectSocket];
//發送消息 @"hello world"只是舉個列子,具體根據服務端的消息格式
[socketServe sendMessage:@"hello world"];
如對本文有疑問,請提交到交流社區,廣大熱心網友會為你解答!!點擊進入社區
您可能感興趣的文章: