// // GiGaSocketRocketUtility.m // LXAnimationTest // // Created by lianxiang on 2018/8/17. // Copyright © 2018年 com.giga.ios. All rights reserved. // #import "GiGaSocketRocketUtility.h" #define dispatch_main_async_safe(block)\ if ([NSThread isMainThread]) {\ block();\ } else {\ dispatch_async(dispatch_get_main_queue(), block);\ } NSString * const kNeedPayOrderNote = @"kNeedPayOrderNote";//发送的通知名 NSString * const kWebSocketDidOpenNote = @"kWebSocketdidReceiveMessageNote"; NSString * const kWebSocketDidCloseNote = @"kWebSocketDidCloseNote"; NSString * const kWebSocketdidReceiveMessageNote = @"kWebSocketdidReceiveMessageNote"; @interface GiGaSocketRocketUtility() { int _index; NSTimer *heartBeat; NSTimeInterval reConnectTime; } @property (nonatomic,strong) SRWebSocket *socket; @property (nonatomic,copy) NSString *urlString; @end @implementation GiGaSocketRocketUtility +(GiGaSocketRocketUtility *)shareInstance{ static GiGaSocketRocketUtility *instance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[self alloc] init]; }); return instance; } //开启链接 -(void)srWebSocketOpenWithURLString:(NSString *)url{ if (!self.socket) { return; } if (!url) { return; } self.urlString = url; self.socket = [[SRWebSocket alloc] initWithURLRequest: [NSURLRequest requestWithURL:[NSURL URLWithString:url]]] ; NSLog(@"请求的websocket地址:%@",self.socket.url.absoluteString); self.socket.delegate = self; [self.socket open]; } //关闭连接 -(void)srWebSocketClose{ if (self.socket) { [self.socket close]; self.socket = nil; [self removeHeartBeat]; } } //移除心跳 -(void)removeHeartBeat{ dispatch_main_async_safe(^{ if (self->heartBeat) { if ([self->heartBeat respondsToSelector:@selector(isValid)]) { if ([self->heartBeat isValid]) { [self->heartBeat invalidate]; self->heartBeat = nil; } } } } ) } //初始化心跳 - (void)initHeartBeat { dispatch_main_async_safe(^{ [self removeHeartBeat]; //心跳设置为3分钟,NAT超时一般为5分钟 self->heartBeat = [NSTimer timerWithTimeInterval:3 target:self selector:@selector(sentheart) userInfo:nil repeats:YES]; //和服务端约定好发送什么作为心跳标识,尽可能的减小心跳包大小 [[NSRunLoop currentRunLoop] addTimer:self->heartBeat forMode:NSRunLoopCommonModes]; }) } -(void)sentheart{ //发送心跳 和后台可以约定发送什么内容 一般可以调用ping 这里发送data [self sendData:@"heart"]; } #define WeakSelf(ws) __weak __typeof(&*self)weakSelf = self - (void)sendData:(id)data { NSLog(@"socketSendData --------------- %@",data); WeakSelf(ws); dispatch_queue_t queue = dispatch_queue_create("giga", NULL); dispatch_async(queue, ^{ if (weakSelf.socket != nil) { // 只有 SR_OPEN 开启状态才能调 send 方法啊,不然要崩 if (weakSelf.socket.readyState == SR_OPEN) { [weakSelf.socket send:data]; // 发送数据 } else if (weakSelf.socket.readyState == SR_CONNECTING) { NSLog(@"正在连接中,重连后其他方法会去自动同步数据"); // 每隔2秒检测一次 socket.readyState 状态,检测 10 次左右 // 只要有一次状态是 SR_OPEN 的就调用 [ws.socket send:data] 发送数据 // 如果 10 次都还是没连上的,那这个发送请求就丢失了,这种情况是服务器的问题了,小概率的 //重连 [self reConnect]; } else if (weakSelf.socket.readyState == SR_CLOSING || weakSelf.socket.readyState == SR_CLOSED) { // websocket 断开了,调用 reConnect 方法重连 NSLog(@"重连"); [self reConnect]; } } else { NSLog(@"没网络,发送失败,一旦断网 socket 会被我设置 nil 的"); } }); } //pingPong - (void)ping{ if (self.socket.readyState == SR_OPEN) { [self.socket sendPing:nil]; } } #pragma mark - socket delegate - (void)webSocketDidOpen:(SRWebSocket *)webSocket{ //每次正常连接的时候清零重连时间 reConnectTime = 0; //开启心跳 [self initHeartBeat]; if (webSocket == self.socket) { NSLog(@"************************** socket 连接成功************************** "); [[NSNotificationCenter defaultCenter] postNotificationName:kWebSocketDidOpenNote object:nil]; } } - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error { if (webSocket == self.socket) { NSLog(@"************************** socket 连接失败************************** "); NSLog(@"err:%@",error); _socket = nil; //连接失败就重连 [self reConnect]; } } - (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean{ if (webSocket == self.socket) { NSLog(@"************************** socket连接断开************************** "); NSLog(@"被关闭连接,code:%ld,reason:%@,wasClean:%d",(long)code,reason,wasClean); [self srWebSocketClose]; [[NSNotificationCenter defaultCenter] postNotificationName:kWebSocketDidCloseNote object:nil]; } } /*该函数是接收服务器发送的pong消息,其中最后一个是接受pong消息的, 在这里就要提一下心跳包,一般情况下建立长连接都会建立一个心跳包, 用于每隔一段时间通知一次服务端,客户端还是在线,这个心跳包其实就是一个ping消息, 我的理解就是建立一个定时器,每隔十秒或者十五秒向服务端发送一个ping消息,这个消息可是是空的 */ -(void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload{ NSString *reply = [[NSString alloc] initWithData:pongPayload encoding:NSUTF8StringEncoding]; NSLog(@"pongPayload===%@",reply); } - (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message{ if (webSocket == self.socket) { NSLog(@"************************** socket收到数据了************************** "); NSLog(@"我这后台约定的 message 是 json 格式数据收到数据,就按格式解析吧,然后把数据发给调用层"); NSLog(@"message:%@",message); [[NSNotificationCenter defaultCenter] postNotificationName:kWebSocketdidReceiveMessageNote object:message]; } } #pragma mark methods //重连机制 -(void)reConnect{ [self srWebSocketClose]; //超过一分钟就不再重连 所以只会重连5次 2^5 = 64 if (reConnectTime > 64) { return; } dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(reConnectTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ self.socket = nil; [self srWebSocketOpenWithURLString:self.urlString]; NSLog(@"重连"); }); //重连时间2的指数级增长 if (reConnectTime ==0 ) { reConnectTime = 2; }else{ reConnectTime *=2; } } - (SRReadyState)socketReadyState{ return self.socket.readyState; } -(void)dealloc{ [[NSNotificationCenter defaultCenter] removeObserver:self]; } @end