MQTT,是:

  • 轻量级的消息订阅和发布(publish/subscribe)协议
  • 建立在TCP/IP协议之上

IoT,internet of things,物联网,MQTT在这方面应用较多。

官方网站:http://mqtt.org/

MQTT协议是针对如下情况设计的:

  • M2M(Machine to Machine) communication,机器端到端通信,比如传感器之间的数据通讯
  • 因为是Machine to Machine,需要考虑:
    • Machine,或者叫设备,比如温度传感器,硬件能力很弱,协议要考虑尽量小的资源消耗,比如计算能力和存储等
    • M2M可能是无线连接,网络不稳定,带宽也比较小

MQTT协议的架构,用一个示例说明。比如有1个温度传感器(1个Machine),2个小的显示屏(2个Machine),显示屏要显示温度传感器的温度值。

可通过MQTT V3.1 Protocol Specification查阅详细规范的细节。

显示器需要先通过MQTT协议subscribe(订阅)一个比如叫temperature的topic(主题):

当温度传感器publish(发布)温度数据,显示器就可以收到了:

注:以上两张图,取自MQTT and CoAP, IoT Protocols

协议里还有2个主要的角色:

  • client,客户端
  • broker,服务器端

它们是通过TCP/IP协议连接的。

因为MQTT是协议,所以不能拿来直接用的,就好比HTTP协议一样。需要找实现这个协议的库或者服务器来运行。

这里是官方的Server support

我服务器端使用nodejs开发,因此选择了:

  • MQTT.js:MQTT协议的底层实现库,服务器端很简易,需要自己编写代码才可使用
  • Mosca:在MQTT.js基础上完善的服务器端

MQTT.js最基本使用

安装是很简单的:

npm install mqtt

MQTT.js实现的服务器端

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
var mqtt = require('mqtt');
//{'topicName':[clientObj,clientObj ..]}
var subscribeTopics={};
//创建服务器对象
var server = mqtt.createServer(function(client) {
//建立连接时触发
client.on('connect', function(packet) {
client.connack({returnCode: 0});
});
//客户端发布主题时触发
client.on('publish', function(packet) {
var topic=packet.topic;
var payload=packet.payload;
//如果没有创建空的主题对应的client数组
if(subscribeTopics[topic]==null){
subscribeTopics[topic]=[];
}else{
//遍历该主题下全部client,并逐一发送消息
for(var i in subscribeTopics[topic]){
var client=subscribeTopics[topic][i];
client.publish({
topic: topic,
payload: payload
});
}
}
});
//当客户端订阅时触发
client.on('subscribe', function(packet) {
var topic=packet.subscriptions[0].topic;
//如没有,创建空的主题对应的client数组
if(subscribeTopics[topic]==null){
subscribeTopics[topic]=[];
}
//如果client数组中没有当前client,加入
if(subscribeTopics[topic].indexOf(client)==-1){
subscribeTopics[topic].push(client);
}
 
});
client.on('pingreq', function(packet) {
client.pingresp();
});
client.on('disconnect', function(packet) {
//遍历所有主题,检查对应的数组中是否有当前client,从数组中删除
for (var topic in subscribeTopics){
var index=subscribeTopics[topic].indexOf(client);
if(index>-1){
subscribeTopics[topic].splice(index,1);
}
}
});
});
//监听端口
server.listen(1883);

这是一个最基本的服务器端,消息的存储和查询都需要自己编程处理。

比如你如果需要用redis保存和触发数据,可参考这篇中文文章:node mqtt server (redis pub/sub)

MQTT.js实现的客户端

代码:

1
2
3
4
5
6
7
8
9
10
11
12
var mqtt = require('mqtt');
client = mqtt.createClient(1883, 'localhost');
client.subscribe('testMessage');
client.publish('testMessage', '发布测试信息');
client.on('message', function (topic, message) {
console.log(message);
client.end();
});

写的很简易,订阅了主题,然后向相同主题发布消息,接收到消息后client停止。

使用Mosca

MQTT.js只是实现了最基础的MQTT协议部分,对于服务器端的处理需要自己完成。

有关MQTT.js是否实现了MQTT server,详细的说明,可参见MQTT Server: MQTT.js or Mosca?

正好,Mosca在MQTT基础上实现了这些,它可以:

  • 作为独立运行的MQTT服务器运行
  • 集成到nodejs程序里使用

