一、概述
前一篇博客(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方法,我已经有消息放入队列了!!!
}
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");
}
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());接口回调。
本文深入分析了EclipsePaho库中用于实现MQTT协议的CommsSender和CommsReceiver类,重点阐述了如何通过Java同步技术将同步的Socket通信异步化,以及这一过程与生产者-消费者模式的紧密联系。通过实例代码解读,详细介绍了消息队列、令牌存储和通知机制的工作原理,展示了如何高效地在客户端与服务器间传输消息。

2522

被折叠的 条评论
为什么被折叠?



