iOS MQTT协议笔记

前言

接到任务项目需要用MQTT来写消息推送,经过一段时间在网上查看资料后写下这篇文章,文章内容大都来自互联网,在文章最后也会贴出相关网址和Demo。写这文章主要目的是自己总结下经验做下笔记,以便日后查阅,并希望能帮助一些需要帮助的人。

MQTT简介

想必能找到我这篇文章的也一定看过不少其它关于MQTT的文章了,简介也应该能背下来了,我这里就不过多介绍了...(此处省略9999+字)

基于MQTT-Client的iOS客户端实现

目前iOS的第三方主要有MQTT-ClientMQTTKit和MQTTSDK。因为MQTTKit较长时间没有维护更新而MQTT-Client维护还是比较频繁的,所以我选择了MQTT-Client,至于MQTTSDK我还没有去了解,有兴趣的同学可以去了解下。使用swift的同学可以使用CocoaMQTT,这个sdk的作者同时也是服务端实现emqtt的作者。当然,MQTT-Client也是有swift的。

MQTT-Client支持pod,方便快速集成到工程中。

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.4'
pod 'MQTTClient'
target 'XXXXX' do
end

简单说下应用场景,app在每部安装的终端上会产生不同的业务数据,而业务数据需要在各终端app上都要看到,所以需要同步所有终端的业务数据。mqtt的消息传输都是通过topic进行的,topic需要创建,按照mqtt实现没有单独的创建topic的方法,topic是和订阅绑定的。也就是说只要订阅了一个topic,服务端首先判断是否有topic存在,如果存在的话把当前客户端加入到订阅列表中,如果不在的话就先创建一个topic,同时把自己添加到订阅列表中。

topic是和订阅绑定的.png

建立连接

如果app需要在多个页面传输数据建议使用单例模式,建立一个全局的连接,复用连接。因为每次连接到服务端也是很消耗资源的。建立连接的代码如下:

self.manager = [[MQTTSessionManager alloc] init];
        self.manager.delegate = self;
        self.manager.subscriptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:MQTTQosLevelExactlyOnce] forKey:[NSString stringWithFormat:@"%@/#", self.base]];
        [self.manager connectTo:@“192.168.1.4” //服务器地址 
                             port:1883 //服务端端口号 
                              tls:false //是否使用tls协议,mosca是支持tls的,如果使用了要设置成true 
                        keepalive:60 //心跳时间,单位秒,每隔固定时间发送心跳包 
                            clean:false //session是否清除,这个需要注意,如果是false,代表保持登录,如果客户端离线了再次登录就可以接收到离线消息。注意:QoS为1和QoS为2,并需订阅和发送一致 
                             auth:true //是否使用登录验证,和下面的user和pass参数组合使用 
                             user:_userName //用户名 
                             pass:_passwd //密码 
                        willTopic:@"" //下面四个参数用来设置如果客户端异常离线发送的消息,当前参数是哪个topic用来传输异常离线消息,这里的异常离线消息都指的是客户端掉线后发送的掉线消息 
                             will:@"" //异常离线消息体。自定义的异常离线消息,约定好格式就可以了 
                          willQos:0 //接收离线消息的级别 0、1、2
                   willRetainFlag:false //只有在为true时,Will Qos和Will Retain才会被读取,此时消息体payload中要出现Will Topic和Will Message具体内容,否则,Will QoS和Will Retain值会被忽略掉
                     withClientId:nil]; //客户端id,需要特别指出的是这个id需要全局唯一,因为服务端是根据这个来区分不同的客户端的,默认情况下一个id登录后,假如有另外的连接以这个id登录,上一个连接会被踢下线

订阅和发送消息

连接一旦建立以后就可以订阅topic和发送消息了,订阅和发送消息代码如下:

//订阅主题。NSDictionary类型,Object 为 QoS,key 为 Topic
self.manager.subscriptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:MQTTQosLevelExactlyOnce] forKey:_topic];

//发送消息,返回值msgid大于0代表发送成功。(注意:这里说“发送成功”并不是指“成功发送给服务器”(无网络情况下也可能会返回大于0))
//msgid:PUBLISH消息的消息标识符。 如果qos为0,则为零。如果qos为1或2,则在消息丢弃时为零
UInt16 msgid = [self.manager sendData:[msg dataUsingEncoding:NSUTF8StringEncoding] //要发送的消息体 
                                  topic:_topic //要往哪个topic发送消息 
                                    qos:MQTTQosLevelExactlyOnce //消息级别 
                                 retain:false];

实际消息发送成功时回调
仅在使用qos 1或2时有效。(记得设置delegate)

//MQTTSessionManagerDelegate
/ **在实际已发送消息时被调用
  @param msgID传递的消息的消息标识符
  @note 仅在使用qos 1或2发布后调用此方法
 */
