一、概述

前一篇博客(MQTT协议实现Eclipse Paho学习总结一) 写了一些MQTT协议相关的一些概述和其实现Eclipse Paho的报文类别,同时对心跳包进行了分析。这篇文章,在不涉及MQTT逻辑实现的基础之上分析一下Eclipse Paho中Socket通信的实现,这里我们主要阐述其采用Java同步技术将同步的Socket通信异步化的过程。

二、上菜

先看一下在org.eclipse.paho.client.mqttv3.internal有两个类,CommsSender,CommsReceiver,通过名字我们知道这个这两个类是关于客户端发送消息和接收消息的两个类。上源码分析。

2.1 CommsSender

我们先来看一下CommsSender的run方法。
  1. public void run() {
  2. final String methodName = "run";
  3. MqttWireMessage message = null;
  4. while (running && (out != null)) {//超级无限循环
  5. try {
  6. message = clientState.get();//主要看这里获取message时进行了阻塞,即clientState.get()方法没有获得消息的时候,代码一直处理阻塞状态,不会一直无限循环!
  7. if (message != null) {
  8. //@TRACE 802=network send key={0} msg={1}
  9. log.fine(className,methodName,"802", new Object[] {message.getKey(),message});
  10. if (message instanceof MqttAck) {
  11. out.write(message);
  12. out.flush();
  13. } else {
  14. MqttToken token = tokenStore.getToken(message);
  15. // While quiescing the tokenstore can be cleared so need
  16. // to check for null for the case where clear occurs
  17. // while trying to send a message.
  18. if (token != null) {
  19. synchronized (token) {//使用了同步,防止一次性多个写操作。
  20. out.write(message);
  21. out.flush();
  22. clientState.notifySent(message);//通知已经发送了一个消息
  23. }
  24. }
  25. }
  26. } else { // null message
  27. //@TRACE 803=get message returned null, stopping}
  28. log.fine(className,methodName,"803");
  29. running = false;
  30. }
  31. } catch (MqttException me) {
  32. handleRunException(message, me);
  33. } catch (Exception ex) {
  34. handleRunException(message, ex);
  35. }
  36. } // end while
  37. //@TRACE 805=<
  38. log.fine(className, methodName,"805");
  39. }
