MQTT笔记

MQTT笔记

参考资料:

MQTT(Message Queuing Telemetry Transport, 消息队列遥测传输)是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,工作在 TCP/IP协议族上。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。

MQTT特点

MQTT协议是为大量计算能力有限,且工作在低带宽、不可靠的网络的远程传感器和控制设备通讯而设计的协议,它具有以下主要的几项特性:

  1. 使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合;

  2. 对负载内容屏蔽的消息传输;

  3. 使用 TCP/IP 提供网络连接;

    主流的MQTT是基于TCP连接进行数据推送的,但是同样有基于UDP的版本,叫做MQTT-SN。

  4. 有三种消息发布服务质量:

    • “至多一次”,消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。
    • “至少一次”,确保消息到达,但消息重复可能会发生。
    • “只有一次”,确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果。
  5. 小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量;

  6. 使用 Last Will 和 Testament 特性通知有关各方客户端异常中断的机制。

    • Last Will:即遗言机制,用于通知同一主题下的其他设备发送遗言的设备已经断开了连接。
    • Testament:遗嘱机制,功能类似于Last Will。

MQTT协议原理

MQTT协议实现方式

实现MQTT协议需要客户端和服务器端通讯完成,在通讯过程中,MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。

MQTT传输的消息分为:主题(Topic)和负载(payload)两部分:

  1. Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload);
  2. payload,可以理解为消息的内容,是指订阅者具体要使用的内容。

网络传输与应用消息

MQTT会构建底层网络传输:它将建立客户端到服务器的连接,提供两者之间的一个有序的、无损的、基于字节流双向传输

当应用数据通过MQTT网络发送时,MQTT会把与之相关的服务质量(QoS)和主题名(Topic)相关连。

MQTT客户端

一个使用MQTT协议的应用程序或者设备,它总是建立到服务器的网络连接。客户端可以:

  1. 发布其他客户端可能会订阅的信息;
  2. 订阅其它客户端发布的消息;
  3. 退订或删除应用程序的消息;
  4. 断开与服务器连接。

MQTT服务器

MQTT服务器也称为"消息代理"(Broker),可以是一个应用程序或一台设备。它是位于消息发布者和订阅者之间,它可以:

  1. 接受来自客户的网络连接;
  2. 接受客户发布的应用信息;
  3. 处理来自客户端的订阅和退订请求;
  4. 向订阅的客户转发应用程序消息。

MQTT协议中的订阅、主题、会话

1. 订阅(Subscription)

订阅包含主题筛选器(Topic Filter)和最大服务质量(QoS)。订阅会与一个会话(Session)关联。一个会话可以包含多个订阅。每一个会话中的每个订阅都有一个不同的主题筛选器。

2. 会话(Session)

每个客户端与服务器建立连接后就是一个会话,客户端和服务器之间有状态交互。会话存在于一个网络之间,也可能在客户端和服务器之间跨越多个连续的网络连接。

3. 主题名(Topic Name)

连接到一个应用程序消息的标签,该标签与服务器的订阅相匹配。服务器会将消息发送给订阅所匹配标签的每个客户端。

4. 主题筛选器(Topic Filter)

一个对主题名通配符筛选器,在订阅表达式中使用,表示订阅所匹配到的多个主题。

5. 负载(Payload)

消息订阅者所具体接收的内容。

MQTT协议中的方法

MQTT协议中定义了一些方法(也被称为动作),来于表示对确定资源所进行操作。这个资源可以代表预先存在的数据或动态生成数据,这取决于服务器的实现。通常来说,资源指服务器上的文件或输出。主要方法有:

  1. Connect::等待与服务器建立连接。
  2. Disconnect:等待MQTT客户端完成所做的工作,并与服务器断开TCP/IP会话。
  3. Subscribe:等待完成订阅。
  4. UnSubscribe:等待服务器取消客户端的一个或多个topics订阅。
  5. Publish:MQTT客户端发送消息请求,发送完成后返回应用程序线程。

MQTT协议数据包结构