-(void)messageDelivered:(UInt16)msgID{
    
}

获取订阅成功的主题:

//注册一个观察者,监听effectiveSubscriptions的改变
[self.manager addObserver:self
                  forKeyPath:@"effectiveSubscriptions"
                     options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
                     context:nil];

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if ([keyPath isEqualToString:@"effectiveSubscriptions"]) {
        MQTTSessionManager *manager = (MQTTSessionManager *)object;
        NSLog(@"订阅成功的主题: %@", manager.effectiveSubscriptions);
        return;
    }
}

接收消息

接收消息有委托实现,实现如下委托MQTTSessionManagerDelegate接收消息

/*
 * MQTTSessionManagerDelegate
 */
- (void)handleMessage:(NSData *)data onTopic:(NSString *)topic retained:(BOOL)retained {
    /*
     * MQTTClient: process received message
     */
    NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}

监控连接状态

注册一个观察者,判断state获取不同的连接状态

//注册观察者,记得在离开页面时移除观察者
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self.manager addObserver:self
                     forKeyPath:@"state"
                        options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
                        context:nil];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    switch (self.manager.state) {
        case MQTTSessionManagerStateClosed: //连接已经关闭
            break;
        case MQTTSessionManagerStateClosing: //连接正在关闭
            break;
        case MQTTSessionManagerStateConnected: //已经连接
            break;
        case MQTTSessionManagerStateConnecting: //正在连接中
            break;
        case MQTTSessionManagerStateError: //异常
            break;
        case MQTTSessionManagerStateStarting: //开始连接
        default:
            break;
    }
}

mqtt协议本身支持断线重连,另外单独说明此sdk在app退出到后台后自动断开连接,当回到前台时会自动重新连接 ! ! !

一些属性的说明

  • clean:值为false,服务器必须在客户端断开之后继续存储/保持客户端的订阅状态。这些状态包括:
    存储订阅的消息QoS1和QoS2消息
    正在发送消息期间连接丢失导致发送失败的消息
    以便当客户端重新连接时以上消息可以被重新传递。
    值为true,服务器需要立刻清理连接状态数据。

  • topic:客户端如果互相通信,必须在同一订阅主题下,即都订阅了同一个topic,客户端之间是没办法直接通讯的。订阅模型显而易见的好处是群发消息的话只需要发布到topic,所有订阅了这个topic的客户端就可以接收到消息了。好比QQ群的群号,你订阅一个topic 就相当于加入了一个群

  • qos:这个代表消息的传输方式,QoS说明如下:
    0代表“至多一次”,消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。
    1代表“至少一次”,确保消息到达,但消息重复可能会发生。
    2代表“只有一次”,确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果。 备注:由于服务端采用Mosca实现,Mosca目前只支持到QoS 1
    如果发送的是临时的消息,例如给某topic所有在线的设备发送一条消息,丢失的话也无所谓,0就可以了(客户端登录的时候要指明支持的QoS级别,同时发送消息的时候也要指明这条消息支持的QoS级别),如果需要客户端保证能接收消息,需要指定QoS为1,如果同时需要加入客户端不在线也要能接收到消息,那么客户端登录的时候要指定session的有效性,接收离线消息需要指定服务端要保留客户端的session状态。

  • retain:true:表示发送的消息需要一直持久保存(不受服务器重启影响),不但要发送给当前的订阅者,并且以后新来的订阅了此Topic name的订阅者会马上得到推送。
    备注:新来乍到的订阅者,只会取出最新的一个RETAIN flag = 1的消息推送。
    false:仅仅为当前订阅者推送此消息。
    假如服务器收到一个空消息体(zero-length payload)、RETAIN = 1、已存在Topic name的PUBLISH消息,服务器可以删除掉对应的已被持久化的PUBLISH消息。

  • 连接异常中断通知机制

在建立连接的时候就把 will(遗嘱)写好保存在服务器,一旦客户端出现异常中断,便会触发服务器发布Will Message消息到Will Topic主题上去,通知Will Topic订阅者,对方因异常退出。

  • will Topic:用来传输异常离线消息的 topic,这里的异常离线消息都指的是客户端掉线后发送的掉线消息
  • will:异常离线消息。自定义的异常离线消息,约定好格式就可以了
  • willQos:和发布消息固的QoS含义一样
  • willRetainFlag:只有在为true时,Will Qos和Will Retain才会被读取,此时消息体中要出现Will Topic和Will Message具体内容,否则,Will QoS和Will Retain值会被忽略掉

网址和Demo

-END-
一个公开的mosquitto MQTT测试服务器 test.mosquitto.org

如果此文章对你有帮助,希望给个❤️。有什么问题欢迎在评论区探讨。

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

推荐阅读更多精彩内容