開篇
最近在使用MQTTClient實現一個類似于消息推送的服務,說實話,真沒怎么使用過MQTTClient,也不知道這是個啥? 好尷尬 ?? ?? ?? 上網了解了一下,發現MQTT功能挺強(牛)大(逼)。這里我使用的是消息推送服務,通過和服務器端協商,終于能夠與服務器連接,并且能夠收發消息了。 所以,簡單總結了一下,有了這篇文章。
MQTT介紹
- MQTT
MQTT基于訂閱者模型架構,客戶端如果互相通信,必須在同一訂閱主題下,即都訂閱了同一個topic,客戶端之間是沒辦法直接通訊的。訂閱模型顯而易見的好處是群發消息的話只需要發布到topic,所有訂閱了這個topic的客戶端就可以接收到消息了。
發送消息必須發送到某個topic,重點說明的是不管客戶端是否訂閱了該topic都可以向topic發送了消息,還有如果客戶端訂閱了該主題,那么自己發送的消息也會接收到。
- MQTT特點
使用發布/訂閱消息模式,提供一對多的消息發布,解除應用程序耦合。這一點很類似于XMPP,但是MQTT的信息冗余遠小于XMPP.
對負載內容屏蔽的消息傳輸。
使用TCP/IP提供網絡連接。主流的MQTT是基于TCP連接進行數據推送的,但是同樣有基于UDP的版本,叫做MQTT-SN。這兩種版本由于基于不同的連接方式,優缺點自然也就各有不同了。-
三種消息傳輸方式QoS:
- 0代表“至多一次”,消息發布完全依賴底層 TCP/IP 網絡。會發生消息丟失或重復。這一級別可用于如下情況,環境傳感器數據,丟失一次讀記錄無所謂,因為不久后還會有第二次發送。
- 1代表“至少一次”,確保消息到達,但消息重復可能會發生。
- 2代表“只有一次”,確保消息到達一次。這一級別可用于如下情況,在計費系統中,消息重復或丟失會導致不正確的結果。 (備注:由于服務端采用Mosca實現,Mosca目前只支持到QoS 1)
如果發送的是臨時的消息,例如給某topic所有在線的設備發送一條消息,丟失的話也無所謂,0就可以了(客戶端登錄的時候要指明支持的QoS級別,同時發送消息的時候也要指明這條消息支持的QoS級別),如果需要客戶端保證能接收消息,需要指定QoS為1,如果同時需要加入客戶端不在線也要能接收到消息,那么客戶端登錄的時候要指定session的有效性,接收離線消息需要指定服務端要保留客戶端的session狀態。
具體MQTT的詳細介紹可以戳這里 https://baike.baidu.com/item/MQTT/3618851?fr=aladdin
MQTTClient的使用
iOS 環境下開發 MQTT 客戶端程序,一般依賴穩定的第三方 FrameWork,由于涉及網絡數據傳輸,建議選擇 Object-c
原生的框架,比如 MQTT-Client-Framework
。
現在一般常用的有兩個MQTT
1) MQTTKit
2) MQTTClient
不過MQTTKit貌似很長時間不維護了, 使用較多的是MQTTClient。
- 集成MQTTClient
MQTT-Client-Framework- 用cocopod直接, pod 'MQTTClient'
- GitHub下載,把相對應的文件夾拖進工程即可
MQTT-Client-FrameWork
包提供的客戶端類有 MQTTSession
和 MQTTSessionManager
,建議使用后者維持靜態資源,而且已經封裝好自動重連等邏輯。初始化時需要傳入相關的網絡參數。
我使用的是第二種, 引入 #import "MQTTClient.h"
#import "MQTTSessionManager.h"
頭文件, 遵循 MQTTSessionManagerDelegate
協議
使用步驟:
- 建立連接
和服務器端確定好MQTT服務器地址,端口號, 用戶名, 密碼, 訂閱主題topic
/**
host: 服務器地址
port: 服務器端口
tls: 是否使用tls協議,mosca是支持tls的,如果使用了要設置成true
keepalive: 心跳時間,單位秒,每隔固定時間發送心跳包, 心跳間隔不得大于120s
clean: session是否清除,這個需要注意,如果是false,代表保持登錄,如果客戶端離線了再次登錄就可以接收到離線消息
auth: 是否使用登錄驗證
user: 用戶名
pass: 密碼
willTopic: 訂閱主題
willMsg: 自定義的離線消息
willQos: 接收離線消息的級別
clientId: 客戶端id,需要特別指出的是這個id需要全局唯一,因為服務端是根據這個來區分不同的客戶端的,默認情況下一個id登錄后,假如有另外的連接以這個id登錄,上一個連接會被踢下線, 我使用的設備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;
- 監控連接狀態
連接當前狀態,添加對應的回調接口,可以進行相關的業務邏輯處理。
// 添加監聽狀態觀察者
[self.sessionManager addObserver:self
forKeyPath:@"state"
options:NSKeyValueObservingOptionInitial | NSKeyValueObservingOptionNew
context:nil];
監聽連接狀態,進行相應處理。
// 監聽當前連接狀態
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
switch (self.sessionManager.state) {
case MQTTSessionManagerStateClosed:
NSLog(@"連接已經關閉");
break;
case MQTTSessionManagerStateClosing:
NSLog(@"連接正在關閉");
break;
case MQTTSessionManagerStateConnected:
NSLog(@"已經連接");
break;
case MQTTSessionManagerStateConnecting:
NSLog(@"正在連接中");
break;
case MQTTSessionManagerStateError: {
NSString *errorCode = self.sessionManager.lastErrorCode.localizedDescription;
NSLog(@"連接異常 ----- %@",errorCode);
}
break;
case MQTTSessionManagerStateStarting:
NSLog(@"開始連接");
break;
default:
break;
}
}
- 接收消息
實現MQTTSessionManagerDelegate
代理方法,處理數據。
// 獲取服務器返回數據
- (void)handleMessage:(NSData *)data onTopic:(NSString *)topic retained:(BOOL)retained {
NSLog(@"------------->>%@",topic);
NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"%@",dataString);
// 進行消息處理
}
- 訂閱和發送消息
// 訂閱主題 NSDictionary類型,Object 為 QoS,key 為 Topic
self.sessionManager.subscriptions = [NSDictionary dictionaryWithObject:[NSNumber numberWithInt:MQTTQosLevelExactlyOnce] forKey:@"hello"];
// 發送消息 返回值msgid大于0代表發送成功
NSString *msg = @"hahaha";
UInt16 msgid = [self.sessionManager sendData:[msg dataUsingEncoding:NSUTF8StringEncoding] //要發送的消息體
topic:@"hello" //要往哪個topic發送消息
qos:0 //消息級別
retain:false];
如果是使用阿里云的服務器替代自己的服務器,需要在阿里云控制臺申請 Topic
,Group ID
等資源。
在建立連接時,傳入的參數值也會有所改變, user
和 pass
由于服務端需要對客戶端進行鑒權,因此需要傳入合法的 user
和 pass
。 user
設置為當前用戶的 AccessKey
,pass
則設置為 MQTT 客戶端 GroupID
的簽名字符串,簽名計算方式是使用 SecretKey
對 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的計算方式是,使用secretkey對groupId做hmac簽名算法,具體實現參考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
對 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;
}
具體介紹請戳下面幫助鏈接
MQTT接入環境配置
阿里云接入MTQQ示例
申請MQ資源