概述
准备工作:
<一> 下载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:@"rn"];
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];
最后
以上就是怕孤单红酒为你收集整理的ios 后台无限心跳实现:GCDAsyncSocket使用的 Voip、NSTimer、10分钟超长链接的全部内容,希望文章能够帮你解决ios 后台无限心跳实现:GCDAsyncSocket使用的 Voip、NSTimer、10分钟超长链接所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复