别再只用 WebSocket 做即时通讯了!MQTT+RabbitMQ 实战教程,轻量又高效

在日常开发中,即时通讯功能并不少见 ------ 电商平台的客服对话、支付完成后的实时通知、设备状态的动态同步,这些场景都需要快速、可靠的消息传递。过去我们常依赖 WebSocket,但最近发现,用 RabbitMQ 结合 MQTT 协议实现即时通讯,不仅代码量更少,带宽占用也更低,甚至无需编写后端代码就能满足基础需求。今天就带大家从零开始,掌握这套轻量级解决方案。

一、先搞懂:MQTT 是什么?核心概念有哪些?

要用好 MQTT,得先理清它的核心逻辑。MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议)是基于发布 / 订阅模式的轻量级协议,底层依赖 TCP/IP,最大优势是 "低开销、高可靠"------ 即使用有限带宽,也能为远程设备提供实时消息服务,特别适合物联网、即时通讯等场景。

它的核心组件只有 5 个,理解后就能轻松上手:

  • Publisher(发布者) :消息的发起者,负责向指定 "主题" 发送消息(比如用户发送的客服消息)。

  • Subscriber(订阅者) :消息的接收者,需先订阅某个 "主题",才能收到该主题下的所有消息(比如客服端接收用户消息)。

  • Broker(代理) :连接发布者和订阅者的 "中间件",负责转发消息,支持 MQTT 协议的消息中间件(如 RabbitMQ、EMQX)都能充当 Broker。

  • Topic(主题) :消息的 "路由标识",类似 "频道",发布者向某个 Topic 发消息,只有订阅了该 Topic 的订阅者能收到(比如 "user/123/msg" 代表用户 123 的消息主题)。

  • QoS(消息质量) :控制消息传递的可靠性,分三个等级,按需选择即可:

    • QoS 0(至多一次) :只发送一次,不保证送达,可能丢失或重复(适合非关键消息,如设备心跳)。
    • QoS 1(至少一次) :确保消息送达,但可能重复(适合重要消息,如通知提醒)。
    • QoS 2(恰好一次) :确保消息仅送达一次,无丢失无重复(适合核心业务,如支付通知)。

二、第一步:启用 RabbitMQ 的 MQTT 功能

RabbitMQ 默认不开启 MQTT 支持,需要手动配置。这里以 Docker 环境为例,步骤简单且跨平台,几分钟就能搞定。

1. 用 Docker 启动 RabbitMQ

首先运行 Docker 命令,拉取并启动 RabbitMQ 容器,同时映射必要端口(1883 是 MQTT 默认端口,15675 是 MQTT WebSocket 端口,15672 是管理控制台端口):

bash

bash 复制代码
docker run -p 5672:5672 -p 15672:15672 -p 1883:1883 -p 15675:15675 --name rabbitmq-mqtt \
-v /mydata/rabbitmq-mqtt/data:/var/lib/rabbitmq \
-d rabbitmq:3.9.11-management
  • 5672:RabbitMQ 默认 AMQP 协议端口(非必需,此处为兼容其他功能);
  • 15672:RabbitMQ 管理控制台端口(用于查看服务状态);
  • 1883:MQTT 协议默认端口(用于 TCP 连接);
  • 15675:MQTT WebSocket 端口(用于前端 / 客户端通过 WebSocket 连接)。

2. 启用 MQTT 插件

容器启动后,需要进入容器内部开启 MQTT 相关插件(开启rabbitmq_web_mqtt会自动关联启用rabbitmq_mqtt):

bash

bash 复制代码
# 进入RabbitMQ容器
docker exec -it rabbitmq-mqtt /bin/bash
# 启用MQTT Web插件
rabbitmq-plugins enable rabbitmq_web_mqtt

3. 验证配置是否成功