MQTT数据包结构如下:

  1. 固定头(Fixed header):存在于所有MQTT数据包中,表示数据包类型及数据包的分组类标识。
  2. 可变头(Variable header):存在于部分MQTT数据包中,数据包类型决定了可变头是否存在及其具体内容。
  3. 消息体(Payload):存在于部分MQTT数据包中,表示客户端收到的具体内容。

MQTT固定头

固定头存在于所有MQTT数据包中,其结构如下:
MQTT固定报头格式

1. MQTT数据包类型

位置:Byte 1中bits 7-4。

表示为一个4位的无符号值,类型、取值及描述如下:

名字报文流动方向描述
Reserved0禁止保留
CONNECT1客户端到服务端客户端请求连接服务端
CONNACK2服务端到客户端连接报文确认
PUBLISH3两个方向都允许发布消息
PUBACK4两个方向都允许QoS 1消息发布收到确认
PUBREC5两个方向都允许发布收到(保证交付第一步)
PUBREL6两个方向都允许发布释放(保证交付第二步)
PUBCOMP7两个方向都允许QoS 2消息发布完成(保证交互第三步)
SUBSCRIBE8客户端到服务端客户端订阅请求
SUBACK9服务端到客户端订阅请求报文确认
UNSUBSCRIBE10客户端到服务端客户端取消订阅请求
UNSUBACK11服务端到客户端取消订阅报文确认
PINGREQ12客户端到服务端心跳请求
PINGRESP13服务端到客户端心跳响应
DISCONNECT14客户端到服务端客户端断开连接
Reserved15禁止保留
2. 标识位

位置:Byte 1中bits 3-0。

在不使用标识位的消息类型中,标识位被作为保留位。如果收到无效的标志时,接收端必须关闭网络连接:

(1)DUP:发布消息的副本。用来在保证消息的可靠传输,如果设置为1,则在下面的变长中增加MessageId,并且需要回复确认,以保证消息传输完成,但不能用于检测消息重复发送。

(2)QoS:发布消息的服务质量,即:保证消息传递的次数

  • Ø00:最多一次,即:<=1
  • Ø01:至少一次,即:>=1
  • Ø10:一次,即:=1
  • Ø11:预留

(3)RETAIN: 发布保留标识,表示服务器要保留这次推送的信息,如果有新的订阅者出现,就把这消息推送给它,如果设有那么推送至当前订阅者后释放。

3. 剩余长度(Remaining Length)

地址:Byte 2。

固定头的第二字节用来保存变长头部和消息体的总大小的,但不是直接保存的。这一字节是可以扩展,其保存机制,前7位用于保存长度,后一部用做标识。当最后一位为1时,表示长度不足,需要使用二个字节继续保存。

MQTT可变头

MQTT数据包中包含一个可变头,它驻位于固定的头和负载之间。可变头的内容因数据包类型而不同,较常的应用是作为包的标识:

很多类型数据包中都包括一个2字节的数据包标识字段,这些类型的包有:PUBLISH (QoS > 0)、PUBACK、PUBREC、PUBREL、PUBCOMP、SUBSCRIBE、SUBACK、UNSUBSCRIBE、UNSUBACK。

Payload消息体

Payload消息体位于MQTT数据包的第三部分,包含CONNECT、SUBSCRIBE、SUBACK、UNSUBSCRIBE四种类型的消息:

  • (1)CONNECT,消息体内容主要是:客户端的ClientID、订阅的Topic、Message以及用户名和密码。
  • (2)SUBSCRIBE,消息体内容是一系列的要订阅的主题以及QoS。
  • (3)SUBACK,消息体内容是服务器对于SUBSCRIBE所申请的主题及QoS进行确认和回复。
  • (4)UNSUBSCRIBE,消息体内容是要订阅的主题。

