MQTT协议

ok,因为接到一个数据资源池的工作,会用到MQTT协议,我来学习记录一下。

概念:

MQTT协议是一种构建在TCP/IP协议族上,基于发布/订阅模式的消息可靠传输和分发协议。

组件:

MQTT客户端:

运行MQTT库的客户端都是MQTT客户端,MQTT测试工具也是客户端,使用MQTT进行通讯的应用传感器也是客户端.......

MQTT Broker:

负责处理客户端请求的关键组件,包括建立连接、断开连接、订阅、取消订阅、消息转发等,可以快速构建MQTT应用。

发布-订阅模式:

与客户端-服务器模式不同的是,发布者(发送消息的客户端)和订阅者(接受消息的客户端)无需直接连接,而是通过MQTT BRoker进行路由和消息分发。

主题:

MQTT协议根据主题来转发消息,类似于URL路径,比如test/topic1,就类似一个通道

QoS:

全程是quality of service,就是消息传递服务质量的保障协议。

消息传递有两方面:

        发布者把消息传递给Broker

        Broker把消息传递给订阅者

发布者把消息传递给Broker的时候会定义一个Qos级别,Broker使用订阅者定义的Qos级别把消息传递给订阅者,但如果后者的级别低于前者,就会以较低的级别去传递消息。

QoS 0:“即发即弃”,当MQTT客户端以QoS 0发布消息时,Broker不确认收到,而且发布者也不会存储和重复消息,只是以最大努力发送消息。

QoS 1:保证消息至少一次成功传递给订阅者。Broker会使用PUBACK确认收到,发布者会存储消息,直到他收到一个PUBACK确认。

发送者会使用每个数据包中的数据包标识符对PUBLISH数据包和PUBACK数据包匹配,如果一定时间内发布者没有收到PUBACK数据包,发布者就会重新发送PUBLISH数据包

QoS 2:发布者以QoS 2级别向Broker发送PUBLISH数据包,Broker收到后会使用PUBLISH数据包的PUBREC回复确认,如果发布者没有收到确认,会再次发送带重复DUP标志的PUBLISH数据包,直到收到确认。

当你使用的时候,发现你明明就想发送一条消息,但是日志显示你发了多条,难道你是真的发了多条吗?非也,可以好好检查一下你设置的QoS

工作流程:

1、客户端使用TCP/IP协议与Broker建立连接,选择加密(TLS/SSL)实现安全通信,客户端需要提供认证信息,并指定会话类型(clean session或persistent session)

2、客户端可以向指定主题发布消息,也可以订阅主题来接受消息。客户端发布消息时,会把消息发送给Broker,客户端订阅消息时,会接受与订阅主题有关的消息。

3、Broker接受到发布的消息,把这些消息转发给订阅了该主题的客户端。根据QoS等级确保消息可靠传输。根绝会话类型断开连接。

代码实现:

1、导入MQTT协议依赖

<!--  MQTT v3.1 -->
<dependency>
	<groupId>org.eclipse.paho</groupId>
	<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
	<version>1.2.5</version>
</dependency>

<!--   MQTT 5.0     https://mvnrepository.com/artifact/org.eclipse.paho/org.eclipse.paho.mqttv5.client -->
<!--		<dependency>-->
<!--			<groupId>org.eclipse.paho</groupId>-->
<!--			<artifactId>org.eclipse.paho.mqttv5.client</artifactId>-->
<!--			<version>1.2.5</version>-->
<!--		</dependency>-->

2、配置

Broker这里暂时用免费的公共的服务

# http
 server.port=8091
 #server.servlet.context-path=/hub

