简介
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,是 TCP/IP 的再封装,由 IBM 在 1999 年发布。MQTT 最大优点在于,地开销,少流量实现网络通信
协议原理
mqtt 协议中有两种角色,客户端和服务端。服务端一般由消息 broker 实现,MQTT 与传统 HTTP 协议有所不同,所有接入的设备都为客户端。
客户端有两种身份发布者(Publish)、订阅者(Subscribe),也可以同时具备两者身份。
顾名思义,发布者可以发布消息,订阅者可以接受消息。
消息内容
MQTT 传输的消息分为:主题(Topic)和负载(payload)两部分:
- (1)Topic,主题,订阅者订阅指定主题后,会收到其他发布者发送到该主题的数据;
- (2)payload,具体的载荷
关键字
一、订阅(Subscription)
订阅包含主题筛选器(Topic Filter)和最大服务质量(QoS)。订阅会与一个会话(Session)关联。一个会话可以包含多个订阅。每一个会话中的每个订阅都有一个不同的主题筛选器。
二、会话(Session)
每个客户端与服务器建立连接后就是一个会话,客户端和服务器之间有状态交互。会话存在于一个网络之间,也可能在客户端和服务器之间跨越多个连续的网络连接。
三、主题名(Topic Name)
连接到一个应用程序消息的标签,该标签与服务器的订阅相匹配。服务器会将消息发送给订阅所匹配标签的每个客户端。
四、主题筛选器(Topic Filter)
一个对主题名通配符筛选器,在订阅表达式中使用,表示订阅所匹配到的多个主题。
五、负载(Payload)
具体数据
前端实现
前端一般使用 websocket 协议实现长链接通信,为了简化 websocket 协议编程,可以直接使用建立在 websocket 上的 mqtt 协议,能够直接得到登陆鉴权、消息分发的功能,免去手动编程解析。
前端 websocket 的 mqtt 协议依赖 mqttws31.js 库实现。
react 项目中,在
1 | <script type="text/javascript" src="./static/mqttws31.js"></script> |
组件中调用:
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 | this.mqttClient = new Paho.MQTT.Client(hostname, port, clientId); this.mqttoptions = { invocationContext: { host: hostname, port: port, clientId: clientId }, timeout: 5, cleanSession: false, useSSL: false, userName: 'chrome', password: 'chrome', onSuccess: this.mqttConnectOk, onFailure: this.mqttConnectFail }; mqttConnectOk = () => { console.log('WebSocket mqtt 连接成功!'); console.log('开始订阅主题'); this.mqttClient.subscribe('out'); console.log('订阅完成'); }; mqttConnectFail = e => { console.log('MQTT连接失败,失败原因是', e); }; messageArrived = message => { let data =`MQTT 收到topic 为 ${message.destinationName} 的消息,具体内容为: ${message.payloadString}, 消息服务等级为 ${message.qos}:` console.log(data); }; connectLost = response => { console.log('MQTT 连接断开'); }; componentDidMount() { this.mqttClient.onMessageArrived = this.messageArrived; this.mqttClient.onConnectionLost = this.connectLost; console.log('开始进行mqtt链接'); this.mqttClient.connect(this.mqttoptions); } |
主要 API
引入依赖文件后,能够得到一个全局的
1.生成 mqtt 客户端
1 | this.mqttClient = new Paho.MQTT.Client(hostname, port, clientId); |
2.建立连接
1 | this.mqttClient.connect(option); |
option 为配置参数,包括以下内容
1 2 3 4 5 6 7 8 9 10 11 12 | invocationContext: { host: hostname,//地址 port: port,//端口 clientId: clientId//链接id }, timeout: 5,//链接超时时间 cleanSession: false,//是否清楚session useSSL: false,//是否使用ssl加密 userName: 'chrome',/用户名 password: 'chrome',//密码 onSuccess: this.mqttConnectOk,//连接成功回调函数 onFailure: this.mqttConnectFail//连接失败回调函数 |
3.订阅主题
1 | this.mqttClient.subscribe("topic name"); |
Topic name 为订阅的主题
4.接收数据
1 | this.mqttClient.onMessageArrived = this.messageArrived; |
接受数据通过回调函数进行处理,回调函数参数是信息体,包括收到的主题名称,数据,Qos 等数据
5.发送数据
1 2 3 | message = new Paho.MQTT.Message("Hello"); message.destinationName = "/World"; client.send(message); |
destinationName 为发送的主题名称
message 为要发送的数据
6.连接断开
1 | this.mqttClient.onConnectionLost = this.connectLost; |
连接断开也是通过回调函数来处理,入参数是失败的原因。在这里可以进行重连,如下。
1 2 3 4 5 6 7 | connectLost = (response) => { console.log("MQTT 连接断开"); console.log("MQTT 开始重连"); this.mqttoptions = Object.assign({}, this.mqttoptionsback); //mqttoptionsback 为 链接option初始化时拷贝的值。 this.mqttClient.connect(this.mqttoptions); console.log("MQTT 重连完成"); }; |
注意:重连时使用的链接 option 对象不能直接使用上一次链接成功后的 option 因为该对象已经被修改。
问题调研
1. 能否控制页面不刷新(消息防丢失处理)
控制页面不刷新的目的是防止实时消息的丢失,在用此用户实际浏览网页过程中,刷新只是一个短暂而又快速的过程,而前端进行 mqtt 连接时将连接配置项
该配置项含义如下:
- clean:false,上文提到,无论是 tcp 实现的 mqtt 还是 websocket 实现的 mqtt,只要客户端与 broker 建立连接后 broker 就维护了一个 session 会话,包含了这个链接的所有信息,消息的收发也是通过该 session 实现的,session 的 key 时客户端链接时的 clientId。当 clean 设置为 false 时,意味着通知 broker,当该客户端掉线后不要清除客户端消息,下次重联客户端仍然采用与之前相同的 clientId 进行链接。
- Qos 为消息服务质量,当设置为 1 或者 2 时,服务端向客户端订阅的某一个主题推送数据时,客户端处在刷新掉线情况下,没有对服务端进行回复,服务端会存储消息到 senssion 中,待到客户端恢复链接后,重新推送给订阅此主题的客户端。(这里客户端的恢复是有 mqtt 协议底层实现的,一般在库中已经实现,使用时不用关心)
消息服务质量 Qos 分别如下:
- QoS0,最多一次送达。
- QoS1,至少一次送达。
- QoS2,准确一次送达。
2. 消息去重
当设置为 qos1 或者 qos2 时可能会出现收到重复消息,此时可以在消息数据中增加应用序号,每次收到后对比,如果重复则丢弃
业务需求实现调研
引入 mqtt 主要是为了满足实时消息推送和在线聊天需求。这两个需求实现需要后台程序、mqtt broker、前台程序相互配合,在技术上可简化为业务服务器实现 数据下发 和接受数据上报两个功能,这里的业务服务器指框图中的后台程序。
下面分功能介绍具体实现
消息推送
用户在登录系统时,根据返回的用户名密码以及 mqtt 地址,与 mqttBroker 进行连接,连接成功后订阅以下主题:
broadcast/company/公司id/role/角色id/user/用户id broadcast/company/+ broadcast/company/公司id/role/+ broadcast/system/:type
第一个主题用于接收下发给登陆用户的信息。
第二个主题用于接收下发给一个公司下所有用户的信息。
第三个主题用于接收下发给一个公司下一个特定角色下所有用户信息。
第四个主题用于接受系统消息。
以向某一用户推送消息为例,当业务逻辑满足一定条件需要向登陆的某一个用户推送实时消息时,业务系统调用 mqtt broker 的 http API,携带主题和数据,broker 收到后转发给通过 mqtt 协议连接的登录用户的浏览器,进行提示,其他几个主题流程相同
在线聊天
在线聊天分为与系统运营与采购商、供应商聊天,采购商、供应商之间进行聊天两种,实现类似。以用户 A 与用户 B 聊天为例。
用户 A、用户 B 在浏览器登陆后通过 mqtt 协议链接 broker,订阅主题
当用户 A 发起聊天输入信息并点击发送后,前端调用业务系统提供的 api,业务后段存储消息并调用 broker 的 API,broker 转发该消息到用户 B 登陆的浏览器。用户 B 向用户 A 回复消息仍然是调用业务系统提供的 API,与 A 向 B 发送一致。整个数据流向是 :
业务系统下发数据与消息推送大致相同,其中前端程序接受消息采用 mqtt 协议,唯一区别是实时聊天用户发送消息时,不使用 mqtt 而是调用业务系统的提供的 http API,业务系统再调用 borker API,borker 转发 这样实现的目的是为了记录用户的聊天数据,
当用户在此进入聊天界面时,可以调用业务系统提供的聊天记录 API,得到之前存储到业务系统中的聊天记录。
其他技术实现方案
业务后台程序与 mqtt broker 的对接另外除上述 htpp 方案外,还可以采用业务后台程序作为 mqtt 客户端接入 mqtt broker 的方式,通配订阅
如果具有二次开发能力,还可以对 Emq 或者 ActiveMq 进行二次开发,实现直接数据入库或者集成到业务系统当中。
以上三种方式,第一种开发最简单,可控性比较强。
broker
mqtt 封装与纯 socket 对比
优点
使用 websocket 的 mqtt 与直接使用 websocket 通信相比,前后台开发工作量能够有效减少,主要有以下几点:
- mqtt 自带鉴权,后台 socket 不需要编写登陆验证等逻辑
- mqtt 自带 topic,可以实现消息分发,不需要编写群发等复杂程序逻辑
- mqtt 自带消息确认功能(qos 1 或者 2),不需要编写消息回传逻辑
缺点
- 需要单独在后台独立运行 mqtt broker
- 需要增加应用程序与 broker 之间的通信逻辑
可用 mqtt broker
目前可用 broker 有 mosquitto、emqx、activemq 等,下表列出几种对比
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传