MQTT协议
作者:互联网
MQTT协议
MQTT简介
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的轻量级协议,该协议构建于TCP/IP协议之上,MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。
MQTT是一个基于客户端-服务器的消息发布/订阅传输协议。MQTT协议是轻量、简单、开放和易于实现的,这些特点使它适用范围非常广泛。在很多情况下,包括受限的环境中,如:机器与机器(M2M)通信和物联网(IoT)。其在,通过卫星链路通信传感器、偶尔拨号的医疗设备、智能家居、及一些小型化设备中已广泛使用。
特性
MQTT协议工作在低带宽、不可靠的网络的远程传感器和控制设备通讯而设计的协议,它具有以下主要的几项特性:
- 使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合。
- 对负载内容屏蔽的消息传输。
- 使用TCP/IP提供网络连接。
主流的MQTT是基于TCP连接进行数据推送的,但是同样有基于UDP的版本,叫做MQTT-SN。这两种版本由于基于不同的连接方式,优缺点自然也就各有不同了。 - 有三种消息发布服务质量(QoS):
- QoS0:“至多一次”,消息发布完全依赖底层TCP/IP网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。这一种方式主要普通APP的推送,倘若你的智能设备在消息推送时未联网,推送过去没收到,再次联网也就收不到了。
- QoS1:“至少一次”,确保消息到达,但消息重复可能会发生。
- QoS2:“只有一次”,确保消息到达一次。在一些要求比较严格的计费系统中,可以使用此级别。在计费系统中,消息重复或丢失会导致不正确的结果。这种最高质量的消息发布服务还可以用于即时通讯类的APP的推送,确保用户收到且只会收到一次。
- 小型传输,开销很小(固定长度的头部是2字节),协议交换最小化,以降低网络流量。
这就是为什么在介绍里说它非常适合“在物联网领域,传感器与服务器的通信,信息的收集”,要知道嵌入式设备的运算能力和带宽都相对薄弱,使用这种协议来传递消息再适合不过了。
MQTT协议格式
MQTT协议使用二进制数据包,包含三个部分,分别是固定头,可变头、消息体;
-
固定头:存在于所有的MQTT数据包中,长度是2-5字节,包括3部分内容,数据包类型(4bit),标识位(4bit),数据包剩余长度大小(1~4Byte),具体含义参考MQTT协议
-
可变头:部分MQTT数据包包含
-
消息体:部分MQTT数据包包含
MQTT协议中,固定头中的数据包剩余长度大小包括可变头和消息体长度,剩余长度可用1~4Byte表示,4Byte最大可表示256MB(0xFFFFFF7F)。有些数据包没有可变头和消息体,比如PINGREQ数据包只有2Byte,由此可以计算MQTT数据包的最大值和最小值
-
最大值:256MB+5Byte,其中256MB是剩余长度最大值,包括可变头和消息体;5Byte是固定头,数据包类型(4bit),标识位(4bit),数据包剩余长度大小(4Byte);
-
最小值:2Byte,比如PINGREQ数据包,只有固定头,没有可变头,消息体;
实现方式
实现MQTT协议需要客户端和服务器端通讯完成,在通讯过程中,MQTT协议中有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。
MQTT Client: 只要设备基于MQTT协议连接了MQTT Broker ,就认为这个设备是MQTT Client, MQTT Client 可以单独作为发布者和订阅者,也可以同时是发布者和订阅者。
MQTT Broker: MQTT Broker 是MQTT协议的核心,主要作用是接收发布者的消息,然后转发给对应的订阅者。Broker可以对Clinet接入进行授权,并对Client进行权限控制。常用的C语言编写的MQTT Broker 开源库有Mosquitto。如果学习MQTT需要Broker环境,可以通过这些开源库自建,也可以用各大云平台提供的Broker服务,如阿里云、腾讯云。
MQTT传输的消息分为:主题(Topic)和负载(payload)两部分:
- Topic,可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload);
- payload,可以理解为消息的内容,是指订阅者具体要使用的内容。
MQTT的搭建(ubuntu)
1.apt-get安装mqtt相关包
apt-get install mosquitto
安装客户端
apt-get install mosquitto-clients
2.测试mosquitto是否正确运行
service mosquitto status
3.本机终端测试mqtt
打开一个终端,订阅主题
mosquitto_sub -h 192.168.158.128 -t "mqtt" -v
- -h 指定要连接的MQTT服务器
- -t 订阅主题,此处为mqtt
- -v 打印更多的调试信息
再打开一个终端,发布主题
mosquitto_pub -h 192.168.158.128 -t "mqtt" -m "hello world"
- -h 指定要连接的MQTT服务器
- -t 向指定主题推送消息
- -m 指定消息内容
MQTT权限配置
前面我们基于Mosquitto服务器已经搭建成功了,但是默认是允许匿名用户登录,对于正式上线的项目则是需要进行用户认证(当然,用户一般都会与数据库映射,不过在这里我们就会直接将用户写入配置文件中)
1.Mosquitto服务器的配置文件为/etc/mosquitto/mosquitto.conf,关于用户认证的方式和读取的配置都在这个文件中进行
配置文件参数说明:
ID | allow_anonymous | password_file | acl_file | result |
---|---|---|---|---|
1 | True(默认) | 允许匿名方式登录 | ||
2 | False | password_file | 开启用户验证机制 | |
3 | False | password_file | acl_file | 开启用户验证机制,但访问控制不起作用 |
4 | True | password_file | acl_file | 用户名及密码不为空,将自动进行用户验证且受到访问控制的限制;用户名及密码为空,将不进行用户验证且受到访问控制的限制 |
5 | False | 无法启动服务 |
allow_anonymous:允许匿名
password-file:密码文件
acl_file:访问控制列表
修改配置文件
vi /etc/mosquitto/mosquitto.conf
allow_anonymous false
password_file /etc/mosquitto/pwfile
acl_file /etc/mosquitto/acl
添加用户信息
mosquitto_passwd -c /etc/mosquitto/pwfile zysheep
添加Topic和用户的关系
user zysheep
topic write mtopic/#
user zysheep
topic read mtopic/#
主题层级分隔符 /
: 用于分割主题层级,/
分割后的主题,这是消息主题层级设计中很重要的符号。 比方说: aaa/bbb和 aaa/bbb/ccc 和aaa/bbb/ccc/ddd ,这样的消息主题格式,是一个层层递进的关系,可通过多层通配符同时匹配两者,或者单层通配符只匹配一个。 这在现实场景中,可以应用到:公司的部门层级推送、国家城市层级推送等包含层级关系的场景。
单层通配符 +
: 单层通配符只能匹配一层主题。比如: aaa/+ 可以匹配 aaa/bbb ,但是不能匹配aaa/bbb/ccc。单独的+
号可以匹配单层的所有推送
多层通配符 #
: 多层通配符可以匹配于多层主题。比如: aaa/# ,不但可以匹配aaa/bbb,还可以匹配aaa/bbb/ccc/ddd。也就是说,多层通配符可以匹配符合通配符之前主题层级的所有子集主题。单独的#
匹配所有的消息主题.
用户认证测试
1.重启Mosquitto步骤,查看mosquitto的进程
ps -aux|grep mosquitto
2. 杀死进程
sudo kill -9 25372
3.重新启动mosquitto
mosquitto -c /etc/mosquitto/mosquitto.conf
4.订阅端启动(不加用户)
mosquitto_sub -h 192.168.158.128 -t "mtopic" -v
拒绝连接:未授权
5.订阅端启动(加用户)
mosquitto_sub -h 192.168.158.128 -t "mtopic" -v -u zysheep -P 123456
- -h 指定要连接的MQTT服务器
- -t 订阅主题,此处为mqtt
- -v 打印更多的调试信息
- -u 设置的用户
- -P 用户密码
6.发布端启动
mosquitto_pub -h 192.168.158.128 -t mtopic -u zysheep -p 123456 -m "hello world"
MQTT实现(Java语言)
Pom
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.1.1</version>
</dependency>
ClientMQTT
package cn.zysheep.mybatisspringboot.mqtt;
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;
/**
* Subscribe 客户端订阅主题
*/
public class ClientMQTT {
public static final String HOST = "tcp://192.168.158.128:1883";
public static final String TOPIC = "mtopic/zysheep/123";
private static final String clientid = "client11";
private MqttClient client;
private MqttConnectOptions options;
private String userName = "zysheep";
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};
// qos 对消息处理的几种机制。
// 0 表示的是订阅者没收到消息不会再次发送,消息会丢失。 最多一次,即:<=1
// 1 表示的是会尝试重试,一直到接收到消息,但这种情况可能导致订阅者收到多次重复消息。 至少一次,即:>=1
// 2 多了一次去重的动作,确保订阅者收到的消息有一次。 一次,即:=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();
}
}
ServerMQTT
package cn.zysheep.mybatisspringboot.mqtt;
import cn.zysheep.mybatisspringboot.entity.Wares;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
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;
/**
* 服务器向多个客户端推送主题,即不同客户端可向服务端订阅相同的主题
*
* Publish 服务端向主题发布消息
*/
public class ServerMQTT {
//tcp://MQTT安装的服务器地址:MQTT定义的端口号
public static final String HOST = "tcp://192.168.158.128:1883";
//定义一个主题
public static final String TOPIC = "mtopic/zysheep/123";
//定义MQTT的ID,可以在MQTT服务配置中指定
private static final String clientid = "server11";
private MqttClient client;
private MqttTopic topic11;
private String userName = "zysheep";
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, JsonProcessingException, InterruptedException {
ServerMQTT server = new ServerMQTT();
server.message = new MqttMessage();
server.message.setQos(1);
server.message.setRetained(true);
Wares wares = new Wares();
wares.setName("常德牌水表");
wares.setPicAddress("常德");
ObjectMapper objectMapper = new ObjectMapper();
String s = objectMapper.writeValueAsString(wares);
server.message.setPayload(s.getBytes());
for (int i = 0; i < 10; i++) {
if (i%2 == 0){
Thread.sleep(5000);
}
server.publish(server.topic11 , server.message);
}
System.out.println(server.message.isRetained() + "------ratained状态");
}
}
PushCallback
package cn.zysheep.mybatisspringboot.mqtt;
import cn.zysheep.mybatisspringboot.entity.Wares;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttMessage;
/**
* 发布消息的回调类
*
* 必须实现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());
String s = new String(message.getPayload());
ObjectMapper objectMapper = new ObjectMapper();
Wares wares = objectMapper.readValue(s, Wares.class);
System.out.println(wares);
//System.out.println("接收消息内容 : " + new String(message.getPayload()));
}
}
标签:协议,MQTT,client,mosquitto,消息,org,import 来源: https://blog.csdn.net/qq_45297578/article/details/118726703