安装很简单:

npm install mosca bunyan -g

作为独立服务器运行

运行:

mosca -v | bunyan

然后,还可以用我上文的客户端代码运行测试。

集成在自己程序中使用

我考虑的后端持久化,是用MongoDB。Mosca另外几个选项:

  • Redis,缺点是更注重作为缓存,而不适合可靠持久化
  • LevelUp,头一次听说,不打算做技术准备了,是用nodejs的包装起来的LevelDB
  • Memory,使用内存,估计默认的就是这个,不适合我使用的情况

首先要安装mosca的库:

npm install mosca

然后,在本机将mongodb运行起来,应该就可以执行下面的代码了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var mosca = require('mosca')
var settings = {
port: 1883,
backend:{
type: 'mongo',
url: 'mongodb://localhost:27017/mqtt',
pubsubCollection: 'ascoltatori',
mongo: {}
},
persistence:{
factory: mosca.persistence.Mongo,
url: "mongodb://localhost:27017/mosca"
}
};
var server = new mosca.Server(settings);
server.on('ready', function(){
console.log('Mosca server is up and running');
});
server.on('published', function(packet, client) {
console.log('Published', packet.payload);
});

直接运行作者文档中的代码会在多次运行客户端后出现错误,我是参考了他2天前加上的示例代码

作者Matteo Collina生活在意大利的博洛尼亚,写代码很勤奋,这个项目更新很快,是不是说明这个方向(mqtt)很活跃呢?

作者也写了个幻灯片,MQTT and Node.js

MQTT高级问题

keepalive和PING

从这篇文章MQTT协议笔记之连接和心跳

心跳时间(Keep Alive timer)

以秒为单位,定义服务器端从客户端接收消息的最大时间间隔。一般应用服务会在业务层次检测客户端网络是否连接,不是TCP/IP协议层面的 心跳机制(比如开启SOCKET的SO_KEEPALIVE选项)。 一般来讲,在一个心跳间隔内,客户端发送一个PINGREQ消息到服务器,服务器返回PINGRESP消息,完成一次心跳交互,继而等待下一轮。若客户端 没有收到心跳反馈,会关闭掉TCP/IP端口连接,离线。 16位两个字节,可看做一个无符号的short类型值。最大值,2^16-1 = 65535秒 = 18小时。最小值可以为0,表示客户端不断开。一般设为几分钟,比如微信心跳周期为300秒。

下面的代码中我设置的是10秒:

1
2
3
4
5
6
7
8
9
10
11
var mqtt = require('mqtt');
var settings = {
keepalive: 10,
protocolId: 'MQIsdp',
protocolVersion: 3,
clientId: 'client-b',
clean: false
}
client = mqtt.createClient(1883, 'localhost',settings);

可以使用MQTT.js编写简单的服务器代码,观察到服务器端接收到PING请求,并发回PING响应:

1
2
3
4
client.on('pingreq', function(packet) {
client.pingresp();
console.log('pingreq & resp');
});

完整代码上面已经贴过,另见Gist

QoS

QoS在MQTT中有(摘自MQ 遥测传输 (MQTT) V3.1 协议规范):

  • “至多一次”,消息发布完全依赖底层 TCP/IP 网络。会发生消息丢失或重复。这一级别可用于如下情况,环境传感器数据,丢失一次读记录无所谓,因为不久后还会有第二次发送。
  • “至少一次”,确保消息到达,但消息重复可能会发生。
  • “只有一次”,确保消息到达一次。这一级别可用于如下情况,在计费系统中,消息重复或丢失会导致不正确的结果。

MQTT.js只是支持了MQTT协议,并没有支持QoS,也就是说,只支持最低级别的“至多一次”(QoS0)。

Mosca支持QoS0和1,但不支持2,见Add support QOS 2

接收离线消息

我在应用中的一个主要场景是,使用MQTT.js+Mosca做聊天服务器。

默认Mosca是不支持离线消息的,表现的现象是,如果是有人(client-a)先在主题上发布了消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var mqtt = require('mqtt');
var settings = {
keepalive: 10,
protocolId: 'MQIsdp',
protocolVersion: 3,
clientId: 'client-a'
}
client = mqtt.createClient(1883, 'localhost',settings);
client.publish('testMessage', '发布new测试信息0',{qos:1,retain: true});
client.publish('testMessage', '发布new测试信息1',{qos:1,retain: true});
client.publish('testMessage', '发布new测试信息2',{qos:1,retain: true});
client.publish('testMessage', '发布new测试信息3',{qos:1,retain: true});
setTimeout(function(){
client.end();
},1000);

