【前端】MQTT:通信与聊天室实战
本文是关于 MQTT.js 使用的详细长文教程。文章会循序渐进,从入门到进阶,涵盖常用功能、实用技巧以及代码示例,帮助读者深入理解如何在 JavaScript 环境中使用 MQTT 协议。整个教程将分为以下几个部分:
MQTT 协议
MQTT(Message Queuing Telemetry Transport)是一种轻量级的发布/订阅协议,设计用于低带宽、不稳定网络环境,广泛应用于物联网(IoT)领域。它基于客户端-代理模式,支持三种消息质量服务(QoS)等级,能够满足不同场景的需求。
MQTT.js 安装
MQTT.js 是一个客户端库,用于在 JavaScript 中使用 MQTT 协议。它支持 Node.js 和浏览器环境,提供了简洁的 API,使得开发者能够快速集成 MQTT 协议。
-
使用 npm 安装(适用于 Node.js 环境):
npm install mqtt --save
-
在浏览器环境中,可以通过 CDN 引入:
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
基础使用
连接到 MQTT 代理
首先,连接到 MQTT 代理服务器是使用 MQTT.js 的第一步。可以通过 mqtt.connect()
方法进行连接。
const mqtt = require('mqtt');
const client = mqtt.connect('mqtt://broker.hivemq.com');client.on('connect', () => {console.log('Connected to MQTT broker');
});
在这个示例中,我们使用了 HiveMQ 的公共 MQTT 代理作为例子,你也可以将 mqtt://broker.hivemq.com
替换为你自己的代理地址。
订阅与发布消息
连接成功后,你可以订阅主题以接收消息,或者发布消息到指定主题。
订阅消息
client.subscribe('my/topic', (err) => {if (!err) {console.log('Subscribed to topic: my/topic');}
});
发布消息
client.publish('my/topic', 'Hello MQTT', (err) => {if (!err) {console.log('Message sent to my/topic');}
});
消息 QoS 等级
MQTT 协议支持三种 QoS 等级:
- QoS 0:最多一次,不保证消息送达。
- QoS 1:至少一次,保证消息送达,但可能重复。
- QoS 2:只有一次,确保消息送达且不重复。
client.publish('my/topic', 'Hello QoS 1', { qos: 1 });
好的,我们来详细讲解如何在 Linux 服务器上部署 MQTT 代理(以 Mosquitto 为例),并设置用户名和密码管理。
MQTT.js 配置与管理
设置连接选项
你可以通过 connect()
方法的第二个参数传递配置项来定制连接选项,例如设置客户端ID、用户名、密码等。
const options = {clientId: 'mqttjs_' + Math.random().toString(16).substr(2, 8),username: 'user',password: 'password',keepalive: 60,clean: true
};const client = mqtt.connect('mqtt://broker.hivemq.com', options);
连接回调与错误处理
可以使用 on('connect')
、on('error')
等事件处理函数来监听连接状态:
client.on('connect', () => {console.log('Connected successfully');
});client.on('error', (err) => {console.error('Connection error: ', err);
});
连接与安全
使用 TLS 加密连接
为了确保连接的安全性,MQTT.js 支持通过 TLS 加密连接。你可以设置 mqtts://
协议来使用加密连接。
const client = mqtt.connect('mqtts://broker.hivemq.com', {port: 8883,username: 'user',password: 'password'
});
MQTT 认证与授权
有些 MQTT 代理需要认证才能连接,MQTT.js 支持传递认证信息,如用户名和密码。
const options = {username: 'username',password: 'password'
};const client = mqtt.connect('mqtt://broker.hivemq.com', options);
高级功能与技巧
持久化会话与遗嘱消息
你可以通过配置持久化会话和遗嘱消息,增强客户端在断开连接后的一些行为。
const options = {clean: false, // 保持会话will: {topic: 'last/will',payload: 'Client disconnected unexpectedly',qos: 1}
};const client = mqtt.connect('mqtt://broker.hivemq.com', options);
消息订阅与发布的优化
当你需要发布大量消息时,可以选择设置 QoS 0 来减少网络负担,或者使用 保留消息 来减少客户端的接收负担。
构建聊天室应用
技术栈
- HTML/CSS/JavaScript:构建前端页面和交互
- MQTT.js:通过 WebSocket 与 MQTT 代理进行消息通信
- Bootstrap:用于布局和响应式设计(可选,但本教程不聚焦于其详细使用)
初始化项目
我们从一个简单的 HTML 文件开始,包含必要的依赖项。项目不依赖于任何后端,因此我们将使用免费的 MQTT 代理服务器进行通信。我们选择 broker.emqx.io,它是一个公开的 MQTT 代理,可以用于测试。
创建项目文件
在你计算机上创建一个文件夹,例如 mqtt-chatroom
,然后在其中创建一个 HTML 文件,如 index.html
。
引入必要的库
首先,我们需要引入 MQTT.js 和 Bootstrap(用于美化布局和使其响应式):
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Responsive MQTT Chatroom</title><!-- 引入 Bootstrap --><link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"><!-- 引入 MQTT.js --><script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
</head>
<body><div class="container mt-4"><h2 class="text-center">MQTT Chatroom</h2><!-- 聊天框区域 --><div id="chat" class="mb-4" style="height: 60vh; overflow-y: auto; background-color: #f8f9fa; border: 1px solid #ddd; padding: 10px;"></div><!-- 输入区域 --><div class="row"><div class="col-12 col-md-2 mb-2 mb-md-0"><input type="text" id="username" class="form-control" placeholder="Your Name"></div><div class="col-12 col-md-8 mb-2 mb-md-0"><input type="text" id="message" class="form-control" placeholder="Type your message here..."></div><div class="col-12 col-md-2"><button id="sendBtn" class="btn btn-primary btn-block">Send</button></div></div></div><script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script><script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.2/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
连接到 MQTT 代理
初始化 MQTT 连接
在前端页面加载时,我们需要通过 MQTT.js 连接到一个 MQTT 代理。此处我们使用 broker.emqx.io 作为测试代理。它支持 WebSocket 协议,允许我们通过浏览器与其通信。
在 HTML 文件中添加以下 JavaScript 代码来连接 MQTT 代理。
<script>// 连接到 MQTT 服务器const client = mqtt.connect("ws://broker.emqx.io:8083/mqtt");// 连接成功时的回调client.on("connect", () => {console.log("Connected to MQTT broker");});
</script>
处理连接
当连接成功后,我们将显示一条日志消息。你可以通过浏览器的控制台查看此信息。为了更好地管理连接,我们将通过 client.on('connect', ...)
回调函数确保连接成功。
发布和订阅消息
发布消息
聊天室的核心功能是能够发送消息并将其展示给所有订阅者。我们通过 MQTT.js 提供的 client.publish()
方法来实现发布消息。
为了让消息在聊天室中展示,我们将每个消息格式化为 JSON 对象,包含 username
和 data
(即消息内容)。
<script>// 发送消息的函数function sendMessage() {const username = document.getElementById("username").value.trim();const message = document.getElementById("message").value.trim();if (username && message) {const payload = JSON.stringify({ username, data: message });client.publish("chatroom", payload); // 发布到 chatroom 房间document.getElementById("message").value = ""; // 清空输入框} else {alert("Please enter both your name and a message.");}}document.getElementById("sendBtn").addEventListener("click", sendMessage);
</script>
订阅消息
为了接收并展示消息,我们需要订阅 chatroom
主题。当有新的消息到达时,client.on('message', ...)
事件会被触发,接收到的消息会被解析并显示到页面上。
<script>client.on("message", (topic, message) => {const { username, data } = JSON.parse(message.toString());const chat = document.getElementById("chat");const messageDiv = document.createElement("div");messageDiv.className = "message";messageDiv.innerHTML = `<span class="username">${username}:</span> ${data}`;chat.appendChild(messageDiv);chat.scrollTop = chat.scrollHeight; // 滚动到底部});// 订阅 chatroom 主题client.subscribe("chatroom", () => {console.log("Subscribed to chatroom");});
</script>
实现房间选择功能
为了实现房间功能,我们允许用户切换聊天室(即订阅不同的主题)。我们添加一个按钮来选择房间,并通过弹窗输入新的房间名称来动态切换。
创建房间选择按钮
在页面的顶部,我们创建一个按钮,用于选择聊天室。点击按钮时会弹出一个对话框,允许用户输入新的房间名。
<div class="room-info"><button id="roomSelectButton" class="btn btn-secondary"><i class="fas fa-home"></i> Select Room</button><span>Current Room: <span id="roomName">chatroom</span></span>
</div>
监听房间选择操作
我们为按钮添加事件监听器,当用户选择新的房间时,我们将取消订阅当前房间并订阅新的房间。房间名将动态显示在页面上。
<script>let currentRoom = "chatroom";document.getElementById("roomSelectButton").addEventListener("click", () => {const newRoom = prompt("Enter new room name:", currentRoom);if (newRoom && newRoom !== currentRoom) {currentRoom = newRoom;client.unsubscribe(currentRoom); // 取消当前房间的订阅client.subscribe(currentRoom); // 订阅新房间document.getElementById("roomName").textContent = currentRoom; // 更新房间名}});
</script>
增加房间切换后的清空消息
当切换房间时,我们希望清空当前聊天室的消息,以便用户看到新的房间消息。我们可以在 subscribe
之后清空消息列表。
client.on("message", (topic, message) => {const { username, data } = JSON.parse(message.toString());const chat = document.getElementById("chat");chat.innerHTML = ""; // 清空当前聊天内容const messageDiv = document.createElement("div");messageDiv.className = "message";messageDiv.innerHTML = `<span class="username">${username}:</span> ${data}`;chat.appendChild(messageDiv);
});
美化和响应式布局
为了让应用在不同设备上都能良好显示,我们使用 Bootstrap 提供的栅格系统来实现响应式布局。你可以
根据需要调整布局、按钮大小、字体等。
完整代码
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>My First Cesium App</title><!-- 加载 Cesium 的 CSS 样式 --><link href="https://cesium.com/downloads/cesiumjs/releases/1.111/Build/Cesium/Widgets/widgets.css" rel="stylesheet"><link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.3.3/css/bootstrap.css"><style>/* 设置页面和Cesium容器的大小 */html,body,#cesiumContainer {width: 100%;height: 100%;margin: 0;padding: 0;overflow: hidden;}</style></head><body><nav class="navbar navbar-expand-lg navbar-light bg-light"><div class="container"><a class="navbar-brand" href="#">Touken的网站</a><button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"aria-controls="navbarNav" aria-expanded="false" aria-label="切换导航"><span class="navbar-toggler-icon"></span></button><div class="collapse navbar-collapse" id="navbarNav"><ul class="navbar-nav ms-auto"><li class="nav-item dropdown"><a class="nav-link dropdown-toggle" href="#" id="navbarDropdown" role="button"data-bs-toggle="dropdown" aria-expanded="false">产品</a><ul class="dropdown-menu" aria-labelledby="navbarDropdown"><li><a class="dropdown-item" href="game.html">命悬一线</a></li><li><a class="dropdown-item" href="chat.html">聊天室</a></li><li><a class="dropdown-item" href="#">产品C</a></li></ul></li><li class="nav-item dropdown"><a class="nav-link dropdown-toggle" href="#" id="navbarDropdownBlog" role="button"data-bs-toggle="dropdown" aria-expanded="false">博客</a><ul class="dropdown-menu" aria-labelledby="navbarDropdownBlog"><li><a class="dropdown-item" href="#">博客文章1</a></li><li><a class="dropdown-item" href="#">博客文章2</a></li><li><a class="dropdown-item" href="#">博客文章3</a></li></ul></li><li class="nav-item dropdown"><a class="nav-link dropdown-toggle" href="#" id="navbarDropdownResources" role="button"data-bs-toggle="dropdown" aria-expanded="false">资源</a><ul class="dropdown-menu" aria-labelledby="navbarDropdownResources"><li><a class="dropdown-item" href="#">资源1</a></li><li><a class="dropdown-item" href="#">资源2</a></li><li><a class="dropdown-item" href="#">资源3</a></li></ul></li><li class="nav-item"><a class="nav-link" href="#">关于我们</a></li><li class="nav-item"><a class="nav-link" href="#">联系我们</a></li></ul></div></div></nav><!-- Cesium 地图容器 --><div id="cesiumContainer"></div><!-- 加载 Cesium 的 JavaScript 文件 --><script src="https://cesium.com/downloads/cesiumjs/releases/1.111/Build/Cesium/Cesium.js"></script><script>Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJmY2YzY2RmNS1iNDQ5LTRkMDEtOGViZi05OGM3MTVjOWU1NGEiLCJpZCI6MjQ3Mzk2LCJpYXQiOjE3Mjg2NDUwOTB9.PFfVhduoFk35Pw05-XXbqPms0m3Fo5KHL0pYVvtyRqg';const viewer = new Cesium.Viewer('cesiumContainer', {homeButton: false,sceneModePicker: false,baseLayerPicker: false,navigationHelpButton: false,animation: false,timeline: false,fullscreenButton: false,vrButton: false,infoBox: true,});// 注册左键点击事件viewer.screenSpaceEventHandler.setInputAction(function (movement) {const cartesian = viewer.camera.pickEllipsoid(movement.position); // 使用 position 而不是 endPositionif (cartesian) {const cartographic = Cesium.Cartographic.fromCartesian(cartesian);const longitude = Cesium.Math.toDegrees(cartographic.longitude);const latitude = Cesium.Math.toDegrees(cartographic.latitude);alert(`经度: ${longitude.toFixed(6)}, 纬度: ${latitude.toFixed(6)}`);}}, Cesium.ScreenSpaceEventType.LEFT_CLICK);// 隐藏商标 viewer._cesiumWidget._creditContainer.style.display = "none"; </script><script src="http://cdn.static.runoob.com/libs/bootstrap/3.3.7/js/bootstrap.min.js"></script></body></html>
总结与展望
通过这篇教程,读者应该能够掌握 MQTT.js 的基础使用,理解 MQTT 协议的工作原理,并能够在自己的项目中有效地应用 MQTT.js。如果有任何问题,欢迎在评论区交流。