基于MQTT协议的 org.eclipse.paho.client.mqttv3 源码学习(二)

一、主要类介绍


二、重点类代码分析

对于长连接,一般是直接从消息的接收和发送类开始读,上面知道paho中消息发送和接收是在CommsSender和CommsReceiver实现的,

所以直接差看CommsSender代码。

[java]  view plain  copy
  1. public void run() {  
  2.         final String methodName = "run";  
  3.         MqttWireMessage message = null;  
  4.         while (running && (out != null)) {  
  5.             try {  
  6.                 message = clientState.get();  
  7.   
  8.                 log("sender 802:begin->" + message.toString());  
  9.                 if (message != null) {  
  10.                     // @TRACE 802=network send key={0} msg={1}  
  11.                     log.fine(className, methodName, "802"new Object[] { message.getKey(), message });  
  12.   
  13.                     if (message instanceof MqttAck) {  
  14.                         out.write(message);  
  15.                         out.flush();  
  16.                     } else {  
  17.                         MqttToken token = tokenStore.getToken(message);  
  18.                         // While quiescing the tokenstore can be cleared so need  
  19.                         // to check for null for the case where clear occurs  
  20.                         // while trying to send a message.  
  21.                         if (token != null) {  
  22.                             synchronized (token) {  
  23.                                 out.write(message);  
  24.                                 try {  
  25.                                     out.flush();  
  26.                                 } catch (IOException ex) {  
  27.                                     // The flush has been seen to fail on  
  28.                                     // disconnect of a SSL socket  
  29.                                     // as disconnect is in progress this should  
  30.                                     // not be treated as an error  
  31.                                     if (!(message instanceof MqttDisconnect))  
  32.                                         throw ex;  
  33.                                 }  
  34.                                 clientState.notifySent(message);  
  35.                             }  
  36.                         }  
  37.                     }  
  38.                     log("sender 805:send success.");  
  39.                 } else { // null message  
  40.                     // @TRACE 803=get message returned null, stopping}  
  41.                     log.fine(className, methodName, "803");  
  42.   
  43.                     running = false;  
  44.                     log("sender 805:send empty.");  
  45.                 }  
  46.             } catch (MqttException me) {  
  47.                 log("sender 804:MqttException-> " + me.getLocalizedMessage());  
  48.                 handleRunException(message, me);  
  49.             } catch (Exception ex) {  
  50.                 log("sender 804:exception-> " + ex.getLocalizedMessage());  
  51.                 handleRunException(message, ex);  
  52.             }  
  53.         } // end while  
  54.   
  55.         // @TRACE 805=<  
  56.         log.fine(className, methodName, "805");  
  57.   
  58.     }  

代码可以看到,是直接一个线程无效循环获取消息然后发送,
message = clientState.get();进入查看消息获取代码

