RabbitMQ在iOS中的集成應用

引言html

隨着公司業務的須要,原來的推送,不能知足現有的要求,好比,有的公司須要局域網部署平臺服務,不能訪問外網,意味着,經過自帶的 APNS遠程推送服務,基本上就掛掉了;再好比,公司的某一個業務,須要實時刷新數據,如股票價格,報警實時監測數據等這種場景。這時候,就須要另闢蹊徑;固然,咱們有不少種選擇,好比,RabbitMQ-實現了高級消息隊列協議的開源消息代理軟件,爲啥要用這個呢,由於Android已經用這個作好了,因此,iOS 也沒有其餘選擇了;ios

在集成客戶端的時候,咱們有一些概念須要知道,如消息隊列,生產者,消費者,交換器,隊列,通道等;git

一.RabbitMQ 的相關概念

1.1 簡介

RabbitMQ 本質上是 MB(Message Broker) 消息代理,能夠這麼認爲,RabbitMQ就像一個 「郵局」,負責收發存儲郵包(消息),惟一與郵局不一樣的是,它不會處理消息內容github

AMQP,即Advanced Message Queuing Protocol,高級消息隊列協議,是應用層協議的一個開放標準;相似Http,https,STMP這種,咱們只須要知道,他是一個協議,實際開發中會遇到,底層實現咱們不用管;web

1.2 MQ的特徵

RabbitMQ 是一個由 Erlang 語言開發的 AMQP 的開源實現。面試

支持多種客戶端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP,Object-C, Swift等,支持AJAX。用於在分佈式系統中存儲轉發消息,在易用性、擴展性、高可用性等方面表現不俗。json

  • 可靠性(Reliability)

RabbitMQ 使用一些機制來保證可靠性,如持久化、傳輸確認、發佈確認。瀏覽器

  • 靈活的路由(Flexible Routing)

在消息進入隊列以前,經過 Exchange 來路由消息的。對於典型的路由功能,RabbitMQ 已經提供了一些內置的 Exchange 來實現。針對更復雜的路由功能,能夠將多個 Exchange 綁定在一塊兒,也經過插件機制實現本身的 Exchange 。bash

  • 消息集羣(Clustering)

多個 RabbitMQ 服務器能夠組成一個集羣,造成一個邏輯 Broker 。服務器

  • 高可用(Highly Available Queues)

隊列能夠在集羣中的機器上進行鏡像,使得在部分節點出問題的狀況下隊列仍然可用。

  • 多種協議(Multi-protocol)

RabbitMQ 支持多種消息隊列協議,好比 STOMP、MQTT 等等。

  • 多語言客戶端(Many Clients)

RabbitMQ 幾乎支持全部經常使用語言,好比 Java、.NET、Ruby, Object-C等等。

  • 管理界面(Management UI)

RabbitMQ 提供了一個易用的用戶界面,使得用戶能夠監控和管理消息 Broker 的許多方面。

  • 跟蹤機制(Tracing)

若是消息異常,RabbitMQ 提供了消息跟蹤機制,使用者能夠找出發生了什麼。

  • 插件機制(Plugin System)

RabbitMQ 提供了許多插件,來從多方面進行擴展,也能夠編寫本身的插件。

1.3 RabbitMQ的概念模型

全部 MQ 產品從模型抽象上來講都是同樣的過程:
消費者(consumer)訂閱某個隊列。生產者(producer)建立消息,而後發佈到隊列(queue)中,最後將消息發送到監聽的消費者。

消息模型
  • AMQP的內部結構

上面介紹RabbitMQ是AMQP的實現,因此其內部也是AMQP的概念

RabbbitMQ的內部結構
  • P(Producer) 生產者,有的也稱之爲 Publisher,消息的發佈者

發送消息的程序或者代碼就是生產者,通常是 服務端

生產者
  • Exchange(交換器)

交換器,用來接收生產者發送的消息並將這些消息路由給服務器中的隊列.生產者把消息發佈到 Exchange 上,消息最終到達隊列並被消費者接收,而 Binding 決定交換器的消息應該發送到那個隊列。

  • routing key

