鑒于服務器已經(jīng)有搭建好的socket服務器氧吐,所以我只關(guān)注iOS端如何通過CocoaSyncSocket實現(xiàn)客服聊天
創(chuàng)建聊天工具單例KefuHelper区丑,遵守協(xié)議GCDAsyncSocketDelegate
- 初始化時,創(chuàng)建socket對象clientSocket【GCDAsyncSocket】
-(instancetype)init{
if (self = [super init]) {
self.clientSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
}
return self;
}
- 鏈接服務器socket
-(void)connect{
NSError *error = nil;
self.isConnected = [self.clientSocket connectToHost:@"47.94.39.111" onPort:8283 error:&error];
QMPLog(@"鏈接socket服務器----%@",error);
}
- GCDAsyncSocket代理方法實現(xiàn)
#pragma mark - GCDAsyncSocketDelegate
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
// NSLog(@"連接主機對應端口%@", sock);
[PublicTool showMsg:@"鏈接成功"];
QMPLog(@"服務器IP: %@-------端口: %d", host,port);
// 連接成功開啟定時器,發(fā)送心跳包壁袄,用于檢測鏈接是否斷開
[self addTimer];
// 連接后,可讀取服務端的數(shù)據(jù)
[self.clientSocket readDataWithTimeout:-1 tag:0];
self.isConnected = YES;
}
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
[PublicTool showMsg:@"斷開連接"];
QMPLog(@"服務器鏈接失敗: -----%@", err);
self.clientSocket.delegate = nil;
self.clientSocket = nil;
self.isConnected = NO;
[self.connectTimer invalidate];
}
// 接收到socket服務器發(fā)來的消息丧靡,發(fā)送消息后端寫了相應API, 不通過socket發(fā)送
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
NSString *text = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
[PublicTool showMsg:[NSString stringWithFormat:@"socket接收數(shù)據(jù)--%@",text]];
// 讀取到服務端數(shù)據(jù)值后,能再次讀取
[self.clientSocket readDataWithTimeout:-1 tag:0];
}
- 心跳連接
- (void)addTimer{
self.connectTimer = [NSTimer scheduledTimerWithTimeInterval:5.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];
}
GCDAsyncSocket常見問題
- Error Domain=GCDAsyncSocketErrorDomain Code=4 "Read operation timed out" UserInfo=0xa8db6a0 {NSLocalizedDescription=Read operation timed out}
scoket讀取數(shù)據(jù)超時饮戳,當網(wǎng)絡不怎么穩(wěn)定通信方給發(fā)送消息的時候時不時的會冒一個這個錯誤呻征,而且Socket也會自動斷開連接。一直跟蹤GCDAsyncSocket.m的代碼5068行<可能代碼有更新的會有點差異>有一個方法
- (void)setupReadTimerWithTimeout:(NSTimeInterval)timeout
這個方法是就是專門監(jiān)聽socket讀取數(shù)據(jù)是否有超時的現(xiàn)象的方法,源代碼設置成if(timeout >= 0.0)即檢測到超時就拋異常 這樣很容易導致socket連接異常叼丑。
處理方式:你可以打印一下這個timeout值关翎,就會大概知道你的socket讀取數(shù)據(jù)超時的范圍,在項目允許的范圍內(nèi)設置這個值的大小鸠信,因為我的項目總是在10以內(nèi)纵寝,所以我設置成if(timeout > 10.0)之后,基本運行的時候就很少拋這個異常了星立。你也可以再接收到這個異常的時候重新連接一次爽茴。
- Error Domain=GCDAsyncSocketErrorDomain Code=3 "Attempt to connect to host timed out" UserInfo=0x7bd14f40 {NSLocalizedDescription=Attempt to connect to host timed out}
socket連接的時候超時葬凳,一般發(fā)生在你向服務端發(fā)送一條連接消息的時候,服務端無響應室奏,一般是由于服務端沒有開啟服務火焰,也有可能是設置響應時間的timeout值過小,在GCDAsyncSocket.m的代碼1938行的位置有一個設置timeout的地方 你可以設置一個稍微比較長的響應時間
- (BOOL)connectToHost:(NSString*)host onPort:(uint16_t)port error:(NSError **)errPtr
{
return [self connectToHost:host onPort:port withTimeout:5 error:errPtr];
}
Error Domain=GCDAsyncSocketErrorDomain Code=51胧沫,網(wǎng)絡斷開昌简,可以檢查一下網(wǎng)絡連接狀態(tài)
Error Domain=NSPOSIXErrorDomain Code=61 "Connection refused" UserInfo=0x7b288750 {NSLocalizedFailureReason=Error in connect() function, NSLocalizedDescription=Connection refused}
服務器沒啟動,或者端口沒開啟琳袄。