自学内容网 自学内容网

【前端】MQTT:通信与聊天室实战

本文是关于 MQTT.js 使用的详细长文教程。文章会循序渐进,从入门到进阶,涵盖常用功能、实用技巧以及代码示例,帮助读者深入理解如何在 JavaScript 环境中使用 MQTT 协议。整个教程将分为以下几个部分:

MQTT 协议

MQTT(Message Queuing Telemetry Transport)是一种轻量级的发布/订阅协议,设计用于低带宽、不稳定网络环境,广泛应用于物联网(IoT)领域。它基于客户端-代理模式,支持三种消息质量服务(QoS)等级,能够满足不同场景的需求。

MQTT.js 安装

MQTT.js 是一个客户端库,用于在 JavaScript 中使用 MQTT 协议。它支持 Node.js 和浏览器环境,提供了简洁的 API,使得开发者能够快速集成 MQTT 协议。

  1. 使用 npm 安装(适用于 Node.js 环境):

    npm install mqtt --save
    
  2. 在浏览器环境中,可以通过 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 对象,包含 usernamedata(即消息内容)。

<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 而不是 endPosition

            if (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。如果有任何问题,欢迎在评论区交流。


原文地址:https://blog.csdn.net/2303_80346267/article/details/143580477

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!