简介
本文主要介绍如何在Browser js,即浏览器环境下,使用 HTML 连接MQTT服务器。
服务器使用EMQX为例。部分代码使用EMQX官方文档。
连接到MQTT服务器分为Websocket
方式连接和Websocket TLS/SSL
方式连接,使用EMQX的公共服务器的话是 Websocket 方式连接,使用EMQX的私有服务器为Websocket TLS/SSL方式连接。Websocket 和Websocket TLS/SSL连接的区别在于设置服务器地址时前者的协议为ws(或mqtt)
,后者的协议为wss
,其他的协议类型都不行。两种连接的端口也不同。
本文所有的调试信息都输出在控制台,请打开控制台以查看输出。
安装依赖
根据官方文档“ MQTT.js
是一个完全开源的 MQTT 协议的客户端库,使用 JavaScript 编写,可用于 Node.js 和浏览器环境。”
CDN地址:https://unpkg.com/mqtt/dist/mqtt.min.js
使用以下代码引入改库
<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
,用户名和密码自己在控制台里定义。
const connectUrl = 'ws://'
//或wws://
const options = {
connectTimeout: 4000,
reconnectPeriod: 1000,
clientId: '',
//设备ID
username: '',
//账号,也可删去不填
password: '',
//密码,也可删去不填
clean: true,
}
设备ID可以自定义,也可以在结尾添加设备的IP地址用于区分设备,因为如果存在相同的设备ID,那么所有拥有相同设备ID的设备将会不断的断开重连。获取IP地址需要调用外部API。使用以下代码在定义的设备ID后添加上IP地址,为了保险起见防止IP地址重复导致设备ID重复,那么还可以在IP地址后再加上几个随机字符。
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地址。
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()
函数结尾添加以下程序用来接收消息
client.on('message', (topic, msg) => {
console.log('收到消息:');
console.log(' 主题:', topic);
console.log(' 消息:', msg.toString());
});
这里使用了 默认主题 的概念,如果程序只使用一个主题的话,可以用 setDefaultTopic(topic)
函数设置默认主题,之后调用订阅,取消订阅,发布消息的函数时就可以不用再填写主题了,函数默认参数值为默认主题。
也可以订阅、取消订阅到发布消息到多个不同的主题,这时只有其中一个主题可以设置为默认主题,其他主题需要手动填写。
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)
}
}
}
完整代码示例
<!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>