MQTT簡析

由于部門做的設(shè)備需要使用到MQTT,在此總結(jié)下。

基本介紹

MQTT(Message Queue Telemetry Transport),遙測傳輸協(xié)議,提供訂閱/發(fā)布模式,更為簡約、輕量,易于使用,針對受限環(huán)境(帶寬低、網(wǎng)絡(luò)延遲高、網(wǎng)絡(luò)通信不穩(wěn)定),可以簡單概括為物聯(lián)網(wǎng)打造,官方總結(jié)特點(diǎn)如下:

  1. 使用發(fā)布/訂閱消息模式,提供一對多的消息發(fā)布,解除應(yīng)用程序耦合。
  1. 對負(fù)載內(nèi)容屏蔽的消息傳輸。
  2. 使用 TCP/IP 提供網(wǎng)絡(luò)連接。
  3. 有三種消息發(fā)布服務(wù)質(zhì)量:
    “至多一次”,消息發(fā)布完全依賴底層 TCP/IP 網(wǎng)絡(luò)。會發(fā)生消息丟失或重復(fù)。這一級別可用于如下情況,環(huán)境傳感器數(shù)據(jù),丟失一次讀記錄無所謂,因?yàn)椴痪煤筮€會有第二次發(fā)送。
    “至少一次”,確保消息到達(dá),但消息重復(fù)可能會發(fā)生。
    “只有一次”,確保消息到達(dá)一次。這一級別可用于如下情況,在計(jì)費(fèi)系統(tǒng)中,消息重復(fù)或丟失會導(dǎo)致不正確的結(jié)果。
  4. 小型傳輸,開銷很小(固定長度的頭部是 2 字節(jié)),協(xié)議交換最小化,以降低網(wǎng)絡(luò)流量。
  5. 使用 Last Will 和 Testament 特性通知有關(guān)各方客戶端異常中斷的機(jī)制。

XMPP和MQTT對比

XMPP
優(yōu)點(diǎn):

  • 要涉及到上層業(yè)務(wù),設(shè)計(jì)到用戶分群分組,用戶層次關(guān)系的維護(hù),xmpp已經(jīng)為你做了很多很多,后期的開發(fā)會很省心;
  • 協(xié)議成熟、強(qiáng)大、可擴(kuò)展性強(qiáng)、目前主要應(yīng)用于許多聊天系統(tǒng)中,且已有開源的Java版的開發(fā)實(shí)例androidpn;
  • 協(xié)議成熟,強(qiáng)大,可擴(kuò)展性強(qiáng),并且有成熟的開源方案。
  • 分布式:任何人都可以運(yùn)行自己的XMPP服務(wù)器,它沒有主服務(wù)器
  • 安全性高:使用TLS等技術(shù)
  • 跨平臺

缺點(diǎn):

  • 協(xié)議雖然完整擴(kuò)展性雖然好,它耗費(fèi)網(wǎng)絡(luò)流量很大,交互此說太多,跑起來比MQTT慢很多;另外有高達(dá)70%的流量是耗費(fèi)在XMPP本身的標(biāo)簽和編解碼上面。
  • 協(xié)議較復(fù)雜、冗余(基于XML)、費(fèi)流量、費(fèi)電,部署硬件成本高;

MQTT
優(yōu)點(diǎn):

  • 專門針對移動互聯(lián)網(wǎng)開發(fā)的輕量級傳輸協(xié)議,二進(jìn)制、協(xié)議簡潔、小巧、可擴(kuò)展性強(qiáng)、省流量、省電,適合做大量節(jié)點(diǎn)弱網(wǎng)絡(luò)差的場景,非常適合現(xiàn)在移動互聯(lián)網(wǎng)的基礎(chǔ)設(shè)施
  • 開源的協(xié)議和實(shí)現(xiàn),擴(kuò)展方便且輕量級;
  • MQTT代碼量也比XMPP小很多,使用容易。
  • 支持的設(shè)備從智能硬件到智能手機(jī)無所不包。

MQTT快速示例

市面上有相當(dāng)多的高質(zhì)量MQTT代理,其中mosquitto是一個(gè)開源的輕量級的C實(shí)現(xiàn),完全兼容了MQTT 3.1和MQTT 3.1.1。下面我們就以mosquitto為例演示一下MQTT的使用。

  • 安裝mosquitto以及搭配的客戶端:
apt-get install mosquitto
apt-get install mosquitto-clients
  • 訂閱一個(gè)主題:
mosquitto_sub -d -t baidu/chatroom
Received CONNACK
Received SUBACK
Subscribed (mid: 1): 0
  • 另外打開一個(gè)SSH連接然后在這個(gè)主題里面打個(gè)招呼:
