ios 後臺無限心跳實現:GCDAsyncSocket使用的 Voip、NSTimer、10分鐘超長連結
阿新 • • 發佈:2019-01-03
http://blog.csdn.net/zhoutaozagt/article/details/52054482
準備工作: <一> 下載AsyncSocket https://github.com/robbiehanson/CocoaAsyncSocket/ 類庫,將GCD資料夾下的GCDAsyncSocket.h, GCDAsyncSocket.m, GCDAsyncUdpSocket.h, GCDAsyncUdpSocket.m 檔案拷貝到自己的project中 <二> 在plist檔案中的Required background modes這一項中新增以下兩項(預設專案中是沒有這一項的,需要手動新增):App play audio or streams audio/video using AirPlay 和 App provides Voice over IP services 。IOS7中沒有這麼麻煩,可以直接點選專案檔案,勾選以下兩項: <三> 新增CFNetwork.framework。 <四>可選項:在使用socket的檔案頭import下面的檔案:(如果沒有import,可以使用NStimer計時完成心跳功能) 開始編碼: 1. socket 連線 即時通訊最大的特點就是實時性,基本感覺不到延時或是掉線,所以必須對socket的連線進行監視與檢測,在斷線時進行重新連線,如果使用者退出登入,要將socket手動關閉,否則對伺服器會造成一定的負荷。 一般來說,一個使用者(對於iOS來說也就是我們的專案中)只能有一個正在連線的socket,所以這個socket變數必須是全域性的,這裡可以考慮使用單例或是GCDAppDelegate進行資料共享,本文使用單例。 如果對一個已經連線的socket物件再次進行連線操作,會丟擲異常(不可對已經連線的socket進行連線)程式崩潰,所以在連線socket之前要對socket物件的連線狀態進行判斷 使用socket進行即時通訊還有一個必須的操作,即對伺服器傳送心跳包,每隔一段時間對伺服器傳送長連線指令(指令不唯一,由伺服器端指定,包括使用socket傳送訊息,傳送的資料和格式都是由伺服器指定),如果沒有收到伺服器的返回訊息,GCDAsyncSocket會得到失去連線的訊息,我們可以在失去連線的回撥方法裡進行重新連線。 2. 先建立一個單例,命名為ZasyncSocket AppDelegate.m #import "ZHeartBeatSocket.h" @interface AppDelegate ()<UITabBarControllerDelegate>{ ZHeartBeatSocket *_socket; } @end - (void)applicationDidEnterBackground:(UIApplication *)application{ //進入後臺,之後每10分鐘發一次通知 [[UIApplication sharedApplication] setKeepAliveTimeout:600 handler:^{ [[NSNotificationCenter defaultCenter]postNotificationName:@"CreatGcdSocket" object:nil userInfo:nil];}]; //如果需要新增NSTimer [_socket runTimerWhenAppEnterBackGround]; } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { _socket = [ZHeartBeatSocket shareZheartBeatSocket]; [_socket initZheartBeatSocket]; return YES; } ZasyncSocket.h #import <Foundation/Foundation.h> @interface ZHeartBeatSocket : NSObject + (instancetype)shareZheartBeatSocket; - (void)initZheartBeatSocket; //建立單例內部的GCDAsyncSocket - (void)runTimerWhenAppEnterBackGround; //如果需要在APP進入後臺開啟NStimer @end ZasyncSocket.m #import "ZHeartBeatSocket.h" #import "GCDAsyncSocket.h" #import <sys/socket.h> #import <netinet/in.h> #import <arpa/inet.h> #import <unistd.h> #define SocketHOST @"192.168.1.5" //伺服器ip地址 #define SocketonPort 8888 //伺服器埠號 @interface ZHeartBeatSocket() <GCDAsyncSocketDelegate>{ GCDAsyncSocket *_asyncSocket; NSString *_getStr; BOOL _isInContentPerform; } @property (nonatomic, retain) NSTimer *connectTimer; // 計時器 @end @implementation ZHeartBeatSocket //單例 + (instancetype)shareZheartBeatSocket{ static dispatch_once_t onceToken; static ZHeartBeatSocket *instance; dispatch_once(&onceToken, ^{ instance = [[ZHeartBeatSocket alloc]init]; }); return instance; } //初始化 GCDAsyncSocket - (void)initZheartBeatSocket{ [self creatSocket]; //註冊APP退到後臺,之後每十分鐘傳送的通知,與VOIP無關,由於等待時間必須大於600s,不使用 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(creatSocket) name:@"CreatGcdSocket" object:nil]; } //INT_MAX 最大時間連結,心跳必須! -(void)creatSocket{ if (_asyncSocket == nil || [_asyncSocket isDisconnected]) { //初始化 GCDAsyncSocket _asyncSocket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; [_asyncSocket enableBackgroundingOnSocketWithCaveat]; NSError *error = nil; if (![_asyncSocket connectToHost:SocketHOST onPort:SocketonPort withTimeout:INT_MAX error:&error]) { //socket通訊已經連線 } }else { //讀取Socket通訊內容 [_asyncSocket readDataWithTimeout:INT_MAX tag:0]; //編寫Socket通訊提交伺服器 NSString *inputMsgStr = [NSString stringWithFormat:@"客戶端收到%@",_getStr]; NSString * content = [inputMsgStr stringByAppendingString:@"\r\n"]; NSData *data = [content dataUsingEncoding:NSISOLatin1StringEncoding]; [_asyncSocket writeData:data withTimeout:INT_MAX tag:0]; [self heartbeat]; } } - (void)heartbeat{ /* *此處是一個心跳請求連結(自己的伺服器),Timeout時間隨意 */ NSLog(@"heart live-----------------"); } #pragma mark - <GCDasyncSocketDelegate> - (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(nullable NSError *)err{ [_asyncSocket disconnect]; [_asyncSocket disconnectAfterReading]; [_asyncSocket disconnectAfterWriting]; [_asyncSocket disconnectAfterReadingAndWriting]; // 伺服器掉線,重連(不知道為什麼我們的伺服器沒兩分鐘重連一次),必須新增 if (!_isInContentPerform) { _isInContentPerform = YES; [self performSelector:@selector(perform) withObject:nil afterDelay:2]; } } - (void)perform{ _isInContentPerform = NO; //_asyncSocket = [[GCDAsyncSocket alloc]initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; NSError *error = nil; [_asyncSocket connectToHost:SocketHOST onPort:SocketonPort withTimeout:INT_MAX error:&error]; } - (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port{ [self creatSocket]; } -(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag{ //接收到訊息。 _getStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; //讀取訊息 [self creatSocket]; } #pragma mark - <可選接入,當伺服器退入後臺啟動timer,包括之前所有的> - (void)runTimerWhenAppEnterBackGround{ // 每隔30s像伺服器傳送心跳包 if (self.connectTimer == nil) { self.connectTimer = [NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(heartbeat) userInfo:nil repeats:YES]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addTimer:self.connectTimer forMode:NSDefaultRunLoopMode]; } [self.connectTimer fire]; //配置所有新增RunLoop後臺的NSTimer可用! UIApplication* app = [UIApplication sharedApplication]; __block UIBackgroundTaskIdentifier bgTask; bgTask = [app beginBackgroundTaskWithExpirationHandler:^{ dispatch_async(dispatch_get_main_queue(),^{ if(bgTask != UIBackgroundTaskInvalid){ bgTask = UIBackgroundTaskInvalid; } }); }]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{ dispatch_async(dispatch_get_main_queue(), ^{ if(bgTask != UIBackgroundTaskInvalid){ bgTask = UIBackgroundTaskInvalid; } }); }); } @end 3. 修改GCDAsyncSocket.m檔案 步驟1:斷點下面語句 CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); 改成:CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); //(這裡需不需要加上我不清楚,反正加上也不會報錯。。。) [(__bridge NSInputStream *)readStream setProperty:NSStreamNetworkServiceTypeVoIP forKey:NSStreamNetworkServiceType]; 步驟2:斷點下面語句 CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); 改成:CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanFalse); CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); //(這裡需不需要加上我不清楚,反正加上也不會報錯。。。) [(__bridge NSOutputStream *)writeStream setProperty:NSStreamNetworkServiceTypeVoIP forKey:NSStreamNetworkServiceType]; 頂 1 踩 0 下一篇ios 簡單獲取地理位置資訊 參考知識庫 img iOS知識庫 3134關注|1400收錄 img Swift知識庫 3359關注|819收錄 猜你在找 iOS專案實戰視訊課程:PM2.5實時查詢AppiOS8開發技術(Swift版):iOS基礎知識從零練就iOS高手實戰班瘋狂IOS講義之Objective-C面向物件設計TCP/IP/UDP Socket通訊開發實戰 適合iOS/Android/Linux 實現iOS長時間後臺的兩種方法Audiosession和VOIP實現iOS長時間後臺的兩種方法Audiosession和VOIPIOS實現Voip應用後臺執行需要的幾個配置項實現iOS長時間後臺的兩種方法Audiosession和VOIPIOS實現Voip應用後臺執行需要的幾個配置項 檢視評論 5樓 Jiurong001 2小時前發表 [回覆] 四個資料夾copy到專案中,直接崩潰 /Users/macbook/Library/Developer/Xcode/DerivedData/YXWincall-eoflkihcgvixehcxyzivicumfdhw/Build/Intermediates/YXWincall.build/Debug-iphonesimulator/YXWincall.build/Objects-normal/x86_64/GCDAsyncUdpSocket-FD11684EAACC957B.o duplicate symbol _OBJC_IVAR_$_GCDAsyncUdpSocket.readStream4 4樓 Jiurong001 4小時前發表 [回覆] 你好,voip後臺模式app實現長時間掛起; sokect 伺服器方面需要做哪些配置;現在,你們上架會被拒嗎?。 3樓 lyt111111111 2016-10-13 11:21發表 [回覆] 樓主, [_asyncSocket enableBackgroundingOnSocketWithCaveat];這個是什麼方法啊?怎麼我這裡報錯呢 還有你這個方案 如果從後臺調回前臺 心跳包也一直在執行 應該在調回前臺的時候把通知和心跳請求清除掉吧? 還有一個問題就是我的app進入後臺後3分鐘的樣子,就被系統殺死了,再次從後臺調到前臺的時候,畫面就是不當時進入後臺時的頁面,而是重啟app 2樓 lyt111111111 2016-10-13 11:14發表 [回覆] 樓主, [_asyncSocket enableBackgroundingOnSocketWithCaveat];這個是什麼方法啊?怎麼我這裡報錯呢 還有你這個方案 如果從後臺調回前臺 心跳包也一直在執行 應該在調回前臺的時候把通知和心跳請求清除掉吧? 還有一個問題就是我的app進入後臺後3分鐘的樣子,就被系統殺死了,再次從後臺調到前臺的時候,畫面就是不當時進入後臺時的頁面,而是重啟app 1樓 lyt111111111 2016-10-13 11:13發表 [回覆] 樓主, [_asyncSocket enableBackgroundingOnSocketWithCaveat];這個是什麼方法啊?怎麼我這裡報錯呢 還有你這個方案 如果從後臺調回前臺 心跳包也一直在執行 應該在調回前臺的時候把通知和心跳請求清除掉吧? 還有一個問題就是我的app進入後臺後3分鐘的樣子,就被系統殺死了,再次從後臺調到前臺的時候,畫面就是不當時進入後臺時的頁面,而是重啟appRe: Jiurong001 4小時前發表 [回覆] 回覆lyt111111111:你們在做 voip 實現後臺模式長時間駐留嗎?實現了嗎現在?