MQTT协议实现Eclipse Paho学习总结二

本文深入分析了EclipsePaho库中用于实现MQTT协议的CommsSender和CommsReceiver类,重点阐述了如何通过Java同步技术将同步的Socket通信异步化,以及这一过程与生产者-消费者模式的紧密联系。通过实例代码解读,详细介绍了消息队列、令牌存储和通知机制的工作原理,展示了如何高效地在客户端与服务器间传输消息。

一、概述

前一篇博客(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方法。
public void run() {
		final String methodName = "run";
		MqttWireMessage message = null;
		while (running && (out != null)) {//超级无限循环
			try {
				message = clientState.get();//主要看这里获取message时进行了阻塞,即clientState.get()方法没有获得消息的时候,代码一直处理阻塞状态,不会一直无限循环!
				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);
								out.flush();
								clientState.notifySent(message);//通知已经发送了一个消息
							}
						}
					}
				} else { // null message
					//@TRACE 803=get message returned null, stopping}
					log.fine(className,methodName,"803");

					running = false;
				}
			} catch (MqttException me) {
				handleRunException(message, me);
			} catch (Exception ex) {		
				handleRunException(message, ex);	
			}
		} // end while
		
		//@TRACE 805=<
		log.fine(className, methodName,"805");

	}
点击message = clientState.get();中get()方法进入之后,方法的部分内容如下:
synchronized (queueLock) {
			while (result == null) {		
				if (pendingMessages.isEmpty() && pendingFlows.isEmpty()) {
					try {
						long ttw = getTimeUntilPing();
						//@TRACE 644=nothing to send, wait for {0} ms
						log.fine(className,methodName, "644", new Object[] {new Long(ttw)});						
 
						queueLock.wait(getTimeUntilPing());//如果pendingMessages队列和pendingFlows队列为空,则放弃queueLock锁,等待,而这个等待时间是有限的,如果长时间没有发送消息,同时等待的时间超过了心跳包发送的时间,那么就往下执行,根据实际情况发送心跳包或者消息。
					} catch (InterruptedException e) {
					}
				}
ClientState中还有一个send()方法,部分内容如下:
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();//通知get方法,我已经有消息放入队列了!!!
			}

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

2.2 CommsReceiver


再来看一下CommsReceiver的run()方法
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();//这里,因为socket.getInputStream()一直在阻塞,如果没有消息是读不到message的,因此在这里的while循环也没有无限制的运行下去,只有在有消息的时候才往下走。socket默认是阻塞的,就是在读的时候如果读不到资源就会一直等待,直到超时(如果设置了超时时间的话),如果服务端和客户端都在读的话而没有写的话就会一直阻塞。你可以使用SocketChannel,设置socket的通道,使其变成非阻塞的。
				
				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);
				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");

				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);方法,部分代码如下:
if (message instanceof MqttPublish) {
				MqttPublish send = (MqttPublish) message;
				switch (send.getMessage().getQos()) {
				case 0:
				case 1:
					if (callback != null) {
						callback.messageArrived(send);
					}
					break;
我们点击进入callback.messageArrived(send);方法,
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();
				}
			}
		}
	}
在这里,同样使用了生产者-消费者模式,在run方法里,我们可以看到其调用了handleMessage,在这个方法里面调用了mqttCallback.messageArrived(destName, publishMessage.getMessage());接口回调。


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值