使用 IBM Lotus Expeditor micro broker MQTT 客戶機發布訊息

genusBIT發表於2008-08-14

通過本文,學習如何使用 IBM Lotus Expeditor micro broker 支援 MQ Telemetry Transport (MQTT) 釋出/訂閱訊息傳遞協議。在本文中,我們將建立一個示例釋出程式、將訊息釋出到一個主題並驗證訊息的接收。

訊息傳遞中介軟體提供了業務整合解決方案上下文中可靠靈活的連線服務。MQ Telemetry Transport (MQTT) 是 IBM 眾多的訊息傳遞中介軟體技術之一,是受 Lotus Expeditor micro broker 支援的一種協議。MQTT 是基於 TCP/IP 的釋出/訂閱訊息傳遞協議,針對低負荷網路的通訊而設計。micro broker 是一種小型的訊息代理(只有不足 2 MB 的 Java 程式碼),主要在小型的應用程式型裝置上部署,通常裝置都在離企業資料中心很遠的位置。在本文中,將建立一個能夠連線到 Lotus Expeditor micro broker 的示例釋出程式,並用它向一個主題釋出訊息和驗證代理對該訊息的接收。學習完本文後,您應該具備了按自己的業務需要建立簡單的 MQTT 釋出程式所需的知識。

有關向相容 MQTT 的代理進行訂閱的歷史、設計動機、協議特點和操作指導,可以在另一篇文章 “配合使用 WebSphere Business Integration Message Broker 和 MQ Telemetry Transport,第 1 部分: 訂閱” 中找到。本文擴充套件了上述文章中所觸及的概念並繼續介紹瞭如何使用 MQTT 釋出訊息。上述文章涵蓋了訂閱方面的內容,使用的是 IBM Websphere Message Broker;本文將探究訊息釋出,使用的是更新的 Lotus Expeditor micro broker。本文所涵蓋的方方面面都可以直接應用到 WebSphere Message Broker,因為二者都支援 MQTT 協議。

本文是用 Java 編寫 MQTT 釋出程式的指南。本文所詳細介紹的這個示例應用程式模仿了機場上有航班抵達時的通知訊息釋出。為了簡化此示例,我們將此場景限制為只包括兩個航空公司(Air Freedom 和 Northern Air)和兩個機場(Raleigh-Durham/RDU 和 London Heathrow/LHR)。要完成本示例的編譯,Lotus Expeditor micro broker 附帶的 MQTT 庫必須可用。此外,為了用示例 MQTT 客戶機連線和釋出訊息,還要安裝和執行 Lotus Expeditor micro broker。

主題

在訊息的釋出/訂閱中,訊息的目的地稱為主題。MQTT 協議具有分層的主題空間,這意味著主題可以按這樣一種方式構成,即訂閱者和釋出者可以使用不同精度指定目的主題。MQTT 對於主題空間有一些強制規定,但設計適合您自己應用程式的邏輯資訊空間則由您負責。

經驗表明,詳細描述的主題空間要比簡單扼要的空間更可取。例如,不建議使用 a、b 或 c 作為主題。使用簡短的主題雖然可以節約頻寬,但一個設計良好的主題空間則應該更具描述性且允許在訂閱應用程式中使用萬用字元,因此更有實際意義和益處。

主題是無格式的字串,可以包含任何單一位元組的字元。但字元 /、+ 和 # 具有特定意義,在本文稍後會詳細討論。主題長度最長可達 32,767 個字元。

圖 1 的示例展示了為飛機抵達和起飛訊息所設計的邏輯主題空間。此主題空間將貫穿全文。


圖 1. 主題空間
主題空間

該主題空間的層次為:Flight Times - Airport - Airline - Arrivals or Departures - Flight Number。要將主題層次轉變成字串,可以使用前斜線 (/) 字元分隔主題層次中的每個部分,這又增加了額外的邏輯層精度。例如,要釋出有關 Air Freedom 1326 次航班抵達 RDU 的訊息, 訊息主題應該是 Flight Times/RDU/Air Freedom/Arrivals/Flight 1326。這種邏輯主題空間允許不同的訂閱者組可以只訂閱他們所感興趣的訊息。而設計糟糕的主題空間則會導致客戶的過度訂閱,即客戶訂閱了超出自身所需的主題空間,繼而會收到很多無關的訊息。這些無關訊息需要客戶應用程式提供額外的訊息過濾以決定訊息是否是所感興趣的。過度訂閱會導致頻寬浪費和客戶機效率的降低。

接下來,我們將描述三組截然不同的訂閱者,他們均對全球範圍內航班的抵達和起飛時間感興趣。

  • 一個人在機場等待飛機抵達
  • 機場顯示航班資訊
  • 航空公司跟蹤準時航班的百分比