生產者在將消息發送給Exchange的時候,通常會指定一個routing key,來指定這個消息的路由規則,而這個routing key須要與Exchange Type及binding key聯合使用才能最終生效。
在Exchange Type與binding key固定的狀況下(在正常使用時通常這些內容都是固定配置好的),咱們的生產者就能夠在發送消息給Exchange時,經過指定routing key來決定消息流向哪裏。
RabbitMQ爲routing key設定的長度限制爲255 bytes。
此外,這個 Key 值,通常是服務端跟客戶端約定好的,能夠是組織ID,也能夠用ID

  • Binding(綁定)

綁定,用於消息隊列和交換器之間的關聯。一個綁定就是基於路由鍵將交換器和消息隊列鏈接起來的路由規則,因此能夠將交換器理解成一個由綁定構成的路由表。

  • Queue(隊列)

消息隊列,用來保存消息直到發送給消費者。它是消息的容器,也是消息的終點。一個消息可投入一個或多個隊列。消息一直在隊列裏面,等待消費者鏈接到這個隊列將其取走。

  • Connection(鏈接)

網絡鏈接,好比一個TCP鏈接,RabbitMQ的OC CLient就是封裝了 開源的TCP實現CocoaAsyncSocket

  • Channel(信道)

信道,多路複用鏈接中的一條獨立的雙向數據流通道。信道是創建在真實的TCP鏈接內地虛擬鏈接,AMQP 命令都是經過信道發出去的,不論是發佈消息、訂閱隊列仍是接收消息,這些動做都是經過信道完成。由於對於操做系統來講創建和銷燬 TCP 都是很是昂貴的開銷,因此引入了信道的概念,以複用一條 TCP 鏈接。

  • Virtual Host(虛擬主機)
    虛擬主機,表示一批交換器、消息隊列和相關對象。虛擬主機是共享相同的身份認證和加密環境的獨立服務器域。每一個 vhost 本質上就是一個 mini 版的 RabbitMQ 服務器,擁有本身的隊列、交換器、綁定和權限機制。vhost 是 AMQP 概念的基礎,必須在鏈接時指定,RabbitMQ 默認的 vhost 是 /
  • C(Consumer) 消費者

訂閱某個隊列,消息的接受者

消費者

1.4 RabbitMQ 中的消息路由

生產者把消息發佈到 Exchange 上,消息最終到達隊列並被消費者接收,而 Binding 決定交換器的消息應該發送到那個隊列。

在綁定(Binding)Exchange與Queue的同時,通常會指定一個binding key;生產者將消息發送給Exchange時,通常會指定一個routing key;當binding key與routing key相匹配時,消息將會被路由到對應的Queue中。
在綁定多個Queue到同一個Exchange的時候,這些Binding容許使用相同的binding key。
binding key 並非在全部狀況下都生效,它依賴於Exchange Type,好比fanout類型的Exchange就會無視binding key,而是將消息路由到全部綁定到該Exchange的Queue。

AMOP的消息路由

1.5 RabbitMQ 的Exchange Types

RabbitMQ經常使用的Exchange Type有==fanout==、==direct==、==topic==、==headers==這四種(AMQP規範裏還提到兩種Exchange Type,分別爲system與自定義,這裏不予以描述),headers 匹配 AMQP 消息的 header 而不是路由鍵,此外 headers 交換器和 direct 交換器徹底一致,但性能差不少,目前幾乎用不到了,因此直接看另外三種類型:

  • direct

direct類型的Exchange路由規則也很簡單,它會把消息路由到那些binding key與routing key徹底匹配的Queue中。

相關代碼以下:

/** 建立信道 */
    id<RMQChannel> ch = [_conn createChannel];
    /** 建立交換器,direct方法,這個須要跟後臺保持一致,包括交換器的名稱,也要跟後臺約定好,還有配置選項,是否持久化等 */
    RMQExchange *x = [ch  direct:@"Mobile_Alarm" options:(RMQExchangeDeclareNoOptions)];
    /** 建立隊列,若是不指定隊列名稱,MQ會默認自動建立一個隊列,前綴以 `rmq-objc-client.gen-` 開頭 */
    RMQQueue *q = [ch queue:@"" options:RMQQueueDeclareExclusive | RMQQueueDeclareAutoDelete];
    /** 隊列綁定交換器,並指定 rountKey,須要跟後臺指定相同的規則,能夠是用戶ID等 */
    [q bind:x routingKey:@"5"];

複製代碼
Exchange Direct類型
  • fanout
    這種類型的Exchange,就是羣發,此exchange的路由規則很簡單直接將消息路由到全部綁定的隊列中,無須對消息的routingkey進行匹配操做。


    MQ fanout.png
/** 須要注意:<RMQConnectionDelegate> 若是建立鏈接,指定代理是 當前class,那麼當前class須要遵照鏈接的代理協議,並實現相關代理方法*/

    /** 建立鏈接 */
    RMQConnection *conn = [[RMQConnection alloc] initWithUri:url5 delegate:self];
    [conn start];
    /** 建立信道 */
    id<RMQChannel> ch = [conn createChannel];
    /** 建立交換器 */
    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 鏈接失敗回調,超時或者地址有誤
- (void)connection:(RMQConnection *)connection failedToConnectWithError:(NSError *)error;
/// 沒有鏈接成功回調
- (void)connection:(RMQConnection *)connection disconnectedWithError:(NSError *)error;
/// 自動恢復鏈接調用
- (void)willStartRecoveryWithConnection:(RMQConnection *)connection;
/// 正在開始恢復鏈接
- (void)startingRecoveryWithConnection:(RMQConnection *)connection;
/// 已經恢復鏈接的時候,調用
- (void)recoveredConnection:(RMQConnection *)connection;
/// 鏈接過程當中,信道異常調用
- (void)channel:(id<RMQChannel>)channel error:(NSError *)error;
複製代碼
  • Topic

前面講到direct類型的Exchange路由規則是徹底匹配binding key與routing key,但這種嚴格的匹配方式在不少狀況下不能知足實際業務需求。topic類型的Exchange在匹配規則上進行了擴展,它與direct類型的Exchage類似,也是將消息路由到binding key與routing key相匹配的Queue中,但這裏的匹配規則有些不一樣,它約定:

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];

複製代碼
MQ topic Type.png

以上圖中的配置爲例,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 客戶端集成

RabbitMQ官方Git倉庫

  • 我用的是CocoaPods集成,在 podfile 中添加:
pod 'RMQClient'

複製代碼

注意:

RabbitMQ pod 內部有 CocoaAsyncSocket,若是項目中,已經集成了,須要刪除多餘的

2.2 源代碼文件

考慮到客戶端架構的特殊性,須要將MQ消息訂閱封裝成一個工具,並配置爲 單例 模式。

導入頭文件 #import <RMQClient.h>

LXCustomRabbitMQManger.h

//開始訂閱消息
- (void)startScribeMessage;
//關閉鏈接
- (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;
    }
}