# mqtt服务器的地址和端口号
mqtt.url=tcp://broker.emqx.io:1883 
# 用户名密码
mqtt.username=
mqtt.password=
# ClientId需要唯一
mqtt.clientId=java-mqtt-client
# 默认主题
mqtt.defaultTopic=test/topic
# clean session为true表示建立一个薪的临时会话,客户端断开会自动销毁
# clean session为false表示建立一个永久会话,客户端断开,会话仍在切保存离线信息,直至会话超时注销
# 持久会话恢复的前提是客户端使用固定的ClientId,如果是动态的,重连后会生成一个新的永久会话
mqtt.cleanSession=true

基于MQTT的消息发送服务

@Service
@Slf4j
public class MqttService implements MqttCallBack {
    @Value("${mqtt.url}")
    private String url;

    @Value("${mqtt.username}")
    private String userName;

    @Value("${mqtt.password}")
    private String passWord;

    @Value("${mqtt.clientId}")
    private String clientId;

    @Value("${mqtt.cleanSession}")
    private boolean cleanSession;

    private MqttClient client;

    private int reconnectDelay = 2000; // 初始化重连延迟时间=2s
    private int maxReconnectAttempts = 3; // 初始化最大重连尝试次数
    private int reconnectAttempt = 0;

    // @PostConstruct标记方法,该方法没有参数,在bean的依赖注入之后被调用
    @PostConstruct
    public void connect() {
        try {
            client = new MqttClient(url, clientId);

            MqttConnectOptions options = new MqttConnectOptions();
            // 取消自动重连,自己控制
            options.setAutomaticReconnect(false);
            if(userName != null && !userName.isEmpty()){
                options.setUserName(userName);
                options.setPassWord(passWord);
            }

            client.setCallBack(this);
            client.connect(options);   
            // 订阅主题,可以多个
            client.subscribe("test/topic1");
        } catch (MqttException e) {
            log.error("------mqtt connect fail", e);
        }
    }

    @Override
    public void connectionLost(){
        log.info("------Connection lost! " + cause.getMessage());
        reconnect();
    }

    private void reconnect(){
        if(reconnectAttempt < maxReconnectAttempts){
            reconnectAttempt++;
            log.info("------Attempting to reconnect in " + reconnectDelay + "ms");

            // 这里用sleep模拟延迟
            try{
                Thread.sleep(reconnectDelay);
            }catch(InterrputedException e){
                Thread.currentThread().interrupt();
            }
        }else{
            log.warn("------max reconnect attempts reached")
        }
    }

    @Override
    public void messageArrived(String topic, MqttMessage message) throw Exception{
        // 消息到达后调用
        log.info("------message arrived, Topic: " + topic +", message: " + new String(message.getPayload()));
        // 这里做处理消息的逻辑
    }

    @Override
    public void deliveryComplete(IMqttDeliveryToken token){
        // 消息完全传递出去后调用
        log.info("------delivery complete!");
        // 这里做一些传递完消息后的清理操作
    }

    // 消息传递的方法
    public void publish(String topic, String payload) throw MqttException {
        MqttMessage message = new MqttMessage(payload.getBytes());
        message.setQos(2);
        client.publish(topic, message);
        log.info("------message published: {}", payload);
    }

    // 断开连接的方法
    @PreDestroy
    public void disconnect() throw MqttException{
        if(client != null &&& client.isConnected()){
            client.disconnect();
            log.info("------disconnected");
        }
    }

}

Controller

@RestController
@RequestMappping("/mqtt")
public class MqttController{

    @Autowired
    private MqttService mqttSerice;

    @RequestMappping(value = "/send", method = {RequestMethod.GET, RequestMethod.POST})
    public ResponseEntity<String> sendMessage(@ReuqestParam String topic, @ReuqestParam String message){
        topic = "test/topic1";
        try{
            mqttSerice.publish(topic, message);
            return ResponseEntity.ok("------message sent successfully");
        }catch(MqttException e){
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                   .body("------failed to sent message: " + e.getMessage());
        }
    }
}

请求:http://localhost:8091/mqtt/send?topic&message=hello world

原文:https://blog.csdn.net/dan_seek/article/details/141098188

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值