[java]  view plain  copy
  1. protected MqttWireMessage get() throws MqttException {  
  2.         final String methodName = "get";  
  3.         MqttWireMessage result = null;  
  4.   
  5.         synchronized (queueLock) {  
  6.             while (result == null) {  
  7.                 // If there is no work wait until there is work.  
  8.                 // If the inflight window is full and no flows are pending wait until space is freed.  
  9.                 // In both cases queueLock will be notified.  
  10.                 if ((pendingMessages.isEmpty() && pendingFlows.isEmpty()) ||   
  11.                     (pendingFlows.isEmpty() && actualInFlight >= this.maxInflight)) {  
  12.                     try {  
  13.                         long ttw = getTimeUntilPing();  
  14.                         //@TRACE 644=wait for {0} ms for new work or for space in the inflight window   
  15.                         log.fine(className,methodName, "644"new Object[] {new Long(ttw)});                          
  16.    
  17.                         queueLock.wait(getTimeUntilPing());  
  18.                     } catch (InterruptedException e) {  
  19.                     }  
  20.                 }  
  21.                   
  22.                 // Handle the case where not connected. This should only be the case if:   
  23.                 // - in the process of disconnecting / shutting down  
  24.                 // - in the process of connecting  
  25.                 if (!connected &&   
  26.                     (pendingFlows.isEmpty() || !((MqttWireMessage)pendingFlows.elementAt(0instanceof MqttConnect))) {  
  27.                     //@TRACE 621=no outstanding flows and not connected  
  28.                     log.fine(className,methodName,"621");  
  29.   
  30.                     return null;  
  31.                 }  
  32.   
  33.                 // Check if there is a need to send a ping to keep the session alive.   
  34.                 // Note this check is done before processing messages. If not done first  
  35.                 // an app that only publishes QoS 0 messages will prevent keepalive processing  
  36.                 // from functioning.  
  37.                 checkForActivity();  
  38.                   
  39.                 // Now process any queued flows or messages  
  40.                 if (!pendingFlows.isEmpty()) {  
  41.                     // Process the first "flow" in the queue  
  42.                     result = (MqttWireMessage)pendingFlows.elementAt(0);  
  43.                     pendingFlows.removeElementAt(0);  
  44.                     if (result instanceof MqttPubRel) {  
  45.                         inFlightPubRels++;  
  46.   
  47.                         //@TRACE 617=+1 inflightpubrels={0}  
  48.                         log.fine(className,methodName,"617"new Object[]{new Integer(inFlightPubRels)});  
  49.                     }  
  50.           
  51.                     checkQuiesceLock();  
  52.                 } else if (!pendingMessages.isEmpty()) {  
  53.                     // If the inflight window is full then messages are not   
  54.                     // processed until the inflight window has space.   
  55.                     if (actualInFlight < this.maxInflight) {  
  56.                         // The in flight window is not full so process the   
  57.                         // first message in the queue  
  58.                         result = (MqttWireMessage)pendingMessages.elementAt(0);  
  59.                         pendingMessages.removeElementAt(0);  
  60.                         actualInFlight++;  
  61.       
  62.                         //@TRACE 623=+1 actualInFlight={0}  
  63.                         log.fine(className,methodName,"623",new Object[]{new Integer(actualInFlight)});  
  64.                     } else {  
  65.                         //@TRACE 622=inflight window full  
  66.                         log.fine(className,methodName,"622");                 
  67.                     }  
  68.                 }             
  69.             }  
  70.         }  
  71.         return result;  
  72.     }  

大致就是阻塞式获取消息,在一个心跳时间内如果没有消息就一直阻塞,超过心跳间隔,自动往队列中加入心跳包 MqttPingReq.

由此可以看出 CommsSender 发送的消息主要是从 ClientState 这个类中get 出来,而ClientState 这个类的作用在上面也说过:

保存正在发布的消息和将要发布的消息的状态信息,对应状态下消息进行必要的处理。

处理方式参见MQTT协议中客户端与服务器connent public unsubscribe,subscribe等消息的交互方式,

我们来看下这个类的主要成员 :


查看ClientState 类说明

[java]  view plain  copy
  1. /** 
  2.  * The core of the client, which holds thestate information for pending and 
  3.  * in-flight messages. 
  4.  * 
  5.  * Messages that have been accepted fordelivery are moved between several objects 
  6.  * while being delivered. 
  7.  * 
  8.  * 1) When the client is not running messagesare stored in a persistent store that 
  9.  * implements the MqttClientPersistent Interface.The default is MqttDefaultFilePersistencew 
  10.  * which stores messages safely across failuresand system restarts. If no persistence 
  11.  * is specified there is a fall back toMemoryPersistence which will maintain the messages 
  12.  * while the Mqtt client isinstantiated. 
  13.  * 
  14.  * 2) When the client or specificallyClientState is instantiated the messages are 
  15.  * read from the persistent store into: 
  16.  * - outboundqos2 hashtable if a QoS 2 PUBLISH orPUBREL 
  17.  * - outboundqos1 hashtable if a QoS 1 PUBLISH 
  18.  * (see restoreState) 
  19.  * 
  20.  * 3) On Connect, copy messages from the outboundhashtables to the pendingMessages or 
  21.  * pendingFlows vector in messageidorder. 
  22.  * - Initial message publish goes onto the pendingmessagesbuffer. 
  23.  * - PUBREL goes onto the pendingflows buffer 
  24.  * (see restoreInflightMessages) 
  25.  * 
  26.  * 4) Sender thread reads messages from the pendingflowsand pendingmessages buffer 
  27.  * one at a time.  The message is removed from the pendingbufferbut remains on the 
  28.  * outbound* hashtable.  The hashtable is the place where thefull set of outstanding 
  29.  * messages are stored in memory. (Persistenceis only used at start up) 
  30.  *  
  31.  * 5) Receiver thread - receives wire messages: 
  32.  *  - if QoS 1 thenremove from persistence and outboundqos1 
  33.  *  - if QoS 2 PUBRECsend PUBREL. Updating the outboundqos2 entry with the PUBREL 
  34.  *    andupdate persistence. 
  35.  *  - if QoS 2 PUBCOMPremove from persistence and outboundqos2  
  36.  * 
  37.  * Notes: 
  38.  * because of the multithreaded natureof the client it is vital that any changes to this 
  39.  * class take concurrency into account.  For instance as soon as a flow / message isput on 
  40.  * the wire it is possible for the receivingthread to receive the ack and to be processing 
  41.  * the response before the sending side hasfinished processing.  For instance aconnect may 
  42.  * be sent, the conack received beforethe connect notify send has been processed! 
  43.  * 
  44.  */  

大致意思就是,程序已运行,但是消息链路还没有开启的情况下,我们从通过MqttClientPersistent 这个接口读取缓存信息;
Qos 2 的PUBLISH消息和 PUBREL 存储到outboundqos2 中,Qos 1的消息存到 outboundqos1 中。消息通过messgeId 作为可以来缓存,
messgeId的范围是1-65535,所以当缓存的值超做这个,消息就会替换掉。
每次发送的时候,客户端读取 pendingMessages,pendingFlows这两个vertor中的数据,初始的消息存pendingMessages,PUBREL消息存储
到 pendingFlows中,消息发送完成后移除,但是outboundQoS2 outboundQoS1等队列中的消息会保留直到接收线程中收到消息回应。
如果Qos 1 移除持久化数据和 outboundqos1 数据
如果Qos 为2 的 PUBREC 则返回 PUBREL响应,更新持久化数据与outboundqos1中消息状态为 PUBREL
如果Qos 2 的PUBCOM 则移除持久化中数据和 outboundqos2中消息。
具体流程可以对比查看 ClientStatesend函数

[java]  view plain  copy
  1. public void send(MqttWireMessage message, MqttToken token) throws MqttException {  
  2.         final String methodName = "send";  
  3.         if (message.isMessageIdRequired() && (message.getMessageId() == 0)) {  
  4.             message.setMessageId(getNextMessageId());  
  5.         }  
  6.         if (token != null ) {  
  7.             try {  
  8.                 token.internalTok.setMessageID(message.getMessageId());  
  9.             } catch (Exception e) {  
  10.             }  
  11.         }  
  12.               
  13.         if (message instanceof MqttPublish) {  
  14.             synchronized (queueLock) {  
  15.                 if (actualInFlight >= this.maxInflight) {  
  16.                     //@TRACE 613= sending {0} msgs at max inflight window  
  17.                     log.fine(className, methodName, "613"new Object[]{new Integer(actualInFlight)});  
  18.   
  19.                     throw new MqttException(MqttException.REASON_CODE_MAX_INFLIGHT);  
  20.                 }  
  21.                   
  22.                 MqttMessage innerMessage = ((MqttPublish) message).getMessage();  
  23.                 //@TRACE 628=pending publish key={0} qos={1} message={2}  
  24.                 log.fine(className,methodName,"628"new Object[]{new Integer(message.getMessageId()), new Integer(innerMessage.getQos()), message});  
  25.   
  26.                 switch(innerMessage.getQos()) {  
  27.                     case 2:  
  28.                         outboundQoS2.put(new Integer(message.getMessageId()), message);  
  29.                         persistence.put(getSendPersistenceKey(message), (MqttPublish) message);  
  30.                         break;  
  31.                     case 1:  
  32.                         outboundQoS1.put(new Integer(message.getMessageId()), message);  
  33.                         persistence.put(getSendPersistenceKey(message), (MqttPublish) message);  
  34.                         break;  
  35.                 }  
  36.                 tokenStore.saveToken(token, message);  
  37.                 pendingMessages.addElement(message);  
  38.                 queueLock.notifyAll();  
  39.             }  
  40.         } else {  
  41.             //@TRACE 615=pending send key={0} message {1}  
  42.             log.fine(className,methodName,"615"new Object[]{new Integer(message.getMessageId()), message});  
  43.               
  44.             if (message instanceof MqttConnect) {  
  45.                 synchronized (queueLock) {  
  46.                     // Add the connect action at the head of the pending queue ensuring it jumps  
  47.                     // ahead of any of other pending actions.  
  48.                     tokenStore.saveToken(token, message);  
  49.                     pendingFlows.insertElementAt(message,0);  
  50.                     queueLock.notifyAll();  
  51.                 }  
  52.             } else {  
  53.                 if (message instanceof MqttPingReq) {  
  54.                     this.pingCommand = message;  
  55.                 }  
  56.                 else if (message instanceof MqttPubRel) {  
  57.                     outboundQoS2.put(new Integer(message.getMessageId()), message);  
  58.                     persistence.put(getSendConfirmPersistenceKey(message), (MqttPubRel) message);  
  59.                 }  
  60.                 else if (message instanceof MqttPubComp)  {  
  61.                     persistence.remove(getReceivedPersistenceKey(message));  
  62.                 }  
  63.                   
  64.                 synchronized (queueLock) {  
  65.                     if ( !(message instanceof MqttAck )) {  
  66.                         tokenStore.saveToken(token, message);  
  67.                     }  
  68.                     pendingFlows.addElement(message);  
  69.                     queueLock.notifyAll();  
  70.                 }  
  71.             }  
  72.         }  
  73.     }  
接下来我们在来查看下CommsReceiver 接收端的代码

[java]  view plain  copy
  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. //阻塞式读取消息  
  10.                 MqttWireMessage message = in.readMqttWireMessage();  
  11.                 log("Receiver 852 message:" + message.toString());  
  12.                 if (message instanceof MqttAck) {  
  13.                     token = tokenStore.getToken(message);  
  14.                     if (token != null) {  
  15.                         synchronized (token) {  
  16.                             // Ensure the notify processing is done under a lock  
  17.                             // on the token  
  18.                             // This ensures that the send processing can  
  19.                             // complete before the  
  20.                             // receive processing starts! ( request and ack and  
  21.                             // ack processing  
  22.                             // can occur before request processing is complete  
  23.                             // if not!  
  24.                             //通知回复确认消息  
  25.                             clientState.notifyReceivedAck((MqttAck) message);  
  26.                         }  
  27.                     } else {  
  28.                         // It its an ack and there is no token then something is  
  29.                         // not right.  
  30.                         // An ack should always have a token assoicated with it.  
  31.                         throw new MqttException(MqttException.REASON_CODE_UNEXPECTED_ERROR);  
  32.                     }  
  33.                 } else {  
  34.                     //通知有新的消息达到,我们进入此查看  
  35.                     // A new message has arrived  
  36.                     clientState.notifyReceivedMsg(message);  
  37.                 }  
  38.             } catch (MqttException ex) {  
  39.                 // @TRACE 856=Stopping, MQttException  
  40.                 log.fine(className, methodName, "856"null, ex);  
  41.   
  42.                 log("Receiver 856:exception->" + ex.toString());  
  43.                 running = false;  
  44.                 // Token maybe null but that is handled in shutdown  
  45.                 clientComms.shutdownConnection(token, ex);  
  46.             } catch (IOException ioe) {  
  47.                 // @TRACE 853=Stopping due to IOException  
  48.                 log.fine(className, methodName, "853");  
  49.   
  50.                 log("Receiver 853:exception->" + ioe.getLocalizedMessage());  
  51.                 log("Receiver 853:exception->" + ioe.toString());  
  52.                 running = false;  
  53.                 // An EOFException could be raised if the broker processes the  
  54.                 // DISCONNECT and ends the socket before we complete. As such,  
  55.                 // only shutdown the connection if we're not already shutting  
  56.                 // down.  
  57.                 if (!clientComms.isDisconnecting()) {  
  58.                     clientComms.shutdownConnection(token, new MqttException(  
  59.                             MqttException.REASON_CODE_CONNECTION_LOST, ioe));  
  60.                 } // else {  
  61.             }  
  62.         }  
  63.   
  64.         // @TRACE 854=<  
  65.         log.fine(className, methodName, "854");  
  66.     }  
