1. 概述

在本教程中,我们将了解如何使用 Eclipse Paho 项目提供的库在 Java 项目中添加 MQTT 消息传递。

2. MQTT 入门

MQTT(MQ 遥测传输)是一种消息传递协议,旨在满足对一种简单而轻量级的方法的需求,该方法将数据传输到/从低功耗设备(例如工业应用中使用的设备)传输数据。

随着IoT(物联网)设备的日益普及,MQTT的使用越来越多,导致OASIS和ISO对其进行标准化。

该协议支持单个消息传递模式,即发布-订阅模式:客户端发送的每条消息都包含一个关联的“主题”,代理使用该主题将其路由到订阅的客户端。主题名称可以是简单的字符串,如“油温”或类似路径的字符串“motor/1/rpm”。

为了接收消息,客户端使用其确切名称或包含受支持的通配符之一的字符串(“#”表示多级主题,“+”表示单级“)订阅一个或多个主题。

 

3. 项目设置 

为了将 Paho 库包含在 Maven 项目中,我们必须添加以下依赖项:

<dependency>
  <groupId>org.eclipse.paho</groupId>
  <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
  <version>1.2.0</version>
</dependency>

最新版本的 Eclipse Paho Java 库模块可以从 Maven Central 下载。

4. 客户端设置 

使用 Paho 库时,为了从 MQTT 代理发送和/或接收消息,我们需要做的第一件事是获取 IMqttClient 接口的实现。 此接口包含应用程序建立与服务器的连接、发送和接收消息所需的所有方法。

Paho 开箱即用,提供了此接口的两个实现,一个异步实现(MqttAsyncClient)和一个同步实现(MqttClient)。 在我们的例子中,我们将专注于同步版本,它具有更简单的语义。

设置本身是一个两步过程:我们首先创建 MqttClient 类的实例,然后将其连接到我们的服务器。以下小节详细介绍了这些步骤。

 

4.1. 创建一个新的 IMqtt客户端实例

以下代码片段演示如何创建新的 IMqttClient 同步实例:

String publisherId = UUID.randomUUID().toString();
IMqttClient publisher = new MqttClient("tcp://iot.eclipse.org:1883",publisherId);

在本例中,我们使用最简单的构造函数,该构造函数采用 MQTT 代理的端点地址和客户端标识符,该标识符唯一标识客户端。

在我们的例子中,我们使用了随机 UUID,因此每次运行时都会生成一个新的客户端标识符。

Paho 还提供了额外的构造函数,我们可以使用这些构造函数来自定义用于存储未确认消息的持久性机制和/或用于运行协议引擎实现所需的后台任务的 ScheduledExecutorService

我们使用的服务器端点是由 Paho 项目托管的公共 MQTT 代理,它允许任何具有互联网连接的人无需任何身份验证即可测试客户端。

 

4.2. 连接到服务器 

我们新创建的 MqttClient 实例未连接到服务器。我们通过调用其 connect() 方法来做到这一点,可以选择传递一个 MqttConnectOptions 实例,该实例允许我们自定义协议的某些方面。

特别是,我们可以使用这些选项来传递其他信息,例如安全凭据、会话恢复模式、重新连接模式等。

将这些选项公开为我们可以使用普通 setter 方法设置的简单属性。我们只需要设置场景所需的属性 - 其余属性将采用默认值。

用于建立与服务器的连接的代码通常如下所示:

MqttConnectOptions options = new MqttConnectOptions();
options.setAutomaticReconnect(true);
options.setCleanSession(true);
options.setConnectionTimeout(10);
publisher.connect(options);

在这里,我们定义连接选项,以便:

  • 如果发生网络故障,库将自动尝试重新连接到服务器
  • 它将丢弃上次运行中未发送的消息
  • 连接超时设置为 10 秒


5. 发送消息

使用已经连接的 MqttClient 发送消息非常简单。我们使用 publish() 方法变体之一将有效负载(始终是字节数组)发送到给定主题,使用以下服务质量选项之一:

  • 0 – “最多一次”语义,也称为“即发即弃”。当消息丢失可以接受时,请使用此选项,因为它不需要任何类型的确认或持久性
  • 1 – “至少一次”语义。当消息丢失不可接受您的订阅者可以处理重复项时,请使用此选项
  • 2 – “恰好一次”语义。当消息丢失不可接受订阅者无法处理重复项时,请使用此选项

在我们的示例项目中,EngineTemperatureSensor 类扮演模拟传感器的角色,每次我们调用其 call() 方法时都会生成新的温度读数。

此类实现了 Callable 接口,因此我们可以轻松地将其与 java.util.concurrent 包中可用的 ExecutorService 实现之一一起使用:

public class EngineTemperatureSensor implements Callable<Void> {

    // ... private members omitted
    
    public EngineTemperatureSensor(IMqttClient client) {
        this.client = client;
    }

    @Override
    public Void call() throws Exception {        
        if ( !client.isConnected()) {
            return null;
        }           
        MqttMessage msg = readEngineTemp();
        msg.setQos(0);
        msg.setRetained(true);
        client.publish(TOPIC,msg);        
        return null;        
    }

    private MqttMessage readEngineTemp() {             
        double temp =  80 + rnd.nextDouble() * 20.0;        
        byte[] payload = String.format("T:%04.2f",temp)
          .getBytes();        
        return new MqttMessage(payload);           
    }
}

MqttMessage 封装了有效负载本身、请求的服务质量以及消息的保留标志。此标志向代理指示它应保留此消息,直到被订阅者使用。

我们可以使用此功能来实现“最近一次的良好”行为,因此当新订阅者连接到服务器时,它将立即收到保留的消息。


6. 接收消息

为了从 MQTT 代理接收消息,我们需要使用 subscribe() 方法变体之一,它允许我们指定:

  • 我们希望接收的消息的一个或多个主题过滤器
  • 关联的 QoS
  • 用于处理收到的消息的回调处理程序

在以下示例中,我们将展示如何将消息侦听器添加到现有 IMqttClient 实例以接收来自给定主题的消息。我们使用 CountDownLatch 作为回调和主执行线程之间的同步机制,每次有新消息到达时都会递减它。

在示例代码中,我们使用了不同的 IMqttClient 实例来接收消息。我们这样做只是为了更清楚地说明哪个客户端做什么,但这不是 Paho 的限制——如果你愿意,你可以使用相同的客户端来发布和接收消息:

CountDownLatch receivedSignal = new CountDownLatch(10);
subscriber.subscribe(EngineTemperatureSensor.TOPIC, (topic, msg) -> {
    byte[] payload = msg.getPayload();
    // ... payload handling omitted
    receivedSignal.countDown();
});    
receivedSignal.await(1, TimeUnit.MINUTES);

上面使用的 subscribe() 变体将 IMqttMessageListener 实例作为其第二个参数。

在我们的例子中,我们使用一个简单的 lambda 函数来处理有效负载并递减计数器。如果在指定的时间窗口(1 分钟)内没有足够的消息到达,await() 方法将引发异常。

使用 Paho 时,我们不需要明确确认消息接收。如果回调正常返回,Paho 会假定它使用成功,并向服务器发送确认。

 

如果回调引发异常,客户端将被关闭。请注意,这将导致发送的 QoS 级别为 0 的任何消息丢失。

使用 QoS 级别 1 或 2 发送的消息将在客户端重新连接并再次订阅主题后由服务器重新发送。


7. 结论

在本文中,我们演示了如何使用 Eclipse Paho 项目提供的库在 Java 应用程序中添加对 MQTT 协议的支持。

该库处理所有低级协议详细信息,使我们能够专注于解决方案的其他方面,同时留出良好的空间来自定义其内部功能的重要方面,例如消息持久性。