ios 后台无限心跳实现:GCDAsyncSocket使用的 Voip、NSTimer、10分钟超长链接

准备工作:

<一>  下载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
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值