// // GWebSocket.m // TUIContact // // Created by gan on 2025/3/24. // #import #import "GWebSocket.h" #import "SocketRocket/SRWebSocket.h" //#import "AFNetworking/AFNetworkReachabilityManager.h" #import "GDBManager.h" //#import "AFNetworkReachabilityManager.h" #import "ChatListStore.h" #import "ChatsStore.h" #import "WebRTCStore.h" #import "UserNetApi.h" static int const kHeartbeatDuration = 1; static int const timeoutError = 5; static NSString *ping = @"{\"code\":0}"; static NSString *redeady = @"{\"code\":1}"; @interface GWebSocket () @property (nonatomic,strong) SRWebSocket *socket; @property (strong, nonatomic) NSTimer *heatBeat; @property (nonatomic,strong) NSString *serverIpString; @property (nonatomic,strong) NSString *userid; @property (nonatomic,assign) BOOL autoReconnect; @property (nonatomic,assign) NSInteger pingCount; @property (nonatomic,assign) NSInteger reconnetCount; @property (nonatomic,assign) BOOL isFirstload; @property (nonatomic,strong) NSString *UUID; @end @implementation GWebSocket + (GWebSocket *_Nonnull)shareInstance{ static id gShareInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ gShareInstance = [[self alloc] init]; }); return gShareInstance; } - (instancetype)init { if (self = [super init]) { self.pingCount = 0; self.reconnetCount = 0; NSUUID *uid = [NSUUID UUID]; _UUID=uid.UUIDString; NSLog(@"_UUID:%@",_UUID); } return self; } #pragma mark - Public - - (void)connectWebSocket { self.autoReconnect = YES; [self initWebSocket]; [self startHeartbeat]; } - (void)closeWebSocket { self.autoReconnect = NO; // _UUID=nil; [self close]; } #pragma mark 探测socket 状态 - (void)chekWebSocket{ } - (void)sendMsg:(NSString *)msg { if (self.socket && self.socket.readyState == SR_OPEN) { // 只有在socket状态为SR_OPEN 时,才可以发送消息 // 在socket状态不为SR_OPEN,可以将消息放进队列里,在websocket连上时,再发送 NSLog(@"sendMsg:%@",msg); [self.socket sendString:msg error:nil]; } else{ NSLog(@"socket--------不可用"); [self reConnect]; } // NSLog(@"_UUID:%@",_UUID); } #pragma mark -- WebSocket //初始化 WebSocket - (void)initWebSocket{ if (_socket) { return; } self.isFirstload =YES; // [self destoryHeartbeat]; NSDictionary *userinfo =[UDManager.shareInstance getDDManager:dkuserinfo]; self.userid =userinfo[@"id"]; NSString *token = (NSString *)[UDManager.shareInstance getSDManager:gkeytoken]; if(_UUID==nil){ NSUUID *uid = [NSUUID UUID]; _UUID=uid.UUIDString; } NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@token=%@&client=mobile&uuid=%@",WebSocketUrl,token,_UUID]]; //请求 //NSLog(@"url:%@",url); NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10]; //初始化请求` _socket = [[SRWebSocket alloc] initWithURLRequest:request]; //代理协议` _socket.delegate = self; //直接连接 [_socket open]; } -(void)sendReady{ NSLog(@"------%@",redeady); [self.socket sendString:redeady error:nil]; } -(void)sendPing{ [self.socket sendString:ping error:nil]; } #pragma mark - Heart Timer - //保活机制 探测包 - (void)startHeartbeat { if(self.heatBeat){ return; } self.heatBeat = [NSTimer scheduledTimerWithTimeInterval:kHeartbeatDuration target:self selector:@selector(heartbeatAction) userInfo:nil repeats:YES]; [self.heatBeat setFireDate:[NSDate distantPast]]; [[NSRunLoop currentRunLoop] addTimer:_heatBeat forMode:NSRunLoopCommonModes]; } //断开连接时销毁心跳 - (void)destoryHeartbeat{ if(self.heatBeat){ [self.heatBeat invalidate]; self.heatBeat = nil; } } // 发送心跳 - (void)heartbeatAction { if (self.socket.readyState == SR_OPEN) { // NSLog(@"heartbeatAction1111"); self.pingCount = self.pingCount+1; self.reconnetCount = self.reconnetCount+1; if(self.pingCount>5){//连续多次发送ping 没有收到回复,发起重连; [self.socket close]; self.socket = nil; self.pingCount=0; [self reConnect]; } if(self.pingCount>=3&&self.socket){ [self sendPing]; } } else{ // NSLog(@"heartbeatAction222"); self.pingCount = self.pingCount+1; self.reconnetCount = self.reconnetCount+1; if(self.pingCount>5){//连续多次发送ping 没有收到回复,发起重连; [self.socket close]; self.socket = nil; self.pingCount=0; [self reConnect]; } } } //重连机制 - (void)reConnect{ if (!self.autoReconnect) { return; } self.socket = nil; [self initWebSocket]; // NSLog(@"reConnect1111---------:%ld",(long)self.reconnetCount); if(self.reconnetCount>15){ NSString *token = [UDManager.shareInstance getSDManager:gkeytoken]; if([token isKindOfClass:[NSString class]]&&token.length>10){ [UserNetApi getUserinfo:^(int code, NSDictionary * res) { NSNumber *gcode=res[@"code"]; NSLog(@"gcode11"); if([gcode intValue]==401){//token有效 [self logoutAct]; } } fail:^(NSError * _Nonnull error) { NSLog(@"11%@", error); }]; } } } // 关闭Socket - (void)close { [self destoryHeartbeat]; [self.socket close]; self.socket = nil; } #pragma mark -- SRWebSocketDelegate //收到服务器消息是回调 - (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message{ //NSLog(@"----XTWebSocket didReceiveMessage:%@",message); if ([message isKindOfClass:[NSString class]]) { NSString *msg = (NSString *)message; NSError *error; self.pingCount = 0; self.reconnetCount = 0; if([msg isEqual:ping]){ if(self.isFirstload){//第一次打开socket数据加载完成需要执行的 } return; } // NSLog(@"didReceiveMessage:%@",msg); NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding]; NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error]; if (error) { NSLog(@"JSON解析错误: %@", error); } else { // NSLog(@"解析后的字典: %@", jsonDict); NSString *code =jsonDict[@"code"]; if([code isEqualToString:SendCode_MESSAGE]){//收到消息数据 [self onmessage:jsonDict[@"message"]]; return; } if([code isEqualToString:SendCode_READ]){//已读消息 [self onRead:jsonDict[@"message"]]; return; } if([code isEqualToString:SendCode_OTHER_LOGIN]){//账号在其他地方登陆 NSDictionary *mmsg = jsonDict[@"message"]; NSString *uuid =mmsg[@"uuid"]; if([uuid isEqualToString:_UUID]){ return; } else{ [self logoutAct]; } return; } if([code isEqualToString:SendCode_NEW_FRIEND]){//好友申请 NSLog(@"好友申请----------"); [[NSNotificationCenter defaultCenter] postNotificationName: nkonAddFriendNote object:nil]; return; } if([code isEqualToString:SendCode_GROUP_VALIDATE]){//群申请验证 [[NSNotificationCenter defaultCenter] postNotificationName: nkonEXGroupdNote object:nil]; return; } if([code isEqualToString:SendCode_WEBRTC_result]){//音视频通话结果 [self onmessage:jsonDict[@"message"]]; return; } if([code isEqualToString:SendCode_WEBRTC_CALL]){//音视频通话请求 [WebRTCStore.shareInstance reciveMsg:jsonDict[@"message"]]; return; } if([code isEqualToString:SendCode_WEBRTC_CLOSE]){//关闭音视频通话 [WebRTCStore.shareInstance reciveMsg:jsonDict[@"message"]]; return; } if([code isEqualToString:SendCode_WEBRTC_BUSY]){//音视频通话忙碌 [WebRTCStore.shareInstance reciveMsg:jsonDict[@"message"]]; return; } if([code isEqualToString:SendCode_WEBRTC_xinling]){//webrtc信令交互 [WebRTCStore.shareInstance reciveMsg:jsonDict[@"message"]]; return; } if([code isEqualToString:SendCode_deletemsg]){//删除消息 return; } if([code isEqualToString:SendCode_WEBRTC_DFBUSY]){//对方正在忙 [WebRTCStore.shareInstance reciveMsg:jsonDict[@"message"]]; return; } if([code isEqualToString:SendCode_RECMsg]){//收到消息回执 return; } } if(self.delegate){ [self.delegate GWebSocketAction:message]; } } } //连接成功 - (void)webSocketDidOpen:(SRWebSocket *)webSocket{ NSLog(@"-----XTWebSocket DidOpen"); // 下面逻辑,根据业务情况处理 if (self.socket != nil) { // 只有 SR_OPEN 开启状态才能调 send 方法啊,不然要崩 if (_socket.readyState == SR_OPEN) { //NSString *jsonString = @"{\"sid\": \"13b313a3-fea9-4e28-9e56-352458f7007f\"}"; //[_socket sendString:jsonString error:nil]; //发送数据包 [self sendReady]; } else if (_socket.readyState == SR_CONNECTING) { NSLog(@"正在连接中,重连后其他方法会去自动同步数据"); // 每隔2秒检测一次 socket.readyState 状态,检测 10 次左右 // 只要有一次状态是 SR_OPEN 的就调用 [ws.socket send:data] 发送数据 // 如果 10 次都还是没连上的,那这个发送请求就丢失了,这种情况是服务器的问题了,小概率的 // 代码有点长,我就写个逻辑在这里好了 } else if (_socket.readyState == SR_CLOSING || _socket.readyState == SR_CLOSED) { // websocket 断开了,调用 reConnect 方法重连 NSLog(@"websocket 断开了,调用 reConnect 方法重连"); [self reConnect]; } } } //连接失败的回调 - (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error{ NSLog(@"XTWebSocket didFailWithError %@",error); // 1.判断当前网络环境,如果断网了就不要连了,等待网络到来,在发起重连 // 2.判断调用层是否需要连接,例如用户都没在聊天界面,连接上去浪费流量 // [self reConnect]; } //连接断开的回调 - (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean{ NSLog(@"XTWebSocket Close code %ld reason %@",(long)code,reason); } - (void)webSocket:(SRWebSocket *)webSocket didReceivePong:(NSData *)pongPayload { NSLog(@"XTWebSocket Pong"); } #pragma mark - 其他 - - (void)dealloc { NSLog(@"LFC: dealloc: %@", self); [[NSNotificationCenter defaultCenter] removeObserver:self]; } #pragma mark -数据处理- -(void)onmessage:(NSDictionary *)msg{ NSLog(@"onmessage:%@",msg); // NSLog(@"%@",msg[@"chatId"]); // NSLog(@"%@",msg[@"fromId"]); //NSLog(@"%@",msg[@"content"]); NSMutableDictionary *mutablemsg = [msg mutableCopy]; if([self.userid isEqual:msg[@"fromId"]]){ [mutablemsg setObject:[NSNumber numberWithBool:YES] forKey:@"mine"]; // NSLog(@"----------------------11"); [ChatsStore.shareInstance deleteMineTmpMsg:msg];//删除本地临时消息 }else{ if([Friendchat isEqual:msg[@"type"]]){ [mutablemsg setObject:msg[@"fromId"] forKey:@"chatId"]; } } [ChatsStore.shareInstance reciveMsg:mutablemsg];//聊天窗信息 [ChatListStore.shareInstance reciveMsg:[mutablemsg copy]];//聊天窗列表 [[NSNotificationCenter defaultCenter] postNotificationName:nkonNewMessageNote object:mutablemsg]; } //消息已收到回执 -(void)sendRecNote:(NSDictionary *)msg{ NSDictionary *dic =@{ @"chatId":msg[@"chatId"], @"userId":self.userid, @"msgId":msg[@"id"], @"type":msg[@"type"] }; NSDictionary *sendInfo = @{ @"code":SendCode_RECMsg, @"message":dic, }; NSError *error; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:sendInfo options:0 error:&error]; if (!jsonData) { NSLog(@"Got an error: %@", error); } else { NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; // NSLog(@"jsonString:%@",jsonString); [self sendMsg:jsonString]; } } //收到已读消息 -(void)onRead:(NSDictionary *)dis{ [ChatsStore.shareInstance updatereadTime:dis]; } //异地登录 -(void)logoutAct{ NSLog(@"logoutAct"); [UserNetApi logout:nil succ:^(int code, NSDictionary * res) { // NSLog(@"res:%@",res); [[NSNotificationCenter defaultCenter] postNotificationName: nkonLogoutSucc object:nil]; } fail:^(NSError * _Nonnull error) { NSLog(@""); }]; } @end