MQTT 协议解析,java使用

来源:互联网 发布:数据采集网络兼职 编辑:程序博客网 时间:2024/03/29 20:28

MQTT简介(http://mqtt.org/)

MQ 遥测传输 (MQTT) 是轻量级基于代理的发布/订阅的消息传输协议,设计思想是开放、简单、轻量、易于实现。这些特点使它适用于受限环境。该协议的特点有:

  • 使用发布/订阅消息模式,提供一对多的消息发布,解除应用程序耦合。
  • 对负载内容屏蔽的消息传输。
  • 使用 TCP/IP 提供网络连接。
  • 小型传输,开销很小(固定长度的头部是 2 字节),协议交换最小化,以降低网络流量。
  • 使用 Last Will 和 Testament 特性通知有关各方客户端异常中断的机制。
  • 有三种消息发布服务质量: 
    • “至多一次”,消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。
    • “至少一次”,确保消息到达,但消息重复可能会发生。
    • “只有一次”,确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果。

下面是mqtt.org上的首段介绍:

It was designed as an extremely lightweight publish/subscribe messaging transport. It is useful for connections with remote locations where a small code footprint is
required and/or network bandwidth is at a premium. For example, it has been used in sensors communicating to a broker via satellite link, over occasional dial-up connections with healthcare providers, and in a range of home automation and small device scenarios.
It is also ideal for mobile applications because of its small size, low power usage, minimised data packets, and efficient distribution of information to one or many receivers

MQTT轻量级基于代理的发布/订阅的消息传输协议,它可以通过很少的代码和带宽和远程设备连接。例如通过卫星和代理连接,通过拨号和医疗保健提供者连接,以及在一些自动化或小型设备上,而且由于小巧,省电,协议开销小和能高效的向一和多个接收者传递信息,故同样适用于称动应用设备上。

相信在想深入学习这协议必是奔着解决某个问题而来的,上面给出了适用的场景,我之所以想深入的学习和了解这个协议,理由如下:

1、可以实现手机消息推送(PUSH)

2、协议简单,最小的头部只需2个字节,特别适合于嵌入式中。

3、这是个了解什么是协议绝好的例子。相比于其它复杂的协议例如tcp,http协议,至少说明文档看的下去。

在这里,我以推送为例子说明,虽然现在现成的推送解决方案已经比较成熟,但是这个Repeat ReInvent the Whell还是要做一下,什么都是拿来主义,和搬运工有什么区别。

一、需要的环境:

1、PHP+Apache或Nginx

2、安装开源代理程序Mosquitto,这里用其做为代理服务器,负责连接和分发。

安装方法很简单,http://mosquitto.org/files/   binary是编译好的,source是源码安装需要的(make & make install 就行)

唯 一要配置的就是在解压后的config.mk,安装完后设置文件是mosquitto.conf

当然主要是设置是否支持ssl,还有就是config.mk最下面的安装位置的设定。这里一切默认。

默认启动是绑定的IP是本地IP,端口是1883可以在mosquitto.conf里设置(要去掉前面的#字注释),Linux 中 -c 可以指定设置文件并运行

比 如: mosquitto -c /etc/mosquitto.conf

二、协议初解

先说一下整个协议的构造,整体上协议可拆分为:

                    固定头部+可变头部+消息体

协议说白了就是对于双方通信的一个约定,比如传过来一段字符流,第1个字节表示什么,第2个字节表示什么。。。。一个约定。

所以在固定头部的构造如下:

1、MessageType(0和15保留,共占4个字节)

[php] view plain copy
  1. public $operations=array(  
  2.          "MQTT_CONNECT"=>1,//请求连接  
  3.          "MQTT_CONNACK"=>2,//请求应答  
  4.          "MQTT_PUBLISH"=>3,//发布消息  
  5.          "MQTT_PUBACK"=>4,//发布应答  
  6.          "MQTT_PUBREC"=>5,//发布已接收,保证传递1  
  7.          "MQTT_PUBREL"=>6,//发布释放,保证传递2  
  8.          "MQTT_PUBCOMP"=>7,//发布完成,保证传递3  
  9.          "MQTT_SUBSCRIBE"=>8,//订阅请求  
  10.          "MQTT_SUBACK"=>9,//订阅应答  
  11.          "MQTT_UNSUBSCRIBE"=>10,//取消订阅  
  12.          "MQTT_UNSUBACK"=>11,//取消订阅应答  
  13.          "MQTT_PINGREQ"=>12,//ping请求  
  14.          "MQTT_PINGRESP"=>13,//ping响应  
  15.          "MQTT_DISCONNECT"=>14//断开连接  
  16.         );   

2、DUP flag

 其是用来在保证消息传输可靠的,如果设置为1,则在下面的变长头部里多加MessageId,并需要回复确认,保证消息传输完成,但不能用于检测消息重复发送。

3、Qos

主要用于PUBLISH(发布态)消息的,保证消息传递的次数。

00表示最多一次 即<=1

01表示至少一次  即>=1

10表示一次,即==1

11保留后用

4、Retain

 主要用于PUBLISH(发布态)的消息,表示服务器要保留这次推送的信息,如果有新的订阅者出现,就把这消息推送给它。如果不设那么推送至当前订阅的就释放了。

5、固定头部的byte 2

是用来保存接下去的变长头部+消息体的总大小的。

但是不是并不是直接保存的,同样也是可以扩展的,其机制是,前7位用于保存长度,后一部用做标识。

我举个例了,即如果计算出后面的大小为0<length<=127的,正常保存

如果是127<length<16383的,则需要二个字节保存了,将第一个字节的最大的一位置1,表示未完。然后第二个字节继续存。

拿130来说,第一个字节存10000011,第二个字节存000000001,也就是0x83,0x01,把两个字节连起来看,第二个字节权重从2的8次开始。

同起可以加第3个字节,最多可以加至第4个字节。故MQTT协议最多可以实现268 435 455 (0xFF, 0xFF, 0xFF, 0x7F)将近256M的数据。可谓能伸能缩。

157 --> (157>127)?[两个字节]:[一个字节]  --> 157 = 29 + 128 --> 第一个字节: 0x1d(29) + 0x80(128)[代表未完待续,最高位补1] = 0x9d ;第二个字节: 157/128 只有一个,所以是 0x01;  -->157长度则为 0x9d,0x01

321 --> (321>127)?[两个字节]:[一个字节]  -->321 = 65 + 2*128 --> 第一个字节: 0x41(65) + 0x80(128)[代表未完待续,最高位补1] = 0xc1 ;第二个字节: 321/128 有2个,所以是 0x02;  -->321长度则为 0xc1,0x02



 可变头部

这个是可变头部的全貌。

1、首先最上面的8个字节是Protocol Name(编码名),UTF编码的字符“MQIsdp”,头两个是编码名提长为6。

这里多说一些,接下去的协议多采用这种方式组合,即头两个字节表示下一部分的长,然后后面跟上内容。这里头两个字节长为6,下面跟6个字符“MQIsdp”。

2、Protocol Version,协议版本号,v3 也是固定的。

3、Connect Flag,连接标识,有点像固定头部的。8位分别代表不同的标志。第1个字节保留。

Clean Session,Will flag,Will Qos, Will Retain都是相对于CONNECT消息来说的。

Clean Session:0表示如果订阅的客户机断线了,那么要保存其要推送的消息,如果其重新连接时,则将这些消息推送。

                            1表示消除,表示客户机是第一次连接,消息所以以前的连接信息。

Will Flag,表示如果客户机在不是在发送DISCONNECT消息中断,比如IO错误等,将些置为1,要求重传。并且下且的WillQos和WillRetain也要设置,消息体中的Topic和MessageID也要设置,就是表示发生了错误,要重传。

Will Qos,在CONNECT非正常情况下设置,一般如果标识了WillFlag,那么这个位置也要标识。

Will RETAIN:同样在CONNECT中,如果标识了WillFlag,那么些位也一定要标识

usename flag和passwordflag,用来标识是否在消息体中传递用户和密码,只有标识了,消息体中的用户名和密码才用效,只标记密码而不标记用户名是不合法的。

4、Keep Alive,表示响应时间,如果这个时间内,连接或发送操作未完成,则断开tcp连接,表示离线。

5、Connect Return Code即通常于CONNACK消息中,表示返回的连接情况,我可以通过此检验连接情况。

6、Topic Name,订阅消息标识,MQTT是基于订阅/发布的消息,那么这个就是消息订阅的标识,像新闻客户端里的订阅不同的栏目一样。用于区别消息的推送类别。

主要用于PUBLISH和SUBSCRIBE中。最大可支持32767个字符,即4个字节。

消息体(PayLoad)

只有3种消息有消息体CONNECT,SUBSCRIBE,SUBACK

CONNECT主要是客户机的ClientID,订阅的Topic和Message以及用户名和密码,其于变长头部中的will是对应的。

SUBSCRIBE是包含了一系列的要订阅的主题以及QOS。

SUBACK是用服务器对于SUBSCRIBE所申请的主题及QOS进行确认和回复。

PUBLISH是消息体中则保存推送的消息,以二进制形式,当然这里的编辑可自定义。

7、Message Identifier

包含于PUBLISH, PUBACK, PUBREC, PUBREL, PUBCOMP, SUBSCRIBE, SUBACK, UNSUBSCRIBE, UNSUBACK.

其为16位字符表示,用于在Qos为1或2时标识Message的,保证Message传输的可靠性。

至于具体的消息例子,我们在后面的代码中慢慢体现。


------MQTT安装----

一、简单介绍

         MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)是IBM开发的一个即时通讯协议,有可能成为物联网的重要组成部分。该协议支持所有平台,几乎可以把所有联网物品和外部连接起来,被用来当做传感器和致动器(比如通过Twitter让房屋联网)的通信协议。(以上内容来源百度)

二、环境介绍

1、系统环境 :centos (腾讯云服务器)

2、MQTT版本:mosquitto-1.4.5

三、环境准备

1、资源获取

# 下载源代码包
wget http://mosquitto.org/files/source/mosquitto-1.4.9.tar.gz
# 解压
tar zxfv mosquitto-1.4.9.tar.gz

2、Linux环境准备

ares.h    sudo yum install libc-ares-dev

uuid.h     yum install libuuid-devel

g++   yum install gcc

       yum install gcc-c++
3、安装

# 进入目录
cd mosquitto-1.4.5
# 编译
make 
# 安装

sudo make install
4、问题
1、提示error,没有用户 mosquitto ,使用下面命令新建一个用户即可
      useradd mosquitto

2、libmosquitto.so.1  文件找不到问题
      //创建链接
      vi /etc/ld.so.conf
      //编辑文件ld.so.conf
      include ld.so.conf.d/*.conf
      /usr/local/lib/libmosquitto.so.1
      //更新链接库
      /sbin/ldconfig -v
5、测试
      putty : 打开三个控制台
      mosquitto -v
      mosquitto_sub -v -t toptest
      mosquitto_pub -t toptest-m helloworld

6、运行后结果
      toptest  helloworld



----win下MQTT-----

寻找过程

在寻找MQTT服务器的过程中,我发现的Mosquitto是一款「An Open Source MQTT v3.1/v3.1.1 Broker」——开源的MQTT代理服务器,其下也有Windows的安装包 
Mosquitto

但是我使用的是Win10 64位系统,这两个都下载安装后不是报错就是缺少dll文件,不能使用

最后参考了前面博客说的,搭建了Apollo服务器,这里使用的Apollo 1.7.1。 
官网:http://activemq.apache.org/index.html 
下载地址:http://activemq.apache.org/apollo/download.html 
快速开始教程:http://activemq.apache.org/apollo/documentation/getting-started.html

搭建MQTT服务器

使用Apollo搭建MQTT服务器步骤:

  1. 下载Apollo服务器并解压,在CMD环境运行其工作目录下的 bin\apollo.cmd,命令后面带上参数「create mybroker」,创建服务器实例。这里需要Java环境,系统环境变量下要有JAVA_HOME。

  2. 创建实例之后会在bin目录下生成mybroker文件夹,其中 etc\apollo.xml 文件下是配置服务器信息的文件,etc\users.properties 文件包含连接MQTT服务器时用到的用户名和密码,初始默认帐号是admin,密码password

  3. 进入 mybroker\bin\ 目录,在CMD输入命令「apollo-broker.cmd run」,可以使用TAB键自动补全,运行后输出信息如下: 
    MQTT服务运行信息 
    其中我们要留意的: 
    MQTT服务器TCP连接端口:tcp://0.0.0.0:61613 
    后台登录接口:https://127.0.0.1:61681/或http://127.0.0.1:61680/

登录服务器后,如果MQTT服务器有客户端连接,后台会显示如下 
后台


------JAVA 测试----

以下例子原型来源于网络,经过自己调试可用,目前仅限于代码的示例;

类:

ServerMQTT

ClientMQTT

PushCallback  

注: 例子中需要修改的地方:

        1、localhost:  修改为上节安装服务的ip地址;

         2、1883:修改为上节安装服务的端口,在配置文件中可修改,默认是(1883);



ServerMQTT类:

[java] view plain copy
  1. /** 
  2.  * Created by Administrator on 17-2-10. 
  3.  */  
  4.   
  5. import org.eclipse.paho.client.mqttv3.MqttClient;  
  6. import org.eclipse.paho.client.mqttv3.MqttConnectOptions;  
  7. import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;  
  8. import org.eclipse.paho.client.mqttv3.MqttException;  
  9. import org.eclipse.paho.client.mqttv3.MqttMessage;  
  10. import org.eclipse.paho.client.mqttv3.MqttPersistenceException;  
  11. import org.eclipse.paho.client.mqttv3.MqttTopic;  
  12. import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;  
  13. /** 
  14.  * 
  15.  * Title:Server 
  16.  * Description: 服务器向多个客户端推送主题,即不同客户端可向服务器订阅相同主题 
  17.  * @author admin 
  18.  * 2017年2月10日下午17:41:10 
  19.  */  
  20. public class ServerMQTT {  
  21.   
  22.     //tcp://MQTT安装的服务器地址:MQTT定义的端口号  
  23.     public static final String HOST = "tcp://localhost:1883";  
  24.     //定义一个主题  
  25.     public static final String TOPIC = "topic11";  
  26.     //定义MQTT的ID,可以在MQTT服务配置中指定  
  27.     private static final String clientid = "server11";  
  28.   
  29.     private MqttClient client;  
  30.     private MqttTopic topic11;  
  31.     private String userName = "mosquitto";  
  32.     private String passWord = "";  
  33.   
  34.     private MqttMessage message;  
  35.   
  36.     /** 
  37.      * 构造函数 
  38.      * @throws MqttException 
  39.      */  
  40.     public ServerMQTT() throws MqttException {  
  41.         // MemoryPersistence设置clientid的保存形式,默认为以内存保存  
  42.         client = new MqttClient(HOST, clientid, new MemoryPersistence());  
  43.         connect();  
  44.     }  
  45.   
  46.     /** 
  47.      *  用来连接服务器 
  48.      */  
  49.     private void connect() {  
  50.         MqttConnectOptions options = new MqttConnectOptions();  
  51.         options.setCleanSession(false);  
  52.         options.setUserName(userName);  
  53.         options.setPassword(passWord.toCharArray());  
  54.         // 设置超时时间  
  55.         options.setConnectionTimeout(10);  
  56.         // 设置会话心跳时间  
  57.         options.setKeepAliveInterval(20);  
  58.         try {  
  59.             client.setCallback(new PushCallback());  
  60.             client.connect(options);  
  61.   
  62.             topic11 = client.getTopic(TOPIC);  
  63.         } catch (Exception e) {  
  64.             e.printStackTrace();  
  65.         }  
  66.     }  
  67.   
  68.     /** 
  69.      * 
  70.      * @param topic 
  71.      * @param message 
  72.      * @throws MqttPersistenceException 
  73.      * @throws MqttException 
  74.      */  
  75.     public void publish(MqttTopic topic , MqttMessage message) throws MqttPersistenceException,  
  76.             MqttException {  
  77.         MqttDeliveryToken token = topic.publish(message);  
  78.         token.waitForCompletion();  
  79.         System.out.println("message is published completely! "  
  80.                 + token.isComplete());  
  81.     }  
  82.   
  83.     /** 
  84.      *  启动入口 
  85.      * @param args 
  86.      * @throws MqttException 
  87.      */  
  88.     public static void main(String[] args) throws MqttException {  
  89.         ServerMQTT server = new ServerMQTT();  
  90.   
  91.         server.message = new MqttMessage();  
  92.         server.message.setQos(1);  
  93.         server.message.setRetained(true);  
  94.         server.message.setPayload("hello,topic11".getBytes());  
  95.         server.publish(server.topic11 , server.message);  
  96.         System.out.println(server.message.isRetained() + "------ratained状态");  
  97.     }  
  98. }  


ClientMQTT类:

[java] view plain copy
  1. /** 
  2.  * 
  3.  * Description: 
  4.  * @author admin 
  5.  * 2017年2月10日下午17:50:15 
  6.  */  
  7.   
  8. import java.util.concurrent.ScheduledExecutorService;  
  9. import org.eclipse.paho.client.mqttv3.MqttClient;  
  10. import org.eclipse.paho.client.mqttv3.MqttConnectOptions;  
  11. import org.eclipse.paho.client.mqttv3.MqttException;  
  12. import org.eclipse.paho.client.mqttv3.MqttTopic;  
  13. import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;  
  14.   
  15. public class ClientMQTT {  
  16.   
  17.     public static final String HOST = "tcp://localhost:1883";  
  18.     public static final String TOPIC = "topic11";  
  19.     private static final String clientid = "client11";  
  20.     private MqttClient client;  
  21.     private MqttConnectOptions options;  
  22.     private String userName = "admin";  
  23.     private String passWord = "password";  
  24.   
  25.     private ScheduledExecutorService scheduler;  
  26.   
  27.     private void start() {  
  28.         try {  
  29.             // host为主机名,clientid即连接MQTT的客户端ID,一般以唯一标识符表示,MemoryPersistence设置clientid的保存形式,默认为以内存保存  
  30.             client = new MqttClient(HOST, clientid, new MemoryPersistence());  
  31.             // MQTT的连接设置  
  32.             options = new MqttConnectOptions();  
  33.             // 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,这里设置为true表示每次连接到服务器都以新的身份连接  
  34.             options.setCleanSession(true);  
  35.             // 设置连接的用户名  
  36.             options.setUserName(userName);  
  37.             // 设置连接的密码  
  38.             options.setPassword(passWord.toCharArray());  
  39.             // 设置超时时间 单位为秒  
  40.             options.setConnectionTimeout(10);  
  41.             // 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制  
  42.             options.setKeepAliveInterval(20);  
  43.             // 设置回调  
  44.             client.setCallback(new PushCallback());  
  45.             MqttTopic topic = client.getTopic(TOPIC);  
  46.             //setWill方法,如果项目中需要知道客户端是否掉线可以调用该方法。设置最终端口的通知消息  
  47.             options.setWill(topic, "close".getBytes(), 2true);  
  48.   
  49.             client.connect(options);  
  50.             //订阅消息  
  51.             int[] Qos  = {1};  
  52.             String[] topic1 = {TOPIC};  
  53.             client.subscribe(topic1, Qos);  
  54.   
  55.         } catch (Exception e) {  
  56.             e.printStackTrace();  
  57.         }  
  58.     }  
  59.   
  60.     public static void main(String[] args) throws MqttException {  
  61.         ClientMQTT client = new ClientMQTT();  
  62.         client.start();  
  63.     }  
  64. }  
PushCallback类:

[java] view plain copy
  1. /** 
  2.  * 
  3.  * Description: 
  4.  * @author admin 
  5.  * 2017年2月10日下午18:04:07 
  6.  */  
  7.   
  8. import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;  
  9. import org.eclipse.paho.client.mqttv3.MqttCallback;  
  10. import org.eclipse.paho.client.mqttv3.MqttMessage;  
  11.   
  12. /** 
  13.  * 发布消息的回调类 
  14.  * 
  15.  * 必须实现MqttCallback的接口并实现对应的相关接口方法CallBack 类将实现 MqttCallBack。 
  16.  * 每个客户机标识都需要一个回调实例。在此示例中,构造函数传递客户机标识以另存为实例数据。 
  17.  * 在回调中,将它用来标识已经启动了该回调的哪个实例。 
  18.  * 必须在回调类中实现三个方法: 
  19.  * 
  20.  *  public void messageArrived(MqttTopic topic, MqttMessage message)接收已经预订的发布。 
  21.  * 
  22.  *  public void connectionLost(Throwable cause)在断开连接时调用。 
  23.  * 
  24.  *  public void deliveryComplete(MqttDeliveryToken token)) 
  25.  *  接收到已经发布的 QoS 1 或 QoS 2 消息的传递令牌时调用。 
  26.  *  由 MqttClient.connect 激活此回调。 
  27.  * 
  28.  */  
  29. public class PushCallback implements MqttCallback {  
  30.   
  31.     public void connectionLost(Throwable cause) {  
  32.         // 连接丢失后,一般在这里面进行重连  
  33.         System.out.println("连接断开,可以做重连");  
  34.     }  
  35.   
  36.     public void deliveryComplete(IMqttDeliveryToken token) {  
  37.         System.out.println("deliveryComplete---------" + token.isComplete());  
  38.     }  
  39.   
  40.     public void messageArrived(String topic, MqttMessage message) throws Exception {  
  41.         // subscribe后得到的消息会执行到这里面  
  42.         System.out.println("接收消息主题 : " + topic);  
  43.         System.out.println("接收消息Qos : " + message.getQos());  
  44.         System.out.println("接收消息内容 : " + new String(message.getPayload()));  
  45.     }  
  46. }  

server类启动的结果:

[html] view plain copy
  1. deliveryComplete---------true  
  2. message is published completely! true  
  3. true------ratained状态  
client类启动的结果:

[html] view plain copy
  1. 接收消息主题 : topic11  
  2. 接收消息Qos : 1  
  3. 接收消息内容 : hello,topic11  


 


原创粉丝点击