MQTT-java 代码解析-PUBLISH

256 阅读1分钟

目标

深入了解MQTT java版本代码库逻辑-“发布主题”

概况

图表中的橙色部分位java 方法名 MQTT PUBLISH代码调用映射关系图.jpg

测试代码

  /**
     *
     * 发布主题内容
     *
     */
    private static void publish(){
        MemoryPersistence persistence = new MemoryPersistence();

        try {
            MqttConnectionOptions connOpts = new MqttConnectionOptions();
            connOpts.setCleanStart(true);
            connOpts.setKeepAliveInterval(30);
            connOpts.setSessionExpiryInterval(60L);
            connOpts.setMaximumPacketSize(1000L);
            connOpts.setTopicAliasMaximum(30);
            connOpts.setReceiveMaximum(1000);
            connOpts.setAutomaticReconnect(true);
            connOpts.setAutomaticReconnectDelay(5, 15);
            connOpts.setMaxReconnectDelay(100);
            connOpts.setRequestProblemInfo(true);
            connOpts.setSendReasonMessages(true);

            MqttMessage msg = new MqttMessage();
            msg.setQos(2);
            msg.setDuplicate(true);
            msg.setRetained(true);
            connOpts.setWill(MQTTConfigue.topic, msg);
            MqttAsyncClient sampleClient = new MqttAsyncClient(MQTTConfigue.broker, MQTTConfigue.clientId, persistence);
            IMqttToken token = sampleClient.connect(connOpts);
            token.waitForCompletion();

            MqttMessage message = new MqttMessage();
            message.setQos(2);
            message.setDuplicate(true);
            message.setRetained(true);
            message.setId(9);

            MqttProperties mqttProperties = new MqttProperties();
            mqttProperties.setCorrelationData("HW".getBytes(StandardCharsets.UTF_8));
            mqttProperties.setTopicAlias(9);
            mqttProperties.setSessionExpiryInterval(50L);
            mqttProperties.setContentType("TEST_PUBLISH");
            mqttProperties.setPayloadFormat(true);
            mqttProperties.setResponseTopic("quick");
            mqttProperties.setMessageExpiryInterval(60*60L);

            List<UserProperty> userPropertyList = new ArrayList<>();
            userPropertyList.add(new UserProperty("first", "firstValue"));
            userPropertyList.add(new UserProperty("second", "secondValue"));
            mqttProperties.setUserProperties(userPropertyList);
            message.setProperties(mqttProperties);

            message.setPayload("ShangHai".getBytes(StandardCharsets.UTF_8));

            sampleClient.publish(MQTTConfigue.topic, message);

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

主题发布流程

创建MQTT消息, 设定头部信息和Payload

//创建MQTT包对象
MqttMessage message = new MqttMessage();
//设置Header信息
message.setQos(2);
message.setDuplicate(true);
message.setRetained(true);
message.setId(9);
......
//添加属性
MqttProperties mqttProperties = new MqttProperties();
mqttProperties.setCorrelationData("HW".getBytes(StandardCharsets.UTF_8));
......
message.setProperties(mqttProperties);
//设置payload
message.setPayload("ShangHai".getBytes(StandardCharsets.UTF_8));

创建PUBLISH消息对象

public MqttPublish(String topic, MqttMessage message, MqttProperties properties) {
   //固定头 :类型 : bit7,bit6,bit5,bit4
   super(MqttWireMessage.MESSAGE_TYPE_PUBLISH);
   //主题
   this.topicName = topic;
   this.payload = message.getPayload();
   this.qos = message.getQos();
   this.dup = message.isDuplicate();
   this.retained = message.isRetained();
   if (properties != null) {
      this.properties = properties;
   } else {
      this.properties = new MqttProperties();
   }
   this.properties.setValidProperties(validProperties);
}

数据拼装

数据拼装的核心为消息头编码,涉及到多种数据类型编码
4字节编码: MqttDataTypes.writeUnsignedFourByteInt()
UTF-8编码: MqttDataTypes.encodeUTF8()
可变字节编码: MqttDataTypes.encodeVariableByteInteger()

Header整体拼装

/**
 * Returns a byte array containing the MQTT header for the message.
 * 
 * @return The MQTT Message Header
 * @throws MqttException
 *             if there was an issue encoding the header
 */
public byte[] getHeader() throws MqttException {
   try {
      // 根据外部传入参数拼装第一个字节
      int first = ((getType() & 0x0f) << 4) ^ (getMessageInfo() & 0x0f);
      byte[] varHeader = getVariableHeader();
      int remLen = varHeader.length + getPayload().length;

      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      DataOutputStream dos = new DataOutputStream(baos);
      //传输固定头第一个字节
      dos.writeByte(first);
      //传输固定头第二个字节(剩余长度)
      dos.write(encodeVariableByteInteger(remLen));
      //传输可变头字节
      dos.write(varHeader);
      dos.flush();
      return baos.toByteArray();
   } catch (IOException ioe) {
      throw new MqttException(ioe);
   }
}

可变头字节整体拼装

备注:writeShort(msgId)对应的是“3.3.2.2 Packet Identifier”规范

@Override
protected byte[] getVariableHeader() throws MqttException {
   try {
      ByteArrayOutputStream baos = new ByteArrayOutputStream();
      DataOutputStream dos = new DataOutputStream(baos);

      //一. 主题名称
      // If we are using a Topic Alias, then the topic should be empty
      if (topicName != null) {
         MqttDataTypes.encodeUTF8(dos, topicName);
      } else {
         MqttDataTypes.encodeUTF8(dos, "");
      }

      //二. 数据包标识
      if (this.qos > 0) {
         dos.writeShort(msgId);
      }
      
      //三. 属性长度 + 属性内容
      // Write Identifier / Value Fields
      byte[] identifierValueFieldsArray = this.properties.encodeProperties();
      dos.write(identifierValueFieldsArray);
      dos.flush();
      return baos.toByteArray();
   } catch (IOException ioe) {
      throw new MqttException(ioe);
   }
}

属性编码

 * Encodes Non-Null Properties that are in the list of valid properties into a
 * byte array.
 * 
 * @return a byte array containing encoded properties.
 * @throws MqttException
 *             if an exception occurs whilst encoding the properties.
 */
public byte[] encodeProperties() throws MqttException {
    try {
       ByteArrayOutputStream baos = new ByteArrayOutputStream();
       DataOutputStream outputStream = new DataOutputStream(baos);
       ......
       
        // Server Keep Alive
        if (serverKeepAlive != null && validProperties.contains(SERVER_KEEP_ALIVE_IDENTIFIER)) {
           outputStream.write(SERVER_KEEP_ALIVE_IDENTIFIER);
           outputStream.writeShort(serverKeepAlive);
        }

       ......
       int length = outputStream.size();
       outputStream.flush();
       ByteArrayOutputStream finalOutput = new ByteArrayOutputStream();
       finalOutput.write(MqttDataTypes.encodeVariableByteInteger(length));
       finalOutput.write(baos.toByteArray());

       return finalOutput.toByteArray();
    } catch (IOException ioe) {
       throw new MqttException(ioe);
    }

}

Payload拼装

payload在MqttMesage中已经是转换好byte数组,因此,不需要重新拼装