1. 程式人生 > >ios 後臺無限心跳實現:GCDAsyncSocket使用的 Voip、NSTimer、10分鐘超長連結

ios 後臺無限心跳實現:GCDAsyncSocket使用的 Voip、NSTimer、10分鐘超長連結


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 實現後臺模式長時間駐留嗎?實現了嗎現在?