打开浏览器访问 RabbitMQ 管理控制台(地址:http://你的服务器IP:15672,默认账号密码都是guest),在 "Listening ports" 列表中,若能看到http/web-mqtt对应 15675 端口、mqtt对应 1883 端口,说明配置成功。

三、用 MQTTX 客户端测试:快速验证即时通讯

在写代码前,先用 MQTTX(一款轻量的 MQTT 客户端工具)测试 RabbitMQ 的 MQTT 功能是否正常,避免后续踩坑。

1. 用 Docker 启动 MQTTX

同样用 Docker 快速部署 MQTTX Web 版,命令如下:

bash

arduino 复制代码
docker run -p 80:80 --name mqttx-web -d emqx/mqttx-web

启动后访问http://你的服务器IP,即可进入 MQTTX 控制台。

2. 创建 MQTT 连接并测试

  1. 点击左侧 "+" 号新建连接,配置关键参数:

    • 名称:自定义(如 "test-connect");
    • 服务器地址:ws://你的服务器IP:15675/ws(注意用 WebSocket 协议,路径为/ws);
    • 端口:15675;
    • 用户名 / 密码:guest/guest
    • MQTT 版本:选择 3.1.1(兼容性更好)。
  2. 连接成功后,添加订阅主题(如 "testTopicA"),QoS 选择 0 或 1;

  3. 再打开一个 MQTTX 窗口,同样连接 RabbitMQ,向 "testTopicA" 主题发送消息(如 "hello MQTT!");

  4. 此时第一个窗口会实时收到消息,说明 RabbitMQ 的 MQTT 转发功能正常。

四、前端零后端代码实现:HTML+JS 打造简单聊天

验证功能正常后,直接用前端技术(HTML+JavaScript)实现即时通讯 ------ 无需写一行后端代码,只需引入MQTT.js库即可。

1. 核心代码实现

创建index.html文件,核心逻辑是 "连接 MQTT 服务→订阅主题→发送 / 接收消息":

html

xml 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>MQTT前端聊天Demo</title>
    <style>
        .message-box { margin-top: 10px; padding: 10px; border: 1px solid #eee; height: 300px; overflow-y: auto; }
        .message-item { margin: 5px 0; padding: 8px; background: #f5f5f5; }
    </style>
</head>
<body>
    <div>
        <label>订阅主题:<span id="subscribedTopic"></span></label><br>
        <label>目标主题:<input id="targetTopic" type="text" placeholder="输入要发送的主题" value="testTopicB"></label><br>
        <label>消息内容:<input id="msgContent" type="text" placeholder="输入消息内容"></label><br>
        <button onclick="sendMsg()">发送消息</button>
        <button onclick="clearMsg()">清空记录</button>
        <div class="message-box" id="messageBox"></div>
    </div>

    <!-- 引入MQTT.js库 -->
    <script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
    <script>
        // 1. 配置MQTT连接参数
        const mqttUrl = 'ws://你的服务器IP:15675/ws'; // RabbitMQ的MQTT WebSocket地址
        const subscribedTopic = getUrlParam('topic') || 'testTopicA'; // 从URL获取订阅主题,默认testTopicA
        document.getElementById('subscribedTopic').textContent = subscribedTopic;

        // 2. 连接MQTT服务
        const client = mqtt.connect(mqttUrl, {
            username: 'guest',
            password: 'guest',
            keepalive: 60 // 心跳间隔(秒)
        });

        // 3. 连接成功后订阅主题
        client.on('connect', () => {
            client.subscribe(subscribedTopic, (err) => {
                if (!err) {
                    addMessage(`✅ 成功订阅主题:${subscribedTopic}`);
                } else {
                    addMessage(`❌ 订阅失败:${err.message}`);
                }
            });
        });

        // 4. 接收消息并展示
        client.on('message', (topic, msg) => {
            addMessage(`📥 收到[${topic}]的消息:${msg.toString()}`);
        });

        // 5. 发送消息到指定主题
        function sendMsg() {
            const targetTopic = document.getElementById('targetTopic').value;
            const content = document.getElementById('msgContent').value;
            if (!targetTopic || !content) {
                alert('请填写目标主题和消息内容!');
                return;
            }
            // 发布消息
            client.publish(targetTopic, content);
            addMessage(`📤 发送到[${targetTopic}]的消息:${content}`);
            document.getElementById('msgContent').value = ''; // 清空输入框
        }

        // 6. 工具函数:添加消息到页面
        function addMessage(text) {
            const msgBox = document.getElementById('messageBox');
            const msgItem = document.createElement('div');
            msgItem.className = 'message-item';
            msgItem.textContent = `[${new Date().toLocaleTimeString()}] ${text}`;
            msgBox.appendChild(msgItem);
            // 滚动到最新消息
            msgBox.scrollTop = msgBox.scrollHeight;
        }

        // 7. 工具函数:清空消息记录
        function clearMsg() {
            document.getElementById('messageBox').innerHTML = '';
        }

        // 8. 工具函数:从URL获取参数
        function getUrlParam(name) {
            const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i');
            const match = window.location.search.substr(1).match(reg);
            return match ? decodeURIComponent(match[2]) : null;
        }
    </script>
</body>
</html>

2. 测试前端聊天功能

  1. index.html放在静态资源目录(如 Nginx 或 SpringBoot 的resources/static);

  2. 打开两个浏览器窗口,分别访问:

    • 窗口 1(订阅 testTopicA):http://你的地址/index.html?topic=testTopicA
    • 窗口 2(订阅 testTopicB):http://你的地址/index.html?topic=testTopicB
  3. 在窗口 1 的 "目标主题" 输入testTopicB,发送消息;窗口 2 会实时收到,反之亦然 ------ 一个简单的跨窗口聊天功能就实现了!

五、SpringBoot 集成 MQTT:应对复杂业务场景

如果需要后端参与消息处理(如消息存储、权限校验、多主题转发),可以将 MQTT 集成到 SpringBoot 中。下面是完整的实现步骤。

1. 添加依赖

pom.xml中引入 Spring 集成 MQTT 的依赖:

xml

xml 复制代码
<!-- Spring Integration MQTT -->
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-mqtt</artifactId>
</dependency>
<!-- 可选:Swagger用于接口测试 -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

2. 配置 MQTT 参数

application.yml中添加 RabbitMQ 的 MQTT 连接信息:

yaml

yaml 复制代码
rabbitmq:
  mqtt:
    url: tcp://你的服务器IP:1883 # MQTT TCP协议地址
    username: guest
    password: guest
    default-topic: testTopic # 默认订阅/发布的主题
server:
  port: 8088 # 应用端口

3. 编写配置类

创建MqttConfig类,读取配置文件中的参数:

java

arduino 复制代码
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "rabbitmq.mqtt")
public class MqttConfig {
    /** MQTT连接地址(TCP协议) */
    private String url;
    /** 连接用户名 */
    private String username;
    /** 连接密码 */
    private String password;
    /** 默认主题 */
    private String defaultTopic;
}

4. 配置消息订阅者(接收消息)

创建MqttInboundConfig,实现 "订阅 MQTT 主题并处理消息" 的逻辑:

java

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessagingException;

@Slf4j
@Configuration
public class MqttInboundConfig {

    @Autowired
    private MqttConfig mqttConfig;

    /** 定义输入通道:用于接收MQTT消息 */
    @Bean
    public MessageChannel mqttInputChannel() {
        return new DirectChannel();
    }

    /** 定义消息生产者:订阅指定主题,将消息转发到输入通道 */
    @Bean
    public MqttPahoMessageDrivenChannelAdapter inbound() {
        // 参数:MQTT连接地址、客户端ID(唯一)、订阅的主题
        MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(
                mqttConfig.getUrl(), "springboot-mqtt-subscriber", mqttConfig.getDefaultTopic()
        );
        adapter.setCompletionTimeout(5000); // 连接超时时间
        adapter.setConverter(new DefaultPahoMessageConverter()); // 消息转换器
        adapter.setQos(1); // 消息质量等级(至少一次)
        adapter.setOutputChannel(mqttInputChannel()); // 绑定输入通道
        return adapter;
    }

    /** 定义消息处理器:处理输入通道中的消息 */
    @Bean
    @ServiceActivator(inputChannel = "mqttInputChannel")
    public MessageHandler mqttMessageHandler() {
        return new MessageHandler() {
            @Override
            public void handleMessage(Message<?> message) throws MessagingException {
                // 此处可添加业务逻辑:如消息存储、格式转换、权限校验等
                log.info("📥 收到MQTT消息:{}", message.getPayload());
            }
        };
    }

    /** MQTT客户端工厂:配置连接参数 */
    @Bean
    public MqttPahoClientFactory mqttClientFactory() {
        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
        org.eclipse.paho.client.mqttv3.MqttConnectOptions options = new org.eclipse.paho.client.mqttv3.MqttConnectOptions();
        options.setServerURIs(new String[]{mqttConfig.getUrl()});
        options.setUserName(mqttConfig.getUsername());
        options.setPassword(mqttConfig.getPassword().toCharArray());
        factory.setConnectionOptions(options);
        return factory;
    }
}

5. 配置消息发布者(发送消息)

创建MqttOutboundConfig,实现 "从后端发送消息到 MQTT 主题" 的逻辑:

java

kotlin 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.messaging.MessageChannel;

@Configuration
public class MqttOutboundConfig {

    @Autowired
    private MqttConfig mqttConfig;

    @Autowired
    private MqttPahoClientFactory mqttClientFactory;

    /** 定义输出通道:用于发送MQTT消息 */
    @Bean
    public MessageChannel mqttOutboundChannel() {
        return new DirectChannel();
    }

    /** 定义消息处理器:将输出通道的消息发布到MQTT主题 */
    @Bean
    @ServiceActivator(inputChannel = "mqttOutboundChannel")
    public MqttPahoMessageHandler mqttOutbound() {
        // 参数:客户端ID(唯一)、客户端工厂
        MqttPahoMessageHandler handler = new MqttPahoMessageHandler("springboot-mqtt-publisher", mqttClientFactory);
        handler.setAsync(true); // 异步发送
        handler.setDefaultTopic(mqttConfig.getDefaultTopic()); // 默认发布主题
        return handler;
    }
}

6. 定义 MQTT 网关(简化发送逻辑)

创建MqttGateway接口,通过注解简化 "发送消息到 MQTT" 的调用:

java

less 复制代码
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.handler.annotation.Header;

@MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
public interface MqttGateway {

    /**
     * 发送消息到默认主题
     * @param payload 消息内容
     */
    void sendToDefaultTopic(String payload);

    /**
     * 发送消息到指定主题
     * @param payload 消息内容
     * @param topic 目标主题
     */
    void sendToTopic(String payload, @Header(MqttHeaders.TOPIC) String topic);

    /**
     * 发送消息到指定主题,并设置QoS
     * @param topic 目标主题
     * @param qos 消息质量等级
     * @param payload 消息内容
     */
    void sendToTopicWithQos(@Header(MqttHeaders.TOPIC) String topic, 
                           @Header(MqttHeaders.QOS) int qos, 
                           String payload);
}

7. 编写测试接口

创建MqttController,提供 HTTP 接口测试 MQTT 消息发送:

java

less 复制代码
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
@RequestMapping("/mqtt")
@Tag(name = "MqttController", description = "MQTT消息测试接口")
public class MqttController {

    @Autowired
    private MqttGateway mqttGateway;

    @PostMapping("/send/default")
    @Operation(summary = "向默认主题发送消息")
    public String sendToDefault(@RequestParam String payload) {
        mqttGateway.sendToDefaultTopic(payload);
        return "消息发送成功:" + payload;
    }

    @PostMapping("/send/custom")
    @Operation(summary = "向自定义主题发送消息")
    public String sendToCustom(@RequestParam String payload, 
                               @RequestParam String topic) {
        mqttGateway.sendToTopic(payload, topic);
        return "向主题[" + topic + "]发送消息成功:" + payload;
    }
}

8. 测试后端集成功能

  1. 启动 SpringBoot 应用,访问 Swagger 地址:http://localhost:8088/swagger-ui.html
  2. 调用/mqtt/send/custom接口,传入payload="来自后端的消息"topic="testTopicA"
  3. 查看应用日志,会打印 "收到 MQTT 消息:来自后端的消息",同时订阅了testTopicA的前端窗口也会实时收到消息,说明集成成功。

六、总结:MQTT+RabbitMQ 的优势与适用场景

相比 WebSocket,MQTT+RabbitMQ 的组合有 3 个明显优势:

  1. 轻量高效:MQTT 协议头部仅 2 字节,带宽占用远低于 WebSocket,适合低带宽场景(如物联网设备)。

  2. 无需手写后端:基础即时通讯场景(如简单聊天、通知)可直接用前端 + RabbitMQ 实现,无需开发后端服务。

  3. 可靠性高:依赖 RabbitMQ 的消息持久化、重试机制,配合 QoS 等级,能避免消息丢失,适合关键业务。

适用场景推荐:

  • 简单即时通讯:客服聊天、跨端通知;

  • 物联网场景:设备状态同步、传感器数据上报;

  • 轻量消息传递:支付通知、订单状态更新。

最后,附上本文实战项目的参考源码(基于 SpringBoot3,含前端 Demo):
github.com/macrozheng/...(该项目是标星 60K + 的电商实战项目,还包含微服务、Docker 部署等功能,值得学习)。

掌握这套方案,下次遇到即时通讯需求,就不用再局限于 WebSocket 啦[))](url)

相关推荐
@大迁世界13 分钟前
TypeScript 的本质并非类型,而是信任
开发语言·前端·javascript·typescript·ecmascript
GIS之路22 分钟前
GDAL 实现矢量裁剪
前端·python·信息可视化
是一个Bug25 分钟前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu1213827 分钟前
React面向组件编程
开发语言·前端·javascript
持续升级打怪中1 小时前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化
GIS之路1 小时前
GDAL 实现矢量合并
前端
hxjhnct1 小时前
React useContext的缺陷
前端·react.js·前端框架
前端 贾公子1 小时前
从入门到实践:前端 Monorepo 工程化实战(4)
前端
菩提小狗1 小时前
Sqlmap双击运行脚本,双击直接打开。
前端·笔记·安全·web安全
前端工作日常2 小时前
我学习到的AG-UI的概念
前端