[开源代码阅读]物联网协议MqTT的一份经典实现

1,544 阅读3分钟

Paho是什么?

Paho是Eclipse IoT开源项目的一个子项目。Paho项目提供了开源可靠的开放标准消息协议的实现,目标是为机器到机器和物联网提供新的、存在的和新兴的应用程序。

Paho是一个开源的MQTT客户端SDK,准确来说是一组,它包含各种语言的实现版本,比如C、Java、Python、javascript、golang等,其中paho.mqtt.java是Java语言的实现版本,这个就是本文的主角。

Paho怎么用?

Paho的API设计的非常清晰简洁。MQTT的发布者和订阅者都是客户端,所以首先要创建一个客户端,然后连接到代理服务器,接下来就可以订阅或者发布消息了。

导入paho包,以maven工程为例子,在pom.xml文件加入下面的依赖

<dependency>
    <groupId>org.eclipse.paho</groupId>
    <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
    <version>1.2.5</version>
</dependency>

创建客户端

// 代理服务器地址(服务端)
String broker = "tcp://127.0.0.1:1883";
String clientId = "mqtt-client-1";
MemoryPersistence persistence = new MemoryPersistence();
MqttClient client = new MqttClient(broker, clientId, persistence);
client.setCallback(new MqttCallback() {
    public void connectionLost(Throwable throwable) {

    }

    public void messageArrived(String s, MqttMessage mqttMessage) throws Exception {
        System.out.println("receive message:" + s + " " + new String(mqttMessage.getPayload()));
    }

    public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {

    }
});

连接代理服务器

MqttConnectOptions options = new MqttConnectOptions();
options.setUserName("admin");
options.setPassword("123456".toCharArray());
client.connect(options);

发布消息

String content = "hello mqtt";
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(content.getBytes());
client.publish("jack", mqttMessage);

订阅消息

String topic = "testtopic";
client.subscribe(topic);

当有'testtopic'主题的消息到达代理服务器后,代理服务器会转发消息给本客户端,最终表现为回调MqttCallback.messageArrived(String topic, MqttMessage message)方法。

Paho设计原理

通过socket实现的通讯,会涉及到从InputStream读取数据和向OutputStream写入数据,实现这样的双向通讯需要使用到多线程技术,还需要考虑CPU资源的合理化利用、生产者消费者等问题。

tmp1.png

Paho用了四个线程来实现这个通讯过程,读和写分别使用两个线程。读和写各使用一个消息队列来存储收发的消息,用于消息缓存和中转,同时使用Java的Object的wait()/notify()来阻塞/唤醒线程,解决生产者消费者问题和CPU资源浪费问题。

tmp-mqtt-protcol.png

协议实现主要集中在org.eclipse.paho.client.mqttv3.internal.wire包,协议一共定义了15种类型的消息,使用面向对象编程,经过抽象封装,MqttWireMessage是消息的基类。

1=Invalid protocol version
2=Invalid client ID
3=Broker unavailable
4=Bad user name or password
5=Not authorized to connect

Paho打印的信息支持多语言。多语言的Message是使用Java的ResourceBundle实现的。

tmp-api.png

API。库提供给外部使用的接口。

从源代码角度看Paho

源代码的解读,关注网络通讯模块,也就是对MqttClient.connect()做一下展开。

org\eclipse\paho\client\mqttv3\MqttClient.java

public void connect(MqttConnectOptions options) throws MqttSecurityException, MqttException {
    aClient.connect(options, null, null).waitForCompletion(getTimeToWait());
}

然后类之间一路调用到ClientComms类,这个类是一个工具类,用来处理客户端和服务端的通讯。

org\eclipse\paho\client\mqttv3\internal\ClientComms.java

public void connect(MqttConnectOptions options, MqttToken token) throws MqttException {
    final String methodName = "connect";
    synchronized (conLock) {
        if (isDisconnected() && !closePending) {
            ... ...
            ConnectBG conbg = new ConnectBG(this, token, connect, executorService);
            // 启动ConnectBG线程,即执行ConnectBG.run()方法
            conbg.start();
        }
        ... ...
    }
}

private class ConnectBG implements Runnable {
    public void run() {
        try {
            ... ...
            // 网络通讯的关键流程就在这里
            NetworkModule networkModule = networkModules[networkModuleIndex];
            // 1 创建socket并连接server
            networkModule.start();
            receiver = new CommsReceiver(clientComms, clientState, tokenStore, networkModule.getInputStream());
            // 2 启动消息接收线程,不断从socket的inputstream读取server发来的数据
            receiver.start("MQTT Rec: "+getClient().getClientId(), executorService);
            sender = new CommsSender(clientComms, clientState, tokenStore, networkModule.getOutputStream());
            // 3 启动消息发送线程,不断从vector读取用户publis的消息,写入到socket的outputstream
            sender.start("MQTT Snd: "+getClient().getClientId(), executorService);
            // 4 启动callback线程,不断从vector读取消息接收线程写入的数据,回调用户代码
            callback.start("MQTT Call: "+getClient().getClientId(), executorService);
            internalSend(conPacket, conToken);
        } 
        ... ...
    }
}

小结

Paho是一个很小的开源项目,java文件只有96个,目标就是实现MqTT协议并封装接口提供给用户使用。项目很小,所以门槛就比较低,都能看的懂。功能虽然简单,但是很多共性的问题都会处理到,比如经典的生产者消费者问题、同步异步问题、多线程问题等,阅读源码的时候会有一定启发。另外,项目是eclipse大厂出品,可以学习大厂的代码设计。