目标
深入了解MQTT java版本代码库逻辑-“发布主题”
概况
图表中的橙色部分位java 方法名
测试代码
/**
*
* 发布主题内容
*
*/
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数组,因此,不需要重新拼装