We are building a project in Java which is using MQTT to communicate with other services using Paho client library. While running the application, I observed that if the broker is down, Paho is not caching failed outbound messages.
Is there a way to make Paho cache failed outbound messages?
I am using Paho version 1.2.5
org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5
Here is how I have configured it:
public void connect(MqttCallback mqttCallback, int maxInflight) {
try {
persistence = new MqttDefaultFilePersistence(mqttPersistenceLocation);
mqttClient = new MqttAsyncClient(broker, clientId, persistence);
connOpts = new MqttConnectOptions();
// always set cleanSession to false if QoS 1 or 2 is used.
connOpts.setCleanSession(false);
connOpts.setAutomaticReconnect(true);
connOpts.setMaxInflight(maxInflight);
mqttClient.setCallback(mqttCallback);
LOGGER.info("Connecting to broker: " + broker + " with maxInflight: " + maxInflight);
mqttClient.connect(connOpts).waitForCompletion();
LOGGER.info("Connected");
} catch (Exception e) {
LOGGER.error("Error while connecting to MQTT:" + e);
}
}
Related
I'm using the MQTT Java library by Paho to detect the status of some devices. I'm facing a weird problem, because every 5 minutes I got a Connection lost error. BTW, with the usage of the reconnect method it works well, but why I got this strange fact? I'm using these lines of code within a Java EE in #Singleton component, which starts at boot.
String id = MqttAsyncClient.generateClientId();
System.out.println("Mqtt " + id + " " + uuid + " " + topics);
MqttClient client = new MqttClient(mqttSettings.generateURI(), id, new MemoryPersistence());
MqttConnectOptions connOpts = new MqttConnectOptions();
connOpts.setCleanSession(true);
connOpts.setAutomaticReconnect(true);
connOpts.setUserName(mqttSettings.getUsername());
connOpts.setPassword(mqttSettings.getPassword().toCharArray());
if (statement) {
setStatement(uuid, connOpts);
}
client.setCallback(callback);
client.connect(connOpts);
System.out.println("Connected? "+client.isConnected());
client.subscribe(topics);
if (statement) {
try {
System.out.println("Publishing installation status online...");
publishOnline(client);
} catch (MqttException e) {
System.out.println("MQTT local");
e.printStackTrace();
}
}
return client;
In a Java server application, we want to use the correlationData, which is part of MQTT5, so we can use this in the reply message to link and validate it when the reply is received.
I'm using the hivemq-mqtt-client library 1.2.2 and connecting to HiveMQ Cloud.
The connection is made like this:
private Mqtt5AsyncClient client;
public MqttConfig(Environment environment) {
client = MqttClient.builder()
.useMqttVersion5()
.identifier("TestServer")
.serverHost(MQTT_SERVER)
.serverPort(MQTT_PORT)
.sslWithDefaultConfig()
.automaticReconnectWithDefaultConfig()
.buildAsync();
client.connectWith()
.simpleAuth()
.username(MQTT_USER)
.password(MQTT_PASSWORD.getBytes())
.applySimpleAuth()
.send()
.whenComplete((connAck, throwable) -> {
if (throwable != null) {
logger.error("Could not connect to MQTT: {}", throwable.getMessage());
} else {
logger.info("Connected to MQTT: {}", connAck.getReasonCode());
}
});
}
public Mqtt5AsyncClient getClient() {
return client;
}
Sending a message is done with this method:
mqttConfig.getClient()
.publishWith()
.topic(destinationTopic)
//.correlationData(correlationData.getBytes())
.responseTopic(responseTopic)
.payload(message.getBytes())
.qos(MqttQos.AT_LEAST_ONCE)
.send()
.whenComplete((result, throwable) -> {
if (throwable != null) {
logger.info("Error sending to '{}': {} - {}", destinationTopic, message, throwable.getMessage());
} else {
logger.info("Message sent to '{}': {} - {}", destinationTopic, message, result);
}
});
When monitoring the messages on http://www.hivemq.com/demos/websocket-client/ and on a subscriber, messages are only received when the line with `correlationData()' is commented, as you can see above.
Both with or without that line, the application logs a successful sending, e.g. with correlation data enabled:
Message sent to 'server/test': testMessage - MqttQos2Result{publish=MqttPublish{topic=server/test, payload=11byte, qos=EXACTLY_ONCE, retain=false, responseTopic=server/test/reply, correlationData=6byte}, pubRec=MqttPubRec{packetIdentifier=1}}
Any idea why the additional correlationData seems to cause that they are not shown on the websocket test page, and are not received on any of the subscribers?
As an experiment, I used the paho 5 library instead of the HiveMQ library with the following code, but had exactly the same behavior and needed to disable the line to see messages passing:
MqttProperties properties = new MqttProperties();
//properties.setCorrelationData(correlationData.getBytes());
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setQos(1);
mqttMessage.setPayload(message.getBytes());
mqttMessage.setProperties(properties);
try {
mqttConfig.getClient().publish(destinationTopic, mqttMessage);
logger.info("Message was sent to '{}': {}", destinationTopic, message);
} catch (MqttException ex) {
logger.error("Error sending to '{}': {} - {}", destinationTopic, message, ex.getMessage());
}
This behaviour has now been fixed by HiveMQ Cloud.
flespi.io uses an auth token as a username, you can set the token as password also, if you want. How to connect to flespi using HiveMQ Java implementation?
Mqtt3AsyncClient client = MqttClient.builder()
.useMqttVersion3()
.identifier(UUID.randomUUID().toString())
.simpleAuth()
.username(mqttClientConfig.getUser())
.password(mqttClientConfig.getPassword().getBytes()).applySimpleAuth()
.serverHost(mqttClientConfig.getBrokerHost())
.serverPort(mqttClientConfig.getBrokerPort())
.buildAsync();
client.connect()
.whenComplete((connAck, throwable) -> {
if (throwable != null) {
logger.error("MQTT connection error: ", throwable);
} else {
client.publishWith()
.topic(mqttClientConfig.getPublishTopic())
.payload(payload.getBytes())
.qos(MqttQos.fromCode(mqttClientConfig.getQos()))
.send()
.whenComplete((mqtt3Publish, subThrowable) -> {
if (subThrowable != null) {
logger.error("MQTT subscripton error: ", subThrowable);
} else {
logger.info("Published on: {}", mqttClientConfig.getTopic());
}
});
client.disconnect();
}
});
I get the error:
INFO n.k.s.mqtt.HiveMqttClient - Connecting to mqtt.flespi.io:8883...
ERROR n.k.s.mqtt.HiveMqttClient - MQTT connection error:
com.hivemq.client.mqtt.exceptions.ConnectionClosedException: Server closed connection without DISCONNECT.
Port 8883 is normally for MQTT over TLS
If so then you need to add something like the following to the builder
.sslWithDefaultConfig()
See the HiveMQ tutorial here
My question is simple.
I want to know how to fix port client has.
According to Eclipse documents and IBM's, User has to fix broker address(this is absolutely natural). But There are no mentions about way of how to fix client site port.
https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_7.5.0/com.ibm.mq.javadoc.doc/WMQMQxrClasses/com/ibm/micro/client/mqttv3/MqttClient.html
MQTT must be also on TCP Layer, so I think it's possible to fix port.
If you have ideas, let me know.
Thanks
Under normal circumstance you don't set the source port for TCP connections, you just let the OS pick a random free port.
If you fix the source port then you can only ever run 1 instance of the client at a time on a given machine and if you get a connection failure you have to wait for the TCP stack to free that socket up again before you can reconnect.
If for some reason you REALLY NEED to fix the source port then you could probably write a custom javax.net.SocketFactory implementation that you can hard code the source port. Then pass this in as part of the MQTTConnectOptions object, but again I'm really struggling to come up with a reason this is a good idea.
You don't specify the client's port, as it is choosen by the OS, just as with any client to server connection, like you don't need to do that with a HTTP connection either.
You specify the IP address and port of the MQTT broker in the URL you pass in the form of tcp://<address>:<port>, for example tcp://localhost:4922.
The code below shows how I connect a Paho client in an OSGi context, where all connections parameters are retrieved from the bundle context.
private void configureMqtt(BundleContext context) throws IOException, MqttException {
String broker = context.getProperty("mqtt.broker");
if (broker == null) {
throw new ServiceException("Define mqtt.broker");
}
String client = context.getProperty("mqtt.clientname");
if (client == null) {
throw new ServiceException("Define mqtt.clientname");
}
String dir = context.getProperty("mqtt.persistence");
if (dir == null) {
dir = "mqtt";
};
File directory = context.getDataFile(dir);
directory.mkdirs();
logger.config(() -> String.format("MQTT broker: %s, clientname: %s persistence: %s", broker, client, directory.getAbsolutePath()));
connectOptions = new MqttConnectOptions();
connectOptions.setWill(GARAGE + "/door", new byte[0], 0, true);
String ka = context.getProperty("mqtt.keepalive");
if (ka != null) {
connectOptions.setKeepAliveInterval(Integer.valueOf(ka));
}
persistence = new MqttDefaultFilePersistence(directory.getCanonicalPath());
mqttClient = new MqttAsyncClient(broker, client, persistence);
mqttClient.setCallback(this);
connect();
}
private void connect() {
logger.fine("Connecting to MQTT broker");
try {
IMqttToken token = mqttClient.connect(connectOptions);
IMqttActionListener listener = new IMqttActionListener() {
#Override
public void onSuccess(IMqttToken asyncActionToken) {
logger.log(Level.INFO, "Connected to MQTT broker");
}
#Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
logger.log(Level.WARNING, "Could not connect to MQTT broker, retrying in 3 seconds", exception);
service.schedule(this::connect, 3, TimeUnit.SECONDS);
}
};
token.setActionCallback(listener);
} catch (MqttException e) {
logger.log(Level.SEVERE, "Cannot reconnect to MQTT broker, giving up", e);
}
}
I am implementing MQTT Client with Eclipse Paho and has some problems:
Both Publisher and Subscriber connect to broker with qos = 1 and setCleanSession =
false.
My flow:
Connect Subscriber and Publisher to broker, it's ok.
Disconnect Subscriber (I force stop My Project which include Subscriber ), Publisher continuing publishing message.
Reconnect Subscriber -> it cannot connect and throw exception: connectionLost.
If i set qos of Subscriber = 0, it not throw exception but The client does not receive messages sent by the publisher while the subscriber is offline, which I do not want
Can someone help me with this?
This is my code in subcriber
try {
// Create an Mqtt client
MqttAsyncClient mqttClient
= new MqttAsyncClient("tcp://" + swmConfig.getMqttApiLink(), "MeasureTransactionApi");
// new MqttAsyncClient(serverURI, clientId, persistence)
MqttConnectOptions connOpts = new MqttConnectOptions();
connOpts.setUserName(swmConfig.getMqttUsername());
connOpts.setPassword(swmConfig.getMqttPassword().toCharArray());
connOpts.setCleanSession(false);
// Connect to RabbitMQ Broker
log.info("Connecting to RabbitMQ broker: " + swmConfig.getMqttApiLink());
IMqttToken conToken = mqttClient.connect(connOpts);
conToken.waitForCompletion(10000);
if (!conToken.isComplete() || conToken.getException() != null) {
log.info("Error connecting: " + conToken.getException());
System.exit(-1);
}
log.info("Connected");
// Latch used for synchronizing b/w threads
final CountDownLatch latch = new CountDownLatch(1);
// Callback - Anonymous inner-class for receiving messages
mqttClient.setCallback(new MqttCallback() {
public void messageArrived(String topic, MqttMessage message) {
String time = new Timestamp(System.currentTimeMillis()).toString();
log.info("\nReceived a Message from RabbitMQ Broker" + "\n\tTime: " + time
+ "\n\tTopic: " + topic + "\n\tMessage: "
+ new String(message.getPayload()) + "\n\tQoS: "
+ message.getQos() + "\n");
handleMQTTMessageService.handleMessageArrived(message);
}
public void connectionLost(Throwable cause) {
log.info("Connection to RabbitMQ broker lost!" + cause.getMessage());
latch.countDown();
}
public void deliveryComplete(IMqttDeliveryToken token) {
log.info("deliveryComplete");
}
});
// Subscribe client to the topic filter with QoS level of 1
log.info("Subscribing client to topic: " + topic);
IMqttToken subToken = mqttClient.subscribe(topic, 1);
subToken.waitForCompletion(10000);
if (!subToken.isComplete() || subToken.getException() != null) {
log.info("Error subscribing: " + subToken.getException());
System.exit(-1);
}
} catch (MqttException me) {
log.error("Error:", me);
}
QOS is independent for publishers and subscribers.
To ensure delivery to the subscribing client you need to subscribe at greater than QOS 0.
What happens to QOS 0 subscriptions depends on the broker, by default most will not queue messages for QOS 0 subscriptions, but mosquitto can be forced to with the queue_qos0_messages configuration flag