那么另外一个人(client-b),随后订阅,仅能看到最后一条消息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var mqtt = require('mqtt');
var settings = {
keepalive: 10,
protocolId: 'MQIsdp',
protocolVersion: 3,
clientId: 'client-b'
}
client = mqtt.createClient(1883, 'localhost',settings);
client.subscribe('testMessage',{qos:1},function(){
console.log('subscribe ok.');
});
client.on("message", function(topic, payload) {
console.log('message: '+payload);
});

运行结果类似这样:

subscribe ok.
message: 发布new测试信息3

离线消息,需要以下几点:

  • 客户端订阅设置QoS=1
  • 客户端连接属性clean: false,作用是断开连接重连的时候服务器端帮助恢复session,不需要再次订阅

用代码说明以下,先运行这段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var mqtt = require('mqtt');
var settings = {
keepalive: 10,
protocolId: 'MQIsdp',
protocolVersion: 3,
clientId: 'client-b',
clean: false
}
client = mqtt.createClient(1883, 'localhost',settings);
client.subscribe('testMessage',{qos:1},function(){
console.log('subscribe ok.');
client.end();
});

然后执行刚才发布多条消息的代码。再执行下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var mqtt = require('mqtt');
var settings = {
keepalive: 10,
protocolId: 'MQIsdp',
protocolVersion: 3,
clientId: 'client-b',
clean: false
}
client = mqtt.createClient(1883, 'localhost',settings);
client.on("message", function(topic, payload) {
console.log('message: '+payload);
});

运行结果类似这样:

message: 发布new测试信息1
message: 发布new测试信息3
message: 发布new测试信息2
message: 发布new测试信息0

收到消息的顺序是乱的,为什么会这样,其实很好理解,为了小型受限设备以及网络不稳定的情况,消息是不好保证顺序的。

解决办法是发送的消息带时间戳,接收后再做排序。

另外,担心客户端没有做client.end()而非正常退出,那么再次连接是否能恢复session,测试了一下,注释client.end(),没有问题,正常收到多条离线消息。

SSL连接

Mosca支持SSL连接,可根据Nodejs TLS创建公钥私钥。

然后类似这样启动:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var mosca = require('mosca')
var SECURE_KEY = __dirname + '/../../test/secure/tls-key.pem';
var SECURE_CERT = __dirname + '/../../test/secure/tls-cert.pem';
var settings = {
port: 8443,
logger: {
name: "secureExample",
level: 40,
},
secure : {
keyPath: SECURE_KEY,
certPath: SECURE_CERT,
}
};
var server = new mosca.Server(settings);
server.on('ready', setup);
// fired when the mqtt server is ready
function setup() {
console.log('Mosca server is up and running')
}

这部分我没有测试,直接转自Mosca Encryption Support

认证和授权

Mosca Authentication提供了个简易的命令行,可创建账号用于认证并授权。

但是它不适合我的需求场景,我需要自己编写认证和授权的逻辑。

虽然在作者官方网站上未找到,但在问题管理记录中提交了这方面的支持:Authentication & Authorization

有下面两条支持,应该可以写出自己的回调,并集成到Mosca中:

  • add a callback to authorize a publish.
  • add a callback to authorize a subscribe.

不过这块没有写代码,只是大致能确定。

性能问题

MQTT.js并不是完整解决方案,不需要考虑它的性能问题。

说一下Mosca,有一个这方面问题作者的答复,what about mosca’s performance,问问题的还是个中国人,我前面还引用了他的文章。作者基本意思是:

It basically depends on the RAM. On an AWS large instance it can reach
10k concurrent connections, with roughly 10k messages/second.

