MQTT 实践

497 阅读3分钟

实践目标

  1. Android 客户端使用
  2. Java 客户端使用

Android 客户端

代码仓库 github.com/eclipse/pah…

工程改造

  • Gradle 及 Grade Plugin 版本

  • build.gradle

    1. compile 变更为 implementation
    2. 精简依赖
    3. 如果Gradle版本>=7.0, 修改工程仓库配置方式
  • 主页面

    1. 采用 PahoExampleActivity.java 做为主页面

    2. 配置自己的MQTT信息

      //服务器地址
      final String serverUri="tcp://192.168.71.99:1883"; 
      //客户端名称
      String clientId = "ExampleAndroidClient";
      //订阅主题名称
      final String subscriptionTopic = "harvey";
      //发布主题名称
      final String publishTopic = "harvey";
      //发布消息内容
      final String publishMessage = "Message from MQTTHarvey's Android Client";
      复制代码
      
    3. 精简PahoExampleActivity.java布局
      仅保留history_recycler_view和fab

    4. 修改消息更新列表

    private void addToHistory(String mainText){
     System.out.println("LOG: " + mainText);
     mAdapter.add(mainText);
     mRecyclerView.post(new Runnable() {
        @Override
        public void run() {
        mAdapter.notifyDataSetChanged();
            mRecyclerView.getLayoutManager().scrollToPosition(mAdapter.getItemCount()-1);  
            Snackbar.make(findViewById(android.R.id.content), mainText, Snackbar.LENGTH_LONG)
                    .setAction("Action", null).show();           
        }
    });
    }   
    复制代码
    

Java 客户端

代码仓库github.com/eclipse/pah…

工程改造

  • 创建MQTT Java工程

    • 依赖 org.eclipse.paho.mqttv5 源码
    • 将mqttv5源码中的resource内容一并引入工程中
  • 创建测试包名 com.harvey.mqtt,添加如下两个文件(ExampleConnect.java, MQTTConfigue.java)

package com.harvey.mqtt;

import org.eclipse.paho.mqttv5.client.*;
import org.eclipse.paho.mqttv5.client.logging.HarveyDebug;
import org.eclipse.paho.mqttv5.client.persist.MemoryPersistence;
import org.eclipse.paho.mqttv5.common.MqttException;
import org.eclipse.paho.mqttv5.common.MqttMessage;
import org.eclipse.paho.mqttv5.common.MqttSubscription;
import org.eclipse.paho.mqttv5.common.packet.MqttProperties;

import java.nio.charset.StandardCharsets;

public class ExampleConnect {

    public static void main(String[] args) {
        //1. 测试连接
//        connect();
        //2. 测试连接及断开
//        connectAndDisconnect();
        //3. 测试连接及Ping
//        connectAndPing();
        //4. 测试订阅及解除订阅
        subscribeAndUnsubscribe();

    }

    /**
     * 连接MQTT服务器
     */
    private static void connect(){
        connect("");
    }

