最近公司在做车联网,想到用MQTT实现多通道连接订阅主题,能够高效的进行消息传递。实现手机端、硬件、网站、后台多端同步。话不多说,上干货。
首先
pod 'MQTTClient'
pod 'MQTTClient/Min'
pod 'MQTTClient/Manager'
pod 'MQTTClient/Websocket'
这里采用的是MQTTClient开源库开发。如果后台的MQTT服务用的ws,则还需要倒入pod 'MQTTClient/Websocket',此处根据自身需要pod即可。
一般情况下我们用MQTTCFSocketTransport设置ip地址以及端口号即可,但是如果像上面的提到的用ws的情况,则需要用到MQTTWebsocketTransport。我的项目就是用的MQTTWebsocketTransport,以下代码可以作为参考。
创建了一个MQTTManager的管理类.h
#import
// 头文件以及配置信息
#import "MQTTHeaderConfig.h"
NS_ASSUME_NONNULL_BEGIN
@protocolMQTTManagerDelegate
@optional
// 连接状态改变回调
-(void)MQTTManagerSessionStatus:(MQTTSessionStatus)status;
// 订阅主题成功|失败回调
-(void)MQTTManagerSubscribeTopicSuccessOrNot:(BOOL)YesOrNot Topic:(NSString*_Nullable)topic;
// 取消订阅主题成功|失败回调
-(void)MQTTManagerCancerSubscribeTopicSuccessOrNot:(BOOL)YesOrNot Topic:(NSString*_Nullable)topic;
// 收到订阅消息
-(void)MQTTManagerReceiveMessage:(MQTTSession*)session data:(NSData*)data onTopic:(NSString*)topic qos:(MQTTQosLevel)qos retained:(BOOL)retained mid:(unsignedint)mid;
@end
@interfaceMQTTManager :NSObject
// 单例
+ (instancetype)manager;
// 配置MQTT
- (void)BindWithCliendId:(NSString*)cliendId SetDelegate:(id)delegate;
// 生成cliendId 的方法
- (NSString*)GetCliendId:(NSString*)userid;
// 订阅主题
- (void)subscribeTopic:(NSString*)topic;
// 取消订阅主题
- (void)unsubscribeTopic:(NSString*)topic;
// 向对应主题发布消息
- (void)sendDataToTopic:(NSString*)topic dict:(NSDictionary*)dict;
- (void)sendDataToTopic:(NSString*)topic SendMsg:(NSString*)msg SendMsgType:(NSString*)type;
// 主动断开连接
- (void)disconnect;
// 重新连接
- (void)MySessionReConnect;
// 代理
@property(nonatomic,weak)id
@property(nonatomic)MQTTSession *__nullable mySession;
@property(nonatomic)MQTTSessionManager*sessionManager;
// 是否连接
@property(nonatomic,assign)BOOL isDiscontent;
// (客户端 id,用于区别客户端)
@property(nonatomic,copy)NSString *_Nullable cliendId;
// 发送的主题
@property(nonatomic,copy)NSString *_Nullable MQTTSendTopic;
// 发送的内容
@property(nonatomic,copy)NSString *_Nullable MQTTSendMsg;
// 发送内容类型
@property(nonatomic,copy)NSString *_Nullable MQTTSendMsgType;
// 客户端确定通知服务器全局主题上线成功,服务器收到的标志 默认为no
@property(nonatomic,assign)BOOL serverDidReceive;
@end
NS_ASSUME_NONNULL_END
.m文件
#import "MQTTManager.h"
#import
@interface MQTTManager()
// 当前 订阅过主题结合
@property(nonatomic,strong)NSMutableArray *subArray;
@end
@implementation MQTTManager
#pragma mark - 懒加载
-(NSMutableArray *)subArray{
if(!_subArray) {
_subArray = [NSMutableArray new];
}
return _subArray;
}
#pragma mark - 单例
+(instancetype)manager{
staticMQTTManager*manager;
staticdispatch_once_tonceToken;
dispatch_once(&onceToken, ^{
manager = [[MQTTManageralloc]init];
// 关闭打印日志
ddLogLevel = DDLogLevelOff;
});
returnmanager;
}
#pragma mark - 生成cliendId 的方法
-(NSString*)GetCliendId:(NSString*)userid{
NSString *cliend_id = [NSString stringWithFormat:@"witcboxapp%@",userid];
returncliend_id;
}
#pragma mark - 配置MQTT操作
-(void)BindWithCliendId:(NSString*)cliendId SetDelegate:(id)delegate{
if(delegate!=nil) {
self.delegate= delegate;
}
// 设置初始值
self.isDiscontent = NO;
// 客户端 Id,用于区别客户端
self.cliendId= cliendId;
// 设置地址和端口号
MQTTWebsocketTransport *transport = [[MQTTWebsocketTransport alloc] init];
transport.host = MQTTHost;
transport.port= MQTTPort;
// 配置MQTTSession
self.mySession = [[MQTTSession alloc] init];
self.mySession.delegate = self;
self.mySession.transport= transport;
self.mySession.userName = MQTTUserName;
self.mySession.password = MQTTPassword;
self.mySession.clientId= cliendId;
self.mySession.connectMessage.qos= 2; // MQTTQosLevelExactlyOnce
// 断线重连时 如果为yes,会自动订阅回消息,如果为no,则要手动订阅topic,不然会收不到消息
self.mySession.willRetainFlag = NO;
// 设置遗言⚠️ 必须设置遗言主题以及msg 不然会报异常
// 开启遗言
self.mySession.willFlag = YES;
// 遗言主题
self.mySession.willTopic =@"主题内容";
// 遗言信息
NSString*willMsgStr =@"主题信息";
NSData *data = [willMsgStr dataUsingEncoding:NSUTF8StringEncoding];
self.mySession.willMsg= data;
// 如果clean设置为true,则代理将不会存储客户端的任何信息,并将清除之前持续会话的所有信息
self.mySession.cleanSessionFlag = true;
// 监听连接状态
[self.mySession addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
// 心跳
self.mySession.keepAliveInterval = 2;
// 连接服务,并设置超时时间
[self.mySession connectAndWaitTimeout:1];
}
#pragma mark ---- 【MQTTManager】连接状态改变
- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context {
NSLog(@"当[MQTTManager]与服务器的连接状态发生变化时,会回调此方法返回当前状态%ld",(long)self.mySession.status);
switch (self.mySession.status) {
case MQTTSessionStatusClosed:
{
NSLog(@"[MQTT连接关闭]");
if (appDelegate.NetworkStatus != AFNetworkReachabilityStatusNotReachable) {
// 这个是为了区分是主动断开还是被动断开
if(!self.isDiscontent){
[selfMySessionReConnect];
}
}
}
break;
case MQTTSessionStatusConnected:
{
NSLog(@"[MQTT连接成功]");
// 取消延迟
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(MySessionReConnect) object:nil];
// 连接成功订阅全局主题以及发送用户上线提醒
NSString *of_user_id = [[NSUserDefaults standardUserDefaults] objectForKey:@"of_user_id"];
if(of_user_id!=nil) {
[self subscribeTopic:[NSString stringWithFormat:IOS_SRT_GlobalTopic,of_user_id]];
}
}
break;
case MQTTSessionStatusConnecting:
{
NSLog(@"[MQTT连接中]");
}
break;
case MQTTSessionStatusError:
NSLog(@"[MQTT连接错误]");
break;
case MQTTSessionStatusDisconnecting:
NSLog(@"[MQTT正在断开连接]");
default:
break;
}
if(self.delegate&&[self.delegaterespondsToSelector:@selector(MQTTManagerSessionStatus:)]) {
// 给使用类返回状态,以便做逻辑处理
NSLog(@"[MQTT正在执行代理]");
[self.delegate MQTTManagerSessionStatus:self.mySession.status];
}
}
#pragma mark - 重新连接
-(void)MySessionReConnect{
// 连接服务,并设置超时时间
[self.mySession connectAndWaitTimeout:1];
}
#pragma mark - 订阅主题
- (void)subscribeTopic:(NSString*)topic {
if (self.mySession.status != MQTTSessionStatusConnected && ![self.subArray containsObject:topic]) {
[self.subArrayaddObject:topic];
return;
}
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
[self.mySession subscribeToTopic:topic atLevel:MQTTQosLevelAtLeastOnce subscribeHandler:^(NSError *error, NSArray
if(error) {
NSLog(@"MQTT订阅主题 - subscribeTopic failed ----- topic = %@ \n %@",topic,error.localizedDescription);
[weakSelf.subArrayaddObject:topic];
[weakSelfsubscribeTopicInArray];
if(weakSelf.delegate&&[weakSelf.delegaterespondsToSelector:@selector(MQTTManagerSubscribeTopicSuccessOrNot:Topic:)]) {
[weakSelf.delegate MQTTManagerSubscribeTopicSuccessOrNot:NO Topic:topic];
}
}else{
if([weakSelf.subArraycontainsObject:topic]) {
[weakSelf.subArrayremoveObject:topic];
}
if(weakSelf.delegate&&[weakSelf.delegaterespondsToSelector:@selector(MQTTManagerSubscribeTopicSuccessOrNot:Topic:)]) {
[weakSelf.delegate MQTTManagerSubscribeTopicSuccessOrNot:YES Topic:topic];
}
NSString *of_user_id = [[NSUserDefaults standardUserDefaults] objectForKey:@"of_user_id"];
NSString*globalTopic = [NSStringstringWithFormat:IOS_SRT_GlobalTopic,of_user_id];
if([topicisEqualToString:globalTopic]) {
NSLog(@"[MQTT全局主题订阅成功]");
// 开启全局timer发送客户端上线通知给服务器
[[MQTTSHLTimer manager] startTimerWithType:carOnlineToServer SendMsgInfo:@{@"sendTopic":[NSString stringWithFormat:IOS_SRT_UserSendTopic,of_user_id]}];
}
NSLog(@"MQTT订阅主题 - subscribeTopic sucessfull 成功! topic = %@ \n %@",topic,gQoss);
}
}];
});
});
}
#pragma mark - 订阅主题失败,设置重新订阅
-(void)subscribeTopicInArray{
for(NSString*topicinself.subArray) {
[selfsubscribeTopic:topic];
}
}
#pragma mark - 取消订阅主题
- (void)unsubscribeTopic:(NSString*)topic {
__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
[self.mySession unsubscribeTopic:topic unsubscribeHandler:^(NSError *error) {
if(error) {
NSLog(@"MQTT取消订阅 - unsubscribeTopic failed ----- topic = %@ \n %@",topic,error.localizedDescription);
if(weakSelf.delegate&&[weakSelf.delegaterespondsToSelector:@selector(MQTTManagerCancerSubscribeTopicSuccessOrNot:Topic:)]) {
[weakSelf.delegate MQTTManagerCancerSubscribeTopicSuccessOrNot:NO Topic:topic];
}
}else{
NSLog(@"MQTT取消订阅 - unsubscribeTopic sucessfull 成功! topic = %@ ",topic);
if(weakSelf.delegate&&[weakSelf.delegaterespondsToSelector:@selector(MQTTManagerCancerSubscribeTopicSuccessOrNot:Topic:)]) {
[weakSelf.delegate MQTTManagerCancerSubscribeTopicSuccessOrNot:YES Topic:topic];
}
}
}];
});
});
}
#pragma mark - 向对应主题发布消息
// 发送对象
- (void)sendDataToTopic:(NSString*)topic dict:(NSDictionary*)dict {
[self.mySession publishJson:dict onTopic:topic];
}
- (void)sendDataToTopic:(NSString*)topic SendMsg:(NSString*)msg SendMsgType:(NSString*)type{
if (self.mySession.status == MQTTSessionStatusConnected) {
// 记录发送的主题以及消息(用作逻辑判断)
self.MQTTSendTopic= topic;
self.MQTTSendMsg = msg;
self.MQTTSendMsgType= type;
NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding];
if(data) {
[self.mySession publishData:data onTopic:topic retain:false qos:MQTTQosLevelAtLeastOnce];
}else{
NSLog(@"[MQTTManager] 发送数据异常");
}
}
}
#pragma mark - 数据接收回调MQTTSessionDelegate
- (void)newMessage:(MQTTSession*)session data:(NSData*)data onTopic:(NSString*)topic qos:(MQTTQosLevel)qos retained:(BOOL)retained mid:(unsignedint)mid {
NSMutableString *string = [[NSMutableString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"thl ----- string = %@ \n -------- topic:%@ --------- \n--------mid:%d--------",string,topic,mid);
// 收到订阅消息
if(self.delegate&& [self.delegaterespondsToSelector:@selector(MQTTManagerReceiveMessage:data:onTopic:qos:retained:mid:)]) {
[self.delegate MQTTManagerReceiveMessage:session data:data onTopic:topic qos:qos retained:retained mid:mid];
}
}
#pragma mark - 主动断开连接
- (void)disconnect {
self.isDiscontent=YES; // 断开连接
// 置空操作
self.delegate=nil;
// 断开连接
[self.mySession disconnect];
// 置空
self.mySession = nil;
// 移除状态监听
[self.mySession removeObserver:self forKeyPath:@"status"];
}
@end