准备工作:
<一> 下载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 实现后台模式长时间驻留吗?实现了吗现在?