背景
MQTT协议是为大量计算能力有限,且工作在低带宽、不可靠的网络的远程传感器和控制设备通讯而设计的协议
使用阿里云提供的MQTT服务
详情可以参考阿里的api和使用例子
话不多说,上代码安排
1.pom文件
<!-- mqtt -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.10</version>
</dependency>
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.2</version>
</dependency>
2.配置文件 application.yaml
mqtt:
instanceId: post-cn-v561idsmm0e
#endPoint: post-cn-xxxxxxxxx-internal.mqtt.aliyuncs.com #内网
endPoint: post-cn-xxxxxxxxx.mqtt.aliyuncs.com
accessKey: aaaaaaaaaaaaaaaaaaaaaaaaa
secretKey: aaaaaaaaaaaaaaaaaaaaaaaaaa
groupId: GID_java
deviceIdNum: 5
maxIntervalTime: 500
maxConnect: 100
qosLevel: 0
3.配置类
说一下遇到的坑
mqttClient.setTimeToWait(60000);
这个要改,因为当时我是放容器里跑的,放外面没事,放容器里不行,发现是超时造成的,时间不能太短也不能太长,毕竟连接要网络不是吗
import com.gatechat.util.ConnectionOptionWrapper;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.*;
@Configuration
@Slf4j
public class MQTTConfig {
/**
* MQ4IOT 实例 ID,购买后控制台获取
*/
@Value("${mqtt.instanceId}")
private String instanceId;
/**
* 接入点地址,购买 MQ4IOT 实例,且配置完成后即可获取,接入点地址必须填写分配的域名,不得使用 IP 地址直接连接,否则可能会导致客户端异常。
*/
@Value("${mqtt.endPoint}")
private String endPoint;
/**
* 账号 accesskey,从账号系统控制台获取
*/
@Value("${mqtt.accessKey}")
private String accessKey;
/**
* 账号 secretKey,从账号系统控制台获取,仅在Signature鉴权模式下需要设置
*/
@Value("${mqtt.secretKey}")
private String secretKey;
/**
* MQ4IOT clientId,由业务系统分配,需要保证每个 tcp 连接都不一样,保证全局唯一,如果不同的客户端对象(tcp 连接)使用了相同的 clientId 会导致连接异常断开。
* clientId 由两部分组成,格式为 GroupID@@@DeviceId,其中 groupId 在 MQ4IOT 控制台申请,DeviceId 由业务方自己设置,clientId 总长度不得超过64个字符。
*/
@Value("${mqtt.groupId}")
private String groupId;
@Value("${mqtt.deviceIdNum}")
private int deviceIdNum;
@Bean(name = "clientIdList")
public List<String> deviceIdList() throws Exception {
//String mac = SyStemMacUtil.getMac();
//mac = "FC-45-96-A1-92-F3";
//mac = mac.replaceAll("-", "");
String code = couponCode();
List<String> clientIdList = new ArrayList<>();
for (int i = 0 ; i < deviceIdNum ; i++) {
String device = "java" + code + (i + 10);
clientIdList.add(groupId + "@@@" + device);
}
return clientIdList;
}
@Bean(name = "mqttClientList")
public ArrayList<MqttClient> mqttClientList(@Qualifier("clientIdList") List<String> clientIdList) throws Exception {
ArrayList<MqttClient> mqttClientList = new ArrayList<>();
for (String clientId : clientIdList) {
ConnectionOptionWrapper connectionOptionWrapper = new ConnectionOptionWrapper(instanceId, accessKey, secretKey, clientId);
MemoryPersistence memoryPersistence = new MemoryPersistence();
MqttClient mqttClient = new MqttClient("tcp://" + endPoint + ":1883", clientId, memoryPersistence);
// MqttClient mqttClient = new MqttClient("tcp://112.65.79.208:1883", clientId, memoryPersistence);
// 客户端设置好发送超时时间,防止无限阻塞
mqttClient.setTimeToWait(60000);
mqttClient.setCallback(new MqttCallbackExtended() {
@Override
public void connectComplete(boolean reconnect, String serverURI) {
/**
* 客户端连接成功后就需要尽快订阅需要的 topic
*/
// log.info(serverURI + "-------------------connect success");
}
@Override
public void connectionLost(Throwable throwable) {
throwable.printStackTrace();
}
@Override
public void messageArrived(String s, MqttMessage mqttMessage) throws Exception {
/**
* 消费消息的回调接口,需要确保该接口不抛异常,该接口运行返回即代表消息消费成功。
* 消费消息需要保证在规定时间内完成,如果消费耗时超过服务端约定的超时时间,对于可靠传输的模式,服务端可能会重试推送,
* 业务需要做好幂等去重处理。超时时间约定参考限制
* https://help.aliyun.com/document_detail/63620.html?spm=a2c4g.11186623.6.546.229f1f6ago55Fj
*/
// log.info("receive msg from topic " + s + " , body is " + new String(mqttMessage.getPayload()));
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
// log.info("send msg succeed topic is : " + iMqttDeliveryToken.getTopics()[0]);
}
});
mqttClient.connect(connectionOptionWrapper.getMqttConnectOptions());
mqttClientList.add(mqttClient);
}
log.info("需创建当前MQTT连接数 : " + deviceIdNum);
log.info("当前已创建MQTT连接数 : " + mqttClientList.size());
return mqttClientList;
}
public String couponCode(){
String s = UUID.randomUUID().toString();
s = s.replaceAll("-","");
return s.substring(3, 13);
}
}
配置类里有个对象ConnectionOptionWrapper 需要我们写一下
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import static org.eclipse.paho.client.mqttv3.MqttConnectOptions.MQTT_VERSION_3_1_1;
/**
* 工具类:负责封装 MQ4IOT 客户端的初始化参数设置
*/
public class ConnectionOptionWrapper {
/**
* 内部连接参数
*/
private MqttConnectOptions mqttConnectOptions;
/**
* MQ4IOT 实例 ID,购买后控制台获取
*/
private String instanceId;
/**
* 账号 accesskey,从账号系统控制台获取
*/
private String accessKey;
/**
* 账号 secretKey,从账号系统控制台获取,仅在Signature鉴权模式下需要设置
*/
private String secretKey;
/**
* MQ4IOT clientId,由业务系统分配,需要保证每个 tcp 连接都不一样,保证全局唯一,如果不同的客户端对象(tcp 连接)使用了相同的 clientId 会导致连接异常断开。
* clientId 由两部分组成,格式为 GroupID@@@DeviceId,其中 groupId 在 MQ4IOT 控制台申请,DeviceId 由业务方自己设置,clientId 总长度不得超过64个字符。
*/
private String clientId;
/**
* Signature 鉴权模式下构造方法
*
* @param instanceId MQ4IOT 实例 ID,购买后控制台获取
* @param accessKey 账号 accesskey,从账号系统控制台获取
* @param clientId MQ4IOT clientId,由业务系统分配
* @param secretKey 账号 secretKey,从账号系统控制台获取
*/
public ConnectionOptionWrapper(String instanceId, String accessKey, String secretKey,
String clientId) throws NoSuchAlgorithmException, InvalidKeyException {
this.instanceId = instanceId;
this.accessKey = accessKey;
this.secretKey = secretKey;
this.clientId = clientId;
mqttConnectOptions = new MqttConnectOptions();
mqttConnectOptions.setUserName("Signature|" + accessKey + "|" + instanceId);
mqttConnectOptions.setPassword(Tools.macSignature(clientId, secretKey).toCharArray());
mqttConnectOptions.setCleanSession(true);
mqttConnectOptions.setKeepAliveInterval(90);
mqttConnectOptions.setAutomaticReconnect(true);
mqttConnectOptions.setMqttVersion(MQTT_VERSION_3_1_1);
mqttConnectOptions.setConnectionTimeout(5000);
}
public MqttConnectOptions getMqttConnectOptions() {
return mqttConnectOptions;
}
}
4.发送mqtt消息
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@Service
@Slf4j
public class MQTTService {
@Autowired
@Qualifier("mqttClientList")
private ArrayList<MqttClient> mqttClientList;
@Value("${mqtt.qosLevel}")
private int qosLevel;
private int index = 0;
/**
*
* @param msg json格式的string
*/
public void publish(String parentTopic, String msg, String deviceId){
try {
MqttClient client = mqttClientList.get(getIndex());
MqttMessage message = new MqttMessage(msg.getBytes());
message.setQos(qosLevel);
/**
* 发送普通消息时,topic 必须和接收方订阅的 topic 一致,或者符合通配符匹配规则
*/
String topic = parentTopic + "/channel/";
if(StringUtils.isNotBlank(deviceId)) {
topic = topic + deviceId;
}
client.publish(topic, message);
} catch (MqttException e) {
e.printStackTrace();
}
}
private synchronized int getIndex() {
index ++;
if(index == mqttClientList.size()) {
index = 0;
}
return index;
}
}