MQTT简析

由于部门做的设备需要使用到MQTT,在此总结下。

基本介绍

MQTT(Message Queue Telemetry Transport),遥测传输协议,提供订阅/发布模式,更为简约、轻量,易于使用,针对受限环境(带宽低、网络延迟高、网络通信不稳定),可以简单概括为物联网打造,官方总结特点如下:

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

XMPP和MQTT对比

XMPP
优点:

  • 要涉及到上层业务,设计到用户分群分组,用户层次关系的维护,xmpp已经为你做了很多很多,后期的开发会很省心;
  • 协议成熟、强大、可扩展性强、目前主要应用于许多聊天系统中,且已有开源的Java版的开发实例androidpn;
  • 协议成熟,强大,可扩展性强,并且有成熟的开源方案。
  • 分布式:任何人都可以运行自己的XMPP服务器,它没有主服务器
  • 安全性高:使用TLS等技术
  • 跨平台

缺点:

  • 协议虽然完整扩展性虽然好,它耗费网络流量很大,交互此说太多,跑起来比MQTT慢很多;另外有高达70%的流量是耗费在XMPP本身的标签和编解码上面。
  • 协议较复杂、冗余(基于XML)、费流量、费电,部署硬件成本高;

MQTT
优点:

  • 专门针对移动互联网开发的轻量级传输协议,二进制、协议简洁、小巧、可扩展性强、省流量、省电,适合做大量节点弱网络差的场景,非常适合现在移动互联网的基础设施
  • 开源的协议和实现,扩展方便且轻量级;
  • MQTT代码量也比XMPP小很多,使用容易。
  • 支持的设备从智能硬件到智能手机无所不包。

MQTT快速示例

市面上有相当多的高质量MQTT代理,其中mosquitto是一个开源的轻量级的C实现,完全兼容了MQTT 3.1和MQTT 3.1.1。下面我们就以mosquitto为例演示一下MQTT的使用。

  • 安装mosquitto以及搭配的客户端:
apt-get install mosquitto
apt-get install mosquitto-clients
  • 订阅一个主题:
mosquitto_sub -d -t baidu/chatroom
Received CONNACK
Received SUBACK
Subscribed (mid: 1): 0
  • 另外打开一个SSH连接然后在这个主题里面打个招呼:
mosquitto_pub -d -t baidu/chatroom -m "Hello World"
Received CONNACK
Sending PUBLISH (d0, q0, r0, m1, 'baidu/chatroom', ... (11 bytes))
  • 此时回到第一个SSH客户端可以看到信息已经接收到了,之后便是心跳消息:
Received PUBLISH (d0, q0, r0, m0, 'baidu/chatroom', ... (11 bytes))
Hello World
Sending PINGREQ
Received PINGRESP
  • 需要注意的是mosquitto客户端默认使用QoS 0,下面我们使用QoS 2订阅这个主题:
mosquitto_sub -d -q 2 -t baidu/chatroom
Received CONNACK
Received SUBACK
Subscribed (mid: 1): 2
  • 切换到另外SSH连接然后在这个主题里面打个招呼:
mosquitto_pub -d -q 2 -t baidu/chatroom -m "Hello World"
Received CONNACK
Sending PUBLISH (d0, q2, r0, m1, 'baidu/chatroom', ... (11 bytes))
Received PUBREC (Mid: 1)
Sending PUBREL (Mid: 1)
Received PUBCOMP (Mid: 1)
  • 此时回到第一个SSH客户端可以看到信息已经接收到了,以及相应的多次握手消息:
Received PUBLISH (d0, q2, r0, m1, 'baidu/chatroom', ... (11 bytes))
Sending PUBREC (Mid: 1)
Received PUBREL (Mid: 1)
Hello World
Sending PUBCOMP (Mid: 1)

MQTT Java客户端实现

使用开源项目https://www.eclipse.org/paho/提供的MQTT服务端tcp://iot.eclipse.org:1883,进行如下实验:

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.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;


/**
 *@Description:
 *@author lx
 *@date 2017-1-12 下午1:19:42
 */
public class TestMQTT {