点击message = clientState.get();中get()方法进入之后,方法的部分内容如下:
  1. synchronized (queueLock) {
  2. while (result == null) {
  3. if (pendingMessages.isEmpty() && pendingFlows.isEmpty()) {
  4. try {
  5. long ttw = getTimeUntilPing();
  6. //@TRACE 644=nothing to send, wait for {0} ms
  7. log.fine(className,methodName, "644", new Object[] {new Long(ttw)});
  8. queueLock.wait(getTimeUntilPing());//如果pendingMessages队列和pendingFlows队列为空,则放弃queueLock锁,等待,而这个等待时间是有限的,如果长时间没有发送消息,同时等待的时间超过了心跳包发送的时间,那么就往下执行,根据实际情况发送心跳包或者消息。
  9. } catch (InterruptedException e) {
  10. }
  11. }
ClientState中还有一个send()方法,部分内容如下:
  1. if (message instanceof MqttPublish) {
  2. synchronized (queueLock) {
  3. if (actualInFlight >= this.maxInflight) {
  4. //@TRACE 613= sending {0} msgs at max inflight window
  5. log.fine(className, methodName, "613", new Object[]{new Integer(actualInFlight)});
  6. throw new MqttException(MqttException.REASON_CODE_MAX_INFLIGHT);
  7. }
  8. MqttMessage innerMessage = ((MqttPublish) message).getMessage();
  9. //@TRACE 628=pending publish key={0} qos={1} message={2}
  10. log.fine(className,methodName,"628", new Object[]{new Integer(message.getMessageId()), new Integer(innerMessage.getQos()), message});
  11. switch(innerMessage.getQos()) {
  12. case 2:
  13. outboundQoS2.put(new Integer(message.getMessageId()), message);
  14. persistence.put(getSendPersistenceKey(message), (MqttPublish) message);
  15. break;
  16. case 1:
  17. outboundQoS1.put(new Integer(message.getMessageId()), message);
  18. persistence.put(getSendPersistenceKey(message), (MqttPublish) message);
  19. break;
  20. }
  21. tokenStore.saveToken(token, message);
  22. pendingMessages.addElement(message);
  23. queueLock.notifyAll();//通知get方法,我已经有消息放入队列了!!!
  24. }

总 的过程如下:send方法将消息放入到pendingMessages队列和pendingFlows当中同时发送消息唤醒等待中的线程,get等待 pendingMessages队列和pendingFlows中的消息,同时等待唤醒,如果有消息放入,同时被唤醒,那么就执行发送消息的操作。这个过 程是不是跟操作系统当中的生产者-消费者的关系一样呢!!!

 

2.2 CommsReceiver

 
再来看一下CommsReceiver的run()方法
  1. public void run() {
  2. final String methodName = "run";
  3. MqttToken token = null;
  4. //在这里,因为客户端无法判断,服务器什么时候能够发消息过来,因此只能采用无限循环的方式,不断的去判断是否有新消息发送过来。
  5. while (running && (in != null)) {//超级无限循环
  6. try {
  7. //@TRACE 852=network read message
  8. log.fine(className,methodName,"852");
  9. MqttWireMessage message = in.readMqttWireMessage();// 这里,因为socket.getInputStream()一直在阻塞,如果没有消息是读不到message的,因此在这里的while循环也没有无限制 的运行下去,只有在有消息的时候才往下走。socket默认是阻塞的,就是在读的时候如果读不到资源就会一直等待,直到超时(如果设置了超时时间的话), 如果服务端和客户端都在读的话而没有写的话就会一直阻塞。你可以使用SocketChannel,设置socket的通道,使其变成非阻塞的。
  10. if (message instanceof MqttAck) {//判断是否是确认包
  11. token = tokenStore.getToken(message);
  12. if (token!=null) {
  13. synchronized (token) {
  14. // Ensure the notify processing is done under a lock on the token
  15. // This ensures that the send processing can complete  before the
  16. // receive processing starts! ( request and ack and ack processing
  17. // can occur before request processing is complete if not!
  18. clientState.notifyReceivedAck((MqttAck)message);
  19. }
  20. } else {
  21. // It its an ack and there is no token then something is not right.
  22. // An ack should always have a token assoicated with it.
  23. throw new MqttException(MqttException.REASON_CODE_UNEXPECTED_ERROR);
  24. }
  25. } else {
  26. // A new message has arrived,一个新消息过来。
  27. clientState.notifyReceivedMsg(message);//点击进入之后
  28. }
  29. }
  30. catch (MqttException ex) {
  31. //@TRACE 856=Stopping, MQttException
  32. log.fine(className,methodName,"856",null,ex);
  33. running = false;
  34. // Token maybe null but that is handled in shutdown
  35. clientComms.shutdownConnection(token, ex);
  36. }
  37. catch (IOException ioe) {
  38. //@TRACE 853=Stopping due to IOException
  39. log.fine(className,methodName,"853");
  40. running = false;
  41. // An EOFException could be raised if the broker processes the
  42. // DISCONNECT and ends the socket before we complete. As such,
  43. // only shutdown the connection if we're not already shutting down.
  44. if (!clientComms.isDisconnecting()) {
  45. clientComms.shutdownConnection(token, new MqttException(MqttException.REASON_CODE_CONNECTION_LOST, ioe));
  46. } // else {
  47. }
  48. }
  49. //@TRACE 854=<
  50. log.fine(className,methodName,"854");
  51. }
 

我们点击进入clientState.notifyReceivedMsg(message);方法,部分代码如下:

  1. if (message instanceof MqttPublish) {
  2. MqttPublish send = (MqttPublish) message;
  3. switch (send.getMessage().getQos()) {
  4. case 0:
  5. case 1:
  6. if (callback != null) {
  7. callback.messageArrived(send);
  8. }
  9. break;

我们点击进入callback.messageArrived(send);方法,

  1. public void messageArrived(MqttPublish sendMessage) {
  2. final String methodName = "messageArrived";
  3. if (mqttCallback != null) {
  4. // If we already have enough messages queued up in memory, wait
  5. // until some more queue space becomes available. This helps
  6. // the client protect itself from getting flooded by messages
  7. // from the server.
  8. synchronized (spaceAvailable) {
  9. if (!quiescing && messageQueue.size() >= INBOUND_QUEUE_SIZE) {
  10. try {
  11. // @TRACE 709=wait for spaceAvailable
  12. log.fine(className, methodName, "709");
  13. spaceAvailable.wait();
  14. } catch (InterruptedException ex) {
  15. }
  16. }
  17. }
  18. if (!quiescing) {
  19. messageQueue.addElement(sendMessage);
  20. // Notify the CommsCallback thread that there's work to do...
  21. synchronized (workAvailable) {
  22. // @TRACE 710=new msg avail, notify workAvailable
  23. log.fine(className, methodName, "710");
  24. workAvailable.notifyAll();
  25. }
  26. }
  27. }
  28. }

在 这里,同样使用了生产者-消费者模式,在run方法里,我们可以看到其调用了handleMessage,在这个方法里面调用了 mqttCallback.messageArrived(destName, publishMessage.getMessage());接口回调。

MQTT协议实现Eclipse Paho学习总结二的更多相关文章

  1. MQTT协议实现Eclipse Paho学习总结

    MQTT协议实现Eclipse Paho学习总结 摘自:https://www.cnblogs.com/yfliufei/p/4383852.html 2015-04-01 14:57 by 辣椒酱, ...

  2. MQTT协议及推送服务(二)

    MQTT简介 MQTT全称叫做Message Queuing Telemetry Transport,意为消息队列遥测传输,是IBM开发的一个即时通讯协议.由于其维护一个长连接以轻量级低消耗著称,所以 ...

  3. [3] MQTT,mosquitto,Eclipse Paho---怎样使用 Eclipse Paho MQTT工具来发送订阅MQTT消息?

    在上两节,笔者主要介绍了 MQTT,mosquitto,Eclipse Paho的基本概念已经怎样安装mosquitto. 在这个章节我们就来看看怎样用 Eclipse Paho MQTT工具来发送接 ...

  4. mqtt协议实现 java服务端推送功能(二)java demo测试

    上一篇写了安装mosQuitto和测试,但是用cmd命令很麻烦,有没有一个可视化软件呢? 有,需要在google浏览器下载一个叫MQTTLens的插件 打开MQTTLens后界面如下: 打开conne ...

  5. 基于RabbitMQ的MQTT协议及应用

    MQTT的开源代码地址先贴在这里:https://github.com/mqtt/mqtt.github.io/wiki/servers MQTT定义: MQTT(Message Queuing Te ...

  6. 采用MQTT协议实现android消息推送(2)MQTT服务端与客户端软件对比、android客户端示列表

    1.服务端软件对比 https://github.com/mqtt/mqtt.github.io/wiki/servers 名称(点名进官网) 特性 简介 收费 支持的客户端语言 IBM MQ 完整的 ...

  7. MQTT协议通俗讲解

    参考 Reference v3.1.1 英文原版 中文翻译版 其他资源 网站 MQTT官方主页 Eclipse Paho 项目主页 测试工具 MQTT Spy(基于JDK) Chrome插件 MQTT ...

  8. MQTT 协议学习:000-有关概念入门

    背景 从本章开始,在没有特殊说明的情况下,文章中的MQTT版本均为 3.1.1. MQTT 协议是物联网中常见的协议之一,"轻量级物联网消息推送协议",MQTT同HTTP属于第七层 ...

  9. MQTT 协议学习: 总结 与 各种定义的速查表

    背景 经过几天的学习与实操,对于MQTT(主要针对 v3.1.1版本)的学习告一段落,为了方便日后的查阅 本文链接:<MQTT 协议学习: 总结 与 各种定义的速查表> 章节整理 MQTT ...

随机推荐

  1. debian下蓝牙适配器的配置和使用

    本文打算将蓝牙适配器和手机蓝牙进行配对. 买了个支持蓝牙4.0协议的蓝牙适配器,将其插入到pc(debian 7.4)的usb口. 查看手机蓝牙信息: 选择手机中"设置"-> ...

  2. Ci下面隐藏index.php的方法

    1.需要apache打开rewrite_module,然后修改httpd.conf的AllowOverride none 为AllowOverride All(里面,不同的环境目录不同) 2.在CI的 ...

  3. php数组转换成js可用的数组的两种方式

    1.如果你理解JSON数据格式的话,这个问题就异常简单: <?php $a =array('1','2','3'); ?> <script language="javasc ...

  4. PL/SQL Developer 的 SQL 编辑窗口显示行号

    版权声明:本文为博主原创文章,未经博主允许不得转载. 一直奇怪为什么 PL/SQL 6 系列的版本可以显示行号,为什么到了 7 .8 版本之后反而还不行了?而且我都已经设置了“显示行号”的呀. 如图: ...

  5. python-编译安装Python2.7

    yum中最新的也是Python 2.6.6,只能下载Python 2.7.5的源代码自己编译安装. 操作步骤如下: 1)下载并解压Python 2.7.9的源代码 cd /opt wget --no- ...

  6. linux命令学习笔记(14):head 命令

    head 与 tail 就像它的名字一样的浅显易懂,它是用来显示开头或结尾某个数量的文字区块,head 用来显 示档案的开头至标准输出中,而 tail 想当然尔就是看档案的结尾. .命令格式: hea ...

  7. 【leetcode刷题笔记】Pow(x, n)

    Implement pow(x, n). 题解:注意两点: 普通的递归把n降为n-1会超时,要用二分的方法,每次把xn = x[n/2] * x[n/2] * xn-[n/2]*2, [n/2]表示n ...

  8. Git远程克隆仓库出现Permission denied (publickey)

    $ git clone git@github.com:DavidWanderer/test1.git Cloning into 'test1'... Warning: Permanently adde ...

  9. 系列文章--Node.js学习笔记系列

    Node.js学习笔记系列总索引 Nodejs学习笔记(一)--- 简介及安装Node.js开发环境 Nodejs学习笔记(二)--- 事件模块 Nodejs学习笔记(三)--- 模块 Nodejs学 ...

  10. 洛谷 2312 / bzoj 3751 解方程——取模

    题目:https://www.luogu.org/problemnew/show/P2312 https://www.lydsy.com/JudgeOnline/problem.php?id=3751 ...