在第一個場景中,假設此人的電話上安裝了 MQTT 客戶應用程式,他在機場等待接機。在本例中,此人只對特定機場(LHR)的某一個航班(Air Freedom 1024 次航班)感興趣。為了接收航班抵達的通知,客戶應用程式訂閱了 Flight Times/LHR/Air Freedom/Arrivals/Flight 1024。

在第二個訂閱場景中,希思羅機場 (LHR) 顯示其航班抵達和起飛的資訊。在本例中,客戶只對 LHR 機場的航班抵達和起飛資訊感興趣。此邏輯主題空間應該允許對 LHR 機場的航班抵達和起飛資訊進行單個訂閱。所需的訂閱主題字串是 Flight Times/LHR/#。# 字元是個特殊的萬用字元,可匹配主題空間內合適位置中的所有主題,可以將其視為是訂閱主題空間的一個子樹。

最後一個場景是航空公司跟蹤其準點航班的資料。我們的示例航空公司是 Northern Air,該公司很關注其全球範圍的準點航班的百分比。因此 Northern Air 需要單一訂閱全球範圍內的航班抵達時間。在本例中,Northern Air 只關心航班抵達,不關心航班起飛。Northern Air 這一特定需求的主題字串是 Flight Times/+/Northern Air/Arrivals/#。此主題字串使用了特殊的萬用字元 + 字元,它讓 Northern Air 得以訂閱所有機場的航班抵達資訊。與 # 萬用字元不同,它只匹配主題空間層次中的某一層,並不試圖匹配層次中的所有稍低的層。

在建立 MQTT 應用程式時,必須認真將主題空間設計得符合邏輯,並且支援靈活訂閱。這種方式確保了釋出的大多數客戶都無需多次訂閱和過度訂閱。

連線 MQTT

用 MQ Telemetry Transport 進行訊息釋出需要連線到 Lotus Expeditor micro broker 或任何支援 MQTT 協議的訊息伺服器,比如,WebSphere Message Broker。連線到 broker 需要幾個步驟。首先,必須構成並向客戶機建立工廠提供 MQTT 屬性物件。此屬性物件提供例項化客戶機的配置。屬性之一是 Boolean 標誌,指定了客戶應用程式是否是一個 clean session 客戶機,如果是的話,每次連線客戶機時都不依賴於先前連線到代理的既有知識(比如任何之前的訂閱或任何等待發布的訊息)。如果此標誌為 false,客戶機狀態就在連線代理過程中保持不變;例如,客戶應用程式無需每次在後續的重連線時都重新訂閱。此外,clean session 設定為 false,客戶機和代理都試圖恢復在連線中斷時所打斷的進展中的訊息交換(根據為訊息指定的服務質量)。要使用非 clean session 客戶機,必須提供 MqttPersistence 介面實現。包含此介面的實現對客戶機建立工廠而言意味著客戶應用程式需要使用持久(可靠)訊息釋出。本例使用的是 clean session 客戶機,並假設網路足夠可靠。屬性配置之後,MQTT 客戶例項會從 MQTT 客戶機工廠獲得。建立 MQTT 客戶例項需要幾個引數,包括一個惟一客戶機 ID、代理 IP 地址和埠以及可選的 MqttProperties 物件。

客戶機 ID 向代理表明客戶機的身份。它主要用來啟用持久訊息的傳遞和在客戶機數個連線和斷開過程中保持訂閱狀態。每個連線到代理的客戶機都要使用不同的客戶機 ID。如果兩個客戶機試圖在連線到代理時使用相同的客戶機 ID,系統只承認後一個連線,前一個連線會強制斷開。這種設計實際上實現了當之前的連線未完全消除時重新連線客戶機。客戶機 ID 最長為 23 字元。參見清單 1。


清單 1. 連線
    /**
    * Create a MqttClient object after configuring the MqttProperties object as
    * required.
    */
   private MqttClient createClient() throws MqttException {

       MqttProperties mqttProps = new MqttProperties();
       // Stateless "clean session" client
       mqttProps.setCleanStart(true);

       /**
        * Create the client from the factory. The client ID for this client is
        * "testClient" and the URL in the second parameter describes the
        * location of the broker, in this case, on the local machine.
        */
       MqttClient mqttClient = MqttClientFactory.INSTANCE.createMqttClient(
               "testClient", "tcp://mybroker:1883", mqttProps);

       return mqttClient;
   }


   /**
    * Connect the MqttClient to a broker.
    * 
    * @throws MqttException
    *             If an error occurs during connection operations.
    */
   private void connect() throws MqttException {

       /**
        * Register this application for callbacks from the client
        */
       client.registerCallback(this);

       /**
        * Connect the client to a broker.
        */
       client.connect();

   } 
  

