基于 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 请求触发自发自收的过程,可以看到如下的打印信息