当连接向一个mqtt服务器时,clientId必须是唯一的。设置一样,导致client.setCallback总是走到 connectionLost回调。报connection reset。调查一天才发现是clientid重复导致。

client = new MqttAsyncClient(serverURIString, "client-id");


clientId是用来保存会话信息。

MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(false);

当服务器将message发布向所有订阅过的客户端,就会清除这个message。如果当前客户端不在线,等它连接时发送。

session与client之间的关系是怎样的?

这样的,比如你一个板子,作为客户端,发起mqtt的连接请求connect到mqtt服务器,比如说就是emqtt服务吧,emqtt服务端收到这个板子的连接请求之后,在tcp层上会和板子建立一个tcp的连接,在emqtt内部,会产生一个进程,和这个板子做数据通讯,同时还会产生一个进程,叫session,这个sessoin是专门管理这个板子订阅的主题,其它板子如果发布了这个板子感兴趣的主题的时候,也会发到这个板子对应的这个session里面,如果这个session收到订阅的主题之后,发现对用的client还活着,就通过这个client把数据经过tcp发到这个板子上,如果发现client已经没有了,就是说板子和服务端断掉了,那么session就会把收到的订阅的主题,先保存在session里面,下次板子连接上了,而且cleansession=false,那么这个session就不会清除,在这次连接时,就会把以前收到的订阅消息,发给板子,大概就是这个意思。


参考:

http://www.blogjava.net/yongboy/archive/2014/02/15/409893.html

http://www.cnblogs.com/znlgis/p/4930990.html

http://blog.csdn.net/ljf10010/article/details/51424506

paho客户端示例

https://github.com/eclipse/paho.mqtt.java/tree/master/org.eclipse.paho.client.mqttv3.test/src/test/java/org/eclipse/paho/client/mqttv3/test

http://www.eclipse.org/paho/files/javadoc/index.html api文档

ibm客户端paho示例:

http://www.programcreek.com/java-api-examples/index.php?source_dir=streamsx.messaging-master/com.ibm.streamsx.messaging/impl/java/src/com/ibm/streamsx/messaging/mqtt/MqttAsyncClientWrapper.java

hivemq的mqtt详解:http://www.hivemq.com/blog/mqtt-essentials-part-8-retained-messages

 http://blog.csdn.net/wuyinxian/article/details/38826259

MQTT Protocol Manual(Apollo中MQTT协议解析)

 
 
 

 

MQTT协议

Apollo允许客户端通过开放的MQTT协议连接。该协议主要是用在资源有限的驱动上,以及网络不稳定的情况下使用,是一个订阅、发布模型。这种驱动通常不适用类似http,stomp这类基于文本,或者类似openfire,AMQP等传统二进制协议。MQTT是一个简介的二进制协议,适用这类驱动资源受限,而且是不稳定的网络条件下。

之前的稳定发布版本中,MQTT是作为一个Apollo的一个插件提供的。但是现在,这个插件已经变为开发项目的一部分。MQTT在Apollo中已经不需要其他配置文件或者是第三方插件支持了。

MQTT是一个线路层的协议,任何实现该协议的客户端都可以连接到Apollo。当然也可以整合其他MQTT兼容的消息代理中。

更多有关MQTT协议内容,参考the MQTT Specification

MQTT协议配置

为了开始使用MQTT协议,首先使用MQTT3.1协议的客户端,连接到Apollo正在监听端口。Apollo会做协议检测,而且自动识别MQTT连接,而且将连接作为MQTT协议处理。你不必要为MQTT协议打开一个端口(STomp,Openfire,AMQP等都是自动识别)。如果你一定指定连接的协议,有下面两种方式:你可以选择不用协议识别,而是为MQTT指定连接:

<connector id="tcp" bind="tcp://0.0.0.0:61613" protocol="mqtt"/>

或者你可以限制哪种协议可以被自动识别。通过下面的<detece>配置方式:

<connector id="tcp" bind="tcp://0.0.0.0:61613">

<detect protocols="mqtt openwire" />

</connector>

<detect> 下protocols 对应的参数通过空格来隔开支持的通信协议。如果只支持一种协议,就不要空格,默认情况下对任何协议生效。