釋出

MQTT 成功連線之後,就可以釋出訊息了。應用程式通過 MQTT 客戶物件釋出訊息。釋出訊息的方法簽名是 int publish(String, MqttPayload, byte, Boolean)。這四個引數詳細解釋如下:

  • String:主題引數的型別是字串,代理用此字串來針對訂閱者的興趣(使用之前描述的訂閱主題語法指定)匹配發布。
  • MqttPayload:第二個引數是一個 MqttPayload 物件。這個 MqttPayload 物件包含應用程式資料和任何此釋出的協議頭。此外,還提供了一個偏移量以便應用程式能夠決定資料在 MqttPayload 中從何處開始。這實現了對底層位元組陣列的訪問,而無需在資料寫到網路後建立額外的副本。提供這種訪問是為了在物件構成後和進行傳輸之前直接在載荷內操縱資料。
  • Byte:第三個引數,是此釋出的服務質量(Quality of Service,QoS)。QoS 有三級:0、1 或 2:

    • QoS 為 0 表明釋出程式和代理都只嘗試訊息的一次傳遞,不會採取任何額外的步驟,亦不會超越 TCP/IP 去確保訊息的傳遞。這一級有時又稱作 “發後即忘”,原因是向目的地傳送訊息後並不驗證訊息的接收。
    • QoS 為 1 表明訊息會被確保傳遞到代理;但訊息可能會傳遞多次。
    • QoS 為 2 則告知 MQTT 傳遞訊息且只傳遞一次。
    QoS 級別增加會導致額外的處理器和網路開銷。QoS 的設定會影響到訊息傳遞解決方案的整體可擴充套件性。而且還會增加客戶機儲存未傳遞訊息的負擔。因而,在為每個所釋出的訊息選擇合適的 QoS 級別時要格外注意。總的來說,若非迫切的傳遞保證所需,儘量使用較低階別的 QoS。隨訊息提供的 QoS 值指定了在客戶機和代理之間釋出訊息的服務質量。此外,該值還決定了代理用來向其訂閱者釋出訊息的最大 QoS 級別。

    訂閱者可以基於每個主題指定訊息傳遞的最大 QoS,所以若訊息在 QoS 2 釋出,此訊息有可能不是在該級別釋出給其訂閱者。訂閱者可以請求為它所收到的訊息提供降級 QoS。也許您會覺得訊息的端到端 QoS 不由釋出程式控制多少有些奇怪,但這樣做會提高訊息使用者的靈活性。當釋出後的訊息傳送給訂閱者時,代理會以訂閱過程中由此訂閱者指定的最高的 QoS 級別或是以此釋出訊息的 QoS 級別(如果此級別更低)傳遞此訊息。例如,以 QoS 2 向訂閱者釋出的訊息,若訂閱者指定此主題的級別為 QoS 1,那麼主題就會以 QoS 1 傳遞。對於同樣級別的標題,若向相同的訂閱者釋出 QoS 0 級別的訊息,那麼此訊息就會以 QoS 0 級別傳送給訂閱者。
  • Boolean:第四個引數是 Boolean 標誌,該標誌表明它是否是保留髮布。保留髮布存在於代理之內,作為針對給定主題收到的最後一個訊息。保留髮佈讓後續訂閱者可以在訂閱之後立即收到關於某主題的最新訊息,即使是他們在此訊息釋出之後才連線。對於啟動之後即刻填充某個顯示應用程式並隨後用對此資訊的後續更改更新它,這種方式十分有效。如果此標誌設為 false,只有當前訂閱到該主題的訂閱者可以收到訊息。清單 2 中所示的示例使用的是一個非保留髮布。

這種釋出方法會返回一個整型訊息 ID。它可以與已註冊的 MqttAdvancedCallback 方法結合使用來檢查訊息是否已由代理接收。

清單 2 中的程式碼釋出的訊息表明 Air Freedom 的 1024 次航班已經抵達倫敦希思羅機場 (LHR)。


