mqttは、ssl一方向双方向検証、詳細な接続パラメーター、切断後の再サブスクリプションなどのツールクラスを実装します。この記事を読んだ後は、mqtt開発で十分です。mqttの概要については、Java Mqtt開発の重要な知識ポイント(キャッシング、メッセージサービスの品質、トピックフィルタリングなど)の記事を参照してください。
1メインカテゴリ
package util.mqtt.test;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.paho.client.mqttv3.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.HashMap;
import java.util.Map;
public class XBMqttDataUtil {
private static Logger logger = LoggerFactory.getLogger(XBMqttDataUtil.class);
private MqttClient mqttClient;
private MqttConnectOptions mqttConnectOptions;
/**
* 创建连接
* @param configParams
* @throws Exception
*/
public void createIotDataSource(Map<String, String> configParams) throws Exception {
//访问ip:port tcp:// or ssl://
String host = configParams.get("MQTT_HOST");
//客户端id 保持唯一
String clientId = configParams.get("MQTT_CLIENTID");
//用户名
String userName = configParams.get("MQTT_USERNAME");
//密码
String password = configParams.get("MQTT_PASSWORD");
//ssl 验证时是双向验证还是单向验证
String sslType=configParams.get("MQTT_SSLTYPE");
//缓存两种模式 存在内存 文件 设置成null 缓存在内存中 最多缓存65535条信息
//ScheduledExecutorService 可以设置线程池大小 默认10;发布消息方法是异步的
this.mqttClient = new MqttClient(host,clientId,null);//new MqttDefaultFilePersistence()
mqttConnectOptions = new MqttConnectOptions();
// 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,
// 这里设置为true表示每次连接到服务器都以新的身份连接
mqttConnectOptions.setCleanSession(false);
// mqttConnectOptions.sto
// 设置超时时间 s
mqttConnectOptions.setConnectionTimeout(20);
// 设置会话心跳时间
mqttConnectOptions.setKeepAliveInterval(10);
mqttConnectOptions.setAutomaticReconnect(true);//设置自动重连
//setWill方法,如果项目中需要知道客户端是否掉线可以调用该方法。设置最终端口的通知消息
// mqttConnectOptions.setWill("sec", "close".getBytes(), 2, true);
if("two".equals(sslType)){
//服务端证书路径
String rootCrtPath=configParams.get("MQTT_SSLROOT_CRTPATH");
//客户端证书路径
String clientCrtPath=configParams.get("MQTT_SSLCLIENT_CRTPATH");
//客户端密匙路径
String clientKeyPath=configParams.get("MQTT_SSLCLIENT_KEYPATH");
//密匙加密密码
String clientPassword=configParams.get("MQTT_SSLPASSWORD");
//ssl 协议版本 一般看mqtt服务端broker设置 不设置默认为TLSv1.1
String sslProtocol=configParams.get("MQTT_SSLPROTOCOL");
logger.info("sslProtocol======{}",sslProtocol);
mqttConnectOptions.setSocketFactory(SslUtil.getSocketFactory(rootCrtPath, clientCrtPath, clientKeyPath, clientPassword,sslProtocol));
}else if("one".equals(sslType)){
String rootCrtPath=configParams.get("MQTT_SSLROOT_CRTPATH");
String sslProtocol=configParams.get("MQTT_SSLPROTOCOL");
mqttConnectOptions.setSocketFactory(SslUtil.getSocketFactorySingle(rootCrtPath,sslProtocol));
}
if(StringUtils.isNotBlank(userName)){
mqttConnectOptions.setUserName(userName);
}
if (StringUtils.isNotBlank(password)){
mqttConnectOptions.setPassword(password.toCharArray());
}
//mqttConnectOptions.setWill(); //可以设置断线发送、接收消息
mqttClient.connect(mqttConnectOptions);
logger.info("mqtt 连接成功!!!");
}
/**
* 完成连接时 主要用于断开连接时 重新订阅
* 网上说 将 mqttConnectOptions.setCleanSession(false); 就可以继续接收,试了下没有效果,用的是开源版
* 的emq测试的,不知道企业版是否有效
*
* testTopic/# #多层通配符 +单层通配符
* topicFilter
* @param topicFilter
* @param iotMqttMessageListener
* @throws Exception
*/
public void setReSubscribe(String topicFilter, MqttMessageListener iotMqttMessageListener) throws Exception {
//先订阅一次
mqttClient.subscribe(topicFilter,iotMqttMessageListener);
//断线重连
mqttClient.setCallback(new MqttCallbackExtended() {
@Override
public void connectComplete(boolean reconnect, String serverURI) {
if(reconnect){
try {
mqttClient.subscribe(topicFilter,iotMqttMessageListener);
logger.info("mqtt重新建立连接后,topic={} 重新订阅!",topicFilter);
} catch (MqttException e) {
e.printStackTrace();
}
}
}
@Override
public void connectionLost(Throwable cause) {
}
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
}
});
}
/**
* 取消订阅
*
* @param topics
* @throws Exception
*/
public void unsubscribe(String[] topics) throws Exception{
mqttClient.unsubscribe(topics);
}
/**
* 是否处于连接状态
* @return
*/
public boolean isConnected() {
return mqttClient.isConnected();
}
/**
* 发布数据
* @param
* @param data
* @throws Exception
*/
public void publish( String publicLiveTopic, String data) throws Exception{
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(data.getBytes("UTF-8"));
//QoS:发布消息的服务质量,即:保证消息传递的次数(消费者收到的次数)
//0:最多一次,即:<=1;每个消息只发一次,也不会缓存下来。
//1:至少一次,即:>=1;一直发送确保消费者至少收到一次,发送失败会缓存下来。
//2:一次,即:=1 一直发送确保消费者只能收到一次;发送失败会缓存下来 。
mqttMessage.setQos(1);
//消费者断开连接后是否接受离线消息
mqttMessage.setRetained(true);
mqttClient.publish(publicLiveTopic,mqttMessage);
logger.info("topic:{} send dataSize {}kb ",publicLiveTopic,data.length()/1024.0);
}
/**
* 断开连接
*/
public void destroy() {
try {
this.mqttClient.disconnect();
logger.info("mqtt 手动断开连接!!!");
} catch (MqttException e) {
//e.printStackTrace();
logger.error("手动断开连接报错error={}",e.getMessage());
}
}
/**
* 设置连接
* @param args
*/
public static void main(String[] args) {
XBMqttDataUtil xbMqttDataUtil=new XBMqttDataUtil();
Map<String,String> configParams =new HashMap<>();
String host="tcp://10.21.80.11:1883";
//String host="ssl://10.251.80.151:1883";
configParams.put("MQTT_HOST",host);
configParams.put("MQTT_CLIENTID","random");
configParams.put("MQTT_USERNAME","admin");
configParams.put("MQTT_PASSWORD","123456");
// configParams.put("MQTT_SSLTYPE","two");
//
// configParams.put("MQTT_SSLROOT_CRTPATH","./root.crt");
// configParams.put("MQTT_SSLCLIENT_CRTPATH","./client.crt");
// configParams.put("MQTT_SSLCLIENT_KEYPATH","./client.key");
// configParams.put("MQTT_SSLPASSWORD","123456");
// configParams.put("MQTT_SSLPROTOCOL","TLSv1.1");
try {
xbMqttDataUtil.createIotDataSource(configParams);
MqttMessageListener mqttMessageListener=new MqttMessageListener();
xbMqttDataUtil.setReSubscribe("test/command/#", new MqttMessageListener());
while (true){
xbMqttDataUtil.publish("test/","hello mqtt!");
Thread.sleep(5000);
}
}catch ( Exception e) {
e.printStackTrace();
}
}
}
2つのssl解析ツール
package util.mqtt.test;
import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMReader;
import org.bouncycastle.openssl.PasswordFinder;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.X509Certificate;
public class SslUtil {
public static SSLSocketFactory getSocketFactory(final String caCrtFile, final String crtFile, final String keyFile,
final String password, String protocal) throws Exception {
Security.addProvider(new BouncyCastleProvider());
// load CA certificate
PEMReader reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(caCrtFile)))));
X509Certificate caCert = (X509Certificate)reader.readObject();
reader.close();
// load client certificate
reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(crtFile)))));
X509Certificate cert = (X509Certificate)reader.readObject();
reader.close();
// load client private key
reader = new PEMReader(
new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(keyFile)))),
new PasswordFinder() {
@Override
public char[] getPassword() {
return password.toCharArray();
}
}
);
KeyPair key = (KeyPair)reader.readObject();
reader.close();
// CA certificate is used to authenticate server
KeyStore caKs = KeyStore.getInstance(KeyStore.getDefaultType());
caKs.load(null, null);
caKs.setCertificateEntry("ca-certificate", caCert);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(caKs);
// client key and certificates are sent to server so it can authenticate us
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
ks.load(null, null);
ks.setCertificateEntry("certificate", cert);
ks.setKeyEntry("private-key", key.getPrivate(), password.toCharArray(), new java.security.cert.Certificate[]{cert});
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, password.toCharArray());
// 空时 默认
if(StringUtils.isBlank(protocal)){
protocal= "TLSv1.1";
}
SSLContext context = SSLContext.getInstance(protocal);//"TLSv1.1"
context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return context.getSocketFactory();
}
public static SSLSocketFactory getSocketFactorySingle(final String caCrtFile, String protocol) throws Exception {
Security.addProvider(new BouncyCastleProvider());
// load CA certificate
PEMReader reader = new PEMReader(new InputStreamReader(new ByteArrayInputStream(Files.readAllBytes(Paths.get(caCrtFile)))));
X509Certificate caCert = (X509Certificate)reader.readObject();
reader.close();
// client key and certificates are sent to server so it can authenticate us
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());//"JKS"
ks.load(null, null);
ks.setCertificateEntry("ca-certificate", caCert);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());//"PKIX"
tmf.init(ks);
// finally, create SSL socket factory
if(StringUtils.isBlank(protocol)){
protocol= "TLSv1.1";
}
SSLContext context = SSLContext.getInstance(protocol);//"TLSv1.1"
context.init(null, tmf.getTrustManagers(), new SecureRandom());
return context.getSocketFactory();
}
}
3メッセージ受信クラス
package util.mqtt.test;
import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
import org.eclipse.paho.client.mqttv3.MqttMessage;
/**
* 订阅信息监听类
*/
public class MqttMessageListener implements IMqttMessageListener {
@Override
public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception {
System.out.println(mqttMessage.getId());
System.out.println(mqttMessage.getPayload());
System.out.println(mqttMessage.getQos());
System.out.println(mqttMessage.isRetained());
System.out.println(mqttMessage.isDuplicate());
System.out.println(topic);
}
}
4つの依存する瓶
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.0</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
</dependency>