MQTT结构分析的更多相关文章

  1. 海鑫智圣:物联网漫谈之MQTT协议

    什么是MQTT协议 MQTT(消息队列遥测传输协议)是IBM在1999年专门针对物联网等应用场景来制订的轻量级双向消息传输协议,它主要是为了解决物联网上使用到的设备的互相通信的问题,以及这些设备与后端 ...

  2. 几个MQTT的知识点

    开始正文前需要感谢一下网友“小龙”和emqtt.io群里的网友们的帮助,本人刚刚开始使用MQTT有很多不懂的地方,在emqtt.io群里询问解决方法的时候,“小龙”给我详细的讲解了一些MQTT的知识点 ...

  3. 基于MQTT协议进行应用开发

    官方协议有句如下的话来形容MQTT的设计思想: "It is designed for connections with remote locations where a "sma ...

  4. MQTT(三)-----连接与心跳

    MQTT协议笔记之连接和心跳 - 推酷 http://www.tuicool.com/articles/AFvmee 互联网推送服务原理:长连接+心跳机制(MQTT协议) - clh604的专栏 - ...

  5. Mosquitto搭建Android推送服务(一)MQTT简介

    总体概要: MQTT系列文章分为4部分 1.MQTT简介 2.mosquitto服务器搭建 3.编写Mosquitto的可视化工具 4.使用Mosquitto完成Android推送服务 文章钢要: 对 ...

  6. 【开源】MQTT推送服务器——zer0MqttServer(Java编写)

    目录 说明 功能 如何使用 参考帮助 说明 重要的放前面:V1.0版本是一个非常基础的版本,除了完整的MQTT协议实现外,其他功能什么都没做. MQTT 协议是 IBM 开发的即时通讯协议,相对于 I ...

  7. TCP/IP, WebSocket 和 MQTT

    按照OSI网络分层模型,IP是网络层协议,TCP是传输层协议,而HTTP和MQTT是应用层的协议.在这三者之间, TCP是HTTP和MQTT底层的协议.大家对HTTP很熟悉,这里简要介绍下MQTT.M ...

  8. 云巴:基于MQTT协议的实时通信编程模型

    概要 有人常问,云巴实时通信系统到底提供了一种怎样的服务,与其他提供推送或 IM 服务的厂商有何本质区别.其实,从技术角度分析,云巴与其它同类厂商都是面向开发者的通信服务,宏观的编程模型都是大同小异, ...

  9. MQTT 消息 发布 订阅

    当连接向一个mqtt服务器时,clientId必须是唯一的.设置一样,导致client.setCallback总是走到 connectionLost回调.报connection reset.调查一天才 ...

随机推荐

  1. 使用cocoaPods import导入时没有提示的解决办法

    1.选择target(就是左边你的工程target)—— BuildSettings —— search Paths 下的 User Header Search Paths(如图所示:)    2.双 ...

  2. MySQL的进程状态

    通过show processlist查看MySQL的进程状态,在State列上面的状态有如下这些: Analyzing线程对MyISAM 表的统计信息做分析(例如, ANALYZE TABLE ).c ...

  3. codeforces 686B

    题意:给出一个序列,只允许进行相邻的两两交换,给出使序列变为非降序列的操作方案. 思路:关键点是操作次数不限,冒泡排序. #include<iostream> #include<cs ...

  4. In a Web Application and Mobile (hybrid), how to know which this platform running?

    needed depending on the platform to change the CSS to suit the size of the font. for example the DbG ...

  5. TCP/IP 协议中的编址

    TCP/IP协议的互联网需要用到四个级别的地址:物理地址.逻辑地址.端口地址和特定应用地址 一.物理地址 物理地址称为链路地址,是由接点所在的局域网或广域网为该结点指定的地址. 这种地址的长度和格式随 ...

  6. Spring MVC的工作原理和机制

    Spring  MVC的工作原理和机制 参考: springMVC 的工作原理和机制 - 孤鸿子 - 博客园https://www.cnblogs.com/zbf1214/p/5265117.html ...

  7. BEC listen and translation exercise 33

    In fact, if it is a really hot day, like the sort of weather we had last summer, you are advised to ...

  8. 洛谷 2312 / bzoj 3751 解方程——取模

    题目:https://www.luogu.org/problemnew/show/P2312 https://www.lydsy.com/JudgeOnline/problem.php?id=3751 ...

  9. Linux 系统通过 Squid 配置实现代理上网

    本文转载自:https://help.aliyun.com/knowledge_detail/41342.html Squid 介绍 Squid 是一个缓存 Internet 数据的软件,其接收用户的 ...

  10. Poj 1316 Self Numbers(水题)

    一.Description In 1949 the Indian mathematician D.R. Kaprekar discovered a class of numbers called se ...