iOS MQTT(MQTTClient) 车联网之实战篇

    最近公司在做车联网,想到用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<MQTTManagerDelegate>delegate;

@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()<MQTTSessionDelegate>

// 当前 订阅过主题结合

@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<NSNumber *> *gQoss) {


                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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容