生产者代码:
public class MqttClientPublish {
private static AtomicInteger count = new AtomicInteger();
private static int qos = 1;
private static String clientId = "JavaClientPublish";
private static String broker = "tcp://172.16.30.40:1883";
private static String topic = "MQTT-JAVA-CLIENT-TEST";
private static final Object lock = new Object();
public static final int THREAD_COUNT = 100;
private static CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT);
public static void main(String[] args) throws Exception {
MemoryPersistence persistence = new MemoryPersistence();
MqttAsyncClient client = new MqttAsyncClient(broker, clientId, persistence);
CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT);
MqttConnectOptions connOpts = new MqttConnectOptions();
connOpts.setMaxInflight(THREAD_COUNT * 10);
connOpts.setCleanSession(false);
connOpts.setAutomaticReconnect(true);
connOpts.setKeepAliveInterval(30);
client.connect(connOpts,new IMqttActionListener(){
//监听器快速返回控制非常重要,否则MQTT客户端的操作将会停止
@Override
public void onSuccess(IMqttToken asyncActionToken) {
for (int i = 0; i < THREAD_COUNT; i++) {
new Sender(barrier, client).start();
}
}
@Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
}
});
countDownLatch.await();
System.out.println("发布完毕!");
}
private static class Sender extends Thread {
private CyclicBarrier barrier;
private MqttAsyncClient asyncClient;
public Sender(CyclicBarrier barrier, MqttAsyncClient asyncClient) {
this.barrier = barrier;
this.asyncClient = asyncClient;
}
@Override
public void run() {
try {
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
int index = count.incrementAndGet();
String content = index + "";
MqttMessage message = new MqttMessage();
message.setQos(qos);
message.setPayload(content.getBytes());
message.setId(index);
try {
asyncClient.publish(topic, message,null,new IMqttActionListener(){
@Override
public void onSuccess(IMqttToken asyncActionToken) {
countDownLatch.countDown();
}
@Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
订阅端代码:
public class MqttClientSubscribe {
private static AtomicInteger total = new AtomicInteger();
private static int qos = 1;
private static String clientId = "JavaClientSubscribe";
private static String broker = "tcp://172.16.30.40:1883";
private static String topic = "MQTT-JAVA-CLIENT-TEST";
private static CountDownLatch countDownLatch = new CountDownLatch(MqttClientPublish.THREAD_COUNT);
public static void main(String[] args) throws Exception{
MemoryPersistence persistence = new MemoryPersistence();
MqttAsyncClient client = new MqttAsyncClient(broker, clientId, persistence);
MqttConnectOptions connOpts = new MqttConnectOptions();
connOpts.setCleanSession(false);
client.connect(connOpts,new IMqttActionListener(){
@Override
public void onSuccess(IMqttToken asyncActionToken) {
try {
client.subscribe(topic, qos, (String topic, MqttMessage message) -> {
String content = new String(message.getPayload());
System.out.println("content : " + content);
Integer index = Integer.valueOf(content);
total.addAndGet(index);
countDownLatch.countDown();
});
}catch (Exception e){
e.printStackTrace();
}
}
@Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
}
});
countDownLatch.await();
System.out.println("total : " + total.get());
}
}
经过多次发布运行,最终结果始终是5050,说明这个客户端是线程安全的。
这里我只是测试客户端的线程是否安全,但是这里的使用方式还是不是特别好,连接和发送的回调是直接在连接和发送动作上面注册的IMqttActionListener接口的实现,这个动作回调会导致整个客户端阻塞,严重影响并发效率,最好的方式是在客户端上面注册MqttCallbackExtended的实现类,这样效率更高。
修改代码实现:
发布端:
public class MqttClientPublish {
private static AtomicInteger count = new AtomicInteger();
private static int qos = 1;
private static String clientId = "JavaClientPublish";
private static String broker = "tcp://172.16.30.40:1883";
private static String topic = "MQTT-JAVA-CLIENT-TEST";
private static final Object lock = new Object();
public static final int THREAD_COUNT = 100;
private static CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT);
public static void main(String[] args) throws Exception {
MemoryPersistence persistence = new MemoryPersistence();
MqttAsyncClient client = new MqttAsyncClient(broker, clientId, persistence, new TimerPingSender()
, ThreadPoolUtil.buildScheduledThreadPoolExecutor(30, "publish-callback"));
CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT);
MqttConnectOptions connOpts = new MqttConnectOptions();
connOpts.setMaxInflight(THREAD_COUNT * 10);
connOpts.setCleanSession(false);
connOpts.setAutomaticReconnect(true);
connOpts.setKeepAliveInterval(30);
client.setCallback(new MqttCallbackExtended() {
@Override
public void connectionLost(Throwable cause) {
}
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
countDownLatch.countDown();
}
@Override
public void connectComplete(boolean reconnect, String serverURI) {
for (int i = 0; i < THREAD_COUNT; i++) {
new Sender(barrier, client).start();
}
}
});
client.connect(connOpts);
countDownLatch.await();
System.out.println("发布完毕!");
}
private static class Sender extends Thread {
private CyclicBarrier barrier;
private MqttAsyncClient asyncClient;
public Sender(CyclicBarrier barrier, MqttAsyncClient asyncClient) {
this.barrier = barrier;
this.asyncClient = asyncClient;
}
@Override
public void run() {
try {
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
int index = count.incrementAndGet();
String content = index + "";
MqttMessage message = new MqttMessage();
message.setQos(qos);
message.setPayload(content.getBytes());
message.setId(index);
try {
asyncClient.publish(topic, message, null, new IMqttActionListener() {
@Override
public void onSuccess(IMqttToken asyncActionToken) {
System.out.println("消息:[{" + message + "}]已经发出去!");
}
@Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
订阅端:
public class MqttClientSubscribe {
private static AtomicInteger total = new AtomicInteger();
private static int qos = 1;
private static String clientId = "JavaClientSubscribe";
private static String broker = "tcp://172.16.30.40:1883";
private static String topic = "MQTT-JAVA-CLIENT-TEST";
private static CountDownLatch countDownLatch = new CountDownLatch(MqttClientPublish.THREAD_COUNT);
public static void main(String[] args) throws Exception {
MemoryPersistence persistence = new MemoryPersistence();
MqttAsyncClient client = new MqttAsyncClient(broker, clientId, persistence, new TimerPingSender()
, ThreadPoolUtil.buildScheduledThreadPoolExecutor(30, "subscribe-callback"));
MqttConnectOptions connOpts = new MqttConnectOptions();
connOpts.setCleanSession(false);
connOpts.setAutomaticReconnect(true);
connOpts.setKeepAliveInterval(30);
client.setCallback(new MqttCallbackExtended() {
@Override
public void connectionLost(Throwable cause) {
}
@Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
try {
String content = new String(message.getPayload());
System.out.println("content : " + content);
Integer index = Integer.valueOf(content);
total.addAndGet(index);
countDownLatch.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
}
@Override
public void connectComplete(boolean reconnect, String serverURI) {
}
});
IMqttToken token = client.connect(connOpts);
token.waitForCompletion();
client.subscribe(topic, qos);
countDownLatch.await();
System.out.println("total : " + total.get());
}
}
注册IMqttActionListener监听器是具体某一个消息的回调,这个监听器是设置在MqttToken里面的,实现了MqttCallback的监听器是客户端级别的回调,是设置到mqttClient对象里面的,每个符合条件的消息动作都会回调。
我们以连接动作为例:
我们再来看看设置的客户端回调:
public void setCallback(MqttCallback callback) {
this.mqttCallback = callback;
comms.setCallback(callback);
}
客户端程序会在发送消息出去的时候把消息放在一个hashtable里面,等到服务器回复的ack消息回来的时候,和这个消息匹配,这个时候会回调commonsCallback类里面的handleActionComplete方法,如下图:
那么它们会阻塞mqtt客户端线程吗?这个问题很重要,关系到我们使用mqtt客户端的效率。
经过以上的分析,其实整个调用栈就很清晰了,客户端回调用户的方法,肯定是在commsCallback这个类开启的,它本身是一个线程类,我们看看它的代码:
public void start(String threadName, ExecutorService executorService) {
this.threadName = threadName;
synchronized (lifecycle) {
if (current_state == State.STOPPED) {
// Preparatory work before starting the background thread.
// For safety ensure any old events are cleared.
messageQueue.clear();
completeQueue.clear();
target_state = State.RUNNING;
if (executorService == null) {
new Thread(this).start();
} else {
callbackFuture = executorService.submit(this);
}
}
}
while (!isRunning()) {
try { Thread.sleep(100); } catch (Exception e) { }
}
}
if (isRunning()) {
// Check for deliveryComplete callbacks...
MqttToken token = null;
synchronized (completeQueue) {
if (!completeQueue.isEmpty()) {
// First call the delivery arrived callback if needed
token = (MqttToken) completeQueue.elementAt(0);
completeQueue.removeElementAt(0);
}
}
if (null != token) {
handleActionComplete(token);
}
这里终于看到调用handleActionComplete方法的地方了,是哪个线程在这里调用的handleActionComplete方法,那么我们的回调方法的逻辑就都是在这个线程里面执行。我们现在就只要找到调用这段代码的线程。
从上面的代码可以看到这个executorService很重要,如果它为空,那么客户端会直接new一个线程来执行回调,如果我们的消息非常多那么这个线程的数量可能就多的惊人了,这种情况应该是所有系统设计者都应该避免的。所以,我们一定要手动设置这个executorService,那么它是从哪里来的呢?我们往上一层一层的查找,终于在MqttAsyncClient的构造方法参数里面找到了它:
1、maxInflight 参数是发出去消息等待回复的数量,如果你发送消息并发量很高,并且网络延迟大,这个参数要设置大一些,否则会出现以下异常:
2、keepAliveInterval心跳包间隔,默认是60s,最好根据自身网络情况设置,一般情况30s合适。
3、cleanSession 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,不会清空持久化的消息,并且会重传这些消息;如果设置为true表示每次连接到服务器都以新的身份连接,清空之前持久化的消息,不会再重传。
4、setWill方法,如果项目中需要知道客户端是否掉线可以调用该方法。设置最终端口的通知消息
5、每创建一个客户端会创建一个消息持久化容器,所以一个应用一般只是用一个单例的mqtt客户端。
6、clientid 作为连接标示的,自定义,不能2个连接用同一个clientid,会挤掉线,