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. hd acm1425

    给你n个整数,请按从大到小的顺序输出其中前m大的数. 先看代码: #include<stdio.h>#include<string.h>#define MAX 1100000i ...

  2. HTML URL 编码:请参阅:http://www.w3school.com.cn/tags/html_ref_urlencode.html

    http://www.w3school.com.cn/tags/html_ref_urlencode.html

  3. sqlserver 2008 创建数据库的时候不是空库,里面总有数据的解决办法

    SqlServer2008 里面有个系统数据库 Model 数据库,在创建新数据库的时候,会以它为模板创建,所以如果发现你的Model数据库比较大,说明里面有很多模板数据.此时如果需要去创建没有数据的 ...

  4. TCP/IP 协议中的编址

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

  5. codevs1281 Xn数列

    题目描述 Description 给你6个数,m, a, c, x0, n, g Xn+1 = ( aXn + c ) mod m,求Xn m, a, c, x0, n, g<=10^18 输入 ...

  6. DIV CSS 笔记

    /*针对谷歌浏览器内核支持的CSS样式*/ <style type="text/css"> @media screen and (-webkit-min-device- ...

  7. Servlet简单增删改查

    前台页面是别人给的. 例子: package cn.itcast.cus.dao; import java.sql.SQLException; import java.util.ArrayList; ...

  8. spring学习-1

    spring框架的组件结构图 IOC(Inversion of Control):反转控制,思想是反转资源获取方向,传统的资源查找方向是组件向容器发起请求资源查找,作为回应,容器适时的返回资源,IOC ...

  9. codeforces 651A A. Joysticks (模拟)

    A. Joysticks time limit per test 1 second memory limit per test 256 megabytes input standard input o ...

  10. IDEA发布运行web项目(曾经遇到的项目启动报404)

    问题: 配置: 配置 facets ,此步很重要,配置 web resource directories ,路径配错,就会报 404 ,一定要定位到项目根目录,也就是下面有整个项目源码的地方 下面是配 ...