原文地址:http://blog.sina.com.cn/s/blog_144141ea00102vgzr.html
在写之前,我们首先了解一下为什么iOS维护长连接需要心跳机制,首先我们知道,维护任何一个长连接都需要心跳机制,客户端发送一个心跳给服务器,服务器给客户端一个心跳应答,这样就形成客户端服务器的一次完整的握手,这个握手是让双方都知道他们之间的连接是没有断开,客户端是在线的。如果超过一个时间的阈值,客户端没有收到服务器的应答,或者服务器没有收到客户端的心跳,那么对客户端来说则断开与服务器的连接重新建立一个连接,对服务器来说只要断开这个连接即可。也就是说,如果想要完成及时聊天的功能,我们需要一个订阅和一个发布功能。话不多说,进入主题,先去Github上down一个MQTTKit框架,网址https://github.com/mobile-web-messaging/MQTTKit (需要 如果你们公司连软件都买不起 趁早辞了吧)然后把MQTTKit导入工程
新建一个类VPKCClientManager
VPKCClientManager.h代码如下
#import
#import "MQTTKit.h"
@interface VPKCClientManager : NSObject
+ (instancetype)sharedClient;
- (void)subscribeMessageWithMessageHandler:(void(^)(NSDictionary *dict))messageHandler;
@end
VPKCClientManager.m代码如下
#import "VPKCClientManager.h"
#define MQTT_HOST @"**************"//MQTT服务器网址 什么?你们后台不会搭建MQTT服务器 呵呵。。
static VPKCClientManager *instance = nil;
@implementation VPKCClientManager
{
MQTTClient *client;
}
+ (instancetype)sharedClient
{
//这里用到了单例 个人感觉不用也行
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
- (instancetype)init
{
if (self = [super init]) {
//这里我们选择了把deviceId作为mqtt的clientId port为端口号 username是用户名 password是密码
NSString *deviceId = [[NSUserDefaultsstandardUserDefaults] objectForKey:@"deviceId"];
NSString *clientId = [NSString stringWithFormat:@"kc-%@", deviceId];
client = [[MQTTClient alloc]initWithClientId:clientId];
client.port = 1883;
client.username = @"kidcares";
client.password = @"12345";
}
return self;
}
// 重点来了 这里是MQTT的订阅方法 在你需要的地方 调用就行
- (void)subscribeMessageWithMessageHandler:(void(^)(NSDictionary *dict))messageHandler
{
[client setMessageHandler:^(MQTTMessage *message){
id json = [NSJSONSerializationJSONObjectWithData:message.payloadoptions:NSJSONReadingAllowFragments error:nil];
messageHandler(json);
}];
[client connectToHost:MQTT_HOSTcompletionHandler:^(MQTTConnectionReturnCode code) {
if (code == ConnectionAccepted) {
[client subscribe:client.clientIDwithCompletionHandler:nil];
}
}];
}
@end
然后在你要用的控制器中导入#import "VPKCClientManager.h"
创建一个对象static VPKCClientManager *clientManager;
clientManager = [VPKCClientManager sharedClient];//初始化
这里就是MQTT的订阅方法 这个方法执行后 在MQTT服务器上就可以看到你的设备 dict就是从MQTT服务器传下来的内容
[clientManagersubscribeMessageWithMessageHandler:^(NSDictionary *dict) {
[clientManagersubscribeMessageWithMessageHandler:^(NSDictionary *dict) {
for (id key in dict) {//因为MQTT可以有多种用处,为了区分各个功能这里我们选择了遍历dict 根据key来判断如何做出响应
if ([key isEqualToString:@"shoutResult"]) {
}
else if ([key isEqualToString:@"chat"])
{
NSDictionary * chat = [dict valueForKey:@"chat"];
playurl = [chat valueForKey:@"fileUrl"];//这里取出服务器文件的地址
time = [chat valueForKey:@"fileRunningTime"];
//这个是文件下载的封装方法 下面会有介绍
[self.audio downloadTaskURL:playurlcompletion:^(NSURL *fileplay) {
[selfperformSelectorOnMainThread:@selector(refreshtable:)withObject:fileplay waitUntilDone:NO];//因为AFN下载涉及到多线程问题,如果不采用主线程加载,UI效果不会刷新
self.url = fileplay;
}];
}
}
}];
至此 MQTT订阅部分完成了 下面我们来说说MQTT的发布
新建一个类VPKCPush
#import
#import "VPKCPushModel.h"
@interface VPKCPush : NSObject
+ (void)pushMessageParameters:(VPKCPushModel *)param success:(void (^)(NSString *msgId))success failure:(void (^)(NSInteger statusCode))failure;
@end
#import "VPKCPush.h"
#import "MJExtension.h"//一个三方的直接生成keyvaluepairs的方法
@implementation VPKCPush
+ (void)pushMessageParameters:(VPKCPushModel *)param success:(void (^)(NSString *msgId))success failure:(void (^)(NSInteger statusCode))failure
{
NSString *url = [KIDCARES_HOSTstringByAppendingString:@"/api/v1/push"];
[VPKCNetworking POSTWithURL:url parameters:[paramkeyValues] success:^(AFHTTPRequestOperation *operation, idresponseObject) {
success([responseObject valueForKey:@"msgId"]);
} failure:^(AFHTTPRequestOperation *operation, NSError*error) {
FAILURE;
}];
}
这里是上传的下载的封装方法 发送消息时调用上传 接受消息调用下载
- (void)fileUploadParameters:(VPKCFileInfo *)file MIMEType:(NSString *)mimeType success:(void (^)(NSString *, NSString*))success failure:(void (^)(NSInteger))failure
{
NSString *url = [KIDCARES_HOSTstringByAppendingString:@"/api/v1/file"];
NSDictionary *param = [NSDictionarydictionaryWithObjectsAndKeys:
file.expiration, @"expiration",
file.expires, @"expires",
file.size, @"size", nil];
NSLog(@"~~~~~~~~~%@",param);
AFHTTPRequestOperationManager *manager = [VPKCNetworking managerWithURL:url httpMethod:@"POST"];
[manager POST:url parameters:paramconstructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileData:file.dataname:@"file" fileName:file.name mimeType:mimeType];
NSLog(@"===========%@",formData);
} success:^(AFHTTPRequestOperation *operation, idresponseObject) {
NSString *fileUrl = [responseObject valueForKey:@"fileUrl"];
NSString *fileMd5 = [responseObject valueForKey:@"fileMd5"];
success(fileUrl, fileMd5);
} failure:^(AFHTTPRequestOperation *operation, NSError*error) {
FAILURE;
}];
}
- (void)fileDownloadUrl:(NSString *)url success:(void (^)(NSString *))success failure:(void (^)(NSInteger))failure
{
AFHTTPRequestOperationManager *manager = [VPKCNetworking managerWithURL:url httpMethod:@"GET"];
[manager GET:url parameters:nilsuccess:^(AFHTTPRequestOperation *operation, idresponseObject) {
NSString *fileName = [NSStringstringWithFormat:@"%.0f.caf", [NSDatetimeIntervalSinceReferenceDate]];
NSString *filePath = [NSTemporaryDirectory()stringByAppendingPathComponent:fileName];
NSData *data = [NSData dataWithData:responseObject];
[data writeToFile:filePath atomically:YES];
success(filePath);
} failure:^(AFHTTPRequestOperation *operation, NSError*error) {
FAILURE;
}];
}
然后在你要发布MQTT消息的控制器里导入头文件
[self.audio uploadTaskSuccess:^(NSString *fileUrl, NSString*fileMd5) {//调用文件上传的方法
VPKCDialogue *request = [[VPKCDialogue alloc] init];
VPKCChat *chat = [[VPKCChat alloc] init];
chat.fileRunningTime = self.audio.duration;
chat.fileUrl = fileUrl;
chat.fileMd5 = fileMd5;
chat.createTime = self.audio.createTime;
NSString *kidDevice = [[NSUserDefaultsstandardUserDefaults] objectForKey:@"kiddeviceId"];
NSArray * kidDevicearray = [[NSArray alloc]initWithObjects:kidDevice, nil];
if ([kidDevicearray count]) {
model = [[VPKCPushModel alloc] initWithShout:chatdevices:@[kidDevice]];
}
[request shoutParameters:model success:^(NSString*msgId) {
sendsuccess = [[UILabel alloc]initWithFrame:CGRectMake(self.view.frame.size.width/2-50,self.view.frame.size.height/2,100, 50)];
sendsuccess.text=@"发送成功";
sendsuccess.textColor = [UIColor whiteColor];
sendsuccess.textAlignment=NSTextAlignmentCenter;
sendsuccess.backgroundColor=[UIColor grayColor];
sendsuccess.alpha=0.7;
[self.view addSubview:sendsuccess];
[NSTimer scheduledTimerWithTimeInterval:2target:self selector:@selector(Timerisover:) userInfo:nilrepeats:NO];
} failure:^(NSInteger statusCode) {
sendfail = [[UILabel alloc]initWithFrame:CGRectMake(self.view.frame.size.width/2-50,self.view.frame.size.height/2,100, 50)];
sendfail.text=@"发送失败";
sendfail.textAlignment=NSTextAlignmentCenter;
sendfail.backgroundColor=[UIColor grayColor];
sendfail.alpha=0.7;
[self.view addSubview:sendfail];
[NSTimer scheduledTimerWithTimeInterval:2target:self selector:@selector(Timerisover1:) userInfo:nilrepeats:NO];
}];
} failure:^(NSInteger statusCode) {
}];
至此MQTT订阅发布的流程已经完成 ,最后强调一点,要实现两个设备之间的通信 必须保证两个设备的同时在MQTT server上在线。