開篇
最近在使用MQTTClient實(shí)現(xiàn)一個(gè)類似于消息推送的服務(wù)怠惶,說(shuō)實(shí)話躺枕,真沒怎么使用過(guò)MQTTClient苏遥,也不知道這是個(gè)啥奸例? 好尷尬 ?? ?? ?? 上網(wǎng)了解了一下彬犯,發(fā)現(xiàn)MQTT功能挺強(qiáng)(牛)大(逼)。這里我使用的是消息推送服務(wù)查吊,通過(guò)和服務(wù)器端協(xié)商谐区,終于能夠與服務(wù)器連接,并且能夠收發(fā)消息了逻卖。 所以宋列,簡(jiǎn)單總結(jié)了一下,有了這篇文章评也。
MQTT介紹
- MQTT
MQTT基于訂閱者模型架構(gòu)炼杖,客戶端如果互相通信,必須在同一訂閱主題下盗迟,即都訂閱了同一個(gè)topic坤邪,客戶端之間是沒辦法直接通訊的。訂閱模型顯而易見的好處是群發(fā)消息的話只需要發(fā)布到topic罚缕,所有訂閱了這個(gè)topic的客戶端就可以接收到消息了艇纺。
發(fā)送消息必須發(fā)送到某個(gè)topic,重點(diǎn)說(shuō)明的是不管客戶端是否訂閱了該topic都可以向topic發(fā)送了消息邮弹,還有如果客戶端訂閱了該主題黔衡,那么自己發(fā)送的消息也會(huì)接收到。
- MQTT特點(diǎn)
使用發(fā)布/訂閱消息模式腌乡,提供一對(duì)多的消息發(fā)布盟劫,解除應(yīng)用程序耦合。這一點(diǎn)很類似于XMPP导饲,但是MQTT的信息冗余遠(yuǎn)小于XMPP.
對(duì)負(fù)載內(nèi)容屏蔽的消息傳輸捞高。
使用TCP/IP提供網(wǎng)絡(luò)連接。主流的MQTT是基于TCP連接進(jìn)行數(shù)據(jù)推送的渣锦,但是同樣有基于UDP的版本硝岗,叫做MQTT-SN。這兩種版本由于基于不同的連接方式袋毙,優(yōu)缺點(diǎn)自然也就各有不同了型檀。-
三種消息傳輸方式QoS:
- 0代表“至多一次”,消息發(fā)布完全依賴底層 TCP/IP 網(wǎng)絡(luò)听盖。會(huì)發(fā)生消息丟失或重復(fù)赋访。這一級(jí)別可用于如下情況坠非,環(huán)境傳感器數(shù)據(jù),丟失一次讀記錄無(wú)所謂,因?yàn)椴痪煤筮€會(huì)有第二次發(fā)送答姥。
- 1代表“至少一次”,確保消息到達(dá)趟大,但消息重復(fù)可能會(huì)發(fā)生门粪。
- 2代表“只有一次”,確保消息到達(dá)一次毛雇。這一級(jí)別可用于如下情況嫉称,在計(jì)費(fèi)系統(tǒng)中,消息重復(fù)或丟失會(huì)導(dǎo)致不正確的結(jié)果灵疮。 (備注:由于服務(wù)端采用Mosca實(shí)現(xiàn)织阅,Mosca目前只支持到QoS 1)
如果發(fā)送的是臨時(shí)的消息,例如給某topic所有在線的設(shè)備發(fā)送一條消息震捣,丟失的話也無(wú)所謂荔棉,0就可以了(客戶端登錄的時(shí)候要指明支持的QoS級(jí)別,同時(shí)發(fā)送消息的時(shí)候也要指明這條消息支持的QoS級(jí)別)蒿赢,如果需要客戶端保證能接收消息江耀,需要指定QoS為1,如果同時(shí)需要加入客戶端不在線也要能接收到消息诉植,那么客戶端登錄的時(shí)候要指定session的有效性祥国,接收離線消息需要指定服務(wù)端要保留客戶端的session狀態(tài)。
具體MQTT的詳細(xì)介紹可以戳這里 https://baike.baidu.com/item/MQTT/3618851?fr=aladdin
MQTTClient的使用
iOS 環(huán)境下開發(fā) MQTT 客戶端程序晾腔,一般依賴穩(wěn)定的第三方 FrameWork舌稀,由于涉及網(wǎng)絡(luò)數(shù)據(jù)傳輸,建議選擇 Object-c
原生的框架灼擂,比如 MQTT-Client-Framework
壁查。
現(xiàn)在一般常用的有兩個(gè)MQTT
1) MQTTKit
2) MQTTClient
不過(guò)MQTTKit貌似很長(zhǎng)時(shí)間不維護(hù)了, 使用較多的是MQTTClient剔应。
- 集成MQTTClient
MQTT-Client-Framework- 用cocopod直接睡腿, pod 'MQTTClient'
- GitHub下載语御,把相對(duì)應(yīng)的文件夾拖進(jìn)工程即可
MQTT-Client-FrameWork
包提供的客戶端類有 MQTTSession
和 MQTTSessionManager
,建議使用后者維持靜態(tài)資源席怪,而且已經(jīng)封裝好自動(dòng)重連等邏輯应闯。初始化時(shí)需要傳入相關(guān)的網(wǎng)絡(luò)參數(shù)。
我使用的是第二種挂捻, 引入 #import "MQTTClient.h"
#import "MQTTSessionManager.h"
頭文件碉纺, 遵循 MQTTSessionManagerDelegate
協(xié)議
使用步驟:
- 建立連接
和服務(wù)器端確定好MQTT服務(wù)器地址,端口號(hào)刻撒, 用戶名骨田, 密碼, 訂閱主題topic
/**
host: 服務(wù)器地址
port: 服務(wù)器端口
tls: 是否使用tls協(xié)議声怔,mosca是支持tls的态贤,如果使用了要設(shè)置成true
keepalive: 心跳時(shí)間,單位秒醋火,每隔固定時(shí)間發(fā)送心跳包, 心跳間隔不得大于120s
clean: session是否清除抵卫,這個(gè)需要注意,如果是false胎撇,代表保持登錄介粘,如果客戶端離線了再次登錄就可以接收到離線消息
auth: 是否使用登錄驗(yàn)證
user: 用戶名
pass: 密碼
willTopic: 訂閱主題
willMsg: 自定義的離線消息
willQos: 接收離線消息的級(jí)別
clientId: 客戶端id,需要特別指出的是這個(gè)id需要全局唯一晚树,因?yàn)榉?wù)端是根據(jù)這個(gè)來(lái)區(qū)分不同的客戶端的姻采,默認(rèn)情況下一個(gè)id登錄后,假如有另外的連接以這個(gè)id登錄爵憎,上一個(gè)連接會(huì)被踢下線, 我使用的設(shè)備UUID
*/
NSString *clientId = [UIDevice currentDevice].identifierForVendor.UUIDString;
MQTTSessionManager *sessionManager = [[MQTTSessionManager alloc] init];
[sessionManager connectTo:@"121.199.19.126"
port:1883
tls:false
keepalive:60 //心跳間隔不得大于120s
clean:true
auth:true
user:@"guest"
pass:@"guest"
will:false
willTopic:nil
willMsg:nil
willQos:0
willRetainFlag:false
withClientId:clientId];
sessionManager.delegate = self;
self.sessionManager = sessionManager;
- 監(jiān)控連接狀態(tài)
連接當(dāng)前狀態(tài)慨亲,添加對(duì)應(yīng)的回調(diào)接口,可以進(jìn)行相關(guān)的業(yè)務(wù)邏輯處理宝鼓。
// 添加監(jiān)聽狀態(tài)觀察者
[self.sessionManager addObserver:self
forKeyPath:@"state"
options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
context:nil];
監(jiān)聽連接狀態(tài)刑棵,進(jìn)行相應(yīng)處理。
// 監(jiān)聽當(dāng)前連接狀態(tài)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
switch (self.sessionManager.state) {
case MQTTSessionManagerStateClosed:
NSLog(@"連接已經(jīng)關(guān)閉");
break;
case MQTTSessionManagerStateClosing:
NSLog(@"連接正在關(guān)閉");
break;
case MQTTSessionManagerStateConnected:
NSLog(@"已經(jīng)連接");
break;
case MQTTSessionManagerStateConnecting:
NSLog(@"正在連接中");
break;
case MQTTSessionManagerStateError: {
NSString *errorCode = self.sessionManager.lastErrorCode.localizedDescription;
NSLog(@"連接異常 ----- %@",errorCode);
}
break;
case MQTTSessionManagerStateStarting:
NSLog(@"開始連接");
break;
default:
break;
}
}
- 接收消息
實(shí)現(xiàn)MQTTSessionManagerDelegate
代理方法愚铡,處理數(shù)據(jù)蛉签。
// 獲取服務(wù)器返回?cái)?shù)據(jù)
- (void)handleMessage:(NSData *)data onTopic:(NSString *)topic retained:(BOOL)retained {
NSLog(@"------------->>%@",topic);
NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",dataString);
// 進(jìn)行消息處理
}
- 訂閱和發(fā)送消息
// 訂閱主題 NSDictionary類型,Object 為 QoS沥寥,key 為 Topic
self.sessionManager.subscriptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:MQTTQosLevelExactlyOnce] forKey:@"hello"];
// 發(fā)送消息 返回值msgid大于0代表發(fā)送成功
NSString *msg = @"hahaha";
UInt16 msgid = [self.sessionManager sendData:[msg dataUsingEncoding:NSUTF8StringEncoding] //要發(fā)送的消息體
topic:@"hello" //要往哪個(gè)topic發(fā)送消息
qos:0 //消息級(jí)別
retain:false];
如果是使用阿里云的服務(wù)器替代自己的服務(wù)器碍舍,需要在阿里云控制臺(tái)申請(qǐng) Topic
,Group ID
等資源邑雅。
在建立連接時(shí)片橡,傳入的參數(shù)值也會(huì)有所改變, user
和 pass
由于服務(wù)端需要對(duì)客戶端進(jìn)行鑒權(quán)淮野,因此需要傳入合法的 user
和 pass
捧书。 user
設(shè)置為當(dāng)前用戶的 AccessKey
吹泡,pass
則設(shè)置為 MQTT 客戶端 GroupID
的簽名字符串,簽名計(jì)算方式是使用 SecretKey
對(duì) GroupID
做 HmacSHA1
散列加密经瓷。
self.manager = [[MQTTSessionManager alloc] init];
self.manager.delegate = self;
self.manager.subscriptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:self.qos]
forKey:[NSString stringWithFormat:@"%@/#", self.rootTopic]];
//password的計(jì)算方式是爆哑,使用secretkey對(duì)groupId做hmac簽名算法,具體實(shí)現(xiàn)參考macSignWithText方法
NSString *passWord = [[self class] macSignWithText:self.groupId secretKey:self.secretKey];
[self.manager connectTo:self.mqttSettings[@"host"]
port:[self.mqttSettings[@"port"] intValue]
tls:[self.mqttSettings[@"tls"] boolValue]
keepalive:60 //心跳間隔不得大于120s
clean:true
auth:true
user:self.accessKey
pass:passWord
will:false
willTopic:nil
willMsg:nil
willQos:0
willRetainFlag:false
withClientId:self.clientId];
使用 SecretKey
對(duì) GroupID
做 HmacSHA1
散列加密
+ (NSString *)macSignWithText:(NSString *)text secretKey:(NSString *)secretKey {
NSData *saltData = [secretKey dataUsingEncoding:NSUTF8StringEncoding];
NSData *paramData = [text dataUsingEncoding:NSUTF8StringEncoding];
NSMutableData* hash = [NSMutableData dataWithLength:CC_SHA1_DIGEST_LENGTH ];
CCHmac(kCCHmacAlgSHA1, saltData.bytes, saltData.length, paramData.bytes, paramData.length, hash.mutableBytes);
NSString *base64Hash = [hash base64EncodedStringWithOptions:0];
return base64Hash;
}
具體介紹請(qǐng)戳下面幫助鏈接
MQTT接入環(huán)境配置
阿里云接入MTQQ示例
申請(qǐng)MQ資源