    public static void main(String args[]){

        //消息的类型
          String topic        = "TOPIC MQTT Examples";
          //消息内容
          String content      = "XX发布了消息";
          //消息发送的模式   选择消息发送的次数,依据不同的使用环境使用不同的模式
          int qos             = 2;
          //服务器地址
          String broker       = "tcp://iot.eclipse.org:1883";
          //客户端的唯一标识
          String clientId     = "CLIENTID JavaSample";
          //消息缓存的方式  内存缓存
          MemoryPersistence persistence = new MemoryPersistence();

          try {
              //创建以恶搞MQTT客户端
              MqttClient sampleClient = new MqttClient(broker, clientId, persistence);
              //消息的配置参数
              MqttConnectOptions connOpts = new MqttConnectOptions();
              //不记忆上一次会话
              connOpts.setCleanSession(true);
              System.out.println("Connecting to broker: "+broker);
              //链接服务器
              sampleClient.connect(connOpts);
              System.out.println("Connected");
              System.out.println("Publishing message: "+content);
              //创建消息
              MqttMessage message = new MqttMessage(content.getBytes());
              //给消息设置发送的模式
              message.setQos(qos);
              //发布消息到服务器
              sampleClient.publish(topic, message);
              System.out.println("Message published");
              //断开链接
              sampleClient.disconnect();
              System.out.println("Disconnected");
              System.exit(0);
          } catch(MqttException me) {
              System.out.println("reason "+me.getReasonCode());
              System.out.println("msg "+me.getMessage());
              System.out.println("loc "+me.getLocalizedMessage());
              System.out.println("cause "+me.getCause());
              System.out.println("excep "+me);
              me.printStackTrace();
          }

    }
}

参考https://github.com/eclipse/paho.mqtt.android

服务端添加SSL加密

这里采用了Moqutte推荐的SSL加密方式(http://andsel.github.io/moquette/),属于SSL加密中的单向加密。

  1. 生成服务器的keystore
    执行命令:
$JAVA_HOME/bin/keytool -keystore serverkeystore.jks -alias testserver -genkey -keyalg RSA

过程中提示输入名字时(CN),必须填写服务器的域名,本地调试时可填写localhost。
然后修改工程里的 /config/moquette.conf 中的jks_path 对应值为serverkeystore.jks 的路径,把同时serverkeystore.jks复制到工程根目录下。

  1. 生成客户端的keystore
    1)导出服务器keystore的证书
    执行命令:
$JAVA_HOME/bin/keytool -export -alias testserver -keystore serverkeystore.jks -file testserver.crt

2)生成客户端的keystore
执行命令:

$JAVA_HOME/bin/keytool -keystore clientkeystore.jks -genkey -keyalg RSA

3)向客户端的keystore 导入服务器keystore的证书,使客户端信任证书。
执行命令:

$JAVA_HOME/bin/keytool -keystore clientkeystore.jks -import -alias testserver -file testserver.crt -trustcacerts

4)客户端启动时加载clientkeystore.jks然后再与服务器的SSL端口进行连接即可。
客户端代码参考:sslSimplePublisher.groovy

客户端添加ssl加密

  1. 生成 .bks文件:
    a、根据上一节,拿到服务器生成的 .jks证书,
    b、到官网下载 bcprov-ext-jdk15on-146.jar ,将该文件放到jdk1.6.0_03\jre\lib\ext目录下.
    c、配置bcprov
    在 jdk_home\jre\lib\security\目录中找到 java.security 在内容增加一行(数字可以自己定义)
security.provider.11=org.bouncycastle.jce.provider.BouncyCastleProvider

d、生成android平台的证书:

keytool -exportcert -alias testserver -file test.cert -keystore  local_clientkeystore.jks
keytool -importcert -keystore test.bks -file test.cert -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider
  1. 在Android工程中加载证书:
    a、生成SSLSocketFactory对象
public class SslUtil {
    public static SSLSocketFactory createSocketFactory(Context context) {
        SSLContext sslContext;
        try {
            KeyStore ks = KeyStore.getInstance("BKS");
            ks.load(context.getResources().openRawResource(R.raw.peer),
                    "123456".toCharArray());                           //该字符串应随机生成,保证每次session唯一;
            KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            //    kmf.init(ks, "passw0rd".toCharArray());
            kmf.init(ks, "123456".toCharArray());
            TrustManagerFactory tmf = TrustManagerFactory
                    .getInstance("X509");
            tmf.init(ks);
            TrustManager[] tm = tmf.getTrustManagers();
            sslContext = SSLContext.getInstance("TLS");

            sslContext.init(kmf.getKeyManagers(), tm, null);
            // SocketFactory factory= SSLSocketFactory.getDefault();

            // Socket socket =factory.createSocket("localhost", 10000);
            SSLSocketFactory ssf = sslContext.getSocketFactory();
            return  ssf;
        }catch (Exception e){
            e.printStackTrace();
            return  null;
        }
    }
}

b、在MqttOptions中添加SSL配置:

mOptions = new MqttConnectOptions();
mOptions.setCleanSession(true);
SSLSocketFactory socketFactory = SslUtil.createSocketFactory(getApplicationContext());
if (socketFactory != null) {
    LogUtil.d("socketFactory is not null");
    mOptions.setSocketFactory(socketFactory);
}
  1. 修改url地址前缀:
    修改url地址由:
    tcp://yoursite.com:8402

    ssl://yoursite.com:2883
  2. 使用wireshark抓包验证,加密成功!

参考文章

MQTT协议笔记之头部信息
MQTT快速入门

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容