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 (){
    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 

@interface ZHeartBeatSocket : NSObject

+ (instancetype)shareZheartBeatSocket;
- (void)initZheartBeatSocket;               //创建单例内部的GCDAsyncSocket
- (void)runTimerWhenAppEnterBackGround;     //如果需要在APP进入后台开启NStimer

@end

ZasyncSocket.m
#import "ZHeartBeatSocket.h"
#import "GCDAsyncSocket.h"

#import 
#import 
#import 
#import 

#define SocketHOST @"192.168.1.5"         //服务器ip地址
#define SocketonPort 8888                 //服务器端口号

@interface ZHeartBeatSocket() {
    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 - 
- (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 实现后台模式长时间驻留吗?实现了吗现在?


你可能感兴趣的:(iOS_socket)