[java]  view plain  copy
  1. 点击进入  clientState.notifyReceivedMsg(message)  
[java]  view plain  copy
  1. protected void notifyReceivedMsg(MqttWireMessage message) throws MqttException {  
  2.         final String methodName = "notifyReceivedMsg";  
  3.         this.lastInboundActivity = System.currentTimeMillis();  
  4.   
  5.         // @TRACE 651=received key={0} message={1}  
  6.         log.fine(className, methodName, "651"new Object[] {  
  7.                 new Integer(message.getMessageId()), message });  
  8.           
  9.         if (!quiescing) {  
  10.             if (message instanceof MqttPublish) {  
  11.                 MqttPublish send = (MqttPublish) message;  
  12.                 switch (send.getMessage().getQos()) {  
  13.                 case 0:  
  14.                 case 1:  
  15.                     if (callback != null) {  
  16.                         callback.messageArrived(send);  
  17.                     }  
  18.                     break;  
  19.                 case 2:  
  20.                     persistence.put(getReceivedPersistenceKey(message),  
  21.                             (MqttPublish) message);  
  22.                     inboundQoS2.put(new Integer(send.getMessageId()), send);  
  23.                     this.send(new MqttPubRec(send), null);  
  24.                 }  
  25.             } else if (message instanceof MqttPubRel) {  
  26.                 MqttPublish sendMsg = (MqttPublish) inboundQoS2  
  27.                         .get(new Integer(message.getMessageId()));  
  28.                 if (sendMsg != null) {  
  29.                     if (callback != null) {  
  30.                         callback.messageArrived(sendMsg);  
  31.                     }  
  32.                 } else {  
  33.                     // Original publish has already been delivered.  
  34.                     MqttPubComp pubComp = new MqttPubComp(message  
  35.                             .getMessageId());  
  36.                     this.send(pubComp, null);  
  37.                 }  
  38.             }  
  39.         }  
  40.     }  