如果你想调整MQTT默认设置,在apollo.xml文件中有一个<connector> 元素,通过MQTT参数配置:

<connector id="tcp" bind="tcp://0.0.0.0:61613">

<mqtt max_message_length="1000" />

</connector>

MQTT元素支持下面几个参数:

  • max_message_length : The size (in bytes) of the largest message that can be sent to the broker. Defaults to 100MB(broker能接受的最大消息量:默认是100M)
  • protocol_filters : A filter which can filter frames being sent/received to and from a client. It can modify the frame or even drop it.(一个控制发送和接收,Client的过滤器框架。可以修改,删除这个框架)
  • die_delay : How long after a connection is deemed to be “dead” before the connection actually closes; default: 5000ms(在实际断开连接之前,会有默认5000ms的时间被认为连接已经dead)

mqtt 配置元素也可以用来控制目的消息头的解析。下面是支持的参数:

  • queue_prefix : a tag used to identify destination types; default: null(用来确认目的地类型)
  • path_separator : used to separate segments in a destination name; default: /(用来分割目的地名称)
  • any_child_wildcard : indicate all child-level destinations that match the wildcard; default: +(识别子目录)
  • any_descendant_wildcard : indicate destinations that match the wildcard recursively; default:#(目标地址通配符)
  • regex_wildcard_start : pattern used to identify the start of a regex(表示正则表达开始)
  • regex_wildcard_end : pattern used to identify the end of a regex(表示正则表达结束)
  • part_pattern : allows you to specify a regex that constrains the naming of topics. (你可以指定正则表达规则)default: [ a-zA-Z0-9\_\-\%\~\:\(\)]+

Client 可用函数库

Apollo 支持MQTT3.1 协议,下面是可用的Clients:

如果要找到新支持的Clients ,可以检索:the MQTT website for its software

在目录example 目录下,你可以找到一些例子,实现了与broker之间收发。

connecting

为了确保broker配置文件的安全,所以只允许一个admin 用户连接,默认的用户名和密码是:admin ,password.

Mqtt 客户端不能specify 虚拟主机(更多请看:see the section on Virtual Hosts in the user guide),以至于默认情况下虚拟主机已经被使用了。通常第一虚拟主机定义在apollo.xml文件中。

Destination 类型

MQTT协议是订阅,发布协议,是不允许真正的利用队列点对点的消息收发。因此Apollo仅允许利用主题,还进行MQTT消息发送。订阅的概念和持久的主题订阅 和其他协议提到的有些类似,同时也被MQTT CONNECT 框架的clean session属性控制。

Clean Sessions

但一个Client 发送一个连接,这个连接中clean session 被设置为false,那么之前连接中有相同Client_id 的session 将会被重复使用。这就意味着Client断开了,订阅依然能收到消息。这就等同与同Apollo建立一个长订阅。

如果 clean session 设置为true ,那么新session就开始了,其他的session会慢慢消失,删除。这就是Apollo中定义的普通的主题订阅。

Topic Retained Messages

如果消息被发布的同时retain 标记被设置,消息将被主题记住,以至于新的订阅到达,最近的retain 消息会被发送到订阅者。比如说:你想发布一个参数,而且你想让最新的这个参数发布到总是可用的订阅了这个主题的客户端上,你就设置在PUBLISH 框架上设置retain 标签。

注意:retained 消息 不会被设置成retained 在 QoS设置为零的broker 重启过程中。

Last Will and Testament Message

当Client第一次连接的时候,有一个will 消息和一个更QoS相关的消息会跟你有关。will消息是一个基础消息,这个基础消息只有在连接异常或者是掉线的时候才会被发送。一般用在你有一个设备,当他们掉了的时候,你需要知道。所以如果一个医疗Client从broker掉线,will消息将会作为一个闹钟主题发送,而且会被系统作为高优先级提醒。

Reliable Messaging

MQTT协议允许Client 发布消息的时候指定Qos参数:

  • At Most Once (QoS=0)
  • At Least Once (QoS=1)
  • Exactly Once (QoS=2)

最多一次

这个设置时推送消息给Client,可靠性最低的一种。如果设置Qos=0,那broker就不会返回结果码,告诉你他收到消息了,也不会在失败后尝试重发。这有点像不可靠消息,如JMS。

至少一次

