http://blog.csdn.net/chenbifeng/article/details/25067761
一、主要类介绍
二、重点类代码分析
对于长连接,一般是直接从消息的接收和发送类开始读,上面知道paho中消息发送和接收是在CommsSender和CommsReceiver实现的,
所以直接差看CommsSender代码。
- public void run() {
- final String methodName = "run";
- MqttWireMessage message = null;
- while (running && (out != null)) {
- try {
- message = clientState.get();
- log("sender 802:begin->" + message.toString());
- if (message != null) {
- // @TRACE 802=network send key={0} msg={1}
- log.fine(className, methodName, "802", new Object[] { message.getKey(), message });
- if (message instanceof MqttAck) {
- out.write(message);
- out.flush();
- } else {
- MqttToken token = tokenStore.getToken(message);
- // While quiescing the tokenstore can be cleared so need
- // to check for null for the case where clear occurs
- // while trying to send a message.
- if (token != null) {
- synchronized (token) {
- out.write(message);
- try {
- out.flush();
- } catch (IOException ex) {
- // The flush has been seen to fail on
- // disconnect of a SSL socket
- // as disconnect is in progress this should
- // not be treated as an error
- if (!(message instanceof MqttDisconnect))
- throw ex;
- }
- clientState.notifySent(message);
- }
- }
- }
- log("sender 805:send success.");
- } else { // null message
- // @TRACE 803=get message returned null, stopping}
- log.fine(className, methodName, "803");
- running = false;
- log("sender 805:send empty.");
- }
- } catch (MqttException me) {
- log("sender 804:MqttException-> " + me.getLocalizedMessage());
- handleRunException(message, me);
- } catch (Exception ex) {
- log("sender 804:exception-> " + ex.getLocalizedMessage());
- handleRunException(message, ex);
- }
- } // end while
- // @TRACE 805=<
- log.fine(className, methodName, "805");
- }
代码可以看到,是直接一个线程无效循环获取消息然后发送,
message = clientState.get();进入查看消息获取代码
- protected MqttWireMessage get() throws MqttException {
- final String methodName = "get";
- MqttWireMessage result = null;
- synchronized (queueLock) {
- while (result == null) {
- // If there is no work wait until there is work.
- // If the inflight window is full and no flows are pending wait until space is freed.
- // In both cases queueLock will be notified.
- if ((pendingMessages.isEmpty() && pendingFlows.isEmpty()) ||
- (pendingFlows.isEmpty() && actualInFlight >= this.maxInflight)) {
- try {
- long ttw = getTimeUntilPing();
- //@TRACE 644=wait for {0} ms for new work or for space in the inflight window
- log.fine(className,methodName, "644", new Object[] {new Long(ttw)});
- queueLock.wait(getTimeUntilPing());
- } catch (InterruptedException e) {
- }
- }
- // Handle the case where not connected. This should only be the case if:
- // - in the process of disconnecting / shutting down
- // - in the process of connecting
- if (!connected &&
- (pendingFlows.isEmpty() || !((MqttWireMessage)pendingFlows.elementAt(0) instanceof MqttConnect))) {
- //@TRACE 621=no outstanding flows and not connected
- log.fine(className,methodName,"621");
- return null;
- }
- // Check if there is a need to send a ping to keep the session alive.
- // Note this check is done before processing messages. If not done first
- // an app that only publishes QoS 0 messages will prevent keepalive processing
- // from functioning.
- checkForActivity();
- // Now process any queued flows or messages
- if (!pendingFlows.isEmpty()) {
- // Process the first "flow" in the queue
- result = (MqttWireMessage)pendingFlows.elementAt(0);
- pendingFlows.removeElementAt(0);
- if (result instanceof MqttPubRel) {
- inFlightPubRels++;
- //@TRACE 617=+1 inflightpubrels={0}
- log.fine(className,methodName,"617", new Object[]{new Integer(inFlightPubRels)});
- }
- checkQuiesceLock();
- } else if (!pendingMessages.isEmpty()) {
- // If the inflight window is full then messages are not
- // processed until the inflight window has space.
- if (actualInFlight < this.maxInflight) {
- // The in flight window is not full so process the
- // first message in the queue
- result = (MqttWireMessage)pendingMessages.elementAt(0);
- pendingMessages.removeElementAt(0);
- actualInFlight++;
- //@TRACE 623=+1 actualInFlight={0}
- log.fine(className,methodName,"623",new Object[]{new Integer(actualInFlight)});
- } else {
- //@TRACE 622=inflight window full
- log.fine(className,methodName,"622");
- }
- }
- }
- }
- return result;
- }
大致就是阻塞式获取消息,在一个心跳时间内如果没有消息就一直阻塞,超过心跳间隔,自动往队列中加入心跳包 MqttPingReq.
由此可以看出 CommsSender 发送的消息主要是从 ClientState 这个类中get 出来,而ClientState 这个类的作用在上面也说过:
保存正在发布的消息和将要发布的消息的状态信息,对应状态下消息进行必要的处理。
处理方式参见MQTT协议中客户端与服务器connent public unsubscribe,subscribe等消息的交互方式,
我们来看下这个类的主要成员 :
- /**
- * The core of the client, which holds thestate information for pending and
- * in-flight messages.
- *
- * Messages that have been accepted fordelivery are moved between several objects
- * while being delivered.
- *
- * 1) When the client is not running messagesare stored in a persistent store that
- * implements the MqttClientPersistent Interface.The default is MqttDefaultFilePersistencew
- * which stores messages safely across failuresand system restarts. If no persistence
- * is specified there is a fall back toMemoryPersistence which will maintain the messages
- * while the Mqtt client isinstantiated.
- *
- * 2) When the client or specificallyClientState is instantiated the messages are
- * read from the persistent store into:
- * - outboundqos2 hashtable if a QoS 2 PUBLISH orPUBREL
- * - outboundqos1 hashtable if a QoS 1 PUBLISH
- * (see restoreState)
- *
- * 3) On Connect, copy messages from the outboundhashtables to the pendingMessages or
- * pendingFlows vector in messageidorder.
- * - Initial message publish goes onto the pendingmessagesbuffer.
- * - PUBREL goes onto the pendingflows buffer
- * (see restoreInflightMessages)
- *
- * 4) Sender thread reads messages from the pendingflowsand pendingmessages buffer
- * one at a time. The message is removed from the pendingbufferbut remains on the
- * outbound* hashtable. The hashtable is the place where thefull set of outstanding
- * messages are stored in memory. (Persistenceis only used at start up)
- *
- * 5) Receiver thread - receives wire messages:
- * - if QoS 1 thenremove from persistence and outboundqos1
- * - if QoS 2 PUBRECsend PUBREL. Updating the outboundqos2 entry with the PUBREL
- * andupdate persistence.
- * - if QoS 2 PUBCOMPremove from persistence and outboundqos2
- *
- * Notes:
- * because of the multithreaded natureof the client it is vital that any changes to this
- * class take concurrency into account. For instance as soon as a flow / message isput on
- * the wire it is possible for the receivingthread to receive the ack and to be processing
- * the response before the sending side hasfinished processing. For instance aconnect may
- * be sent, the conack received beforethe connect notify send has been processed!
- *
- */
大致意思就是,程序已运行,但是消息链路还没有开启的情况下,我们从通过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函数
- public void send(MqttWireMessage message, MqttToken token) throws MqttException {
- final String methodName = "send";
- if (message.isMessageIdRequired() && (message.getMessageId() == 0)) {
- message.setMessageId(getNextMessageId());
- }
- if (token != null ) {
- try {
- token.internalTok.setMessageID(message.getMessageId());
- } catch (Exception e) {
- }
- }
- if (message instanceof MqttPublish) {
- synchronized (queueLock) {
- if (actualInFlight >= this.maxInflight) {
- //@TRACE 613= sending {0} msgs at max inflight window
- log.fine(className, methodName, "613", new Object[]{new Integer(actualInFlight)});
- throw new MqttException(MqttException.REASON_CODE_MAX_INFLIGHT);
- }
- MqttMessage innerMessage = ((MqttPublish) message).getMessage();
- //@TRACE 628=pending publish key={0} qos={1} message={2}
- log.fine(className,methodName,"628", new Object[]{new Integer(message.getMessageId()), new Integer(innerMessage.getQos()), message});
- switch(innerMessage.getQos()) {
- case 2:
- outboundQoS2.put(new Integer(message.getMessageId()), message);
- persistence.put(getSendPersistenceKey(message), (MqttPublish) message);
- break;
- case 1:
- outboundQoS1.put(new Integer(message.getMessageId()), message);
- persistence.put(getSendPersistenceKey(message), (MqttPublish) message);
- break;
- }
- tokenStore.saveToken(token, message);
- pendingMessages.addElement(message);
- queueLock.notifyAll();
- }
- } else {
- //@TRACE 615=pending send key={0} message {1}
- log.fine(className,methodName,"615", new Object[]{new Integer(message.getMessageId()), message});
- if (message instanceof MqttConnect) {
- synchronized (queueLock) {
- // Add the connect action at the head of the pending queue ensuring it jumps
- // ahead of any of other pending actions.
- tokenStore.saveToken(token, message);
- pendingFlows.insertElementAt(message,0);
- queueLock.notifyAll();
- }
- } else {
- if (message instanceof MqttPingReq) {
- this.pingCommand = message;
- }
- else if (message instanceof MqttPubRel) {
- outboundQoS2.put(new Integer(message.getMessageId()), message);
- persistence.put(getSendConfirmPersistenceKey(message), (MqttPubRel) message);
- }
- else if (message instanceof MqttPubComp) {
- persistence.remove(getReceivedPersistenceKey(message));
- }
- synchronized (queueLock) {
- if ( !(message instanceof MqttAck )) {
- tokenStore.saveToken(token, message);
- }
- pendingFlows.addElement(message);
- queueLock.notifyAll();
- }
- }
- }
- }
- public void run() {
- final String methodName = "run";
- MqttToken token = null;
- while (running && (in != null)) { //无限循环
- try {
- // @TRACE 852=network read message
- log.fine(className, methodName, "852");
- //阻塞式读取消息
- MqttWireMessage message = in.readMqttWireMessage();
- log("Receiver 852 message:" + message.toString());
- if (message instanceof MqttAck) {
- token = tokenStore.getToken(message);
- if (token != null) {
- synchronized (token) {
- // Ensure the notify processing is done under a lock
- // on the token
- // This ensures that the send processing can
- // complete before the
- // receive processing starts! ( request and ack and
- // ack processing
- // can occur before request processing is complete
- // if not!
- //通知回复确认消息
- clientState.notifyReceivedAck((MqttAck) message);
- }
- } else {
- // It its an ack and there is no token then something is
- // not right.
- // An ack should always have a token assoicated with it.
- throw new MqttException(MqttException.REASON_CODE_UNEXPECTED_ERROR);
- }
- } else {
- //通知有新的消息达到,我们进入此查看
- // A new message has arrived
- clientState.notifyReceivedMsg(message);
- }
- } catch (MqttException ex) {
- // @TRACE 856=Stopping, MQttException
- log.fine(className, methodName, "856", null, ex);
- log("Receiver 856:exception->" + ex.toString());
- running = false;
- // Token maybe null but that is handled in shutdown
- clientComms.shutdownConnection(token, ex);
- } catch (IOException ioe) {
- // @TRACE 853=Stopping due to IOException
- log.fine(className, methodName, "853");
- log("Receiver 853:exception->" + ioe.getLocalizedMessage());
- log("Receiver 853:exception->" + ioe.toString());
- running = false;
- // An EOFException could be raised if the broker processes the
- // DISCONNECT and ends the socket before we complete. As such,
- // only shutdown the connection if we're not already shutting
- // down.
- if (!clientComms.isDisconnecting()) {
- clientComms.shutdownConnection(token, new MqttException(
- MqttException.REASON_CODE_CONNECTION_LOST, ioe));
- } // else {
- }
- }
- // @TRACE 854=<
- log.fine(className, methodName, "854");
- }
- 点击进入 clientState.notifyReceivedMsg(message)
- protected void notifyReceivedMsg(MqttWireMessage message) throws MqttException {
- final String methodName = "notifyReceivedMsg";
- this.lastInboundActivity = System.currentTimeMillis();
- // @TRACE 651=received key={0} message={1}
- log.fine(className, methodName, "651", new Object[] {
- new Integer(message.getMessageId()), message });
- if (!quiescing) {
- if (message instanceof MqttPublish) {
- MqttPublish send = (MqttPublish) message;
- switch (send.getMessage().getQos()) {
- case 0:
- case 1:
- if (callback != null) {
- callback.messageArrived(send);
- }
- break;
- case 2:
- persistence.put(getReceivedPersistenceKey(message),
- (MqttPublish) message);
- inboundQoS2.put(new Integer(send.getMessageId()), send);
- this.send(new MqttPubRec(send), null);
- }
- } else if (message instanceof MqttPubRel) {
- MqttPublish sendMsg = (MqttPublish) inboundQoS2
- .get(new Integer(message.getMessageId()));
- if (sendMsg != null) {
- if (callback != null) {
- callback.messageArrived(sendMsg);
- }
- } else {
- // Original publish has already been delivered.
- MqttPubComp pubComp = new MqttPubComp(message
- .getMessageId());
- this.send(pubComp, null);
- }
- }
- }
- }
- 这里可以看出,如果是Qos 1的消息或者Qos 2 MqttPubRel,我们直接回调告诉消息已到达,点击进入 callback.messageArrived(sendMsg);
- public void messageArrived(MqttPublish sendMessage) {
- final String methodName = "messageArrived";
- if (mqttCallback != null) {
- // If we already have enough messages queued up in memory, wait
- // until some more queue space becomes available. This helps
- // the client protect itself from getting flooded by messages
- // from the server.
- synchronized (spaceAvailable) {
- if (!quiescing && messageQueue.size() >= INBOUND_QUEUE_SIZE) {
- try {
- // @TRACE 709=wait for spaceAvailable
- log.fine(className, methodName, "709");
- spaceAvailable.wait();
- } catch (InterruptedException ex) {
- }
- }
- }
- if (!quiescing) {
- messageQueue.addElement(sendMessage);
- // Notify the CommsCallback thread that there's work to do...
- synchronized (workAvailable) {
- // @TRACE 710=new msg avail, notify workAvailable
- log.fine(className, methodName, "710");
- workAvailable.notifyAll();
- }
- }
- }
- }
- 所做的操作就是将数据插入到 消息队列中,然后唤醒 workAvailable 这个锁,在 CommsCallback类中所有这个锁对应的地方,可以查看到
- public void run() {
- final String methodName = "run";
- while (running) {
- try {
- // If no work is currently available, then wait until there is
- // some...
- try {
- synchronized (workAvailable) {
- if (running & messageQueue.isEmpty() && completeQueue.isEmpty()) {
- // @TRACE 704=wait for workAvailable
- log.fine(className, methodName, "704");
- workAvailable.wait();
- }
- }
- } catch (InterruptedException e) {
- }
- if (running) {
- // Check for deliveryComplete callbacks...
- if (!completeQueue.isEmpty()) {
- // First call the delivery arrived callback if needed
- MqttToken token = (MqttToken) completeQueue.elementAt(0);
- handleActionComplete(token);
- completeQueue.removeElementAt(0);
- }
- // Check for messageArrived callbacks...
- if (!messageQueue.isEmpty()) {
- // Note, there is a window on connect where a publish
- // could arrive before we've
- // finished the connect logic.
- MqttPublish message = (MqttPublish) messageQueue.elementAt(0);
- handleMessage(message);
- messageQueue.removeElementAt(0);
- }
- }
- if (quiescing) {
- clientState.checkQuiesceLock();
- }
- synchronized (spaceAvailable) {
- // Notify the spaceAvailable lock, to say that there's now
- // some space on the queue...
- // @TRACE 706=notify spaceAvailable
- log.fine(className, methodName, "706");
- spaceAvailable.notifyAll();
- }
- } catch (Throwable ex) {
- // Users code could throw an Error or Exception e.g. in the case
- // of class NoClassDefFoundError
- // @TRACE 714=callback threw exception
- log.fine(className, methodName, "714", null, ex);
- running = false;
- clientComms.shutdownConnection(null, new MqttException(ex));
- }
- }
- }
三、总结
ClientState 类中, pendingMessages容器存放MqttPubish消息,而pendingFlows消息则存放 MqttPubRel,MqttConnect,MqttPingReq,MqttAck等
Send 方法将消息放入到容器中,同时唤醒等待发送的线程。
CommsSender 这个发送线程 通过 ClientStatele类get() 方法等待 pendingMessages和pendingFlows 这个这两个队列中放入消息,
如果有消息放入,且同时被唤醒,那么就执行消息发送操作。
CommsSender 接收线程中阻塞式获取消息根据不通的消息类型已经Qos level,通过CommsCallback及ClientStatele中notifyReceivedMsg 来执行相应的操作。