MQTT控制报文

  1. CONNECT – 连接服务端

    客户端到服务端的网络连接建立后,客户端发送给服务端的第一个报文必须是CONNECT报文。

    在一个网络连接上,客户端只能发送一次CONNECT报文。服务端必须将客户端发送的第二个CONNECT报文当作协议违规处理并断开客户端的连接 。

  2. CONNACK – 确认连接请求

    服务端发送CONNACK报文响应从客户端收到的CONNECT报文。服务端发送给客户端的第一个报文必须是CONNACK。

    如果客户端在合理的时间内没有收到服务端的CONNACK报文,客户端应该关闭网络连接。合理 的时间取决于应用的类型和通信基础设施。

  3. PUBLISH – 发布消息

    PUBLISH控制报文是指从客户端向服务端或者服务端向客户端传输一个应用消息。

  4. PUBACK –发布确认

    PUBACK报文是对QoS 1等级的PUBLISH报文的响应。

  5. PUBREC – 发布收到(QoS 2,第一步)

    PUBREC报文是对QoS等级2的PUBLISH报文的响应。它是QoS 2等级协议交换的第二个报文。

  6. PUBREL – 发布释放(QoS 2,第二步)

    PUBREL报文是对PUBREC报文的响应。它是QoS 2等级协议交换的第三个报文。

  7. PUBCOMP – 发布完成(QoS 2,第三步)

    PUBCOMP报文是对PUBREL报文的响应。它是QoS 2等级协议交换的第四个也是最后一个报文。

  8. SUBSCRIBE - 订阅主题

    客户端向服务端发送SUBSCRIBE报文用于创建一个或多个订阅。每个订阅注册客户端关心的一个或多个主题。为了将应用消息转发给与那些订阅匹配的主题,服务端发送PUBLISH报文给客户端。SUBSCRIBE报文也(为每个订阅)指定了最大的QoS等级,服务端根据这个发送应用消息给客户端。

  9. SUBACK – 订阅确认

    服务端发送SUBACK报文给客户端,用于确认它已收到并且正在处理SUBSCRIBE报文。

    SUBACK报文包含一个返回码清单,它们指定了SUBSCRIBE请求的每个订阅被授予的最大QoS等级。

  10. UNSUBSCRIBE –取消订阅

    客户端发送UNSUBSCRIBE报文给服务端,用于取消订阅主题。

  11. UNSUBACK – 取消订阅确认

    服务端发送UNSUBACK报文给客户端用于确认收到UNSUBSCRIBE报文。

  12. PINGREQ – 心跳请求

    客户端发送PINGREQ报文给服务端的。用于:

    • 在没有任何其它控制报文从客户端发给服务的时,告知服务端客户端还活着。
    • 请求服务端发送 响应确认它还活着。
    • 使用网络以确认网络连接没有断开。
  13. PINGRESP – 心跳响应

    服务端发送PINGRESP报文响应客户端的PINGREQ报文。表示服务端还活着。

  14. DISCONNECT –断开连接

    DISCONNECT报文是客户端发给服务端的最后一个控制报文。表示客户端正常断开连接。

    客户端发送DISCONNECT报文之后:

    • 必须关闭网络连接。
    • 不能通过那个网络连接再发送任何控制报文。

    服务端在收到DISCONNECT报文时:

    • 必须丢弃任何与当前连接关联的未发布的遗嘱消息。
    • 应该关闭网络连接,如果客户端 还没有这么做。

MQTT的搭建(ubuntu)

1、apt-get安装mqtt相关包

2、测试mosquitto是否正确运行

3、本机终端测试mqtt

打开一个终端,订阅主题

mosquitto_sub -h 192.168.1.102 -t "mqtt" -v

【-h】指定要连接的MQTT服务器
【-t】订阅主题,此处为mqtt
【-v】打印更多的调试信息

再打开一个终端,发布主题

mosquitto_pub -h 192.168.1.102 -t "mqtt" -m "Hello Stonegeek"

【-h】指定要连接的MQTT服务器
【-t】向指定主题推送消息
【-m】指定消息内容

结果展示

MQTT权限配置

前面我们基于Mosquitto服务器已经搭建成功了,但是默认是允许匿名用户登录,对于正式上线的项目则是需要进行用户认证(当然,用户一般都会与数据库映射,不过在这里我们就会直接将用户写入配置文件中)

1、Mosquitto服务器的配置文件为/etc/mosquitto/mosquitto.conf,关于用户认证的方式和读取的配置都在这个文件中进行

配置文件参数说明:

