使用 WebSocket 链接 MQTT 服务器

近年来随着 Web 前端的快速发展,浏览器新特性层出不穷,愈来愈多的应用能够在浏览器端经过浏览器渲染引擎实现,Web 应用的即时通讯方式 WebSocket 也所以获得了普遍的应用。javascript

WebSocket 是一种在单个 TCP 链接上进行全双工通信的协议。WebSocket 通讯协议于2011年被 IETF 定为标准 RFC 6455,并由 RFC 7936 补充规范。WebSocket API 也被 W3C 定为标准。html

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,容许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只须要完成一次握手,二者之间就直接能够建立持久性的链接,并进行双向数据传输。 ^1前端

MQTT 协议第 6 章 详细约定了 MQTT 在 WebSocket [RFC6455] 链接上传输须要知足的条件,协议内容不在此详细赘述。java

两款客户端比较

Paho.mqtt.js

Paho 是 Eclipse 的一个 MQTT 客户端项目,Paho JavaScript Client 是其中一个基于浏览器的库,它使用 WebSockets 链接到 MQTT 服务器。相较于另外一个 JavaScript 链接库来讲,其功能较少,不推荐使用。git

MQTT.js

MQTT.js 是一个彻底开源的 MQTT 协议的客户端库,使用 JavaScript 编写,可用于 Node.js 和浏览器。在 Node.js 端能够经过全局安装使用命令行链接,同时支持 MQTT/TCP、MQTT/TLS、MQTT/WebSocket 链接;值得一提的是 MQTT.js 还对微信小程序有较好的支持。github

本文将使用 MQTT.js 库进行 WebSocket 的链接讲解。web

安装 MQTT.js

若是读者机器上装有 Node.js 运行环境,可直接使用 npm 命令安装 MQTT.js。shell

在当前目录安装

npm install mqtt --save

CDN 引用

或免安装直接使用 CDN 地址npm

<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>

<script>
    // 将在全局初始化一个 mqtt 变量
    console.log(mqtt)
</script>

链接至 MQTT 服务器

本文将使用 EMQ X 提供的 免费公共 MQTT 服务器,该服务基于 EMQ X 的 MQTT 物联网云平台 建立。服务器接入信息以下:小程序

  • Broker: broker.emqx.io
  • TCP Port: 1883
  • Websocket Port: 8083

EMQ X 使用 8083 端口用于普通链接,8084 用于 SSL 上的 WebSocket 链接。

为了简单起见,让咱们将订阅者和发布者放在同一个文件中:

const clientId = 'mqttjs_' + Math.random().toString(16).substr(2, 8)

const host = 'ws://broker.emqx.io:8083/mqtt'

const options = {
  keepalive: 60,
  clientId: clientId,
  protocolId: 'MQTT',
  protocolVersion: 4,
  clean: true,
  reconnectPeriod: 1000,
  connectTimeout: 30 * 1000,
  will: {
    topic: 'WillMsg',
    payload: 'Connection Closed abnormally..!',
    qos: 0,
    retain: false
  },
}

console.log('Connecting mqtt client')
const client = mqtt.connect(host, options)

client.on('error', (err) => {
  console.log('Connection error: ', err)
  client.end()
})

client.on('reconnect', () => {
  console.log('Reconnecting...')
})

链接地址

上文示范的链接地址能够拆分为: ws: // broker . emqx.io : 8083 /mqtt

协议 // 主机名 . 域名 : 端口 / 路径

初学者容易出现如下几个错误:

  • 链接地址没有指明协议:WebSocket 做为一种通讯协议,其使用 ws (非加密)、wss (SSL 加密) 做为协议标识。MQTT.js 客户端支持多种协议,链接地址需指明协议类型;
  • 链接地址没有指明端口:MQTT 并未对 WebSocket 接入端口作出规定,EMQ X 上默认使用 8083 8084 分别做为非加密链接、加密链接端口。而 WebSocket 协议默认端口同 HTTP 保持一致 (80/443),不填写端口则代表使用 WebSocket 的默认端口链接;而使用标准 MQTT 链接时则无需指定端口,如 MQTT.js 在 Node.js 端可使用 mqtt://localhost 链接至标准 MQTT 1883 端口,当链接地址是 mqtts://localhost 则链接到 8884 端口;
  • 链接地址无路径:MQTT-WebSoket 统一使用 /path 做为链接路径,链接时需指明,在 EMQ X 上使用的路径为 /mqtt
  • 协议与端口不符:使用了 wss 链接却链接到 8083 端口;
  • 在 HTTPS 下使用非加密的 WebSocket 链接: Google 等机构在推动 HTTPS 的同时也经过浏览器约束进行了安全限定,即 HTTPS 链接下浏览器会自动禁止使用非加密的 ws 协议发起链接请求;
  • 证书与链接地址不符: 篇幅较长,详见下文 EMQ 启用 SSL/TLS 加密链接

链接选项

