基於 MQTT 可以實現很多場景,例如現在使用比較多的物聯網,還有消息的實時推送。聯網的設備連接上 apollo 服務器以後,一直監聽 apollo 推送過來的信令/消息即可。
1、web 服務端向聯網的設備推送信令/消息,上述截圖的流程(1.1-1.2)。
1.1、web 服務端向 apollo 服務器發送信令/消息。
1.2、聯網的設備通過訂閱的主題,收到 web 服務端推送的信令/消息。
2、聯網的設備 1 向聯網的設備 2 發送信令/消息,上述截圖的流程(2.1-2.4)。
2.1、設備 1 向 apollo 服務器發送接收方爲設備 2 的消息/信令。
2.2、設備 2 向 web 服務端發起登錄。
2.3、設備 2 在 web 服務端登錄成功後,設備 2 與 apollo 服務器建立長連接。
2.4、設備 2 通過訂閱的主題,收到設備 1 推送的信令/消息。
現在,整體結構已經比較明顯了,接下來就會介紹 web 服務端的實現
基於 MQTT 長連接的 web 端實現
基於《Springboot 集成 MQTT —— 搭建 apollo 服務器(Windows)》 一文中搭建的 apollo 服務器,web 端需要配置 apollo 的連接。
標記部分的內容如下
# 用戶名
mqtt.username=admin
# 密碼
mqtt.password=password
mqtt.url=tcp://127.0.0.1:61613
# 生產者客戶端 ID
mqtt.send.clientId=mqttSendClient
# 消費者客戶端 ID
mqtt.recv.clientId=mqttRecvClient
添加上述配置的時候,應該會發現,有些內容不會自動提示,我們需要手動的將上述配置信息配置到 MQTT 客戶端的實例中。新增 MqttConfig 類。
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.MessageProducer;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.MessagingException;
@Configuration
public class MqttConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(MqttConfig.class);
public static final String CHANNEL_RECV = "recvMsgChannel"; // 訂閱消息的信道
public static final String CHANNEL_SEND = "sendMsgChannel"; // 發佈消息的信道
public static final String TOPIC = "topic";
@Value("${mqtt.username}")
private String username;
@Value("${mqtt.password}")
private String password;
@Value("${mqtt.url}")
private String url;
@Value("${mqtt.send.clientId}")
private String senderClientId;
@Value("${mqtt.recv.clientId}")
private String recverClientId;
// MQTT 客戶端的連接器哦誒之
@Bean
public MqttConnectOptions getMqttConnectOptions() {
MqttConnectOptions options = new MqttConnectOptions();
// 是否清空 session。false:服務器會保留客戶端的連接記錄,true:每次連接服務器都以新身份連接
options.setCleanSession(true);
options.setUserName(username); // 連接用戶
options.setPassword(password.toCharArray()); // 連接密碼
options.setServerURIs(url.split(",")); // 連接的服務器 url
options.setConnectionTimeout(10); // 超時時間(單位:s)
options.setKeepAliveInterval(20); // 保活心跳(單位:s),此方法沒有重連機制
return options;
}
// 構造 MQTT 客戶端
@Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
factory.setConnectionOptions(getMqttConnectOptions());
return factory;
}
// MQTT 生產者客戶端
@Bean
@ServiceActivator(inputChannel = CHANNEL_SEND)
public MessageHandler mqttMsgSend() {
MqttPahoMessageHandler messageHandler = new MqttPahoMessageHandler(senderClientId, mqttClientFactory());
messageHandler.setAsync(true);
return messageHandler;
}
// MQTT 消息訂閱綁定(消費者)
@Bean
public MessageProducer mqttMsgRecv() {
// 可以訂閱多個 Topic 的消息
MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(
recverClientId, mqttClientFactory(), TOPIC.split(","));
adapter.setCompletionTimeout(5000);
adapter.setConverter(new DefaultPahoMessageConverter());
adapter.setQos(2);
adapter.setOutputChannel(mqttInboundChannel()); // 設置訂閱通道
return adapter;
}
// MQTT信息通道(消費者)
@Bean(name = CHANNEL_RECV)
public MessageChannel mqttInboundChannel() {
return new DirectChannel();
}
// MQTT消息處理器(消費者,用於服務端自發自收的測試)
@Bean
@ServiceActivator(inputChannel = CHANNEL_RECV)
public MessageHandler handler() {
return new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
LOGGER.error("msg:{}", message.getPayload());
}
};
}
}
新增一個消息發送接口 IMqttSender 。
import com.hosh.tech.config.MqttConfig;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.stereotype.Component;
/**
* MQTT生產者消息發送接口
* MessagingGateway要指定生產者的通道名稱
*/
@Component
@MessagingGateway(defaultRequestChannel = MqttConfig.CHANNEL_SEND)
public interface IMqttSender {
/**
* 發送信息到MQTT服務器(實現發送全用戶消息)
* @param data 消息內容
*/
void sendToMqtt(String data);
/**
* 發送信息到 MQTT 服務器(實現發送公告類消息——P2M)
* @param topic 主題
* @param payload 消息內容
*/
void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic,
String payload);
/**
* 發送信息到 MQTT 服務器(實現發送點對點的消息)
* @param topic 主題
* @param qos 對消息處理的幾種機制
* 0 表示的是訂閱者沒收到消息不會再次發送,消息會丟失
* 1 表示的是會嘗試重試,一直到接收到消息,但這種情況可能導致訂閱者收到多次重複消息
* 2 多了一次去重的動作,確保訂閱者收到的消息有一次
* @param payload 消息內容
*/
void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic,
@Header(MqttHeaders.QOS) int qos,
String payload);
}
測試 web 服務端的推送
web 服務端的發送消息的實現
爲了方便測試,消息爲自發自收,收消息的實現已經在 MqttConfig 中給出
通過 http 請求觸發自發自收的過程,可以看到如下的打印信息