MQTT EMQX中如何监听客户端上下线?
最近自助洗车项目改用了全新的客户端通讯方案MQTT,发现了一些问题。
之前使用Websocket方式,服务端在发送入场开门命令的时候如果try catch到错误,认为门店电路或网络出现问题,就不会生成订单。而改用MQTT后,开门指令是发送给EMQX服务端,只能检测到在发送这一层不出现问题,而如果客户端没有订阅服务端主题的情况下,并不能接收开门指令,会造成的现象就是:订单生成了,门却没开。遂有了以下的解决方案。
ps:本文重在解决思路,而非代码。如果尚未接触过EMQX,可能会影响您的观看。
最初方案1:$SYS 系统主题
提示
EMQX 默认只允许本机的 MQTT 客户端订阅 $SYS 主题,请参照 内置 ACL 修改发布订阅 ACL 规则。
监听客户端上下线事件
$SYS` 主题前缀: `$SYS/brokers/${node}/clients/
主题 (Topic) | 说明 |
---|---|
${clientid}/connected | 上线事件。当任意客户端上线时,EMQX 就会发布该主题的消息 |
${clientid}/disconnected | 下线事件。当任意客户端下线时,EMQX 就会发布该主题的消息 |
connected
事件消息的 Payload 解析成 JSON 格式如下:
{
"username": "foo",
"ts": 1625572213873,
"sockport": 1883,
"proto_ver": 4,
"proto_name": "MQTT",
"keepalive": 60,
"ipaddress": "127.0.0.1",
"expiry_interval": 0,
"connected_at": 1625572213873,
"connack": 0,
"clientid": "emqtt-8348fe27a87976ad4db3",
"clean_start": true
}
disconnected
事件消息的 Payload 解析成 JSON 格式如下:
{
"username": "foo",
"ts": 1625572213873,
"sockport": 1883,
"reason": "tcp_closed",
"proto_ver": 4,
"proto_name": "MQTT",
"ipaddress": "127.0.0.1",
"disconnected_at": 1625572213873,
"clientid": "emqtt-8348fe27a87976ad4db3"
}
思路:
- 订阅主题 S Y S / b r o k e r s / SYS/brokers/ SYS/brokers/{node}/clients/${clientid}/(connected/disconnected)
- 订阅后客户端上下线均会发送上下线信息,然后在接收消息端处理业务逻辑即可。
缺点:
- 该方案因为是订阅主题,如果emqx服务崩溃导致重连后,因为订阅该主题的本身需要重连,而在重连期间或许其他客户端已经连接而没能收到主题消息(这里有点绕),导致并不一定能接收到topic,可靠性很差。
- 实际生成中没法使用,而且官方也不推荐这种方式。当时想着这样的方式简单,最后实验结果是根本没法使用。
最终解决方案:WebHook
WebHook 是由 emqx_web_hook (opens new window)插件提供的 将 EMQX 中的钩子事件通知到某个 Web 服务 的功能。
WebHook 的内部实现是基于 钩子,但它更靠近顶层一些。它通过在钩子上的挂载回调函数,获取到 EMQX 中的各种事件,并转发至 emqx_web_hook 中配置的 Web 服务器。
WebHook 对于事件的处理是单向的,它仅支持将 EMQX 中的事件推送给 Web 服务,并不关心 Web 服务的返回。 借助 Webhook 可以完成设备在线、上下线记录,订阅与消息存储、消息送达确认等诸多业务。
总结就是,当客户端上线后,给指定的web地址发送一条信息。
如何使用
- Webhook 的配置文件位于
etc/plugins/emqx_web_hook.conf
,配置项的详细说明可以查看 配置项。这个配置非常简单,打开指定文件后,官方已经写好这些了,只需要修改web.hook.url,和在你需要的钩子事件前的#号去掉即可。
web.hook.url = http://127.0.0.1:8080/webhook
web.hook.rule.client.connected.1 = {"action": "on_client_connected"}
web.hook.rule.client.disconnected.1 = {"action": "on_client_disconnected"}
#web.hook.rule.client.connect.1 = {"action": "on_client_connect"}
#web.hook.rule.client.connack.1 = {"action": "on_client_connack"}
#web.hook.rule.client.subscribe.1 = {"action": "on_client_subscribe"}
#web.hook.rule.client.unsubscribe.1 = {"action": "on_client_unsubscribe"}
web.hook.rule.session.subscribed.1 = {"action": "on_session_subscribed"}
web.hook.rule.session.unsubscribed.1 = {"action": "on_session_unsubscribed"}
#web.hook.rule.session.terminated.1 = {"action": "on_session_terminated"}
#web.hook.rule.message.publish.1 = {"action": "on_message_publish"}
#web.hook.rule.message.delivered.1 = {"action": "on_message_delivered"}
#web.hook.rule.message.acked.1 = {"action": "on_message_acked"}
- emqx 启用插件 webhook插件(emqx有web控制台,在插件里启用就可以)
- 然后就是自行搭建一个web服务,当客户端有指定事件后会主动推送。
优点:
- 该方案只要web服务端不崩溃,就一定会收到上下线事件,可靠性有保障。
- 耦合性更低。
改进:
- 该方案我没发现如何加密通讯,所以如果web服务对外暴露,小黑可以模拟发送一些数据,导致业务错乱。我的方案是在web端限制访问ip为emqx的服务端访问。
- 该方案可做为设备在线/不在线状态的辅助,更新到数据库。定时监测不在线状态事件大于指定时间的即告警通知人工处理,目前我的方案是离线15分钟会发送信息到公众号。
- 为什么说该方案作为辅助。本来打算该方案作为最终方案的,后来发现还有一个可靠性更高的,所以该方案就作为辅助了。可靠性更高的就是Emqx提供的 HTTP API ,在每次发送命令前调用检查,就能确认机器是否在线,该方案下次分享了。
如果本文对您有帮助,就点个star鼓励下吧~
想要探讨/摸鱼的小伙伴也可加我 157239486