IDallow_anonymouspassword_fileacl_fileresult
1True(默认)允许匿名方式登录
2Falsepassword_file开启用户验证机制
3Falsepassword_fileacl_file开启用户验证机制,但访问控制不起作用
4Truepassword_fileacl_file用户名及密码不为空,将自动进行用户验证且受到访问控制的限制;用户名及密码为空,将不进行用户验证且受到访问控制的限制
5False无法启动服务

allow_anonymous允许匿名

password-file密码文件

acl_file访问控制列表

2、修改配置文件

命令:sudo vi /etc/mosquitto/mosquitto.conf

3、添加用户信息

命令解释: -c 创建一个用户、/etc/mosquitto/pwfile.example 是将用户创建到 pwfile.example 文件中、admin 是用户名。

同样连续会提示连续输入两次密码。注意第二次创建用户时不用加 -c 如果加 -c 会把第一次创建的用户覆盖。

至此两个用户创建成功,此时如果查看 pwfile.example 文件会发现其中多了两个用户。

4、添加Topic和用户的关系

5、用户认证测试

(1)重启Mosquitto步骤

查看mosquitto的进程

命令:ps -aux|grep mosquitto

(2)杀死进程

命令:sudo kill -9 pid

(3)启动

命令:mosquitto -c /etc/mosquitto/mosquitto.conf

(4)订阅端启动(不加用户)

订阅端启动(加用户)

(5)发布端启动

MQTT实现(Java语言)

注意:由于我们在上面配置了MQTT的用户权限控制,所以下面的用户只能使用stonegeek登录,否则项目会运行报错,而且我们在上面设置的访问控制列表中只有mtopic主题,所以我们必须使用此主题,否则,订阅者会收不到已发布的主题内容(已经测试过了)

下面是我们Java语言实现的MQTT服务的发布/订阅

1、添加Maven依赖

<dependency>
  <groupId>org.eclipse.paho</groupId>
  <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
  <version>1.1.1</version>
</dependency>

2、ServerMQTT.class

package com.stonegeek;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
import org.eclipse.paho.client.mqttv3.MqttTopic;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
/**
 * Created by StoneGeek on 2018/6/5.
 * 博客地址:http://www.cnblogs.com/sxkgeek * 服务器向多个客户端推送主题,即不同客户端可向服务端订阅相同的主题
 */
public class ServerMQTT {
    //tcp://MQTT安装的服务器地址:MQTT定义的端口号
    public static final String HOST = "tcp://192.168.1.102:1883";
    //定义一个主题
    public static final String TOPIC = "mtopic";
    //定义MQTT的ID,可以在MQTT服务配置中指定
    private static final String clientid = "server11";

    private MqttClient client;
    private MqttTopic topic11;
    private String userName = "stonegeek";
    private String passWord = "123456";

    private MqttMessage message;

    /**
     * 构造函数
     * @throws MqttException
     */
    public ServerMQTT() throws MqttException {
        // MemoryPersistence设置clientid的保存形式,默认为以内存保存
        client = new MqttClient(HOST, clientid, new MemoryPersistence());
        connect();
    }

