什么是MQ TT ?
是基于发布/订阅模型轻量级的传输协议
应用在哪里 ?
物联网、移动互联网、智能硬件、智慧城市、远程医疗、电力、能源等领域
优点 ?
- 轻量级(lightweight)别问为什么用英文,因为最近看英文文档light不是“灯”的意思吗?后来才知道还有少量的,也顺便让我装一下,我比较装您见谅
- 节省带宽(以前带宽很贵)
- 支持 QoS(设备网络环境复杂)
- 不关心PayLoad 不关心数据是什么 也可以是音频、文本、JSON、图片...
- 有持续感知会话的能力 知道对方是否在线
QOS(服务质量--全称Quality Of Service)
-
QoS 0:消息最多传递一次。 如果当时客户端不可用,则会丢失该消息。发布者发送一条消息之后,就不再关心它有没有发送到对 方,也不设置任何重发机制。(有可能消息会丢失)
-
QoS 1:消息传递至少 1 次。 包含了简单的重发机制,发布者发送消息之后等待接收者的 ACK,如果没收到 ACK 则重新发送消息。 这种模式能保证消息至少能到达一次,但无法保证消息重复。 (会接收到一次消息但是不保证会不会重复)
-
QoS 2:消息仅传送一次。 设计了重发和重复消息发现机制,保证消息到达对方并且严格只到达一次。(只会接收一次消息)
清除会话(Clean Session)机制
对于那些想重新连接后,收到离线期间错过的消息的客户端,可在连接时设置关闭清除会话,此时服务端将会为客户端存储订阅关系及离线消息,并在客户端重新连接后发送给客户端。
安全双向通信
客户端和服务端不需要直接建立连接进行通讯,也不需要同时在线,而是由消息服务器进行消息的分发
MQ TT支持 TLS/SSl来确定安全通讯,同时在MQTT协议当中提供了客户端的ID、用户名、密码在应用层确立身份授权和验证
在线状态感知
为了应对网络环境不稳定的情况下,MQTT提供了心跳保活(Keep Alive) 机制, 客户端与服务端长时间没有进行消息通讯,Keep Alive保持连接不被断开,若一断开客户端能即刻感知到实行重新连接
同时MQTT还有 LastWill(遗嘱消息),让服务端发现客户端异常下线的情况下,会发送一条信息到特定的MQTT主题(Topic)当中
另外,部分 MQTT 服务器如 EMQX 也提供了上下线事件通知功能,当后端服务订阅了特定主题后,即可 收到所有客户端的上下线事件,这样有助于后端服务统一处理客户端的上下线事件
MQTT和消息队列的区别
他们都是采用发布/订阅模型进行数据通讯的,消息队列主要应用在服务端之间的消息存储和传递,这类通常是数据量大客户端少的情况,MQTT更适合海量的客户端接入管理与信息传输
不同于消息队列的主题、MQTT的主题是不需要主动创建的,MQTT客户端在订阅和发布的时候就会创建,也不需要开发者主动删除,如果没有订阅,没有发布这个主题那就不存在了
如何使用go进行MQTT发送消息和订阅消息
Tips: 你需要使用就将下列所有的代码放在同一个go文件中即可
GO语言下载MQTT的SDK
go get github.com/eclipse/paho.mqtt.golang
go get github.com/gorilla/websocket
go get golang.org/x/net/proxy
初始化mqtt客户端
import (
mqtt "github.com/eclipse/paho.mqtt.golang"
"time"
)
// 关于连接的配置
const (
QPS = 1
SERVERADDRESS = "http://127.0.0.1:1883"
DELAY = time.Second
CLIENTID = "A"
WRITETOLOG = true // 如果为true那么published的消息会被打印到控制台
)
func NewMqttClient() mqtt.Client {
opts := mqtt.NewClientOptions()
opts.AddBroker(SERVERADDRESS)
opts.SetClientID(CLIENTID)
opts.SetOrderMatters(false) // 允许消息无序发送(除非有序传递是必要的
opts.ConnectTimeout = time.Second
opts.WriteTimeout = time.Second
opts.KeepAlive = 10
opts.PingTimeout = time.Second
// 自动化连接管理 在网络断开后重新连接
opts.ConnectRetry = true
opts.AutoReconnect = true
// 当连接丢失的时候触发的回调函数
opts.OnConnectionLost = func(cl mqtt.Client, err error) {
// 可能是用于断开连接后做的事情
}
opts.OnConnect = func(mqtt.Client) {
// 连接成功后做的事情
}
opts.OnReconnecting = func(mqtt.Client, *mqtt.ClientOptions) {
// 处理重新连接的业务
}
client := mqtt.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
panic(token.Error())
}
return client
}
定义mqtt的发送消息和订阅消息的异常处理
type MQTT struct {
Client mqtt.Client
}
// Pub 发送消息
func (m MQTT) Pub(topic string, qos byte, retained bool, message any) {
// retained为true的话会保留最新消息的副本,在有新的客户端接入时就会发送给他拿到最新的消息
msg, err := json.Marshal(message)
if err != nil {
fmt.Println("解析为json数据错误", err.Error())
return
}
if WRITETOLOG {
fmt.Printf("发送的消息: %s\n", msg)
}
t := m.Client.Publish(topic, qos, retained, msg)
go func() {
_ = t.Wait()
if t.Error() != nil {
fmt.Printf("发布消息失败: %s\n", err)
}
}()
}
// Sub 订阅消息
func (m MQTT) Sub(topic string, qos byte, callback func(client mqtt.Client, msg mqtt.Message)) {
t := m.Client.Subscribe(topic, qos, callback)
go func() {
_ = t.Wait()
if t.Error() != nil {
fmt.Println("订阅异常 ", t.Error())
return
}
}()
}
应用
func SubMessage() {
client := NewMqttClient()
mq := MQTT{
client,
}
responseChan := make(chan *Message, 1)
mq.Sub("你的topic", 1, func(client mqtt.Client, msg mqtt.Message) {
var response Message
if err := json.Unmarshal(msg.Payload(), &response); err != nil {
log.Println("解析mqtt响应数据失败: ", err.Error())
return
}
if response.Method == "自己定义你需要读的数据的条件" {
// 将满足条件的数据写入信道
responseChan <- &response
}
})
// 等待 MQTT 回复或超时
select {
case <-time.NewTimer(2 * time.Second).C:
// 超时,断开连接并返回空值
fmt.Println("mqtt连接超时")
return
case response := <-responseChan:
// 接收到 MQTT 回复,返回满足条件的数据
// TODO:: 处理接收到的数据
fmt.Println(response)
}
}
未完成
后续会使用docker来部署消息服务器
- 做定时器发送消息到消息服务器中
- 订阅发送时给的topic并输出