本章用于记录实现前端与设备的通信功能所面临的问题和当前解决的思路。
PS:首先声明下。因为本人后端能力不强,因此这个设计肯定不是非常好的。但是我尽力能想到的方法了。望各位大佬还可以提供宝贵的建议。
由于目前client
和server
代码是分开编写的,目的是不想让MQTT和express的代码互相嵌套(2年前写的互相套什么app.post()
套一堆client.on
然后client.on
又套数据库的查询...,自己都不知道当时是怎么跑得起来的。并且用之前的想法测试,都失败了(迷惑而神奇?!))但是目前面临的问题:router
的 res
需要携带数据返回。如何将MQTT的message
携带的消息放入 res
并且保证不会返回出错。这个问题还需要优化。希望各位大佬可以指点!
需求
在本系统中,针对用户发送控制设备或读取设备信息的GET/POST请求后,服务端需要发送MQTT协议给硬件,同时也需要监听硬件返回的信息从而返回给用户。整体流程如图所示(画的有点抽象)
举个例子:当用户点击获取设备温度的按钮时,会发生GET/POST
请求给我们使用Express
搭建的服务端(对应图中的1)。服务端接收到这个请求后,会发送特定主题的对应的MQTT消息给broker
,由broker
转发根据主题转发给设备(对应图中的2与3)。当设备收到该主题的消息时(假设主题是:我需要你告诉我目前你的温度),会发布一条消息给broker
(假设主题是:这是设备目前的温度)。那么此时client
就需要提前订阅这个主题的消息,以便可以接收到。当该消息通过broker
转发给client
后(对应5),我们的服务端需要获取到这个消息的内容,然后转发给前端(对应6)。
目前想到的思路
其实 1-6都好解决,但是问题的关键在于client
收到了MQTT消息后,如何传递给服务端? 在MQTT中,client
是通过client.on("messgae", (topic, message) => { })
方法监听收到的消息。也就是说我们只能在这个回调函数中拿到设备发送过来的消息。且由于我们的client.js
与 app.js
是两个独立的文件。因此我目前想并尝试了4种方法:
- 将
client
导出 (也就是整个逻辑仅有一个client统一接收和发送消息) - 在
app.js
中编写对应获取数据的函数,将函数放入client.on("message")
监听事件的回调函数中。也就是我们服务端的client
始终监听(on 方法),当收到用户的请求时,就发布一个消息。 - 将
client
与服务端之间再弄一个http通信。也就是user - server - client - broker - device
- 每次遇到这种请求的时候,
Server
自动为用户创建一个client,依次让client
上线、发布(对应图中的2)、订阅、监听(对应图中的5)。并在失败/成功的时候返回这个消息(对应6),然后使用end()主动将其下线。(这也是目前采用的方法。)即我们收到了用户需要控制设备的消息,然后new 一个client
专门针对这个消息来处理,然后将这个对象收到的消息返回。让res = 这个消息
即可。(这样的好处就是我们的 app.post 请求的整体逻辑和读取数据库一样。都是在回调函数中return res)
目前存在的问题
目前针对这4种方案可能存在的问题:
- 获取到MQTT返回的消息可能会冲突(或者说会多次调用监听函数,需要多次定义
client.on("message")
方法,然后在不同的回调函数中处理 res 的返回)但是当多个用户同时进行这样的操作的时候,因为对于相同的client,其实本质上就是多订阅了更多的消息,而无论在哪里的client.on("message")
都会监听到。从而可能导致发送给用户1的消息错误发给了用户2。此外,后续存数据库的操作也都需要放在client.on方法
中。 - 这个函数按道理只会触发一次(用户发送请求的时候触发)。但放到 on 里面会多次执行,也就是每当收到消息的时候,这个函数都会被触发。并且如何去确定是哪一个 res 对象,这个好像不太能实现。
- 延迟可能会提升,多了一个Http请求的时间。且需要判断下返回给哪个请求。
- 对broker的负载变高了,多个client一直上下线,发布订阅等。资源可能有点浪费
目前对于方案4的代码:
我们把整个流程(2-6)可以统一封装成一个函数sendMQTT
:传入的参数包括:发布的主题和消息(对应流程3),订阅的主题(对应流程5),以及当前请求所要返回的res对象。我们在这个函数里获取MQTT的消息数据并返回。
js
const mqtt = require("mqtt");
function sendMQTT(pubTopic, subTopic, message, res) {
const client = mqtt.connect("mqtt://localhost:9000");
client.publish(pubTopic, message, (err) => {
if (err) {
res.sendStatus(500);
client.end();
}
});
client.subscribe(subTopic, (err) => {
if (err) {
res.sendStatus(502);
client.end();
}
});
client.on("message", (topic, message) => {
if (topic === subTopic) {
res.json(message);
}
client.end();
});
}
module.exports = sendMQTT;
然后是服务端获取前端请求的路由:这里我们设置前端传来的是id
js
// 用于测试客户端与服务端之间的通信
const express = require("express");
const mqtt = require("mqtt");
const sendMQTT = require("../utils/sendMQTT");
const app = express();
app.get("/get/:id", (req, res) => {
const id = req.params.id;
sendMQTT(`pub${id}`, `sub${id}`, `getDeviceData`, res);
});
app.post("/post", (req, res) => {
console.log(req);
res.send("Hello, World!");
});
// app.use("/devices", deviceRouter);
app.listen(3000, () => {
console.log("Server is running on port 3000");
});
module.exports = app;
broker 还是和之前一样。 我们先启动broker,然后启动 express 服务端。在 postman 中模拟。流程如下:
-
postman 创建一个 MQTT 连接broker,并订阅所需要的主题 (
pubTopic
) -
使用 postman 发送对应的get请求。
-
使用 postman 发布消息(主题为
subTopic
) -
通过两个 postman 查看收到的消息。