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

来源:互联网 发布:耐克淘宝正品店 编辑:程序博客网 时间:2024/06/05 05:31

一、主要类介绍


二、重点类代码分析

对于长连接,一般是直接从消息的接收和发送类开始读,上面知道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 errorif (!(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 connectingif (!connected &&  (pendingFlows.isEmpty() || !((MqttWireMessage)pendingFlows.elementAt(0) instanceof MqttConnect))) {//@TRACE 621=no outstanding flows and not connectedlog.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 messagesif (!pendingFlows.isEmpty()) {// Process the first "flow" in the queueresult = (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 queueresult = (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 fulllog.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 windowlog.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 messagelog.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 arrivedclientState.notifyReceivedMsg(message);}} catch (MqttException ex) {// @TRACE 856=Stopping, MQttExceptionlog.fine(className, methodName, "856", null, ex);log("Receiver 856:exception->" + ex.toString());running = false;// Token maybe null but that is handled in shutdownclientComms.shutdownConnection(token, ex);} catch (IOException ioe) {// @TRACE 853=Stopping due to IOExceptionlog.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 spaceAvailablelog.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 workAvailablelog.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 workAvailablelog.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 neededMqttToken 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 spaceAvailablelog.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 exceptionlog.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 来执行相应的操作。


    

0 0
原创粉丝点击