    private static void connect(String id){
        MemoryPersistence persistence = new MemoryPersistence();

        try {
            MqttConnectionOptions connOpts = new MqttConnectionOptions();
            connOpts.setKeepAliveInterval(0);
            MqttAsyncClient sampleClient = new MqttAsyncClient(MQTTConfigue.broker, MQTTConfigue.clientId+id, persistence);
            IMqttToken token = sampleClient.connect(connOpts);
            token.waitForCompletion();

        } 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();
        }
    }

    /**
     * 连接 -> 断开
     */
    private static void connectAndDisconnect(){
        MemoryPersistence persistence = new MemoryPersistence();

        try {
            MqttConnectionOptions connOpts = new MqttConnectionOptions();
            connOpts.setKeepAliveInterval(0);
            MqttAsyncClient sampleClient = new MqttAsyncClient(MQTTConfigue.broker, MQTTConfigue.clientId, persistence);
            IMqttToken token = sampleClient.connect(connOpts);
            token.waitForCompletion();

            sampleClient.disconnect();
            token.waitForCompletion();

        } 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();
        }
    }

    /**
     * 连接 -> ping
     */
    private static void connectAndPing(){
        MemoryPersistence persistence = new MemoryPersistence();

        try {
            MqttConnectionOptions connOpts = new MqttConnectionOptions();
            connOpts.setKeepAliveInterval(10);
            MqttAsyncClient sampleClient = new MqttAsyncClient(MQTTConfigue.broker, MQTTConfigue.clientId, persistence);
            IMqttToken token = sampleClient.connect(connOpts);
            token.waitForCompletion();

        } 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();
        }
    }

    /**
     *
     * 订阅主题 & 解除订阅
     *
     */
    private static void subscribeAndUnsubscribe(){
        MemoryPersistence persistence = new MemoryPersistence();

        try {
            MqttConnectionOptions connOpts = new MqttConnectionOptions();
            connOpts.setKeepAliveInterval(0);
            MqttAsyncClient sampleClient = new MqttAsyncClient(MQTTConfigue.broker, MQTTConfigue.clientId, persistence);
            IMqttToken token = sampleClient.connect(connOpts);
            token.waitForCompletion();

            MqttSubscription subscription2 = new MqttSubscription(MQTTConfigue.topic+"C");
            MqttSubscription subscription = new MqttSubscription(MQTTConfigue.topic);
            MqttProperties mqttProperties = new MqttProperties();
            mqttProperties.setPayloadFormat(true);
            sampleClient.subscribe(new MqttSubscription[]{subscription}, null, new MqttActionListener() {
                @Override
                public void onSuccess(IMqttToken asyncActionToken) {
                    try {
                        if(asyncActionToken.getTopics()[0].equals(MQTTConfigue.topic))
                          sampleClient.unsubscribe(MQTTConfigue.topic);

                        for (String topic : asyncActionToken.getTopics()){
                            if(topic.equals(MQTTConfigue.topic)){
                                sampleClient.unsubscribe(MQTTConfigue.topic);
                            } else if(topic.equals(MQTTConfigue.topic+"C")){
                                sampleClient.unsubscribe(MQTTConfigue.topic+"C");
                            }
                        }
                    } catch (MqttException e) {
                        e.printStackTrace();
                    }

                }

                @Override
                public void onFailure(IMqttToken asyncActionToken, Throwable exception) {

                }
            }, new IMqttMessageListener() {
                @Override
                public void messageArrived(String topic, MqttMessage message) throws Exception {
                    System.out.println(new String(message.getPayload(), StandardCharsets.UTF_8));
                }
            }, mqttProperties);


        } 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();
        }
    }

}
  • 在包 org.eclipse.paho.mqttv5.client.logging 下创建日志打印文件
package org.eclipse.paho.mqttv5.client.logging;

import java.text.SimpleDateFormat;
import java.util.Date;

public class HarveyDebug {

    public static void d(String info){
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");//设置日期格式
        String date = df.format(new Date());// new Date()为获取当前系统时间,也可使用当前时间戳
        System.out.println(date + " " + info);
    }

    public static void d(){
        System.out.println("");
    }

}
package com.harvey.mqtt;

public class MQTTConfigue {

    public static String topic        = "harvey";
    public static String content      = "Message from MQTTHarvey'InteIIiJ IDEA ";
    public static int qos             = 2;
    public static String broker       = "tcp://127.0.0.1:1883";
    public static String clientId     = "InteIIiJ IDEA Client ";

}
  • 修改原文件

public class MqttInputStream extends InputStream {
......
public MqttWireMessage readMqttWireMessage() throws IOException, MqttException {
      final String methodName ="readMqttWireMessage";
      
      MqttWireMessage message = null;
      try {
         // read header
         if (remLen < 0) {
            // Assume we can read the whole header at once.
            // The header is very small so it's likely we
            // are able to read it fully or not at all.
            // This keeps the parser lean since we don't
            // need to cope with a partial header.
            // Should we lose synch with the stream,
            // the keepalive mechanism would kick in
            // closing the connection.
            bais.reset();
            
            byte first = in.readByte();
            clientState.notifyReceivedBytes(1);

            byte type = (byte) ((first >>> 4) & 0x0F);
            if ((type < MqttWireMessage.MESSAGE_TYPE_CONNECT) ||
                  (type > MqttWireMessage.MESSAGE_TYPE_AUTH)) {
               // Invalid MQTT message type...
               throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_INVALID_MESSAGE);
            }

            //TODO HARVEY
            final String[] PACKET_NAMES = { "reserved", "CONNECT", "CONNACK", "PUBLISH", "PUBACK", "PUBREC",
                  "PUBREL", "PUBCOMP", "SUBSCRIBE", "SUBACK", "UNSUBSCRIBE", "UNSUBACK", "PINGREQ", "PINGRESP", "DISCONNECT",
                  "AUTH" };
            HarveyDebug.d();
            HarveyDebug.d("Receive 包类型:" + PACKET_NAMES[type]);

            byte reserved = (byte) (first & 0x0F);
            MqttWireMessage.validateReservedBits(type, reserved);
            
            remLen = MqttDataTypes.readVariableByteInteger(in).getValue();
            bais.write(first);
            bais.write(MqttWireMessage.encodeVariableByteInteger((int)remLen));
            packet = new byte[(int)(bais.size()+remLen)];
            if(this.clientState.getIncomingMaximumPacketSize() != null && 
                  bais.size()+remLen > this.clientState.getIncomingMaximumPacketSize() ) {
               // Incoming packet is too large
               throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_INCOMING_PACKET_TOO_LARGE);
            }
            packetLen = 0;
         }
         