[java]  view plain  copy
  1. 这里可以看出,如果是Qos 1的消息或者Qos 2 MqttPubRel,我们直接回调告诉消息已到达,点击进入 callback.messageArrived(sendMsg);     
[java]  view plain  copy
  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.     }  
[java]  view plain  copy
  1. 所做的操作就是将数据插入到 消息队列中,然后唤醒 workAvailable 这个锁,在 CommsCallback类中所有这个锁对应的地方,可以查看到  
[java]  view plain  copy
  1. public void run() {  
  2.     final String methodName = "run";  
  3.     while (running) {  
  4.         try {  
  5.             // If no work is currently available, then wait until there is  
  6.             // some...  
  7.             try {  
  8.                 synchronized (workAvailable) {  
  9.                     if (running & messageQueue.isEmpty() && completeQueue.isEmpty()) {  
  10.                         // @TRACE 704=wait for workAvailable  
  11.                         log.fine(className, methodName, "704");  
  12.                         workAvailable.wait();  
  13.                     }  
  14.                 }  
  15.             } catch (InterruptedException e) {  
  16.             }  
  17.   
  18.             if (running) {  
  19.                 // Check for deliveryComplete callbacks...  
  20.                 if (!completeQueue.isEmpty()) {  
  21.                     // First call the delivery arrived callback if needed  
  22.                     MqttToken token = (MqttToken) completeQueue.elementAt(0);  
  23.                     handleActionComplete(token);  
  24.                     completeQueue.removeElementAt(0);  
  25.                 }  
  26.   
  27.                 // Check for messageArrived callbacks...  
  28.                 if (!messageQueue.isEmpty()) {  
  29.                     // Note, there is a window on connect where a publish  
  30.                     // could arrive before we've  
  31.                     // finished the connect logic.  
  32.                     MqttPublish message = (MqttPublish) messageQueue.elementAt(0);  
  33.   
  34.                     handleMessage(message);  
  35.                     messageQueue.removeElementAt(0);  
  36.                 }  
  37.             }  
  38.   
  39.             if (quiescing) {  
  40.                 clientState.checkQuiesceLock();  
  41.             }  
  42.   
  43.             synchronized (spaceAvailable) {  
  44.                 // Notify the spaceAvailable lock, to say that there's now  
  45.                 // some space on the queue...  
  46.   
  47.                 // @TRACE 706=notify spaceAvailable  
  48.                 log.fine(className, methodName, "706");  
  49.                 spaceAvailable.notifyAll();  
  50.             }  
  51.         } catch (Throwable ex) {  
  52.             // Users code could throw an Error or Exception e.g. in the case  
  53.             // of class NoClassDefFoundError  
  54.             // @TRACE 714=callback threw exception  
  55.             log.fine(className, methodName, "714"null, ex);  
  56.             running = false;  
  57.             clientComms.shutdownConnection(nullnew MqttException(ex));  
  58.         }  
  59.     }  
  60. }  


程序通过handleActionComplete(token);handleMessage(message);通知用户发布一个消息完成和 有新的订阅消息到达。

三、总结
ClientState 类中, pendingMessages容器存放MqttPubish消息,而pendingFlows消息则存放 MqttPubRel,MqttConnect,MqttPingReq,MqttAck等
Send 方法将消息放入到容器中,同时唤醒等待发送的线程。
 
CommsSender 这个发送线程 通过 ClientStatele类get() 方法等待 pendingMessages和pendingFlows 这个这两个队列中放入消息,
如果有消息放入,且同时被唤醒,那么就执行消息发送操作。
 
CommsSender 接收线程中阻塞式获取消息根据不通的消息类型已经Qos level,通过CommsCallback及ClientStatele中notifyReceivedMsg 来执行相应的操作。

猜你喜欢

转载自blog.csdn.net/qq_29923439/article/details/76576955