文章首次整理自 個人博客:RabbitMQ在iOS中的應用
引言
隨著公司業(yè)務的需要领虹,原來的推送运翼,不能滿足現(xiàn)有的要求,比如壳咕,有的公司需要局域網(wǎng)部署平臺服務席揽,不能訪問外網(wǎng),意味著谓厘,通過自帶的 APNS遠程推送服務幌羞,基本上就掛掉了;再比如,公司的某一個業(yè)務竟稳,需要實時刷新數(shù)據(jù)属桦,如股票價格,報警實時監(jiān)測數(shù)據(jù)等這種場景他爸。這時候聂宾,就需要另辟蹊徑;當然诊笤,我們有很多種選擇系谐,比如,RabbitMQ-實現(xiàn)了高級消息隊列協(xié)議的開源消息代理軟件讨跟,為啥要用這個呢纪他,因為Android已經(jīng)用這個做好了,所以晾匠,iOS 也沒有其他選擇了;
在集成客戶端的時候茶袒,我們有一些概念需要知道,如消息隊列凉馆,生產(chǎn)者薪寓,消費者,交換器澜共,隊列,通道等向叉;
一.RabbitMQ 的相關(guān)概念
1.1 簡介
RabbitMQ 本質(zhì)上是 MB(Message Broker) 消息代理,可以這么認為咳胃,RabbitMQ就像一個 “郵局”植康,負責收發(fā)存儲郵包(消息),唯一與郵局不同的是展懈,它不會處理消息內(nèi)容
AMQP销睁,即Advanced Message Queuing Protocol供璧,高級消息隊列協(xié)議,是應用層協(xié)議的一個開放標準冻记;類似Http,https,STMP這種睡毒,我們只需要知道,他是一個協(xié)議冗栗,實際開發(fā)中會遇到演顾,底層實現(xiàn)我們不用管;
1.2 MQ的特征
RabbitMQ 是一個由 Erlang 語言開發(fā)的 AMQP 的開源實現(xiàn)隅居。
支持多種客戶端钠至,如:Python、Ruby胎源、.NET棉钧、Java、JMS涕蚤、C宪卿、PHP、ActionScript万栅、XMPP佑钾、STOMP,Object-C, Swift等烦粒,支持AJAX休溶。用于在分布式系統(tǒng)中存儲轉(zhuǎn)發(fā)消息,在易用性撒遣、擴展性邮偎、高可用性等方面表現(xiàn)不俗。
- 可靠性(Reliability)
RabbitMQ 使用一些機制來保證可靠性义黎,如持久化、傳輸確認豁跑、發(fā)布確認廉涕。
- 靈活的路由(Flexible Routing)
在消息進入隊列之前,通過 Exchange 來路由消息的艇拍。對于典型的路由功能狐蜕,RabbitMQ 已經(jīng)提供了一些內(nèi)置的 Exchange 來實現(xiàn)。針對更復雜的路由功能卸夕,可以將多個 Exchange 綁定在一起层释,也通過插件機制實現(xiàn)自己的 Exchange 。
- 消息集群(Clustering)
多個 RabbitMQ 服務器可以組成一個集群快集,形成一個邏輯 Broker 贡羔。
- 高可用(Highly Available Queues)
隊列可以在集群中的機器上進行鏡像廉白,使得在部分節(jié)點出問題的情況下隊列仍然可用。
- 多種協(xié)議(Multi-protocol)
RabbitMQ 支持多種消息隊列協(xié)議乖寒,比如 STOMP猴蹂、MQTT 等等。
- 多語言客戶端(Many Clients)
RabbitMQ 幾乎支持所有常用語言楣嘁,比如 Java、.NET逐虚、Ruby, Object-C等等聋溜。
- 管理界面(Management UI)
RabbitMQ 提供了一個易用的用戶界面,使得用戶可以監(jiān)控和管理消息 Broker 的許多方面叭爱。
- 跟蹤機制(Tracing)
如果消息異常撮躁,RabbitMQ 提供了消息跟蹤機制,使用者可以找出發(fā)生了什么涤伐。
- 插件機制(Plugin System)
RabbitMQ 提供了許多插件馒胆,來從多方面進行擴展,也可以編寫自己的插件凝果。
1.3 RabbitMQ的概念模型
所有 MQ 產(chǎn)品從模型抽象上來說都是一樣的過程:
消費者(consumer)訂閱某個隊列祝迂。生產(chǎn)者(producer)創(chuàng)建消息,然后發(fā)布到隊列(queue)中器净,最后將消息發(fā)送到監(jiān)聽的消費者型雳。
- AMQP的內(nèi)部結(jié)構(gòu)
上面介紹RabbitMQ是AMQP的實現(xiàn),所以其內(nèi)部也是AMQP的概念
- P(Producer) 生產(chǎn)者山害,有的也稱之為 Publisher,消息的發(fā)布者
發(fā)送消息的程序或者代碼就是生產(chǎn)者纠俭,一般是 服務端
- Exchange(交換器)
交換器,用來接收生產(chǎn)者發(fā)送的消息并將這些消息路由給服務器中的隊列.生產(chǎn)者把消息發(fā)布到 Exchange 上浪慌,消息最終到達隊列并被消費者接收冤荆,而 Binding 決定交換器的消息應該發(fā)送到那個隊列。
- routing key
生產(chǎn)者在將消息發(fā)送給Exchange的時候权纤,一般會指定一個routing key钓简,來指定這個消息的路由規(guī)則,而這個routing key需要與Exchange Type及binding key聯(lián)合使用才能最終生效汹想。
在Exchange Type與binding key固定的情況下(在正常使用時一般這些內(nèi)容都是固定配置好的)外邓,我們的生產(chǎn)者就可以在發(fā)送消息給Exchange時,通過指定routing key來決定消息流向哪里古掏。
RabbitMQ為routing key設(shè)定的長度限制為255 bytes损话。
此外,這個 Key 值槽唾,一般是服務端跟客戶端約定好的丧枪,可以是組織ID光涂,也可以用ID
- Binding(綁定)
綁定,用于消息隊列和交換器之間的關(guān)聯(lián)豪诲。一個綁定就是基于路由鍵將交換器和消息隊列連接起來的路由規(guī)則顶捷,所以可以將交換器理解成一個由綁定構(gòu)成的路由表。
- Queue(隊列)
消息隊列屎篱,用來保存消息直到發(fā)送給消費者服赎。它是消息的容器,也是消息的終點交播。一個消息可投入一個或多個隊列重虑。消息一直在隊列里面,等待消費者連接到這個隊列將其取走秦士。
- Connection(連接)
網(wǎng)絡連接缺厉,比如一個TCP連接,RabbitMQ的OC CLient就是封裝了 開源的TCP實現(xiàn)CocoaAsyncSocket
- Channel(信道)
信道隧土,多路復用連接中的一條獨立的雙向數(shù)據(jù)流通道提针。信道是建立在真實的TCP連接內(nèi)地虛擬連接,AMQP 命令都是通過信道發(fā)出去的曹傀,不管是發(fā)布消息辐脖、訂閱隊列還是接收消息,這些動作都是通過信道完成皆愉。因為對于操作系統(tǒng)來說建立和銷毀 TCP 都是非常昂貴的開銷嗜价,所以引入了信道的概念,以復用一條 TCP 連接幕庐。
- Virtual Host(虛擬主機)
虛擬主機久锥,表示一批交換器、消息隊列和相關(guān)對象异剥。虛擬主機是共享相同的身份認證和加密環(huán)境的獨立服務器域瑟由。每個 vhost 本質(zhì)上就是一個 mini 版的 RabbitMQ 服務器,擁有自己的隊列冤寿、交換器错妖、綁定和權(quán)限機制。vhost 是 AMQP 概念的基礎(chǔ)疚沐,必須在連接時指定,RabbitMQ 默認的 vhost 是 /
- C(Consumer) 消費者
訂閱某個隊列潮模,消息的接受者
1.4 RabbitMQ 中的消息路由
生產(chǎn)者把消息發(fā)布到 Exchange 上亮蛔,消息最終到達隊列并被消費者接收,而 Binding 決定交換器的消息應該發(fā)送到那個隊列擎厢。
在綁定(Binding)Exchange與Queue的同時究流,一般會指定一個binding key辣吃;生產(chǎn)者將消息發(fā)送給Exchange時,一般會指定一個routing key芬探;當binding key與routing key相匹配時神得,消息將會被路由到對應的Queue中。
在綁定多個Queue到同一個Exchange的時候偷仿,這些Binding允許使用相同的binding key哩簿。
binding key 并不是在所有情況下都生效,它依賴于Exchange Type酝静,比如fanout類型的Exchange就會無視binding key节榜,而是將消息路由到所有綁定到該Exchange的Queue。
1.5 RabbitMQ 的Exchange Types
RabbitMQ常用的Exchange Type有==fanout==别智、==direct==宗苍、==topic==、==headers==這四種(AMQP規(guī)范里還提到兩種Exchange Type薄榛,分別為system與自定義讳窟,這里不予以描述),headers 匹配 AMQP 消息的 header 而不是路由鍵敞恋,此外 headers 交換器和 direct 交換器完全一致丽啡,但性能差很多,目前幾乎用不到了耳舅,所以直接看另外三種類型:
- direct
direct類型的Exchange路由規(guī)則也很簡單碌上,它會把消息路由到那些binding key與routing key完全匹配的Queue中。
相關(guān)代碼如下:
/** 創(chuàng)建信道 */
id<RMQChannel> ch = [_conn createChannel];
/** 創(chuàng)建交換器浦徊,direct方法馏予,這個需要跟后臺保持一致,包括交換器的名稱盔性,也要跟后臺約定好霞丧,還有配置選項,是否持久化等 */
RMQExchange *x = [ch direct:@"Mobile_Alarm" options:(RMQExchangeDeclareNoOptions)];
/** 創(chuàng)建隊列冕香,如果不指定隊列名稱蛹尝,MQ會默認自動創(chuàng)建一個隊列,前綴以 `rmq-objc-client.gen-` 開頭 */
RMQQueue *q = [ch queue:@"" options:RMQQueueDeclareExclusive | RMQQueueDeclareAutoDelete];
/** 隊列綁定交換器悉尾,并指定 rountKey突那,需要跟后臺指定相同的規(guī)則,可以是用戶ID等 */
[q bind:x routingKey:@"5"];
-
fanout
這種類型的Exchange,就是群發(fā)构眯,此exchange的路由規(guī)則很簡單直接將消息路由到所有綁定的隊列中愕难,無須對消息的routingkey進行匹配操作。
/** 需要注意:<RMQConnectionDelegate> 如果創(chuàng)建連接,指定代理是 當前class,那么當前class需要遵守連接的代理協(xié)議猫缭,并實現(xiàn)相關(guān)代理方法*/
/** 創(chuàng)建連接 */
RMQConnection *conn = [[RMQConnection alloc] initWithUri:url5 delegate:self];
[conn start];
/** 創(chuàng)建信道 */
id<RMQChannel> ch = [conn createChannel];
/** 創(chuàng)建交換器 */
RMQExchange *x = [ch fanout:@"Alarm"];
RMQQueue *q = [ch queue:@"" options:RMQQueueDeclareExclusive];
/** 綁定交換器 */
[q bind:x];
/** 訂閱消息 */
[q subscribe:^(RMQMessage * _Nonnull message) {
NSLog(@"Received %@", [[NSString alloc] initWithData:message.body encoding:NSUTF8StringEncoding]);
}];
///socket 連接失敗回調(diào)葱弟,超時或者地址有誤
- (void)connection:(RMQConnection *)connection failedToConnectWithError:(NSError *)error;
/// 沒有連接成功回調(diào)
- (void)connection:(RMQConnection *)connection disconnectedWithError:(NSError *)error;
/// 自動恢復連接調(diào)用
- (void)willStartRecoveryWithConnection:(RMQConnection *)connection;
/// 正在開始恢復連接
- (void)startingRecoveryWithConnection:(RMQConnection *)connection;
/// 已經(jīng)恢復連接的時候,調(diào)用
- (void)recoveredConnection:(RMQConnection *)connection;
/// 連接過程中猜丹,信道異常調(diào)用
- (void)channel:(id<RMQChannel>)channel error:(NSError *)error;
- Topic
前面講到direct類型的Exchange路由規(guī)則是完全匹配binding key與routing key芝加,但這種嚴格的匹配方式在很多情況下不能滿足實際業(yè)務需求。topic類型的Exchange在匹配規(guī)則上進行了擴展射窒,它與direct類型的Exchage相似藏杖,也是將消息路由到binding key與routing key相匹配的Queue中,但這里的匹配規(guī)則有些不同轮洋,它約定:
routing key為一個句點號“. ”分隔的字符串(我們將被句點號“. ”分隔開的每一段獨立的字符串稱為一個單詞)制市,如“device.userId”、“alarm.type”弊予,
binding key與routing key一樣也是句點號“. ”分隔的字符串
binding key中可以存在兩種特殊字符“”與“#”祥楣,用于做模糊匹配,其中“”用于匹配一個單詞汉柒,“#”用于匹配多個單詞(可以是零個)
RMQConnection * connection = [[RMQConnection alloc] initWithUri:url delegate:[RMQConnectionDelegateLogger new]];
[connection start];
id<RMQChannel>channel = [connection createChannel];
RMQExchange * exchange = [channel topic:@"topic_logs" options:RMQExchangeDeclarePassive];
[exchange publish:finalData routingKey:[NSString stringWithFormat:@"device.%@",didStr]];
[connection close];
以上圖中的配置為例误褪,routingKey=”dev.alarm.device”的消息會同時路由到QA與QB,routingKey=”dev.alarm.type”的消息會路由到QA碾褂,routingKey=”lazy.brown.fox”的消息會路由到Q2兽间,routingKey=”water.type.ID”的消息會路由到QB;routingKey=”device.user.water”正塌、routingKey=”alarmtype”的消息將會被丟棄嘀略,因為它們沒有匹配任何bindingKey。
二.RabbitMQ 的集成
2.1 客戶端集成
- 我用的是CocoaPods集成,在 podfile 中添加:
pod 'RMQClient'
注意:
RabbitMQ pod 內(nèi)部有 CocoaAsyncSocket,如果項目中乓诽,已經(jīng)集成了帜羊,需要刪除多余的
2.2 源代碼文件
考慮到客戶端架構(gòu)的特殊性,需要將MQ消息訂閱封裝成一個工具,并配置為 單例 模式鸠天。
導入頭文件 #import <RMQClient.h>
LXCustomRabbitMQManger.h
//開始訂閱消息
- (void)startScribeMessage;
//關(guān)閉連接
- (void)closeConnection;
LXCustomRabbitMQManger.m
@interface LXCustomRabbitMQManger()<RMQConnectionDelegate>
@property (nonatomic,strong) RMQConnection *conn;
@property (nonatomic,strong) LXCustomAlarmModel *alarmModel;
@end
- (void)startScribeMessage {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(activeNotification:) name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundNotification:) name:UIApplicationDidEnterBackgroundNotification object:nil];
}
- (void)closeConnection {
if (_conn) {
[self.conn close];
self.conn = nil;
}
}
//創(chuàng)建本地推送
- (void)registerNotification:(NSInteger )alerTime {
kweakSelf;
kStrongSelf;
// 使用 UNUserNotificationCenter 來管理通知
if (@available(iOS 10.0, *)) {
// 使用 UNUserNotificationCenter 來管理通知
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
//需創(chuàng)建一個包含待通知內(nèi)容的 UNMutableNotificationContent 對象讼育,注意不是 UNNotificationContent ,此對象為不可變對象。
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
content.title = [NSString localizedUserNotificationStringForKey:self.alarmModel.alarm_type arguments:nil];
content.body = [NSString localizedUserNotificationStringForKey:self.alarmModel.geography
arguments:nil];
content.sound = [UNNotificationSound defaultSound];
// 在 alertTime 后推送本地推送
UNTimeIntervalNotificationTrigger* trigger = [UNTimeIntervalNotificationTrigger
triggerWithTimeInterval:alerTime repeats:NO];
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"FiveSecond"
content:content trigger:trigger];
//添加推送成功后的處理稠集!
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
/*
DQAlertView *reservationAlert = [[DQAlertView alloc] initWithTitle:self.alarmModel.alarm_type
message:self.alarmModel.geography
delegate:self cancelButtonTitle:@"取消"
otherButtonTitles:@"確定"];
reservationAlert.shouldDimBackgroundWhenShowInView = YES;
reservationAlert.shouldDismissOnOutsideTapped = YES;
[reservationAlert show];
[reservationAlert actionWithBlocksCancelButtonHandler:^{
} otherButtonHandler:^{
//跳轉(zhuǎn)
LXDeviceAlarmModel *alarmIDModel = [[LXDeviceAlarmModel alloc] init];
alarmIDModel.ID = strongSelf.alarmModel.ID;
LXDeviceDetailController *deviceDetailVC = [[LXDeviceDetailController alloc] init];
deviceDetailVC.deviceDealType = KearlyNoDealType;
deviceDetailVC.deviceAlarm = alarmIDModel;
[[self getCurrentVC].navigationController pushViewController:deviceDetailVC animated:YES];
}];
*/
UIAlertController *alert = [UIAlertController alertControllerWithTitle:self.alarmModel.alarm_type message:self.alarmModel.geography preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:nil];
UIAlertAction *confirmAction = [UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
LXDeviceAlarmModel *alarmIDModel = [[LXDeviceAlarmModel alloc] init];
alarmIDModel.ID = strongSelf.alarmModel.ID;
LXDeviceDetailController *deviceDetailVC = [[LXDeviceDetailController alloc] init];
deviceDetailVC.deviceDealType = KearlyNoDealType;
deviceDetailVC.deviceAlarm = alarmIDModel;
[[self getCurrentVC].navigationController pushViewController:deviceDetailVC animated:YES];
}];
[alert addAction:cancelAction];
[alert addAction:confirmAction];
[[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:alert animated:YES completion:nil];
}];
}
else {
UILocalNotification *notification = [[UILocalNotification alloc] init];
// 設(shè)置觸發(fā)通知的時間
NSDate *fireDate = [NSDate dateWithTimeIntervalSinceNow:1];
NSLog(@"fireDate=%@",fireDate);
notification.fireDate = fireDate;
// 時區(qū)
notification.timeZone = [NSTimeZone defaultTimeZone];
// 設(shè)置重復的間隔
notification.repeatInterval = kCFCalendarUnitSecond;
// 通知內(nèi)容
notification.alertBody = self.alarmModel.alarm_type;
notification.applicationIconBadgeNumber = 1;
// 通知被觸發(fā)時播放的聲音
notification.soundName = UILocalNotificationDefaultSoundName;
// 通知參數(shù)
NSDictionary *userDict = [NSDictionary dictionaryWithObject:self.alarmModel.geography forKey:@"key"];
notification.userInfo = userDict;
// ios8后奶段,需要添加這個注冊,才能得到授權(quán)
if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerUserNotificationSettings:)]) {
UIUserNotificationType type = UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound;
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:type
categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
// 通知重復提示的單位剥纷,可以是天痹籍、周、月
notification.repeatInterval = NSCalendarUnitDay;
} else {
// 通知重復提示的單位晦鞋,可以是天词裤、周刺洒、月
notification.repeatInterval = NSDayCalendarUnit;
}
// 執(zhí)行通知注冊
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
}
}
- (void)receiveRabbitServicerMessage {
// NSString *url5 = @"amqp://web_user:123@192.178.0.2:5672/device_log_2";
DebugLog(@"MQ服務器地址:%@",url5);
if (_conn == nil) {
_conn = [[RMQConnection alloc] initWithUri:url5 delegate:self];
}
[_conn start];
id<RMQChannel> ch = [_conn createChannel];
RMQExchange *x = [ch direct:@"Mobile_Electric_Alarm" options:(RMQExchangeDeclareNoOptions)];
RMQQueue *q = [ch queue:@"" options:RMQQueueDeclareExclusive | RMQQueueDeclareAutoDelete];
[q bind:x routingKey:kUserInfo.company_id];
kweakSelf;
[q subscribe:^(RMQMessage * _Nonnull message) {
id result = [[NSString alloc] initWithData:message.body encoding:NSUTF8StringEncoding];
NSDictionary *dict = [self dictionaryWithJsonString:result];
if ([dict isKindOfClass:[NSDictionary class]]) {
weakSelf.alarmModel = [LXCustomAlarmModel mj_objectWithKeyValues:dict];
[weakSelf registerNotification:1];
}
}];
}
- (void)emitLogDirect:(NSString *)msg severity:(NSString *)severity {
// NSString *url5 = @"amqp://web_user:123@192.178.0.2:5672/device_log_2";
DebugLog(@"MQ發(fā)送服務器地址:%@",url5);
RMQConnection *conn = [[RMQConnection alloc] initWithUri:url5 delegate:self];
self.conn = conn;
[conn start];
id<RMQChannel> ch = [conn createChannel];
RMQExchange *x = [ch direct:@"Mobile_Electric_Alarm" options:(RMQExchangeDeclareNoOptions)];
RMQQueue *q = [ch queue:@"" options:RMQQueueDeclareExclusive | RMQQueueDeclareAutoDelete];
[q bind:x routingKey:kUserInfo.company_id];
[x publish:[msg dataUsingEncoding:NSUTF8StringEncoding] routingKey:severity];
NSLog(@"Sent '%@'", msg);
[conn close];
}
#pragma mark - 系統(tǒng)的通知監(jiān)聽
- (void)activeNotification:(NSNotification *)notification{
if (_conn == nil) {
//登錄成功
if ([[LXUserInfoManger shareLXUserInfoManger].currentUserInfo.is_cloud isEqualToString:@"0"]) {
//MQ推送
[self receiveRabbitServicerMessage];
}
}
}
- (void)backgroundNotification:(NSNotification *)notification{
[self closeConnection];
}
- (void)connection:(RMQConnection *)connection failedToConnectWithError:(NSError *)error {
if (error) {
NSLog(@"%@",error);
NSLog(@"連接超時");
[self closeConnection];
}else{
}
}
- (void)connection:(RMQConnection *)connection disconnectedWithError:(NSError *)error {
if (error) {
NSLog(@"%@",error);
}
else{
NSLog(@"連接成功");
}
}
- (void)willStartRecoveryWithConnection:(RMQConnection *)connection {
DebugLog(@"將要開始恢復鏈接");
}
- (void)startingRecoveryWithConnection:(RMQConnection *)connection {
DebugLog(@"開始恢復鏈接");
}
- (void)recoveredConnection:(RMQConnection *)connection {
DebugLog(@"恢復鏈接");
}
- (void)channel:(id<RMQChannel>)channel error:(NSError *)error {
if (error) {
NSLog(@"%@",error);
[self closeConnection];
}
}
//獲取當前屏幕顯示的viewcontroller,當接收到推送需要跳轉(zhuǎn)VC
- (UIViewController *)getCurrentVC {
UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
UIViewController *currentVC = [self getCurrentVCFrom:rootViewController];
return currentVC;
}
- (UIViewController *)getCurrentVCFrom:(UIViewController *)rootVC {
UIViewController *currentVC;
if ([rootVC presentedViewController]) {
// 視圖是被presented出來的
rootVC = [rootVC presentedViewController];
}
if ([rootVC isKindOfClass:[UITabBarController class]]) {
// 根視圖為UITabBarController
currentVC = [self getCurrentVCFrom:[(UITabBarController *)rootVC selectedViewController]];
} else if ([rootVC isKindOfClass:[UINavigationController class]]){
// 根視圖為UINavigationController
currentVC = [self getCurrentVCFrom:[(UINavigationController *)rootVC visibleViewController]];
} else {
// 根視圖為非導航類
currentVC = rootVC;
}
return currentVC;
}
//接收到推送吼砂,json格式字符串轉(zhuǎn)字典:因為MQ推過來的消息是 String 類型的
- (NSDictionary *)dictionaryWithJsonString:(NSString *)jsonString {
if (jsonString == nil) {
return nil;
}
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *err;
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:jsonData
options:NSJSONReadingMutableContainers
error:&err];
if(err) {
NSLog(@"json解析失敗:%@",err);
return nil;
}
return dic;
}
2.3 本地安裝啟動MQ服務
如果電腦安裝 brew 軟件包工具的話鼎文,執(zhí)行并啟動
brew install rabbitmq
sudo ./rabbitmq-server
瀏覽器訪問:localhost:15672,默認賬號為:guest 密碼: guest
三.RabbitMQ 遇到的問題
3.1 地址不對渔肩,connect fail
Received connection: <RMQConnection: 0x103fa1fc0> disconnectedWithError: Error Domain=GCDAsyncSocketErrorDomain Code=7 "Socket closed by remote peer" UserInfo={NSLocalizedDescription=Socket closed by remote peer}
解決辦法:MQ的鑒權(quán),O-C跟Java的格式不太一樣拇惋,Java是通過Name,Host,Ip等周偎,iOS的是直接設(shè)置地址
說明:
- 協(xié)議頭:如果是Https的話,協(xié)議用:amqps撑帖,如果是Http蓉坎,用amqp
- 用戶名:userName
- 用戶密碼:password
- ip地址:182.142.123等
- port端口:5672
- 虛擬主機:由后臺定義,可有可無胡嘿,根據(jù)需求蛉艾,myloghost
3.2 服務端與客戶端參數(shù)配置有誤,消息隊列持久化
Error Domain=com.rabbitmq.rabbitmq-objc-client Code=406 "PRECONDITION_FAILED - inequivalent arg 'durable' for queue 'Mobile_Electric_Alarm' in vhost 'device_log_2': received 'false' but current is 'true'" UserInfo={NSLocalizedDescription=PRECONDITION_FAILED - inequivalent arg 'durable' for queue 'Mobile_Electric_Alarm' in vhost 'device_log_2': received 'false' but current is 'true'}
看3.3解決辦法
3.3 MQ 找不到對應的隊列
Error Domain=com.rabbitmq.rabbitmq-objc-client Code=404 "NOT_FOUND - no queue 'rmq-objc-client.gen-3B6E0E14-06E6-4AFC-B6E1-42A7F8FCD218-46927-00002C8AE36AB350' in vhost 'device_log_2'" UserInfo={NSLocalizedDescription=NOT_FOUND - no queue 'rmq-objc-client.gen-3B6E0E14-06E6-4AFC-B6E1-42A7F8FCD218-46927-00002C8AE36AB350' in vhost 'device_log_2'}
解決辦法:
Exchange 交換器的配置參數(shù),跟后臺約定的不一致衷敌,有時候后臺也不知道自己配置的啥勿侯,就需要嘗試了;
/**
typedef NS_OPTIONS(NSUInteger, RMQExchangeDeclareOptions) {
RMQExchangeDeclareNoOptions = 0,
/// 被動聲明
RMQExchangeDeclarePassive = 1 << 0,
///交換器持久化
RMQExchangeDeclareDurable = 1 << 1,
/// 自動銷毀交換器缴罗,當所有的消息隊列都被使用
RMQExchangeDeclareAutoDelete = 1 << 2,
/// 配置的交換器內(nèi)部構(gòu)造對應用發(fā)布者不可見
RMQExchangeDeclareInternal = 1 << 3,
/// @brief
RMQExchangeDeclareNoWait = 1 << 4,
};*/
RMQExchange *x = [ch direct:@"Mobile_Electric_Alarm" options:(RMQExchangeDeclareNoOptions)];
/**
* 隊列的配置可選參數(shù)
*
typedef NS_OPTIONS(NSUInteger, RMQQueueDeclareOptions) {
RMQQueueDeclareNoOptions = 0,
/// 被動聲明
RMQQueueDeclarePassive = 1 << 0,
/// 隊列持久化
RMQQueueDeclareDurable = 1 << 1,
/// 只能被當前的連接授權(quán)訪問
RMQQueueDeclareExclusive = 1 << 2,
/// 自動刪除
RMQQueueDeclareAutoDelete = 1 << 3,
///
RMQQueueDeclareNoWait = 1 << 4,
};
*/
RMQQueue *q = [ch queue:@"" options:RMQQueueDeclareExclusive | RMQQueueDeclareAutoDelete];
如果連接成功后助琐,那么在MQ的管理臺,就可以看到當前連接的消費者了
[圖片上傳失敗...(image-97aaad-1534667228188)]
[參考文章]
交流討論
RabbitMQ在iOS中的使用資料很少面氓,如果有問題的話兵钮,歡迎留言探討。
另外舌界,如果你覺得我的文章對你有一定的幫助掘譬,非常感謝能夠點贊,謝謝禀横!