334 lines
11 KiB
Objective-C
Executable File
334 lines
11 KiB
Objective-C
Executable File
//
|
||
// WebSocketManager.m
|
||
// iosWebSocket
|
||
//
|
||
// Created by yanglele on 2019/5/30.
|
||
// Copyright © 2019 yanglele. All rights reserved.
|
||
//
|
||
|
||
#import "WebSocketManager.h"
|
||
|
||
#define bj_main_async_safe(block)\
|
||
if ([NSThread isMainThread]) {\
|
||
block();\
|
||
} else {\
|
||
dispatch_async(dispatch_get_main_queue(), block);\
|
||
}
|
||
|
||
@interface WebSocketManager ()<SRWebSocketDelegate>
|
||
|
||
@property(nonatomic, strong) NSTimer *headerBeatTimer; //心跳定时器
|
||
@property(nonatomic, strong) NSTimer *networkTestingTimer; //没有网络的时候检测定时器
|
||
@property(nonatomic, assign) NSTimeInterval reConnectTime; //重连时间
|
||
@property(nonatomic, strong) NSMutableArray *sendDataArray; //存储要发送给服务器的数据
|
||
@property(nonatomic, assign) BOOL isActiveClose; //用于判断是否主动关闭长连接,如果是主动断开连接,连接失败的代理中,就不用执行 重新连接方法
|
||
|
||
/*房间ID*/
|
||
@property(nonatomic, copy) NSString *roomID;
|
||
|
||
@end
|
||
|
||
|
||
@implementation WebSocketManager
|
||
|
||
+(instancetype)shared{
|
||
static WebSocketManager *__instance = nil;
|
||
static dispatch_once_t onceToken;
|
||
dispatch_once(&onceToken, ^{
|
||
__instance = [[WebSocketManager alloc] init];
|
||
});
|
||
return __instance;
|
||
}
|
||
|
||
-(instancetype)init{
|
||
self = [super init];
|
||
if (self) {
|
||
self.reConnectTime = 0;
|
||
self.isActiveClose = NO;
|
||
self.sendDataArray = [[NSMutableArray alloc] init];
|
||
}
|
||
return self;
|
||
}
|
||
|
||
//建立长连接
|
||
-(void)connectServerWithRoomID:(NSString *)roomID{
|
||
if(self.webScoket){
|
||
return;
|
||
}
|
||
self.roomID = roomID;
|
||
self.webScoket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:GVUSER.socketUrl]];
|
||
self.webScoket.delegate = self;
|
||
[self.webScoket open];
|
||
}
|
||
|
||
-(void)sendPing:(id)sender{
|
||
// NSLog(@"sendPing heart");
|
||
NSData *heartData = [[NSData alloc] initWithBase64EncodedString:@"heart" options:NSUTF8StringEncoding];
|
||
[self.webScoket sendPing:heartData error:NULL];
|
||
}
|
||
|
||
//关闭长连接
|
||
-(void)webSocketClose{
|
||
self.isActiveClose = YES;
|
||
self.isConnect = NO;
|
||
self.socketStatus = WebSocketStatusDefault;
|
||
|
||
if (self.webScoket) {
|
||
[self.webScoket close];
|
||
self.webScoket = nil;
|
||
}
|
||
//关闭心跳定时器
|
||
[self destoryHeartBeat];
|
||
//关闭网络检测定时器
|
||
[self destoryNetWorkStartTesting];
|
||
}
|
||
|
||
#pragma mark socket delegate
|
||
//已经连接
|
||
-(void)webSocketDidOpen:(SRWebSocket *)webSocket{
|
||
NSLog(@"✅✅已经连接,开启心跳");
|
||
self.isConnect = YES;
|
||
self.socketStatus = WebSocketStatusConnect;
|
||
|
||
//{"action":"register","data":{"hid":"","uid":""}}
|
||
NSDictionary *dataDict = @{
|
||
@"hid":C_string(_roomID),
|
||
@"uid":C_string(BJUserManager.userInfo.uid),
|
||
};;
|
||
NSDictionary *sendInfo = @{
|
||
@"action":@"register",
|
||
@"data": dataDict
|
||
};
|
||
|
||
NSString *jsonString = sendInfo.mj_JSONString;
|
||
[self.webScoket sendString:jsonString error:nil];
|
||
[self initHeartBeat];//开始心跳
|
||
}
|
||
|
||
//连接失败
|
||
-(void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error{
|
||
NSLog(@"连接失败");
|
||
self.isConnect = NO;
|
||
self.socketStatus = WebSocketStatusDisConnect;
|
||
NSLog(@"连接失败,这里可以实现掉线自动重连,要注意以下几点");
|
||
NSLog(@"1.判断当前网络环境,如果断网了就不要连了,等待网络到来,在发起重连");
|
||
NSLog(@"2.判断调用层是否需要连接,不需要的时候不k连接,浪费流量");
|
||
NSLog(@"3.连接次数限制,如果连接失败了,重试10次左右就可以了");
|
||
//判断网络环境
|
||
if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable) {
|
||
//没有网络,开启网络监测定时器
|
||
[self noNetWorkStartTesting];//开启网络检测定时器
|
||
}else{
|
||
[self reConnectServer];//连接失败,重新连接
|
||
}
|
||
|
||
}
|
||
|
||
//接收消息
|
||
-(void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message{
|
||
// NSLog(@"✅✅接收消息 ---- %@", message);
|
||
if (self.delegate && [self.delegate respondsToSelector:@selector(webSocketDidReceiveMessage:)]) {
|
||
[self.delegate webSocketDidReceiveMessage:[self dictionaryWithJsonString:message]];
|
||
}
|
||
}
|
||
//字符串转字典
|
||
- (NSDictionary *)dictionaryWithJsonString:(NSString *)jsonString
|
||
{
|
||
if (jsonString == nil) {
|
||
return nil;
|
||
}
|
||
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
|
||
NSError *err;
|
||
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData
|
||
options:NSJSONReadingMutableContainers
|
||
error:&err];
|
||
if(err)
|
||
{
|
||
NSLog(@"json解析失败:%@",err);
|
||
return nil;
|
||
}
|
||
return dic;
|
||
}
|
||
//- (BOOL)sendString:(NSString *)string error:(NSError **)error
|
||
//{
|
||
// if (self.socketStatus != SR_OPEN) {
|
||
// NSString *message = @"Invalid State: Cannot call `sendString:error:` until connection is open.";
|
||
// if (error) {
|
||
//// *error = SRErrorWithCodeDescription(2134, message);
|
||
// }
|
||
//// SRDebugLog(message);
|
||
// return NO;
|
||
// }
|
||
//
|
||
// string = [string copy];
|
||
// dispatch_async(_workQueue, ^{
|
||
// [self _sendFrameWithOpcode:SROpCodeTextFrame data:[string dataUsingEncoding:NSUTF8StringEncoding]];
|
||
// });
|
||
// return YES;
|
||
//}
|
||
//关闭连接
|
||
-(void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean{
|
||
self.isConnect = NO;
|
||
if (self.isActiveClose) {
|
||
self.socketStatus = WebSocketStatusDefault;
|
||
return;
|
||
}else{
|
||
self.socketStatus = WebSocketStatusDisConnect;
|
||
}
|
||
NSLog(@"❌❌被关闭连接,code:%ld,reason:%@,wasClean:%d",code,reason,wasClean);
|
||
|
||
[self destoryHeartBeat]; //断开时销毁心跳
|
||
|
||
//判断网络
|
||
if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable) {
|
||
//没有网络,开启网络监测定时器
|
||
[self noNetWorkStartTesting];
|
||
}else{
|
||
//有网络
|
||
NSLog(@"关闭网络");
|
||
self.webScoket = nil;
|
||
[self reConnectServer];
|
||
}
|
||
}
|
||
|
||
|
||
/**
|
||
接受服务端发生Pong消息,我们在建立长连接之后会建立与服务器端的心跳包
|
||
心跳包是我们用来告诉服务端:客户端还在线,心跳包是ping消息,于此同时服务端也会返回给我们一个pong消息
|
||
*/
|
||
-(void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongData{
|
||
// NSLog(@"✅✅接受ping 数据 --> %@",pongData);
|
||
}
|
||
|
||
#pragma mark NSTimer
|
||
//初始化心跳
|
||
-(void)initHeartBeat{
|
||
if (self.headerBeatTimer) {
|
||
return;
|
||
}
|
||
[self destoryHeartBeat];
|
||
bj_main_async_safe(^{
|
||
self.headerBeatTimer = [NSTimer timerWithTimeInterval:10 target:self selector:@selector(senderheartBeat) userInfo:nil repeats:YES];
|
||
[[NSRunLoop currentRunLoop] addTimer:self.headerBeatTimer forMode:NSRunLoopCommonModes];
|
||
});
|
||
}
|
||
|
||
//重新连接
|
||
-(void)reConnectServer{
|
||
|
||
//关闭之前的连接
|
||
[self webSocketClose];
|
||
|
||
//重连10次 2^10 = 1024
|
||
if (self.reConnectTime > 1024) {
|
||
self.reConnectTime = 0;
|
||
return;
|
||
}
|
||
|
||
__weak typeof(self)ws = self;
|
||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.reConnectTime * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||
if (ws.webScoket.readyState == SR_OPEN && ws.webScoket.readyState == SR_CONNECTING) {
|
||
return ;
|
||
}
|
||
|
||
[ws connectServerWithRoomID:self.roomID];
|
||
NSLog(@"重新连接......");
|
||
if (ws.reConnectTime == 0) {//重连时间2的指数级增长
|
||
ws.reConnectTime = 2;
|
||
}else{
|
||
ws.reConnectTime *= 2;
|
||
}
|
||
});
|
||
}
|
||
|
||
//发送心跳
|
||
-(void)senderheartBeat{
|
||
// NSLog(@"senderheartBeat");
|
||
//和服务端约定好发送什么作为心跳标识,尽可能的减小心跳包大小
|
||
__weak typeof (self) ws = self;
|
||
bj_main_async_safe(^{
|
||
if (ws.webScoket.readyState == SR_OPEN) {
|
||
[ws sendPing:nil];
|
||
}else if (ws.webScoket.readyState == SR_CONNECTING){
|
||
NSLog(@"正在连接中");
|
||
[ws reConnectServer];
|
||
}else if (ws.webScoket.readyState == SR_CLOSED || ws.webScoket.readyState == SR_CLOSING){
|
||
NSLog(@"断开,重连");
|
||
[ws reConnectServer];
|
||
}else{
|
||
NSLog(@"没网络,发送失败,一旦断网 socket 会被我设置 nil 的");
|
||
}
|
||
});
|
||
}
|
||
|
||
//取消心跳
|
||
-(void)destoryHeartBeat{
|
||
__weak typeof(self) ws = self;
|
||
bj_main_async_safe(^{
|
||
if (ws.headerBeatTimer) {
|
||
[ws.headerBeatTimer invalidate];
|
||
ws.headerBeatTimer = nil;
|
||
}
|
||
});
|
||
}
|
||
|
||
//没有网络的时候开始定时 -- 用于网络检测
|
||
-(void)noNetWorkStartTestingTimer{
|
||
__weak typeof(self)ws = self;
|
||
bj_main_async_safe(^{
|
||
ws.networkTestingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(noNetWorkStartTesting) userInfo:nil repeats:YES];
|
||
[[NSRunLoop currentRunLoop] addTimer:ws.networkTestingTimer forMode:NSDefaultRunLoopMode];
|
||
});
|
||
}
|
||
|
||
//定时检测网络
|
||
-(void)noNetWorkStartTesting{
|
||
if (AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus != AFNetworkReachabilityStatusNotReachable) {
|
||
//关闭网络检测定时器
|
||
[self destoryNetWorkStartTesting];
|
||
//重新连接
|
||
[self reConnectServer];
|
||
}
|
||
}
|
||
//取消网络检测
|
||
-(void)destoryNetWorkStartTesting{
|
||
__weak typeof(self) ws = self;
|
||
bj_main_async_safe(^{
|
||
if (ws.networkTestingTimer) {
|
||
[ws.networkTestingTimer invalidate];
|
||
ws.networkTestingTimer = nil;
|
||
}
|
||
});
|
||
}
|
||
|
||
//发送数据给服务器
|
||
-(void)sendDataToServer:(NSString *)data{
|
||
[self.sendDataArray addObject:data];
|
||
|
||
//没有网络
|
||
if(AFNetworkReachabilityManager.sharedManager.networkReachabilityStatus == AFNetworkReachabilityStatusNotReachable){
|
||
//开启网络检测定时器
|
||
[self noNetWorkStartTesting];
|
||
}else{
|
||
if (self.webScoket != nil) {
|
||
//只有长连接OPEN开启状态才能调用send方法
|
||
if (self.webScoket.readyState == SR_OPEN) {
|
||
[self.webScoket send:data];
|
||
}else if (self.webScoket.readyState == SR_CONNECTING){
|
||
//正在连接
|
||
NSLog(@"正在连接中,重连后会去自动同步数据");
|
||
}else if(self.webScoket.readyState == SR_CLOSING || self.webScoket.readyState == SR_CLOSED){
|
||
//调用 reConnectServer 方法重连,连接成功后 继续发送数据
|
||
[self reConnectServer];
|
||
}
|
||
}else{
|
||
[self connectServerWithRoomID:self.roomID];//连接服务器
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
|
||
@end
|