简介
本文主要介绍如何在Browser js,即浏览器环境下,使用 HTML 连接MQTT服务器。
服务器使用EMQX为例。部分代码使用EMQX官方文档。
连接到MQTT服务器分为Websocket
方式连接和Websocket TLS/SSL
方式连接,使用EMQX的公共服务器的话是 Websocket 方式连接,使用EMQX的私有服务器为Websocket TLS/SSL方式连接。Websocket 和Websocket TLS/SSL连接的区别在于设置服务器地址时前者的协议为ws(或mqtt)
,后者的协议为wss
,其他的协议类型都不行。两种连接的端口也不同。
本文所有的调试信息都输出在控制台,请打开控制台以查看输出。
EMQX公共服务器链接,使用说明。
EMQX私有服务器链接,使用说明。
安装依赖
根据官方文档“ MQTT.js
是一个完全开源的 MQTT 协议的客户端库,使用 JavaScript 编写,可用于 Node.js 和浏览器环境。”
CDN地址:https://unpkg.com/mqtt/dist/mqtt.min.js
使用以下代码引入改库
1
| <script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
|
定义基本信息
Websocket 使用ws协议,或mqtt
Websocket TLS/SSL使用wss协议,其他的协议类型都不行。
端口需要写在URL的后面
根据官方文档“MQTT-WebSocket 统一使用 /path
作为连接路径,连接时需指明,而 EMQX Broker 使用的路径为 /mqtt
。”
如果使用EMQX的公共服务器,使用 Websocket
连接,服务器地址为broker.emqx.io
,端口为 8083
,即 broker.emqx.io:8083/mqtt
,连接用户名为 emqx
,密码为 public
,也可以不填。
如果使用私有服务器,使用 Websocket TLS/SSL
连接,服务器地址根据控制台具体信息,端口为 8084
,用户名和密码自己在控制台里定义。
1 2 3 4 5 6 7 8 9 10 11 12 13
| const connectUrl = 'ws://' const options = { connectTimeout: 4000, reconnectPeriod: 1000, clientId: '', username: '', password: '', clean: true, }
|
设备ID可以自定义,也可以在结尾添加设备的IP地址用于区分设备,因为如果存在相同的设备ID,那么所有拥有相同设备ID的设备将会不断的断开重连。获取IP地址需要调用外部API。使用以下代码在定义的设备ID后添加上IP地址,为了保险起见防止IP地址重复导致设备ID重复,那么还可以在IP地址后再加上几个随机字符。
1 2 3 4 5 6 7 8 9 10 11 12
| function getIPAddress() { return fetch("https://api.ipify.org?format=json") .then(response => response.json()) .then(data => { options.clientId += '-'+data.ip; options.clientId += '-' + Math.random().toString(16).substring(2, 8); }) .catch(error => { console.log("Error:", error); }); }
|
建立连接
连接到mqtt服务器的语句在 mqttConnet()
函数内,因为如果在设备ID后添加IP地址,需要从外部API获取IP地址,但是需要一定的时间,所以这里使用了 async/await
,并在 mqttConnet()
函数内调用 getIPAddress()
来对函数进行阻塞,直到获取到IP地址,以确保在连接mqtt服务器时已经获取到IP地址并添加到设备ID后,否则连接到服务器时还未获得到IP地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| var client; const qos=0;
async function mqttConnet() { await getIPAddress(); client = mqtt.connect(connectUrl, options);
client.on('error', (error) => { console.log('连接失败: ', error); client.end(); });
client.on('reconnect', () => { console.log('重连中...'); });
client.on('connect', () => { console.log('连接成功:' + options.clientId); });
client.on("close", () => { console.log('已经断开连接'); }); }
|
订阅主题、接收发送消息等
在 mqttConnet()
函数结尾添加以下程序用来接收消息
1 2 3 4 5
| client.on('message', (topic, msg) => { console.log('收到消息:'); console.log(' 主题:', topic); console.log(' 消息:', msg.toString()); });
|
这里使用了 默认主题 的概念,如果程序只使用一个主题的话,可以用 setDefaultTopic(topic)
函数设置默认主题,之后调用订阅,取消订阅,发布消息的函数时就可以不用再填写主题了,函数默认参数值为默认主题。
也可以订阅、取消订阅到发布消息到多个不同的主题,这时只有其中一个主题可以设置为默认主题,其他主题需要手动填写。
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 42 43 44 45 46 47 48 49 50 51
| var defaultTopic;
function setDefaultTopic(topic){ defaultTopic = topic; }
function subscribe(topic=defaultTopic){ client.subscribe(topic, {qos} ,(error) => { if (error) { console.log('订阅失败:',error); return; } console.log('订阅主题:', topic); }); }
function unsubscribe(topic=defaultTopic){ client.unsubscribe(topic, {qos}, (error) => { if (error) { console.log('取消订阅失败:', error); return; } console.log('取消订阅主题:', topic); }); }
function publish(payload, topic=defaultTopic){ client.publish(topic, payload, { qos }, (error) => { if (error) { console.log('发布失败:',error); return; } console.log('发布成功:'); console.log(' 主题:', topic); console.log(' 消息:', payload); }); }
function disconnect() { if (client.connected) { try { client.end(false, () => { console.log('断开连接') }) } catch (error) { console.log('断开连接失败:', error) } } }
|
完整代码示例
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
| <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>AIRCON</title>
<script src="https://unpkg.com/mqtt@5.0.3/dist/mqtt.min.js"></script> <script> var IP=''; const connectUrl = 'ws://broker.emqx.io:8083/mqtt'; const options = { connectTimeout: 4000, reconnectPeriod: 1000, clientIdHead: 'html', clean: true, };
var client; var defaultTopic; const qos=0;
function getIPAddress() { return fetch("https://api.ipify.org?format=json") .then(response => response.json()) .then(data => { options.clientId = options.clientIdHead+'-'+data.ip; options.clientId += '-' + Math.random().toString(16).substring(2, 8); }) .catch(error => { console.log("Error:", error); }); }
mqttConnect(); async function mqttConnect() { await getIPAddress(); client = mqtt.connect(connectUrl, options);
client.on('error', (error) => { console.log('连接失败: ', error); client.end(); });
client.on('reconnect', () => { console.log('重连中...'); });
client.on('connect', () => { console.log('连接成功:' + options.clientId); });
client.on("close", () => { console.log('已经断开连接'); });
client.on('message', (topic, msg) => { console.log('收到消息:'); console.log(' 主题:', topic); console.log(' 消息:', msg.toString()); }); }
function setDefaultTopic(topic){ defaultTopic = topic; console.log('设置默认主题:', topic); }
function subscribe(topic=defaultTopic){ client.subscribe(topic, {qos} ,(error) => { if (error) { console.log('订阅失败:',error); return; } console.log('订阅主题:', topic); }); }
function unsubscribe(topic=defaultTopic){ client.unsubscribe(topic, {qos}, (error) => { if (error) { console.log('取消订阅失败:', error); return; } console.log('取消订阅主题:', topic); }); } function publish(payload, topic=defaultTopic){ client.publish(topic, payload, { qos }, (error) => { if (error) { console.log('发布失败:',error); return; } console.log('发布成功:'); console.log(' 主题:', topic); console.log(' 消息:', payload); }); }
function disconnect() { if (client.connected) { try { client.end(false, () => { console.log('断开连接') }) } catch (error) { console.log('断开连接失败:', error) } } } </script> </head>
<body> <input type="text" id="defaultTopic" value="输入默认主题"/> <button onclick="setDefaultTopic(document.getElementById('defaultTopic').value)" class="inputbutton">设置默认主题</button> <button onclick="subscribe()" class="inputbutton">订阅主题</button> <button onclick="publish('on')" class="inputbutton">开</button> <button onclick="publish('off')" class="inputbutton">关</button> <button onclick="unsubscribe()" class="inputbutton">取消订阅主题</button> <button onclick="disconnect()" class="inputbutton">断开连接</button> <button onclick="mqttConnect()" class="inputbutton">连接</button> </body> </html>
|