//建立本地推送
- (void)registerNotification:(NSInteger )alerTime {
    kweakSelf;
    kStrongSelf;
    // 使用 UNUserNotificationCenter 來管理通知
    if (@available(iOS 10.0, *)) {
        // 使用 UNUserNotificationCenter 來管理通知
        UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
        //需建立一個包含待通知內容的 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:^{ //跳轉 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];
        // 設置觸發通知的時間
        NSDate *fireDate = [NSDate dateWithTimeIntervalSinceNow:1];
        NSLog(@"fireDate=%@",fireDate);
        
        notification.fireDate = fireDate;
        // 時區
        notification.timeZone = [NSTimeZone defaultTimeZone];
        // 設置重複的間隔
        notification.repeatInterval = kCFCalendarUnitSecond;
        
        // 通知內容
        notification.alertBody =  self.alarmModel.alarm_type;
        notification.applicationIconBadgeNumber = 1;
        // 通知被觸發時播放的聲音
        notification.soundName = UILocalNotificationDefaultSoundName;
        // 通知參數
        NSDictionary *userDict = [NSDictionary dictionaryWithObject:self.alarmModel.geography forKey:@"key"];
        notification.userInfo = userDict;
        
        // ios8後,須要添加這個註冊,才能獲得受權
        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;
        }
        // 執行通知註冊
        [[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發送服務器地址:%@",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 - 系統的通知監聽
- (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,當接收到推送須要跳轉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格式字符串轉字典:由於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 軟件包工具的話,執行並啓動

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的鑑權,O-C跟Java的格式不太同樣,Java是經過Name,Host,Ip等,iOS的是直接設置地址

amqps://user:pass@hostname:1234/myvhost

說明:

  • 協議頭:若是是Https的話,協議用:amqps,若是是Http,用amqp
  • 用戶名:userName
  • 用戶密碼:password
  • ip地址:182.142.123等
  • port端口:5672
  • 虛擬主機:由後臺定義,無關緊要,根據需求,myloghost

3.2 服務端與客戶端參數配置有誤,消息隊列持久化

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 交換器的配置參數,跟後臺約定的不一致,有時候後臺也不知道本身配置的啥,就須要嘗試了;

/** typedef NS_OPTIONS(NSUInteger, RMQExchangeDeclareOptions) { RMQExchangeDeclareNoOptions = 0, /// 被動聲明 RMQExchangeDeclarePassive = 1 << 0, ///交換器持久化 RMQExchangeDeclareDurable = 1 << 1, /// 自動銷燬交換器,當全部的消息隊列都被使用 RMQExchangeDeclareAutoDelete = 1 << 2, /// 配置的交換器內部構造對應用發佈者不可見 RMQExchangeDeclareInternal = 1 << 3, /// @brief RMQExchangeDeclareNoWait = 1 << 4, };*/
    RMQExchange *x = [ch  direct:@"Mobile_Electric_Alarm" options:(RMQExchangeDeclareNoOptions)];
    /** * 隊列的配置可選參數 * typedef NS_OPTIONS(NSUInteger, RMQQueueDeclareOptions) { RMQQueueDeclareNoOptions = 0, /// 被動聲明 RMQQueueDeclarePassive = 1 << 0, /// 隊列持久化 RMQQueueDeclareDurable = 1 << 1, /// 只能被當前的鏈接受權訪問 RMQQueueDeclareExclusive = 1 << 2, /// 自動刪除 RMQQueueDeclareAutoDelete = 1 << 3, /// RMQQueueDeclareNoWait = 1 << 4, }; */
    RMQQueue *q = [ch queue:@"" options:RMQQueueDeclareExclusive | RMQQueueDeclareAutoDelete];


複製代碼

若是鏈接成功後,那麼在MQ的管理臺,就能夠看到當前鏈接的消費者了

[圖片上傳失敗...(image-97aaad-1534667228188)]

[參考文章]

1> RabbbitMQ官網

2> RabbitMQ基礎概念詳細介紹

3> 消息隊列之 RabbitMQ

4> RabbitMQ——第一篇:RabbitMQ介紹

交流討論

RabbitMQ在iOS中的使用資料不多,若是有問題的話,歡迎留言探討。

另外,若是你以爲個人文章對你有必定的幫助,很是感謝可以點贊,謝謝!

做爲一個開發者,有一個學習的氛圍跟一個交流圈子特別重要,這是一個個人iOS交流羣:810733363。微信交流羣能夠加小編微信(18173184023 )。無論你是小白仍是大牛歡迎入駐 ,分享BAT,阿里面試題、面試經驗,討論技術, 你們一塊兒交流學習成長!但願幫助開發者少走彎路。 

來源:本文爲第三方轉載,若有侵權請聯繫小編刪除。

相關文章
相關標籤/搜索