上面代码中, options 是客户端链接选项,如下是主要参数说明,其他参数详见https://www.npmjs.com/package/mqtt#connect

  • keepalive:心跳时间,默认 60秒,设置 0 为禁用;
  • clientId: 客户端 ID ,默认经过 'mqttjs_' + Math.random().toString(16).substr(2, 8) 随机生成;
  • username:链接用户名(可选);
  • password:链接密码(可选);
  • clean:true,设置为 false 以在离线时接收 QoS 1 和 2 消息;
  • reconnectPeriod:默认 1000 毫秒,两次从新链接之间的间隔,客户端 ID 重复、认证失败等客户端会从新链接;
  • connectTimeout:默认 30 * 1000毫秒,收到 CONNACK 以前等待的时间,即链接超时时间;
  • will:遗嘱消息,当客户端严重断开链接时,Broker 将自动发送的消息。 通常格式为:
    • topic:要发布的主题
    • payload:要发布的消息
    • qos:QoS
    • retain:保留标志

订阅/取消订阅

链接成功以后才能订阅,且订阅的主题必须符合 MQTT 订阅主题规则;

注意 JavaScript 的异步非阻塞特性,只有在 connect 事件后才能确保客户端已成功链接,或经过 client.connected 判断是否链接成功:

client.on('connect', () => {
  console.log('Client connected:' + clientId)
  // Subscribe
  client.subscribe('testtopic', { qos: 0 })
})
// Unsubscribe
client.unubscribe('testtopic', () => {
  console.log('Unsubscribed')
})

发布/接收消息

发布消息到某主题,发布的主题必须符合 MQTT 发布主题规则,不然将断开链接。发布以前无需订阅该主题,但要确保客户端已成功链接:

// Publish
client.publish('testtopic', 'ws connection demo...!', { qos: 0, retain: false })
// Received
client.on('message', (topic, message, packet) => {
  console.log('Received Message: ' + message.toString() + '\nOn topic: ' + topic)
})

微信小程序

MQTT.js 库对微信小程序特殊处理,使用 wxs 协议标识符。注意小程序开发规范中要求必须使用加密链接,链接地址应相似为 wxs://broker.emqx.io:8084/mqtt

EMQ X 启用 SSL/TLS 加密链接

EMQ 内置自签名证书,默认已经启动了加密的 WebSocket 链接,但大部分浏览器会报证书无效错误如 net::ERR_CERT_COMMON_NAME_INVALID (Chrome、360 等 webkit 内核浏览器在开发者模式下, Console 选项卡 能够查看大部分链接错误)。致使该错误的缘由是浏览器没法验证自签名证书的有效性,读者需从证书颁发机构购买可信任证书,并参考该篇文章中的相应部分进行配置操做:EMQ X MQTT 服务器启用 SSL/TLS 安全链接

这里就总结启用 SSL/TLS 证书须要具有的条件是:

  • 将域名绑定到 MQTT 服务器公网地址:CA 机构签发的证书签名是针对域名的;
  • 申请证书:向 CA 机构申请所用域名的证书,注意选择一个可靠的 CA 机构且证书要区分泛域名与主机名;
  • 使用加密链接的时候选择 wss 协议,并 使用域名链接 :绑定域名-证书以后,必须使用域名而非 IP 地址进行链接,这样浏览器才会根据域名去校验证书以在经过校验后创建链接。

EMQ X 配置

打开 etc/emqx.conf 配置文件,修改如下配置:

# wss 监听地址
listener.wss.external = 8084

# 修改密钥文件地址
listener.wss.external.keyfile = etc/certs/cert.key

# 修改证书文件地址
listener.wss.external.certfile = etc/certs/cert.pem

完成后重启 EMQ X 便可。

可使用你的证书与密钥文件直接替换到 etc/certs/ 下。

在 Nginx 上配置反向代理与证书

使用 Nginx 来反向代理并加密 WebSocket 能够减轻 EMQ X 服务器计算压力,同时实现域名复用,同时经过 Nginx 的负载均衡能够分配多个后端服务实体。

# 建议 WebSocket 也绑定到 443 端口
listen 443, 8084;
server_name example.com;

ssl on;

ssl_certificate /etc/cert.crt;  # 证书路径
ssl_certificate_key /etc/cert.key; # 密钥路径


# upstream 服务器列表
upstream emq_server {
    server 10.10.1.1:8883 weight=1;
    server 10.10.1.2:8883 weight=1;
    server 10.10.1.3:8883 weight=1;
}

# 普通网站应用
location / {
    root www;
    index index.html;
}

# 反向代理到 EMQ X 非加密 WebSocket
location / {
    proxy_redirect off;
    # upstream
    proxy_pass http://emq_server;
    
    proxy_set_header Host $host;
    # 反向代理保留客户端地址
    proxy_set_header X-Real_IP $remote_addr;
    proxy_set_header X-Forwarded-For $remote_addr:$remote_port;
    # WebSocket 额外请求头
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection “upgrade”;
}

其它资源

项目完整代码请见:https://github.com/emqx/MQTT-Client-Examples/tree/master/mqtt-client-WebSocket

一款在线的 MQTT WebSocket 链接测试工具:https://www.emqx.io/cn/mqtt/mqtt-websocket-toolkit

版权声明: 本文为 EMQ 原创,转载请注明出处。

原文连接:https://www.emqx.io/cn/blog/connect-to-mqtt-broker-with-websocket