该设置会确保消息会被至少一次推送到Client。如果推送设置为至少推送一次,Apollo会返回一个回调函数,确保代理已经收到消息,而且确保会确保推送该消息。如果Client 将发布了一个Qos=1的消息,如果在指定的时间内没有收到回复,Client会希望重新发布这个消息。所以可能存在这种情况:代理收到一个需要推送的消息,然后又收到一个消息推送到同一个Client。所以如果传输过程中PUBACK丢失,Client会重新发送,而且不会去检测是否是重发,broker就将消息发送到订阅主题中。

恰好一次

该设置是可靠等级最高的。他会确保发布者不仅仅会推送,而且不会像Qos=1 那样,会被接收两次。当然这个设置会增加网络的负载。当一个消息被发布出去的时候,broker会保存该消息的id,而且会利用任何长连接,坚持要把该消息推送给目标地址。如果Client收到PUBREC 标志,那就表明broker已经收到消息了。 这个时候broker会期待Client发送一个PUBREL 来清除session 中消息id,broker如果发送成功就会发送一个PUBCOMP通知Client。

Wildcard Subscriptions

通配用在主题的目标地址中。这能实现一个主题发送到多个用户,或者多层用户中。

  • / is used to separate names in a path(分割路径)
  • + is used to match any name in a path(通配地址任何字符)
  • # is used to recursively match path names(递归通配)

比如通配可能这样来用:

  • PRICE/# : Any price for any product on any exchange(任何交易中任何产品的价格)
  • PRICE/STOCK/# : Any price for a stock on any exchange(任何交易中的股票价格)
  • PRICE/STOCK/NASDAQ/+ : Any stock price on NASDAQ(纳斯达克的任何股票价格)
  • PRICE/STOCK/+/IBM : Any IBM stock price on any exchange(任何交易中IBM股票价格)

Keep Alive

Apollo只有在Client指定了CONNECT的KeepAlive 值的时候,才会设置保持连接、心跳检测。如果one Client指定了keepalive,apollo 将会使用1.5*keepalive值。这个在MQTT中有说明。

Destination Name Restrictions