mosquitto_pub -d -t baidu/chatroom -m "Hello World"
Received CONNACK
Sending PUBLISH (d0, q0, r0, m1, 'baidu/chatroom', ... (11 bytes))
  • 此時(shí)回到第一個(gè)SSH客戶端可以看到信息已經(jīng)接收到了,之后便是心跳消息:
Received PUBLISH (d0, q0, r0, m0, 'baidu/chatroom', ... (11 bytes))
Hello World
Sending PINGREQ
Received PINGRESP
  • 需要注意的是mosquitto客戶端默認(rèn)使用QoS 0,下面我們使用QoS 2訂閱這個(gè)主題:
mosquitto_sub -d -q 2 -t baidu/chatroom
Received CONNACK
Received SUBACK
Subscribed (mid: 1): 2
  • 切換到另外SSH連接然后在這個(gè)主題里面打個(gè)招呼:
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)
  • 此時(shí)回到第一個(gè)SSH客戶端可以看到信息已經(jīng)接收到了,以及相應(yīng)的多次握手消息:
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客戶端實(shí)現(xiàn)

使用開源項(xiàng)目https://www.eclipse.org/paho/提供的MQTT服務(wù)端tcp://iot.eclipse.org:1883,進(jìn)行如下實(shí)驗(yàn):

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";
          //消息內(nèi)容
          String content      = "XX發(fā)布了消息";
          //消息發(fā)送的模式   選擇消息發(fā)送的次數(shù),依據(jù)不同的使用環(huán)境使用不同的模式
          int qos             = 2;
          //服務(wù)器地址
          String broker       = "tcp://iot.eclipse.org:1883";
          //客戶端的唯一標(biāo)識
          String clientId     = "CLIENTID JavaSample";
          //消息緩存的方式  內(nèi)存緩存
          MemoryPersistence persistence = new MemoryPersistence();

          try {
              //創(chuàng)建以惡搞MQTT客戶端
              MqttClient sampleClient = new MqttClient(broker, clientId, persistence);
              //消息的配置參數(shù)
              MqttConnectOptions connOpts = new MqttConnectOptions();
              //不記憶上一次會話
              connOpts.setCleanSession(true);
              System.out.println("Connecting to broker: "+broker);
              //鏈接服務(wù)器
              sampleClient.connect(connOpts);
              System.out.println("Connected");
              System.out.println("Publishing message: "+content);
              //創(chuàng)建消息
              MqttMessage message = new MqttMessage(content.getBytes());
              //給消息設(shè)置發(fā)送的模式
              message.setQos(qos);
              //發(fā)布消息到服務(wù)器
              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

服務(wù)端添加SSL加密

這里采用了Moqutte推薦的SSL加密方式(http://andsel.github.io/moquette/),屬于SSL加密中的單向加密。

  1. 生成服務(wù)器的keystore
    執(zhí)行命令:
$JAVA_HOME/bin/keytool -keystore serverkeystore.jks -alias testserver -genkey -keyalg RSA

過程中提示輸入名字時(shí)(CN),必須填寫服務(wù)器的域名,本地調(diào)試時(shí)可填寫localhost。
然后修改工程里的 /config/moquette.conf 中的jks_path 對應(yīng)值為serverkeystore.jks 的路徑,把同時(shí)serverkeystore.jks復(fù)制到工程根目錄下。

  1. 生成客戶端的keystore
    1)導(dǎo)出服務(wù)器keystore的證書
    執(zhí)行命令:
$JAVA_HOME/bin/keytool -export -alias testserver -keystore serverkeystore.jks -file testserver.crt

2)生成客戶端的keystore
執(zhí)行命令:

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

3)向客戶端的keystore 導(dǎo)入服務(wù)器keystore的證書,使客戶端信任證書。
執(zhí)行命令:

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

4)客戶端啟動時(shí)加載clientkeystore.jks然后再與服務(wù)器的SSL端口進(jìn)行連接即可。
客戶端代碼參考:sslSimplePublisher.groovy

客戶端添加ssl加密

  1. 生成 .bks文件:
    a、根據(jù)上一節(jié),拿到服務(wù)器生成的 .jks證書,
    b、到官網(wǎng)下載 bcprov-ext-jdk15on-146.jar ,將該文件放到j(luò)dk1.6.0_03\jre\lib\ext目錄下.
    c、配置bcprov
    在 jdk_home\jre\lib\security\目錄中找到 java.security 在內(nèi)容增加一行(數(shù)字可以自己定義)
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());                           //該字符串應(yīng)隨機(jī)生成,保證每次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抓包驗(yàn)證,加密成功!

參考文章

MQTT協(xié)議筆記之頭部信息
MQTT快速入門

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,001評論 1 370
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內(nèi)容