    /**
     *  用来连接服务器
     */
    private void connect() {
        MqttConnectOptions options = new MqttConnectOptions();
        options.setCleanSession(false);
        options.setUserName(userName);
        options.setPassword(passWord.toCharArray());
        // 设置超时时间
        options.setConnectionTimeout(10);
        // 设置会话心跳时间
        options.setKeepAliveInterval(20);
        try {
            client.setCallback(new PushCallback());
            client.connect(options);

            topic11 = client.getTopic(TOPIC);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     *
     * @param topic
     * @param message
     * @throws MqttPersistenceException
     * @throws MqttException
     */
    public void publish(MqttTopic topic , MqttMessage message) throws MqttPersistenceException,
            MqttException {
        MqttDeliveryToken token = topic.publish(message);
        token.waitForCompletion();
        System.out.println("message is published completely! "
                + token.isComplete());
    }

    /**
     *  启动入口
     * @param args
     * @throws MqttException
     */
    public static void main(String[] args) throws MqttException {
        ServerMQTT server = new ServerMQTT();

        server.message = new MqttMessage();
        server.message.setQos(1);
        server.message.setRetained(true);
        server.message.setPayload("hello,topic11".getBytes());
        server.publish(server.topic11 , server.message);
        System.out.println(server.message.isRetained() + "------ratained状态");
    }
}

3、ClientMQTT.class

package com.stonegeek;
import java.util.concurrent.ScheduledExecutorService;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttTopic;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
/**
 * Created by StoneGeek on 2018/6/5.
 * 博客地址:http://www.cnblogs.com/sxkgeek
 */public class ClientMQTT {
    public static final String HOST = "tcp://192.168.1.102:1883";
    public static final String TOPIC = "mtopic";
    private static final String clientid = "client11";
    private MqttClient client;
    private MqttConnectOptions options;
    private String userName = "stonegeek";
    private String passWord = "123456";

    private ScheduledExecutorService scheduler;

    private void start() {
        try {
            // host为主机名,clientid即连接MQTT的客户端ID,一般以唯一标识符表示,MemoryPersistence设置clientid的保存形式,默认为以内存保存
            client = new MqttClient(HOST, clientid, new MemoryPersistence());
            // MQTT的连接设置
            options = new MqttConnectOptions();
            // 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,这里设置为true表示每次连接到服务器都以新的身份连接
            options.setCleanSession(true);
            // 设置连接的用户名
            options.setUserName(userName);
            // 设置连接的密码
            options.setPassword(passWord.toCharArray());
            // 设置超时时间 单位为秒
            options.setConnectionTimeout(10);
            // 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制
            options.setKeepAliveInterval(20);
            // 设置回调
            client.setCallback(new PushCallback());
            MqttTopic topic = client.getTopic(TOPIC);
            //setWill方法,如果项目中需要知道客户端是否掉线可以调用该方法。设置最终端口的通知消息
            options.setWill(topic, "close".getBytes(), 2, true);

            client.connect(options);
            //订阅消息
            int[] Qos  = {1};
            String[] topic1 = {TOPIC};
            client.subscribe(topic1, Qos);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws MqttException {
        ClientMQTT client = new ClientMQTT();
        client.start();
    }
}

4、PushCallback.class

package com.stonegeek;  

import org.eclipse.paho.client.mqttv3.MqttCallback;  
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;  
import org.eclipse.paho.client.mqttv3.MqttMessage;  

/**  
 * Created by StoneGeek on 2018/6/5.  
 * 博客地址:http://www.cnblogs.com/sxkgeek  
 * 发布消息的回调类   
 *  
 * 必须实现MqttCallback的接口并实现对应的相关接口方法CallBack 类将实现 MqttCallBack。   
 * 每个客户机标识都需要一个回调实例。在此示例中,构造函数传递客户机标识以另存为实例数据。   
 * 在回调中,将它用来标识已经启动了该回调的哪个实例。   
 * 必须在回调类中实现三个方法:   
 *  
 *  public void messageArrived(MqttTopic topic, MqttMessage message)接收已经预订的发布。   
 *  
 *  public void connectionLost(Throwable cause)在断开连接时调用。   
 *  
 *  public void deliveryComplete(MqttDeliveryToken token))   
 *  接收到已经发布的 QoS 1 或 QoS 2 消息的传递令牌时调用。   
 *  由 MqttClient.connect 激活此回调。   
 */  
public class PushCallback implements MqttCallback{  
    public void connectionLost(Throwable cause) {  
        // 连接丢失后,一般在这里面进行重连  
        System.out.println("连接断开,可以做重连");  
    }  

    public void deliveryComplete(IMqttDeliveryToken token) {  
        System.out.println("deliveryComplete---------" + token.isComplete());  
    }  

    public void messageArrived(String topic, MqttMessage message) throws Exception {  
        // subscribe后得到的消息会执行到这里面  
        System.out.println("接收消息主题 : " + topic);  
        System.out.println("接收消息Qos : " + message.getQos());  
        System.out.println("接收消息内容 : " + new String(message.getPayload()));  
    }  
}

5、结果展示

  • 7
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值