路径名称限制了使用(a-z, A-Z, 0-9, _, - %, ~, :, ' ', '(', ')' ,. )字符,通配符(*)在复杂的分隔符中。而且确保使用utf-8来编译你的URL。

 package com.xxx.mqtt;

 import java.net.URI;

 import org.eclipse.paho.client.mqttv3.DisconnectedBufferOptions;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
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.MqttPersistenceException;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; public class MyMqttClient implements MqttCallback { private static final MemoryPersistence DATA_STORE = new MemoryPersistence();
private static final String topic = "mytopic"; private String HOST = "127.0.0.1";
private int PORT = 1883;
private String USERNAME = "user";
private String PASSWORD = "password";
private String serverURIString = "tcp://" + HOST + ":" + PORT; String clientId = "client-1"; MqttAsyncClient client;
// Tokens
IMqttToken connectToken;
IMqttDeliveryToken pubToken; public static void main(String[] args) {
MyMqttClient app = new MyMqttClient();
app.asyncClient();
try {
Thread.sleep(20000);
app.disconnect();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("end");
} public void blockingClient() { try {
MqttClient sampleClient = new MqttClient(serverURIString, clientId);
MqttConnectOptions connOpts = new MqttConnectOptions();
connOpts.setCleanSession(true);
connOpts.setUserName(USERNAME);
connOpts.setPassword(PASSWORD.toCharArray());
System.out.println("Connecting to broker: " + serverURIString);
sampleClient.connect(connOpts);
sampleClient.subscribe("#", 1);
System.out.println("Connected");
// System.out.println("Publish message: " + content);
// MqttMessage message = new MqttMessage(content.getBytes());
// message.setQos(qos);
sampleClient.setCallback(this);
// sampleClient.publish(topic, message);
// System.out.println("Message published");
try {
Thread.sleep(10000000);
System.out.println("Disconnected");
sampleClient.disconnect();
} catch (Exception e) {
e.printStackTrace();
} } 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("except " + me);
me.printStackTrace();
}
} public void asyncClient() {
info(" MQTT init start."); // Tokens
IMqttToken connectToken;
IMqttDeliveryToken pubToken; // Client Options
MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(false);
options.setAutomaticReconnect(true); options.setUserName(USERNAME);
options.setPassword(PASSWORD.toCharArray()); try {
client = new MqttAsyncClient(serverURIString, clientId); DisconnectedBufferOptions disconnectedOpts = new DisconnectedBufferOptions();
disconnectedOpts.setBufferEnabled(true);
client.setBufferOpts(disconnectedOpts); connectToken = client.connect(options);
connectToken.waitForCompletion();//异步变成了同步。可以用IMqttCallbackListen..在connect时候设置回调。
boolean isConnected = client.isConnected();
info("Connection isConnected: " + isConnected); if (connectToken.isComplete() && connectToken.getException() == null && client.isConnected()) {
info("[Connect:] Success: "); //$NON-NLS-1$ //$NON-NLS-2$
client.setCallback(this); } else {
info("[Connect:] faild: "); //$NON-NLS-1$ //$NON-NLS-2$
} // MqttTopic topic = client.getTopic(topic);
// topic.
//setWill方法,如果项目中需要知道客户端是否掉线可以调用该方法。设置最终端口的通知消息
// options.setWill(topic, "close".getBytes(), 2, true); IMqttToken subToken = client.subscribe("#", 1); subToken.waitForCompletion(1000); if (subToken.isComplete()) {
info("subToken complete.");
if (subToken.getException() != null) {
info("Topics Subscription failed." + subToken.getException()); //$NON-NLS-1$
}
} else {
info("subToken not complete.");
if (subToken.getException() != null) {
info("Topics Subscription failed." + subToken.getException()); //$NON-NLS-1$
}
} info("init end"); } catch (MqttException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} } //String clientId, String topic, String message
public void send() {
String topic;
String message;
info("===Send Message start.===");
message = "Hello, boy."; boolean isConnected = client.isConnected();
if (!isConnected) {
//no need. it will auto reconnect and send.
} // Publish Message
try {
pubToken = client.publish(topic, new MqttMessage(message.getBytes())); info("Publish attempted: isComplete:" + pubToken.isComplete()); pubToken.waitForCompletion();
} catch (MqttPersistenceException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (MqttException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} // Check that Message has been delivered
info("Message Delivered: " + pubToken.isComplete());
info("=== send end.====");
} void disconnect() {
IMqttToken disconnectToken;
try {
disconnectToken = client.disconnect();
disconnectToken.waitForCompletion();
client.close();
} catch (MqttException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
client = null;
} void info(String s) {
System.out.println(s);
} public void connectionLost(Throwable thrwbl) {
// TODO Auto-generated method stub
info("connectionLost"); info("MQTT is disconnected from topic: {}. Message: {}. Cause: {}" + topic + thrwbl.getMessage() + thrwbl.getCause().getMessage());
thrwbl.printStackTrace(); } public void deliveryComplete(IMqttDeliveryToken arg0) {
// TODO Auto-generated method stub
info("deliveryComplete"); } public void messageArrived(String arg0, MqttMessage arg1) throws Exception {
// TODO Auto-generated method stub
String message = new String(arg1.getPayload());
String topic = arg0; info("xxx Receive : topic=" + topic + "; message=" + message); }
}
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>com.italktv.mqtt.client</groupId>
<artifactId>mqttclient</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging> <name>mqttclient</name>
<url>http://maven.apache.org</url> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties> <dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency> <dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.1.0</version>
</dependency> </dependencies>
</project>

上面是maven管理项目的pom.xml

MQTT 消息 发布 订阅的更多相关文章

  1. redis实现消息发布/订阅

    redis实现简单的消息发布/订阅模式. 消息订阅者: package org.common.component; import org.slf4j.Logger; import org.slf4j. ...

  2. 【python】-- RabbitMQ Publish\Subscribe(消息发布\订阅)

    RabbitMQ RabbitMQ Publish\Subscribe(消息发布\订阅) 1对1的消息发送和接收,即消息只能发送到指定的queue里,但这样使用有些局限性,有些时候你想让你的消息被所有 ...

  3. springboot集成redis实现消息发布订阅模式-双通道(跨多服务器)

    基础配置参考https://blog.csdn.net/llll234/article/details/80966952 查看了基础配置那么会遇到一下几个问题: 1.实际应用中可能会订阅多个通道,而一 ...

  4. MQTT的学习研究(十四) MQTT moquette 的 Callback API 消息发布订阅的实现

    在moquette-mqtt中提供了回调callback模式的发布和订阅但是在订阅之后没有发现有消息接收的方法,参看moquette-mqtt中Block,Future式的发布订阅基础是callbac ...

  5. MQTT的学习研究(十一) IBM MQTT 简单发布订阅实例

    package com.etrip.push; import com.ibm.mqtt.MqttAdvancedCallback; import com.ibm.mqtt.MqttClient; im ...

  6. redis 的消息发布订阅

    redis支持pub/sub功能(可以用于消息服务器),这个功能类似mq,这里做一个简单的介绍 Pub/Sub Pub/Sub 从字面上理解就是发布(Publish)与订阅(Subscribe),在R ...

  7. Redis 学习(三) —— 事务、消息发布订阅

    一.Redis事务 Redis 提供的事务机制与传统的数据库事务有些不同,传统数据库事务必须维护以下特性:原子性(Atomicity), 一致性(Consistency),隔离性(Isolation) ...

  8. php实现redis消息发布订阅

    基础介绍 Pub/Sub功能(means Publish, Subscribe)即发布及订阅功能 基于事件的系统中,Pub/Sub是目前广泛使用的通信模型,它采用事件作为基本的通信机制,提供大规模系统 ...

  9. springboot2.X 集成redis+消息发布订阅

    需求场景:分布式项目中,每个子项目有各自的 user 数据库, 在综合管理系统中存放这所有用户信息, 为了保持综合管理系统用户的完整性, 子系统添加用户后将用户信息以json格式保存至redis,然后 ...

随机推荐

  1. Ignite China 2015 之行

    微软首届Ignite China选择了金秋十月的北京,在顺义的九华山庄举办.这几天北京的空气特别好,再加上郊区高楼少,令人心胸开阔了不少.这次Ignite之行的任务有两个,其一是27号晚上与Windo ...

  2. ORACLE数据库汉字占几个字节问题

    一同事由于系统需求关系,将SQL SERVER数据库的一个表导入ORACLE数据库时,发现居然报错:ORA-12899: value too large for column xxxx (actual ...

  3. SQL Server 2008 R2 升级到 Service Pack 3后Report Builder启动不了

    一同事将测试服务器从SQL Server 2008 R2 SP2升级到了SQL Server 2008 R2 SP3后发现Report Service的报表编辑时启动不了Report Builder, ...

  4. MongoDB学习笔记~MongoDB实体中的值对象

    回到目录 注意,这里说的值对象是指在MongoDB实体类中的,并不是DDD中的值对象,不过,两者也是联系,就是它是对类的补充,自己本身没有存在的价值,而在值对象中,也是不需要有主键Id的,这与DDD也 ...

  5. 深入理解java内存模型系列文章

    转载关于java内存模型的系列文章,写的非常好. 深入理解java内存模型(一)--基础 深入理解java内存模型(二)--重排序 深入理解java内存模型(三)--顺序一致性 深入理解java内存模 ...

  6. Eclipse不自动编译java文件的终极解决方案

    最近我的eclipse经常犯傻,项目中总是有很多,启动项目也是没有启动类.查了下项目中生成的class文件,我靠竟然没有,或者还是以前的.原来是eclipse犯傻了,它没帮我自动编译java文件.一般 ...

  7. Python urllib2 调试

    #!/usr/bin/env python # coding=utf-8 __author__ = 'zhaoyingnan' import urllib import urllib2 import ...

  8. 【java开发】封装与继承

    2.封装 把属性封起来(私有化private) 提供了一对公有(public)的方法(getter/setter)来对属性进行操作(读取和设置) 这样做以后可以对属性值的有效性进行判断,避免出现不合法 ...

  9. ADO.net 连接字符串中的 |DataDirectory| 是什么

    |DataDirectory| does not come from config settings; you're mixing up three different things: Configu ...

  10. JustWeTools - 自定义控件集

    JustWeTools - Some useful tools 项目地址 JustWe 现在有哪些模块? View自定义控件 PaintView画图工具(包含重构压感新版) CodeView代码编辑 ...