清單 2. 釋出
/**
* Invoke from the command line with a single parameter, the broker URI,
* e.g. tcp://mybroker:1883.
*/
public static void main(String args[]) {

    MqttPublisher publisher = null;

    try {

        publisher = new MqttPublisher(args[0]);

        /**
        * Connect the newly created publisher to the supplied broker.
        */
        publisher.connect();

        /**
        * Publish an "Arrival" message.
        */
                
        publisher.publishMessage(
            "Flight Times/LHR/Air Freedom/Arrivals/Flight 1024",
            (byte) 2, "Arrived");

        /**
        * Sleep for 1 second waiting to receive notification of
        * publication. Real applications should use appropriate
        * inter-thread signaling mechanisms such as wait/notify, 
        * cyclic barriers or latches.
        */
        Thread.sleep(1000);

    }
    catch (MqttException exception) {
        System.err.println("Exception occurred during either instantiation, 
        connection, or publication: "
            + exception.getMessage());
    }
    catch (InterruptedException exception) {
        System.err.println("Interrupted while waiting for publication: "
            + exception.getMessage());
    }
    finally {
        try {
            /**
            * Close the publisher if instantiated.
            */
            if (publisher != null) {
                publisher.disconnectClient();
            }
        }
        catch (MqttException exception) {
            System.err.println("Exception occurred closing publisher: "
                    + exception.getMessage());
        }
    }
}


/**
* Construct a new MqttPublisher containing an unconnected MqttClient.
* 
* @param brokerURL
*            Broker URL to (eventually) connect to.
* @throws MqttException
*             If an underlying MQTT error occurs instantiating the client
*             object.
*/
private MqttPublisher(String brokerURL) throws MqttException {
    this.brokerURL = brokerURL;
    this.client = createClient();
}
 
/**
* Publish a string as a message in byte form. with the given quality of
* service to the given topic.
*/
public void publishMessage(String topic, byte qos, String message)  throws MqttException {
    client.publish(topic, new MqttPayload(message.getBytes(), 0), qos,
            false);
}

回撥

此示例客戶機連線到代理,並具有釋出訊息的能力。回撥可為釋出程式提供增強的功能性,這與我們的另一篇文章中所描述的客戶機訂閱類似。要收到釋出的確認,必須建立回撥處理程式,此處理程式還必須用 MQTT 客戶機物件註冊。有兩種回撥處理程式,簡單回撥處理程式和高階回撥處理程式。它們分別由 MqttCallback 介面和 MqttAdvancedCallback 介面實現。MqttAdvancedCallback 介面用來擴充套件 MqttCallback,所以在使用高階回撥介面時,也必須實現在簡單回撥介面中定義的方法。要使用高階介面,除了從簡單回撥函式中繼承的方法之外,還必須實現另外三個方法:subscribed(int, byte[])、unsubscribed(int) 和 published(int)。

前兩個回撥方法:subscribed(int, byte[]) 和 unsubscribed(int) 主要針對想要監視訂閱確認的客戶機。當代理確認了訂閱請求後,即呼叫訂閱方法。同樣地,第二個方法 unsubscribed 在從主題取消訂閱的請求得到確認後呼叫。由於本示例只側重於釋出,所以示例客戶機沒有使用這兩個方法;然而,骨架實現還是成功的程式碼編譯所必需的。

釋出客戶機最感興趣的方法是 published(int)。此方法提供了訊息已成功傳遞給代理的通知。此方法只有一個整型引數,即 messageID。應用程式可能希望將這個 messageID 與釋出方法所返回的 messageID 進行匹配。回撥只能用於以 QoS 級別 1 或 2 釋出的那些訊息。MqttPersistence 實現所提供的客戶端永續性與 QoS 1 或 2 的結合使用實現了為確保訊息傳遞而使用的回撥冗餘。MQTT 客戶機可跟蹤連線故障時的所有釋出,並試圖一旦重建連線就即刻完成訊息傳遞。但某些應用程式卻願意進行分步訊息傳遞或採用其自身的傳遞保障機制。

要接收通知,高階回撥介面的實現必須要用 MQTT 客戶端註冊。registerCallback 方法提供 MQTT 客戶機的註冊特性。清單 3 中的示例程式碼由清單 2 擴充套件而來,是一個功能全面的 MQTT 釋出程式。MqttAdvancedCallback 介面由此類實現,並由之前建立的 MQTT 物件註冊。該類可使用單一一個引數從命令列開啟,而此引數中包含代理 URI,比如,tcp://mybroker:1883。

結束語

MQTT 是面向釋出/訂閱訊息範型的一個功能強大的傳送機制。在需要小型客戶機和低網路負荷的情況下,它能提供在其他釋出/訂閱協議上增強了的實用工具。本文介紹瞭如何建立功能全面的 MQTT 釋出程式。示例客戶機連線到代理並向主題釋出訊息。此示例還展示了 MqttAdvancedCallback 介面,以用來通知訊息已傳送給代理。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/14751907/viewspace-426973/,如需轉載,請註明出處,否則將追究法律責任。

相關文章