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

一、主要类介绍


二、重点类代码分析

对于长连接,一般是直接从消息的接收和发送类开始读,上面知道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等消息的交互方式,

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


查看ClientState 类说明

/**
 * 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();
				}
			}
		}
	}
接下来我们在来查看下CommsReceiver 接收端的代码

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));
			}
		}
	}


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

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


    

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值