守护线程顾名思意就是为了监控和处理核心业务线程异常的线程。它很强大,很实时,能处理各种阻塞等异常引起的核心线程僵死。
守护线程在应用起来后一般就起来,它大部分时间都是在睡觉,当应用的处理业务核心线程处于频发危险阶段时叫醒它,让它实时的监控核心线程,等过了危险路段再让它做梦去。。所以它耗电量很小。
守护线程需要永远活着,只是它大部分时间在睡觉,少部分时间当看门狗,监控核心线程。它分为典型的三种工作模式:核心线程没有起来时长期睡觉(间隔28分钟),核心线程起来并进入危险区(connect连接前后)需要实时监控(20秒),核心线程起来进入不太危险区域(网络闪断也能搞死长连接线程,通而不达网络也能让select失效,导致永远收不到服务器的响应消息,毕竟这种异常很奇靶,也不知道它何时出现何地出现,不需要实时监测)需要线程间心跳检测(75秒,只需要检测长连接正常时是否有服务器的消息返回就可以)。
它的功能主要有:
1.拉起核心线程。由于核心线程可能存在频繁的起来和结束,导致线程没有实际起起来。当发现应该出现场景没有核心线程就再起核心线程。这种场景我实际在linux环境下遇到过,起线程时系统返回成功,但是由于网络原因起的太频繁,导致没有实际没有起起来,线程函数没有执行。毕竟系统返回起线程成功,到线程实际运行起来之间有150毫秒左右的时间偏差。
2.重新创建核心线程。
长连接线程网络不稳定原因和手机切换前后台导致,connect阻塞75秒才返回失败(请参照文章,手机socket套接字75秒超时问题及解决方案:http://blog.csdn.net/jia12216/article/details/50133891)。这个时间等待时间太长了,所以守护线程发现这种情况要给予告警,并结束这个异常线程。苹果的在阻塞中的线程无法强制干掉再起来一个核心线程,所以只能让它活过来时直接结束,这就要在程序离开危险阶段时处理是否需要直接结束掉的异常处理策略了。
长连接线程由于网络不稳定原因和手机切换前后台导致,connect永远阻塞线程,守护线程发现这种情况要结束这个异常线程。苹果系统的在阻塞中的线程无法强制干掉再起来一个核心线程。
当然也不能因为网络通而不达网络而不断起核心线程,虽然线程过了connect函数后可能会被立刻结束掉,但是频繁起线程是不科学的。若核心线程是长连接线程,可以再另起一个线程,当socket套接字超时(75秒,为了处理函数执行时间和中间的sleep时间可以设置为80秒)时再删除待删除线程,起新的长连接线程。保证最多两个线程在阻塞中。这样既能保证核心线程的安全性又能保证及时性。
真对通而不达网络引起的select异常可以通过守护线程的常规监控和收到服务器的响应超时时间间隔超时来发现。真对网络异常引起的connect函数返回1,可以通过错误码为63和65来发现。部分情况可以参照文章《通而不达网络的发现与解决方案》(http://blog.csdn.net/jia12216/article/details/50163319)
实现方法:
当应用起来时,起一个守 和一个管道。当用户启动核心长连接业务线程,并记录当前线程号到当前线程队列。当程序运行到容易阻塞的connect前记录当前时间,并发送管道消息给守护线程让它修改为监控阻塞的时间间隔(20秒)。
守护线程每10秒会超时一次判断长连接线程的连接是否超时,若超时把长连接线程号从当前线程号队列移动到待删除长连接线程号队列,并重新建立长连接线程。当connect返回发现本线程号在待删除长连接线程号队列中,就从待删除长连接线程队列中删除该线程号,并立刻优雅的结束线程。
若connect返回时发现本线程号不待删除长连接线程号队列中,就通过管道给守护线程发送i消息,让它修改为idle的时间间隔(28*60秒,因为移动运营商设置的网络ip回收时间是30分钟,所以要把它的时间短于30分钟)。
可以通过一个下面一个实际实现的一个代码片段来实现守护线程发现核心业务阻塞并处理的来观察守护线程的应用。可以看到守护就是一个一直运行的零延迟可变时间间隔的定时器(可变定时器,参考文章:http://blog.csdn.net/jia12216/article/details/50110001)。
使用tcp长连接来和服务端进行数据传输,在IOS平台上,由于苹果的后台机制,会有以下问题:
当程序退到后台的时候,所有线程被挂起,系统会回收所有的socket资源,那么socket连接就会被关闭,因此无法再进行数据的传输。
就是你设置了voip也不能保证每10秒有发送消息到服务器的控制权,600秒,用户早茶凉了,所以不能用voip。至于后台不在活跃的线程和长连接被挂起,也没有很好的办法,至少你的应用要保证具有后台定位,voip等保证后台应用可以运行的配置。
配置keep-alive handler
一个voip类的app需要周期性的被唤醒以防止失去跟服务器的连接。为了达到这个目的,IOS系统允许我们通过setKeepAliveTimeout:handler:方法来实现保持连接。通常我们都在软件切到后台以后,也就是在applicationDidEnterBackground:才需要实现保持连接的方法。Handler配置完后,系统会根据需要在time out设置的时间到期之前唤醒处于挂起状态的app,至少被调用一次handler中的方法。
1、 handler
需要说明的是,这个handler在后台执行的时候需要尽可能快的返回,因为系统只给了最多10s的时间去执行它。如果10s内没有执行完,而且没有去申请额外的执行时间,系统将会吧app挂起。
2、timeout
设置handler时,需要指定app需要的最大超时时间。IOS系统允许的最小值是600s,如果想设置个小点的值,handler的设置就会失败。系统在执行handler内容的时候,只保证超时时间到之前会执行,而不保证准确的执行时间点,系统是会根据当时的任务情况等系统条件确定一个合适的时间点,已达到延长电池寿命的目的。
所以无网络进入后台,设置voip也不能保证,应用进入后台8分钟后,守护线程,长连接线程,日志线程不被挂起。 甚至应用被杀掉。所以要在进入后台时要保证有网络,进入前台时要监控守护线程和长连接线程是否还活着。
//系统启动时的初始化函数 -(void)initData { FLDDLogDebug(@"函数"); //通过判断是否生成管道来防止重复初始化 if(nil == self.guardModifyWaitTimePipe) { self.guardModifyWaitTimePipe = [NSPipe pipe]; self.guardModifyWaitTimePipeWriteHandle = [self.guardModifyWaitTimePipe fileHandleForWriting]; self.fdGuardModifyWaitTimeReadPipe = [[self.guardModifyWaitTimePipe fileHandleForReading] fileDescriptor]; self.reponseTime = 0; [self guardThread]; } } //获取本线程的线程号 -(NSString *)getSocketThreadSno { NSString * socketThreadDescription = [NSThread currentThread].description; NSRange range = [socketThreadDescription rangeOfString:@"number = "]; NSRange range1 = [socketThreadDescription rangeOfString:@","]; if((range.length <= 0) || ((range1.length <= 0))) { return nil; } else { range = NSMakeRange (range.location + range.length, range1.location - range.location - range.length); NSString *number = [socketThreadDescription substringWithRange : range]; return number; } } //把线程号存储到线程号队列 -(void)storeSocketThreadSno { FLDDLogDebug(@"函数"); NSString *number = [self getSocketThreadSno]; self.usingSocketThreadSnoArrFlag = YES; if(number.length > 0) { _socketThreadSno = [number longLongValue]; // [self.socketThreadSnoArr addObject:number]; if(_connectTime > 0) { //连接时被阻塞 NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:number,@"num",[NSString stringWithFormat:@"%lld",_connectTime],@"connectTime",nil]; [self.socketThreadSnoArr addObject:dic]; } else { NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:number,@"num",[NSString stringWithFormat:@"%lld",(long long)[[NSDate date] timeIntervalSince1970]],@"connectTime",nil]; [self.socketThreadSnoArr addObject:dic]; } FLDDLogDebug(@"SocketThreadSn:%@", number); } else { _socketThreadSno = -1; } self.usingSocketThreadSnoArrFlag = NO; } //创建核心业务线程函数 -(void)creatPushConnection { FLDDLogDebug(@"函数"); if(!(self.isCloseSocket)) { // self.needCloseSocket = NO; if(SOCKECT_CONNECT_SUCESS == self.socketConnectStat) { [self.writeHandle writeData: [@",cancel quit" dataUsingEncoding: NSASCIIStringEncoding]]; if(self.hitOnLineTime > 0) { self.onlineSuccessBlock(YES, nil); } self.hitOnLineTime = 0; return; } else { if(self.hitOnLineTime > 0) { self.onLineFailedBlock(NO); } self.hitOnLineTime = 0; return; } } self.isCloseSocket = NO; [self socketBSDThread]; } //核心长连接业务线程 -(void)socketBSDThread { FLDDLogDebug(@"函数"); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[NSThread currentThread] setName:@"socket thread"]; _socketThread = [NSThread currentThread]; [self storeSocketThreadSno]; FLDDLogDebug(@"socket thread"); NSDate *curDate = nil; curDate = [NSDate date]; long long frontHearTime = (long long)[curDate timeIntervalSince1970]; long long nowTime = frontHearTime; int replyConnectTimes = 0; unsigned char input_msg[BUFFER_SIZE]; int server_sock_fd; self.socketConnectStat = SOCKECT_CONNECT_INIT; self.fd = -1; server_sock_fd = -1; bzero(input_msg, BUFFER_SIZE); self.socketConnectStat = SOCKECT_CONNECT_INIT; replyConnectTimes = 0; //重新申请socket大循环 SKIP: FLDDLogDebug(@"SKIP fd= %ld\n", (long)self.fd); //禁止发送队列变更标志 self.sendingNewMessageDic = nil; [self.sendMessagesArray removeAllObjects]; [self.processedSendMsgArray removeAllObjects]; [self.messagesArray removeAllObjects]; self.reponseTime = 0; _bSendingFlag = NO; struct sockaddr_in server_addr; server_addr.sin_len = sizeof(struct sockaddr_in); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(self.socketPort); server_addr.sin_addr.s_addr = inet_addr([self.socketHost UTF8String]); bzero(&(server_addr.sin_zero),8); //暂时是socket断连,并且要发送的消息清空缓冲区,防止瞬时发送大批量消息,只发送最后条消息。 bzero(input_msg, BUFFER_SIZE); //socket异常或首次建立,若失败需要前3次每秒连接一次,后进入一次长等待。 curDate = [NSDate date]; nowTime = (long long)[curDate timeIntervalSince1970]; self.pushTime = 0; self.fd = -1; server_sock_fd = -1; server_sock_fd = socket(AF_INET, SOCK_STREAM, 0); self.fd = server_sock_fd; if (server_sock_fd == -1) { FLDDLogDebug(@"socket error"); return; } else { FLDDLogDebug(@"socket sucess \n"); FLDDLogDebug(@"g_fd= %ld\n", (long)server_sock_fd); } //重新建立socket连接大循环 while(1) { FLDDLogDebug(@"main loop \n"); //将打开的socket设为非阻塞的,可以用fcntl(socket, F_SETFL, O_NDELAY)完成, 若网络有问题会75秒返回结果 fcntl((int)socket, F_SETFL, O_NDELAY); // if ((NotReachable == [self getNetworkStatus]) || (ReachableUnknown == [self getNetworkStatus])) // { // sleep(3); // break; // } if((NotReachable == [self getNetworkStatus]) || (ReachableUnknown == [self getNetworkStatus])) { close(server_sock_fd); self.socketConnectStat = SOCKECT_CONNECT_ABNORMAL; // curDate = [NSDate date]; // nowTime = (long long)[curDate timeIntervalSince1970]; // frontHearTime = nowTime; sleep(3); // _connectTime = 0; goto SKIP; } else if(EhostUnreach == [self getNetworkStatus]) { //疑似受限网络,防止不断连接网络 sleep(3); } _connectTime = (long long)[[NSDate date] timeIntervalSince1970]; FLDDLogDebug(@"connect before:_connectTime = %lld, Thread Sno: %lld\n", (long long)([[NSDate date] timeIntervalSince1970]*1000), self.socketThreadSno); [self.guardModifyWaitTimePipeWriteHandle writeData: [g_guardThreadStartActualTimeMonitor dataUsingEncoding: NSASCIIStringEncoding]]; int i = connect(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in)); FLDDLogDebug(@"connect after:_connectTime = %lld, Thread Sno: %lld\n", (long long)([[NSDate date] timeIntervalSince1970]*1000), self.socketThreadSno); // if(self.testGuardThread == 1) // { // sleep(200); //测试守护线程 // } // self.testGuardThread++ ; // if(self.usingSocketThreadSnoArrFlag) { close(server_sock_fd); self.socketConnectStat = SOCKECT_CONNECT_ABNORMAL; [self.guardModifyWaitTimePipeWriteHandle writeData: [g_guardThreadIdle dataUsingEncoding: NSASCIIStringEncoding]]; sleep(3); goto SKIP; } else { self.usingSocketThreadSnoArrFlag = YES; NSMutableArray *currentDeleteSocketThreadSnoArr = [NSMutableArray arrayWithArray:self.deleteSocketThreadSnoArr]; NSString *number = [self getSocketThreadSno]; for (NSDictionary* dic in currentDeleteSocketThreadSnoArr) { //防范式编程,数据异常,理论上不会出现 if(0 == dic.count) { close(server_sock_fd); self.socketConnectStat = SOCKECT_CONNECT_ABNORMAL; self.usingSocketThreadSnoArrFlag = NO; [currentDeleteSocketThreadSnoArr removeAllObjects]; [self.guardModifyWaitTimePipeWriteHandle writeData: [g_guardThreadIdle dataUsingEncoding: NSASCIIStringEncoding]]; sleep(3); goto SKIP; } else if([[dic valueForKey:@"num"] isEqualToString:number]) { self.usingSocketThreadSnoArrFlag = NO; [currentDeleteSocketThreadSnoArr removeObject:dic]; //由于connect 函数超时,守护线程已经重新长连接线程,需要立刻结束该线程,防止出现两个长连接线程 FLDDLogDebug(@"关闭连接时间超过20秒的线程, Thread Sno: %lld\n", self.socketThreadSno); return; } } self.usingSocketThreadSnoArrFlag = NO; } _connectTime = 0; FLDDLogDebug(@"errno:%d\n", errno); [self.guardModifyWaitTimePipeWriteHandle writeData: [g_guardThreadStartRegularMonitor dataUsingEncoding: NSASCIIStringEncoding]]; //socket select处理区域,这只是很少部分代码,只是为了说明如何发现通不达网络和网络不正常如何发现 self.fd = server_sock_fd;; fd_set client_fd_set; fd_set read_fd_set; int max_fd = server_sock_fd; struct timeval tv; long waitTimeInterval = 1; if((SOCKECT_CONNECT_SUCESS != self.socketConnectStat)) { waitTimeInterval = CONNECT_WAIT_TIME; } else if(self.hitOnLineTime > 0) { waitTimeInterval = CONNECT_WAIT_TIME; } else if((0 == self.messagesArray.count) && (0 == self.sendMessagesArray.count) && (0 == self.processedSendMsgArray.count)) { waitTimeInterval = SELECT_INTERVAL_TIME; } else { waitTimeInterval = CONNECT_WAIT_TIME; } _bSendingFlag = NO; tv.tv_sec = waitTimeInterval; tv.tv_usec = 0; long ret = select(max_fd + 1, &read_fd_set, &client_fd_set, NULL, &tv); if (ret < 0 ) { FLDDLogDebug(@"select 出错!\n"); //发connect调用,这时返回-1,但是errno被设为EINPROGRESS,意即connect仍旧在进行还没有完成. //getsockopt(socket, SOL_SOCKET, SO_ERROR, &error, sizeof(int)); //防止监控socket的select连续处于连接中(返回小于0的情况),若10秒内连续出现这种情况就干掉socket重新连接 //连接成功后再返回-1,一概按照socket异常,重新建立socket,由服务器关闭socket nowTime = (long)[[NSDate date] timeIntervalSince1970]; // socketConnectStartTime = nowTime; if(EINPROGRESS != errno) { close(server_sock_fd); self.socketConnectStat = SOCKECT_CONNECT_ABNORMAL; goto SKIP; } } else if(ret ==0){ FLDDLogDebug(@"select 超时!\n"); } else { //处理重新建立socket连接时的等待时间策略 curDate = [NSDate date]; nowTime = (long long)[curDate timeIntervalSince1970]; close(server_sock_fd); FLDDLogDebug(@"socket err \n"); //连接到受限网络 /* No route to host */ /* Software caused connection abort */ if((EhostUnreach == errno) || (ECONNABORTED == errno)) { replyConnectTimes = 0; self.networkStatus = EhostUnreach; FLDDLogInfo("收到网络异常通知,没有网络! 网络状态:%ld", (long)(self.networkStatus)); [[NSNotificationCenter defaultCenter] postNotificationName:REACHABILITY_NO_NET_NOTIFICATION object:nil]; sleep(CONNECT_MAX_WAIT_TIME); } else if(++replyConnectTimes > REPLY_CONNECT_MAX_TIMES*CONNECT_WAIT_TIME) { if(nowTime - frontHearTime < (REPLY_CONNECT_MAX_TIMES + 2) * CONNECT_WAIT_TIME) { FLDDLogDebug(@"sleep: %d秒\n", CONNECT_MAX_WAIT_TIME); //3次连接失败,进入一次较长时间的等待期,防止出现连接不上服务器,快速循环,耗电量的大增 sleep(CONNECT_MAX_WAIT_TIME); replyConnectTimes = 0; } else { //当出现通而不达的情况(有网络,但是不能正常连接到服务器的情况),connect,会出现75秒才返回的情况,这种情况不需要等待直接发起连接 FLDDLogDebug(@"nowTime - frontHearTime:%lld", nowTime - frontHearTime); } } else { FLDDLogDebug(@"sleep: %d秒\n", CONNECT_WAIT_TIME); sleep(CONNECT_WAIT_TIME); } goto SKIP; } } }); } //守护线程 -(void)guardThread { FLDDLogDebug(@"函数"); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [[NSThread currentThread] setName:@"socket thread"]; FLDDLogDebug(@"guard thread"); //守护线程大循环 while(1) { fd_set read_fd_set; FD_ZERO(&read_fd_set); FD_SET(self.fdGuardModifyWaitTimeReadPipe, &read_fd_set); struct timeval tv; tv.tv_sec = self.guardThreadWaitTimeInterval; tv.tv_usec = 0; //socket一般都处于可写状态,除了网卡满了写不进去的罕见情况,并且若时线程外发送的消息都写了管道消息,管道有数据可度也会结束侦听,所以该处侦听一般都是立刻响应 long ret = select(self.fdGuardModifyWaitTimeReadPipe + 1, &read_fd_set, NULL, NULL, &tv); if(ret > 0) { //先把消息接收过来,后面再处理,防止再处理消息时,连接异常。 if (FD_ISSET(self.fdGuardModifyWaitTimeReadPipe, &read_fd_set)) { FLDDLogDebug(@"ret = %ld\n", ret); long nbytes; unsigned char readBuffer[PIPE_MESSAGE_MAX_BUFFER_LEN] = {0}; nbytes = read(self.fdGuardModifyWaitTimeReadPipe, readBuffer, PIPE_MESSAGE_MAX_BUFFER_LEN); if (nbytes <= 0) { FLDDLogDebug(@"no data."); } else { //防止收到的管道消息粘包,超过最大缓冲区长度,最后一个缓冲区字节强制置为结束符号 readBuffer[PIPE_MESSAGE_MAX_BUFFER_LEN - 1] = '\n'; NSString *str = [[NSString alloc] initWithString:[NSString stringWithFormat:@"%s", readBuffer]]; FLDDLogDebug(@"pipe data:%@", str); NSRange rangeGuardThreadIdle=[str rangeOfString:g_guardThreadIdle options:NSBackwardsSearch]; NSRange rangeGardThreadStartActualTimeMonitor =[str rangeOfString:g_guardThreadStartActualTimeMonitor options:NSBackwardsSearch]; NSRange rangeGuardThreadStartRegularMonitor =[str rangeOfString:g_guardThreadStartRegularMonitor options:NSBackwardsSearch]; if(rangeGuardThreadIdle.length > 0) { FLDDLogDebug(@"收到守护线程空闲的消息,停止实时监控长连接线程:%@", str); //如果已经连接Xcode调试不再处理connect异常的问题 if(!(isatty(STDOUT_FILENO))) { self.guardThreadWaitTimeInterval = GUARD_THREAD_MAX_WAIT_TIME_INTERVAL; } } else if(rangeGardThreadStartActualTimeMonitor.length > 0) { FLDDLogDebug(@"收到守护线程开始实时监控的消息,开始实时监控长连接线程:%@", str); //如果已经连接Xcode调试不再处理connect异常的问题 if(!(isatty(STDOUT_FILENO))) { self.guardThreadWaitTimeInterval = GUARD_THREAD_MIN_WAIT_TIME_INTERVAL; } } else if(rangeGuardThreadStartRegularMonitor.length > 0) { FLDDLogDebug(@"收到守护线程开始常规监控的消息,开始常规监控长连接线程:%@", str); //如果已经连接Xcode调试不再处理connect异常的问题 if(!(isatty(STDOUT_FILENO))) { self.guardThreadWaitTimeInterval = SOCKET_MAX_TIMEOUT_TIME; } } } } } else if(ret ==0){ FLDDLogDebug(@"select 超时!\n"); [self handleConnectAbort]; } else { FLDDLogDebug(@"select 出错!\n"); } } }); } //守护线程处理核心长连接线程是否异常,若有异常,重新起长连接线程,并且把线程号记录到待关闭线程队列中,等待它阻塞结束直接结束异常线程 -(BOOL)handleConnectAbort { //如果已经连接Xcode调试不再处理connect异常的问题 if(isatty(STDOUT_FILENO)) { return NO; } if((([SingleAsyncSocket sharedInstance].connectTime > 0) && ([[NSDate date] timeIntervalSince1970] - [SingleAsyncSocket sharedInstance].connectTime > SOCKET_CONNECT_ABORT_WAIT_TIME) && (!(self.usingSocketThreadSnoArrFlag))) || ((self.reponseTime != 0) && ((long long)[[NSDate date] timeIntervalSince1970] - self.reponseTime > LIMITED_NET_CONNECT_MAX_WAIT_TIME) && (SOCKECT_CONNECT_SUCESS == self.socketConnectStat))) { // if((_connectTime > 0) && ((long long)([[NSDate date] timeIntervalSince1970]) - _connectTime > LIMITED_NET_CONNECT_MAX_WAIT_TIME)) if((!(isatty(STDOUT_FILENO))) && (_connectTime > 0) && ((long long)([[NSDate date] timeIntervalSince1970]) - _connectTime > LIMITED_NET_CONNECT_MAX_WAIT_TIME)) { self.networkStatus = EhostUnreach; FLDDLogInfo("收到网络异常通知,没有网络! 网络状态:%ld", (long)(self.networkStatus)); [[NSNotificationCenter defaultCenter] postNotificationName:REACHABILITY_NO_NET_NOTIFICATION object:nil]; } self.usingSocketThreadSnoArrFlag = YES; //防止不断创建线程 NSMutableArray *currentDeleteSocketThreadSnoArr = [NSMutableArray array]; if(self.deleteSocketThreadSnoArr.count > 0) { currentDeleteSocketThreadSnoArr = [NSMutableArray arrayWithArray:self.deleteSocketThreadSnoArr]; } if(currentDeleteSocketThreadSnoArr.count > 0) { NSDictionary* dic = currentDeleteSocketThreadSnoArr[0]; long long socketConnectTime = [[dic valueForKey:@"connectTime"] longLongValue]; // socket套接字的最大连接时间是75秒,若超过75秒还不返回那么它可能永远不可能返回了,所以可以重新建立socket线程了。为了处理线程的sleep处理,把这个时间延长了一下时间 if((long long)[[NSDate date] timeIntervalSince1970] - socketConnectTime > SOCKET_MAX_TIMEOUT_TIME + LIMITED_NET_CONNECT_MAX_WAIT_TIME) { [currentDeleteSocketThreadSnoArr removeAllObjects]; self.deleteSocketThreadSnoArr = currentDeleteSocketThreadSnoArr; } if(currentDeleteSocketThreadSnoArr.count > 0) { self.usingSocketThreadSnoArrFlag = NO; //没有超过套接字超时时间,就不创建线程。 return NO; } } long long currentSocketThreadSno = _socketThreadSno; if(currentSocketThreadSno > 0) { NSString *number = [NSString stringWithFormat:@"%lld",currentSocketThreadSno]; if(self.socketThreadSnoArr.count > 0) { NSMutableArray *currentSocketThreadSnoArr = [NSMutableArray arrayWithArray:self.socketThreadSnoArr]; BOOL findFlag = NO; for (NSDictionary* dic in currentDeleteSocketThreadSnoArr) { if(0 == dic.count) { self.usingSocketThreadSnoArrFlag = NO; return NO; } else if([[dic valueForKey:@"num"] isEqualToString:number]) { findFlag = YES; } } if(!findFlag) { if(_connectTime > 0) { //连接时被阻塞 NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:number,@"num",[NSString stringWithFormat:@"%lld",_connectTime],@"connectTime",nil]; [currentDeleteSocketThreadSnoArr addObject:dic]; } else { NSDictionary *dic = [NSDictionary dictionaryWithObjectsAndKeys:number,@"num",[NSString stringWithFormat:@"%lld",(long long)[[NSDate date] timeIntervalSince1970]],@"connectTime",nil]; [currentDeleteSocketThreadSnoArr addObject:dic]; } } else { self.usingSocketThreadSnoArrFlag = NO; return NO; } findFlag = NO; NSDictionary* dict = nil; for (NSDictionary* dic in currentSocketThreadSnoArr) { if(0 == dic.count) { self.usingSocketThreadSnoArrFlag = NO; return NO; } else if([[dic valueForKey:@"num"] isEqualToString:number]) { findFlag = YES; dict = dic; } } if(findFlag) { [currentSocketThreadSnoArr removeObject:dict]; } else { self.usingSocketThreadSnoArrFlag = NO; return NO; } self.deleteSocketThreadSnoArr = currentDeleteSocketThreadSnoArr; self.socketThreadSnoArr = currentSocketThreadSnoArr; } else { self.usingSocketThreadSnoArrFlag = NO; return NO; } } self.usingSocketThreadSnoArrFlag = NO; self.isCloseSocket = YES; [self creatPushConnection]; _connectTime = 0; self.socketConnectStat = SOCKECT_CONNECT_ABNORMAL; self.usingSocketThreadSnoArrFlag = NO; return YES; } return NO; } //解析从服务器发过来的区域,在长连接线程启动后每10秒和服务器之间进行一次交互 -(void)decodeMessageWithRecvMsg:(unsigned char *)recv_msg msgByteNum:(long)byte_num { FLDDLogDebug(@"函数"); //消息解码逻辑处理区域 NSDate *curDate = nil; curDate = [NSDate date]; long long nowTime = (long long)[curDate timeIntervalSince1970]; self.reponseTime = nowTime; }
宏定义代码:
static NSString *const g_guardThreadIdle = @"GuardThreadIdle"; // 守护线程空闲 static NSString *const g_guardThreadStartActualTimeMonitor = @"GuardThreadStartActualTimeMonitor"; // 守护线程开始实时监控 static NSString *const g_guardThreadStartRegularMonitor = @"GuardThreadStartRegularMonitor"; // 守护线程开始常规监控 #define LIMITED_NET_CONNECT_MAX_WAIT_TIME 15*2 //受限网络创建socket最大连接时间,若超过30秒,弹出无网络显示框,并且重现清零计数,单位为秒 #define SOCKET_MAX_TIMEOUT_TIME 75 //手机socket套接字最大超时时间,在业务线程运行期间守护线程等待间隔,当超过75秒没有发现服务有响应时,弹出无网络显示框,单位为秒 #define GUARD_THREAD_MAX_WAIT_TIME_INTERVAL (28*60) //守护线程最大等待时间间隔,单位:秒 #define GUARD_THREAD_MIN_WAIT_TIME_INTERVAL 15 //守护线程最小等待时间间隔,单位:秒 正常的手机网络的连接超时时间是6秒 //程序进入后台变换高德位置定位的时间间隔,防止用户把应用切换到后台,停留在一个位置长时间收不到经纬度,导致应用被系统杀掉 单位:秒 showsUserLocation #define CHANGE_SHOWS_USER_LOCATION_TIME (9*60) #define CONNECT_TIMEOUT_TIME 22 //防止监控socket的select连续处于连接中(返回小于0的情况),若10秒内连续出现这种情况就干掉socket重新连接
在connect前后的代码:
_connectTime = (long long)[[NSDate date] timeIntervalSince1970];
FLDDLogDebug(@”_connectTime = %lld\n”, _connectTime);
if((NotReachable == [self getNetworkStatus]) || (ReachableUnknown == [self getNetworkStatus]))
{
close(server_sock_fd);
self.socketConnectStat = SOCKECT_CONNECT_ABNORMAL;
curDate = [NSDate date];
nowTime = (long long)[curDate timeIntervalSince1970];
frontHearTime = nowTime;
sleep(3);
_connectTime = 0;
goto SKIP;
}
// nowTime = (long)[[NSDate date] timeIntervalSince1970];
int i = connect(server_sock_fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in)); FLDDLogDebug(@"connect after _connectTime = %lld\n", (long long)[[NSDate date] timeIntervalSince1970]);
增强的网络状态宏定义:
typedef NS_ENUM(NSInteger, NetworkStatus) { // Apple NetworkStatus Compatible Names. ReachableUnknown = -1, //正在获取网络中,没有收到过网络通知 NotReachable = 0, //网络通知发现的无网络 ReachableViaWiFi = 2, //网络通知发现的wifi网络 ReachableViaWWAN = 1, //网络通知发现手机网络和手机通过手机状态栏发现的手机网络 ReachableViaHaveNet = 3, //http和长连接发现的有网络,具体类型不知道 EhostUnreach = 4 //长连接根据网络错误码63,65发现通而不达网络和服务器响应超时发现的疑似通而不达网络 };
//获取网络状态 -(NetworkStatus)getNetworkStatus { FLDDLogDebug(@"函数"); if((self.networkStatus == ReachableViaWiFi) || (self.networkStatus == ReachableViaWWAN)) { return self.networkStatus; } else if((self.networkStatus == ReachableUnknown) || (self.networkStatus == ReachableViaHaveNet) || (self.networkStatus == NotReachable) || (self.networkStatus != EhostUnreach)) { NETWORK_TYPE phoneNet = [self getNetworkTypeFromStatusBar]; if(NETWORK_TYPE_NONE == phoneNet) { self.networkStatus = NotReachable; } else if(NETWORK_TYPE_WIFI == phoneNet) { //防止连接到通而不达的网络 // self.networkStatus = ReachableViaWiFi; // [[NSNotificationCenter defaultCenter] postNotificationName:REACHABILITY_RECOVE_NOTIFICATION object:nil]; } else if((NETWORK_TYPE_2G == phoneNet) || (NETWORK_TYPE_3G == phoneNet) || (NETWORK_TYPE_4G == phoneNet) || (NETWORK_TYPE_5G == phoneNet)) { self.networkStatus = ReachableViaWWAN; [[NSNotificationCenter defaultCenter] postNotificationName:REACHABILITY_RECOVE_NOTIFICATION object:nil]; } return self.networkStatus; } else { return self.networkStatus; } } //通过手机网络状态栏判断网络状态 -(NETWORK_TYPE)getNetworkTypeFromStatusBar { UIApplication *app = [UIApplication sharedApplication]; NSArray *subviews = [[[app valueForKey:@"statusBar"] valueForKey:@"foregroundView"] subviews]; NSNumber *dataNetworkItemView = nil; for (id subview in subviews) { if([subview isKindOfClass:[NSClassFromString(@"UIStatusBarDataNetworkItemView") class]]) { dataNetworkItemView = subview; break; } } NETWORK_TYPE nettype = NETWORK_TYPE_NONE; NSNumber * num = [dataNetworkItemView valueForKey:@"dataNetworkType"]; nettype = [num intValue]; return nettype; } typedef NS_ENUM(NSInteger, NETWORK_TYPE) { NETWORK_TYPE_NONE= 0, NETWORK_TYPE_2G= 1, NETWORK_TYPE_3G= 2, NETWORK_TYPE_4G= 3, NETWORK_TYPE_5G= 4,// 5G目前为猜测结果 NETWORK_TYPE_WIFI= 5, };