一、概述
前一篇博客(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方法。
[java] view plaincopy
- 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()方法进入之后,方法的部分内容如下:
[java] view plaincopy
- 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()方法,部分内容如下:
[java] view plaincopy
- 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()方法
[java] view plaincopy
- 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);方法,部分代码如下:
[java] view plaincopy
- 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);方法,
[java] view plaincopy
- 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());接口回调。