         // read remaining packet
         if (remLen >= 0) {
            // the remaining packet can be read with timeouts
            readFully();

            // reset packet parsing state 
            remLen = -1;
            
            byte[] header = bais.toByteArray();
            System.arraycopy(header,0,packet,0, header.length);
            message = MqttWireMessage.createWireMessage(packet);

            //TODO HARVEY
            byte [] hs = message.getHeader();
            StringBuilder sb = new StringBuilder();
            for(byte b : hs){
               sb.append(Tools.toBinaryString(b&0xff)).append(" ");
            }
            HarveyDebug.d("Receive Message Header(二进制内容) : " + sb.toString());
            HarveyDebug.d("Receive Message Header(字符串内容) : " + new String(hs, "UTF-8"));

            byte [] ps = message.getPayload();
            StringBuilder sb2 = new StringBuilder();
            for(byte b : ps){
               sb2.append(Tools.toBinaryString(b&0xff)).append(" ");
            }
            HarveyDebug.d("Receive Message payload(二进制内容) : " + sb2.toString());
            HarveyDebug.d("Receive Message payload(字符串内容) : " + new String(ps, "UTF-8"));

            // @TRACE 530= Received {0} 
            log.fine(CLASS_NAME, methodName, "530",new Object[] {message});
         }
      } catch (SocketTimeoutException e) {
         // ignore socket read timeout
         //TODO HARVEY
//       System.out.println(e.getMessage());
      }
      
      return message;
   }
......
}
package com.harvey.mqtt;

public class MQTTConfigue {

    public static String topic        = "harvey";
    public static String content      = "Message from MQTTHarvey'InteIIiJ IDEA ";
    public static int qos             = 2;
    public static String broker       = "tcp://127.0.0.1:1883";
    public static String clientId     = "InteIIiJ IDEA Client ";

}

public class MqttOutputStream extends OutputStream {
......
public void write(MqttWireMessage message) throws IOException, MqttException {
   final String methodName = "write";
   byte[] bytes = message.getHeader();
   byte[] pl = message.getPayload();
   if(this.clientState.getOutgoingMaximumPacketSize() != null && 
         bytes.length+pl.length > this.clientState.getOutgoingMaximumPacketSize() ) {
      // Outgoing packet is too large
      throw ExceptionHelper.createMqttException(MqttClientException.REASON_CODE_OUTGOING_PACKET_TOO_LARGE);
   }

   //调试日志S HARVEY
   final String[] PACKET_NAMES = { "reserved", "CONNECT", "CONNACK", "PUBLISH", "PUBACK", "PUBREC",
         "PUBREL", "PUBCOMP", "SUBSCRIBE", "SUBACK", "UNSUBSCRIBE", "UNSUBACK", "PINGREQ", "PINGRESP", "DISCONNECT",
         "AUTH" };
   byte type = (byte) ((bytes[0] >>> 4) & 0x0F);
   HarveyDebug.d();
   HarveyDebug.d("Send 包类型:" + PACKET_NAMES[type]);

   StringBuffer sb = new StringBuffer();
   for(byte b : bytes){
      sb.append(Tools.toBinaryString(b&0xff)).append(" ");
   }
   HarveyDebug.d("Send Message Header(二进制内容) : " + sb.toString());
   HarveyDebug.d("Send Message Header(字符串内容) : " + new String(bytes, "UTF-8"));
       //调试日志E

   out.write(bytes,0,bytes.length);
   clientState.notifySentBytes(bytes.length);
   
       int offset = 0;
       int chunckSize = 1024;
       while (offset < pl.length) {
           int length = Math.min(chunckSize, pl.length - offset);
           out.write(pl, offset, length);
           offset += chunckSize;
           clientState.notifySentBytes(length);
       }
   StringBuilder sb2 = new StringBuilder();
   for(byte b : pl){
      sb2.append(Tools.toBinaryString(b&0xff)).append(" ");
   }
   HarveyDebug.d("Send Message payload(二进制内容) : " + sb2.toString());
   HarveyDebug.d("Send Message payload(字符串内容) : 内容长度=" + pl.length + ",内容=" + new String(pl, "UTF-8"));

   // @TRACE 529= sent {0}
       log.fine(CLASS_NAME, methodName, "529", new Object[]{message});
}
......
}

实践代码仓库

github.com/harveyblack…